diff --git a/.github/ISSUE_TEMPLATE/BugReport_en.yaml b/.github/ISSUE_TEMPLATE/BugReport_en.yaml index 9a89651e..bd9c2bcf 100644 --- a/.github/ISSUE_TEMPLATE/BugReport_en.yaml +++ b/.github/ISSUE_TEMPLATE/BugReport_en.yaml @@ -11,7 +11,7 @@ body: label: Prerequisites description: Thank you for taking the time to fill out this issue report! Before you begin, you will need to confirm the following. options: - - label: I have searched for related issues in the [议题](https://github.com/XtremeWave/FinalSuspect) list. + - label: I have searched for related issues in the [议题](https://github.com/Slok7565/FinalSuspect) list. required: true - label: I am using the latest version of FinalSuspect. required: true @@ -43,7 +43,7 @@ body: - type: textarea attributes: label: "Relevant log output" - description: "Please refer to [this document](https://www.xtreme.net.cn/docs/FS/en-us/Guide/OutputLog) to export FinalSuspect logs." + description: "Please refer to [this document](https://www.Final.net.cn/docs/FS/en-us/Guide/OutputLog) to export FinalSuspect logs." render: shell - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/BugReport_zh.yaml b/.github/ISSUE_TEMPLATE/BugReport_zh.yaml index fc8c84ba..3f657b61 100644 --- a/.github/ISSUE_TEMPLATE/BugReport_zh.yaml +++ b/.github/ISSUE_TEMPLATE/BugReport_zh.yaml @@ -11,7 +11,7 @@ body: label: 前置条件 description: 感谢你花时间填写此错误报告!在开始之前,您需要确认以下内容。 options: - - label: 已经在[议题列表](https://github.com/XtremeWave/FinalSuspect/issue)中搜索了相关问题。 + - label: 已经在[议题列表](https://github.com/Slok7565/FinalSuspect/issue)中搜索了相关问题。 required: true - label: 您使用的是最新版本的FinalSuspect。 required: true @@ -43,7 +43,7 @@ body: - type: textarea attributes: label: "相关日志输出" - description: "请[根据此文档](https://www.xtreme.net.cn/docs/FS/Guide/OutputLog)导出FS日志并将其粘贴至此处。" + description: "请[根据此文档](https://www.Final.net.cn/docs/FS/Guide/OutputLog)导出FS日志并将其粘贴至此处。" render: shell - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/FeatureRequest_en.yaml b/.github/ISSUE_TEMPLATE/FeatureRequest_en.yaml index be48df39..b333a751 100644 --- a/.github/ISSUE_TEMPLATE/FeatureRequest_en.yaml +++ b/.github/ISSUE_TEMPLATE/FeatureRequest_en.yaml @@ -12,7 +12,7 @@ body: label: Prerequisites description: Hello! Thank you for submitting a new feature suggestion for FinalSuspect. Before we begin, we highly recommend reading through the [Open Source Guides](https://opensource.guide/), which will greatly improve our mutual efficiency. options: - - label: I have searched for related issues in the [议题](https://github.com/XtremeWave/FinalSuspect/issue) list. + - label: I have searched for related issues in the [议题](https://github.com/Slok7565/FinalSuspect/issue) list. required: true - label: I am using the latest version of FinalSuspect. required: true diff --git a/.github/ISSUE_TEMPLATE/FeatureRequest_zh.yaml b/.github/ISSUE_TEMPLATE/FeatureRequest_zh.yaml index 58625374..2469d560 100644 --- a/.github/ISSUE_TEMPLATE/FeatureRequest_zh.yaml +++ b/.github/ISSUE_TEMPLATE/FeatureRequest_zh.yaml @@ -12,7 +12,7 @@ body: label: 前置条件 description: 你好!感谢你为FinalSuspect提交新功能建议。在开始之前.请先确认以下内容。 options: - - label: 已经在[议题](https://github.com/XtremeWave/FinalSuspect/issue)列表中搜索了相关问题。 + - label: 已经在[议题](https://github.com/Slok7565/FinalSuspect/issue)列表中搜索了相关问题。 required: true - label: 您使用的是最新版本的FinalSuspect。 required: true diff --git a/.gitignore b/.gitignore index e9d8580b..06f5d105 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,25 @@ - +# 忽略所有 .editorconfig 文件 +# 定义代码编辑器配置的文件 *.editorconfig +# Visual Studio 的临时文件和目录 .vs/ +# 导出目录 Export/ + +# 编译临时文件、发布配置文件、开发配置 FinalSuspect/obj/ FinalSuspect/Properties/PublishProfiles/ FinalSuspect/FodyWeavers.xsd +# 解决方案 /FinalSuspect.sln -*.zip \ No newline at end of file + +# 忽略 Assets/Mod 下所有内容 +Assets/Mod/** + +# 白名单:保留 BaseBepInEx 目录及其所有内容 +!Assets/Mod/BaseBepInEx/ +Assets/Mod/BaseBepInEx/** +!Assets/Mod/BaseBepInEx/**/* \ No newline at end of file diff --git a/Assets/Sounds/.HowToGetmd5.txt b/Assets/.HowToGetmd5.txt similarity index 68% rename from Assets/Sounds/.HowToGetmd5.txt rename to Assets/.HowToGetmd5.txt index cebc3092..257edab5 100644 --- a/Assets/Sounds/.HowToGetmd5.txt +++ b/Assets/.HowToGetmd5.txt @@ -1,6 +1,4 @@ -//��ע�⣬�뽫{{path}}��Ϊ�Լ�Ҫ��ȡ���ļ���·�� -��������PowerShell���е����Win+R��PowerShell���� -//"Please note, please change the {{path}} to your own path +//Change the {{path}} to your own path Here is the command to run in PowerShell (Win+R to open PowerShell): diff --git a/Assets/Configs/FACList.json b/Assets/Configs/FACList.json index bfe60b91..3c6fbc7f 100644 --- a/Assets/Configs/FACList.json +++ b/Assets/Configs/FACList.json @@ -1,61 +1,71 @@ -{ - "Cheats": [ - "b3VycmVnaW9uIzI5OTIsXHU3QzczXHU1QTFDXHU2ODUx", - "bXVkZHl0ZWUjNzI2MCxcdTVFMDVcdTU0RTVcdTUyMkJcdTUyMDBcdTYyMTE=", - "cnVnZ2xhY2UjNzI1NyxcdTRGNjBcdTU5N0RcdTU1NEE=", - "ZGFpbHltYXplIzM2NjksUGFwZXJjaGlwcw==", - "ZGV3c3VwcGx5IzQxOTksUGluY2hpYw==", - "YXdld2l0dGVkIzE1NDEsamlt", - "YXdhcmVhd2UjMzcyNSxCcmlza2ZsYXNr", - "dGlsZW1vdXN5IzY5MTksXHU0RTA5XHU2QjIxXHU1MTQz", - "aG9tZXlyaWZsZSM5MDk1LENSNw==", - "c3VwYmxvbmQjODcyMSxNdXN0ZXJycnJy", - "c2FrZWNpdmljIzM4MzQsTG9uZ21vb3I=", - "ZmxlZXRrZXkjMjc5MCxSdXNzZXRwb3Jl", - "Zml6enljdXJ2ZSM1ODc1LFx1NTQ4Qw==", - "bWVkaXVtbGFjZSM2MDU5LFNsaWNrbmFtZQ==", - "aW5zaWRlc251ZyM1ODU4LFx1NEY2MFx1NTcyOFx1NUU3Mlx1NEVDMFx1NEU0OA==", - "aW5uZXJkb2RnZSM2MjY5LFx1NTM5Rlx1NzI0OFx1OTc1RW1vZA==", - "YWdpbGVuZWFwIzE0MjEsRGlzaGZh", - "aGFsY3lvbmRldyM2Nzk3LElTaG93U3BlZWQ=", - "bG9jYWxjbHVlIzkwNDAsYlx1N0FEOSBcdTZCOEJcdTY2QjRmZWxsPw==", - "ZmxleHNsZWVweSM3NjkzLFx1NzE0RVx1OTk3Qw==", - "c3F1aXNoc2FmZSMxMjMyLFx1ODJCMVx1NzUxRlx1NTQ3M1x1NzY4NFx1NTQwRFx1NUI1Nw==", - "dGlwYnJpZ2h0Izk5NjksMTE=", - "bm91bnB1cnBsZSM4NDcxLGV3cjMyNDI0", - "cXVpbGxlYXJseSM5NDY2LD8=", - "c3R1Y2tpbmsjODg5MixBSVx1NEVCQVx1NjczQQ==", - "bmVlZHJlbWlzcyMyMzI2LDY5", - "c2hpbnlsdXJlIzAzNDYsTUQxMQ==", - "dmlzdWFsYm93IzAyMDYsXHUzMDEwXHU4QzkzXHU4QzkzXHU5NjUwXHU1QjlBXHUzMDExXHU2MjExXHU2NjJGXHU3MkZD", - "dGFraW5nY2VudCM4Mzg0LGk=", - "c3VwZXJiZmlsbSM4MDk4LEplc3Nl", - "dGlsZW1vdXN5IzY5MTksXHU1OTdEXHU4MUVEXHU3Njg0XHU0RUJB", - "aGFsZmF3YWtlIzYzNDIsWU9V", - "bWljZXNhZ2UjNTc5MixDbGFtaXR5", - "cHVwYWxzaGFuayM0MjYxLDk5OVx1NjExRlx1NTE5Mlx1NzA3NQ==", - "d2l0ZmVydmlkIzY3MTMscmF2ZW4=", - "bWVkaWFub2FzdCMwMTk4LGZveHk=", - "Y29taWNmb3gjMDYzNSxwcmlkZQ==", - "ZmluaXNoYmFieSMyODUwLHBvcDY2Ng==", - "dGlwc3llbGl0ZSMzNTU2LExpbnVz", - "am9nZ2VybGVmdCM3MDkzLFJ1bjRMaWZl", - "bG9yZGx5YXRvbSM4OTk3LDA=", - "cGxhaW5jb2RlIzMyNjAsSQ==", - "c29saWRoYXkjMjE0MyxJTG92ZVlvdQ==", - "aG9tZWx5c29mYSM3MjEwLE5hZ3Zh", - "bW92aWVidWxreSMzODQ3LGxlIGdob3N0ZQ==", - "Y29zYXB0IzYyNDMsXHU1MzYxXHU2NUFGXHU3Mjc5", - "bm9tYWRtb3JhbCMzOTIwLEV6IENoZWF0ZWQ=", - "d29rY2xhc3RpYyM3Njc4LFx1ODJFNlx1NUMzRFx1NzUxOFx1Njc2NQ==", - "ZXBvY2h3YXZ5IzUwMTgsXHU5NzUyXHU1RTFEXHU1MzREXHU3MzY4XHU2MTFCXHU1OTc2XHU1MTU0XHU1MzREXHU3NjBCXHU0RTg2", - "Y29ybmVyd29udCMwMDkxLFx1N0ZEMlx1NTkyN1x1NTkyNw==", - "a2VlbnRydWcjMTY0Myxf", - "YWxwaGFjb29rIzY2MjQsTm9uYWx1cw==" - ], - "Griefer": [ - "c2V0bW9sYXIjNDM3NCxcdTg1Q0ZcdTY1Q0ZcdTk4NzZcdTk0ODg=", - "b3ZlcnRmZWFzdCM1ODAyLFx1OUVEMVx1NUY3MQ==", - "bW9yZWtub3QjODE1MCxMdXJrZXI=" - ] -} +{ + "Cheats": [ + "b3VycmVnaW9uIzI5OTIsXHU3QzczXHU1QTFDXHU2ODUx", + "bXVkZHl0ZWUjNzI2MCxcdTVFMDVcdTU0RTVcdTUyMkJcdTUyMDBcdTYyMTE=", + "cnVnZ2xhY2UjNzI1NyxcdTRGNjBcdTU5N0RcdTU1NEE=", + "ZGFpbHltYXplIzM2NjksUGFwZXJjaGlwcw==", + "ZGV3c3VwcGx5IzQxOTksUGluY2hpYw==", + "YXdld2l0dGVkIzE1NDEsamlt", + "YXdhcmVhd2UjMzcyNSxCcmlza2ZsYXNr", + "dGlsZW1vdXN5IzY5MTksXHU0RTA5XHU2QjIxXHU1MTQz", + "aG9tZXlyaWZsZSM5MDk1LENSNw==", + "c3VwYmxvbmQjODcyMSxNdXN0ZXJycnJy", + "c2FrZWNpdmljIzM4MzQsTG9uZ21vb3I=", + "ZmxlZXRrZXkjMjc5MCxSdXNzZXRwb3Jl", + "Zml6enljdXJ2ZSM1ODc1LFx1NTQ4Qw==", + "bWVkaXVtbGFjZSM2MDU5LFNsaWNrbmFtZQ==", + "aW5zaWRlc251ZyM1ODU4LFx1NEY2MFx1NTcyOFx1NUU3Mlx1NEVDMFx1NEU0OA==", + "aW5uZXJkb2RnZSM2MjY5LFx1NTM5Rlx1NzI0OFx1OTc1RW1vZA==", + "YWdpbGVuZWFwIzE0MjEsRGlzaGZh", + "aGFsY3lvbmRldyM2Nzk3LElTaG93U3BlZWQ=", + "bG9jYWxjbHVlIzkwNDAsYlx1N0FEOSBcdTZCOEJcdTY2QjRmZWxsPw==", + "ZmxleHNsZWVweSM3NjkzLFx1NzE0RVx1OTk3Qw==", + "c3F1aXNoc2FmZSMxMjMyLFx1ODJCMVx1NzUxRlx1NTQ3M1x1NzY4NFx1NTQwRFx1NUI1Nw==", + "dGlwYnJpZ2h0Izk5NjksMTE=", + "bm91bnB1cnBsZSM4NDcxLGV3cjMyNDI0", + "cXVpbGxlYXJseSM5NDY2LD8=", + "c3R1Y2tpbmsjODg5MixBSVx1NEVCQVx1NjczQQ==", + "bmVlZHJlbWlzcyMyMzI2LDY5", + "c2hpbnlsdXJlIzAzNDYsTUQxMQ==", + "dmlzdWFsYm93IzAyMDYsXHUzMDEwXHU4QzkzXHU4QzkzXHU5NjUwXHU1QjlBXHUzMDExXHU2MjExXHU2NjJGXHU3MkZD", + "dGFraW5nY2VudCM4Mzg0LGk=", + "c3VwZXJiZmlsbSM4MDk4LEplc3Nl", + "dGlsZW1vdXN5IzY5MTksXHU1OTdEXHU4MUVEXHU3Njg0XHU0RUJB", + "aGFsZmF3YWtlIzYzNDIsWU9V", + "bWljZXNhZ2UjNTc5MixDbGFtaXR5", + "cHVwYWxzaGFuayM0MjYxLDk5OVx1NjExRlx1NTE5Mlx1NzA3NQ==", + "d2l0ZmVydmlkIzY3MTMscmF2ZW4=", + "bWVkaWFub2FzdCMwMTk4LGZveHk=", + "Y29taWNmb3gjMDYzNSxwcmlkZQ==", + "ZmluaXNoYmFieSMyODUwLHBvcDY2Ng==", + "dGlwc3llbGl0ZSMzNTU2LExpbnVz", + "am9nZ2VybGVmdCM3MDkzLFJ1bjRMaWZl", + "bG9yZGx5YXRvbSM4OTk3LDA=", + "cGxhaW5jb2RlIzMyNjAsSQ==", + "c29saWRoYXkjMjE0MyxJTG92ZVlvdQ==", + "aG9tZWx5c29mYSM3MjEwLE5hZ3Zh", + "bW92aWVidWxreSMzODQ3LGxlIGdob3N0ZQ==", + "Y29zYXB0IzYyNDMsXHU1MzYxXHU2NUFGXHU3Mjc5", + "bm9tYWRtb3JhbCMzOTIwLEV6IENoZWF0ZWQ=", + "d29rY2xhc3RpYyM3Njc4LFx1ODJFNlx1NUMzRFx1NzUxOFx1Njc2NQ==", + "ZXBvY2h3YXZ5IzUwMTgsXHU5NzUyXHU1RTFEXHU1MzREXHU3MzY4XHU2MTFCXHU1OTc2XHU1MTU0XHU1MzREXHU3NjBCXHU0RTg2", + "Y29ybmVyd29udCMwMDkxLFx1N0ZEMlx1NTkyN1x1NTkyNw==", + "a2VlbnRydWcjMTY0Myxf", + "MTMzNyM5NjQ4LDEzMzc=", + "Z2FsYXh5YWZmbCM1NjcxLE1hdGNoRHVjaw==", + "dGVzdG5pa28jMTE0NixLYW1pMTMzNw==" + ], + "Griefer": [ + "c2V0bW9sYXIjNDM3NCxcdTg1Q0ZcdTY1Q0ZcdTk4NzZcdTk0ODg=", + "b3ZlcnRmZWFzdCM1ODAyLFx1OUVEMVx1NUY3MQ==", + "bW9yZWtub3QjODE1MCxMdXJrZXI=", + "bXVsY2hzbW9reSMyMTc0LFx1Nzk1RVx1OTAwNlx1NTkyN1x1NUUxRA==", + "dGlueXN0cmFwIzk5NDgsXHU3OTVFXHU5MDA2XHU1OTI3XHU1RTFE", + "ZnVuZWFydGh5IzUxNzMsXHU3OTVFXHU5MDA2XHU1OTI3XHU1RTFE", + "ZnJlZXplZHJpbSM1NzAyLFx1Nzk1RVx1OTAwNlx1NTkyN1x1NUUxRA==", + "bGV2ZWxib3dsIzA1MDUsXHU3OTVFXHU5MDA2XHU1OTI3XHU1RTFE", + "YmVlZWFydGh5IzUzMTksXHU3OTVFXHU5MDA2XHU1OTI3XHU1RTFE", + "cG90YXRvd2VzdCM1NTI5LFx1Nzk1RVx1OTAwNlx1NTkyN1x1NUUxRA==", + "bmV4dHN1bSM2NjAzLFx1Nzk1RVx1OTAwNlx1NTkyN1x1NUUxRA==" + ] +} \ No newline at end of file diff --git a/Assets/Images/AuthorLogo1.png b/Assets/Images/AuthorLogo1.png new file mode 100644 index 00000000..6413a270 Binary files /dev/null and b/Assets/Images/AuthorLogo1.png differ diff --git a/Assets/Images/AuthorLogo2.png b/Assets/Images/AuthorLogo2.png new file mode 100644 index 00000000..81e2bf8d Binary files /dev/null and b/Assets/Images/AuthorLogo2.png differ diff --git a/Assets/Images/EditTag.png b/Assets/Images/EditTag.png new file mode 100644 index 00000000..c5a2d20e Binary files /dev/null and b/Assets/Images/EditTag.png differ diff --git a/Assets/Images/FinalSuspect-BG-MiraHQ-Preview.png b/Assets/Images/FinalSuspect-BG-MiraHQ-Preview.png new file mode 100644 index 00000000..f76eefb9 Binary files /dev/null and b/Assets/Images/FinalSuspect-BG-MiraHQ-Preview.png differ diff --git a/Assets/Images/FinalSuspect-BG-MiraHQ.jpg b/Assets/Images/FinalSuspect-BG-MiraHQ.jpg deleted file mode 100644 index 635df2df..00000000 Binary files a/Assets/Images/FinalSuspect-BG-MiraHQ.jpg and /dev/null differ diff --git a/Assets/Images/FinalSuspect-BG-MiraHQ.png b/Assets/Images/FinalSuspect-BG-MiraHQ.png new file mode 100644 index 00000000..44b3dfdb Binary files /dev/null and b/Assets/Images/FinalSuspect-BG-MiraHQ.png differ diff --git a/Assets/Images/FinalSuspect-BG-MiraStudio-Preview.png b/Assets/Images/FinalSuspect-BG-MiraStudio-Preview.png new file mode 100644 index 00000000..778adc05 Binary files /dev/null and b/Assets/Images/FinalSuspect-BG-MiraStudio-Preview.png differ diff --git a/Assets/Images/FinalSuspect-BG-NewYear-Preview.png b/Assets/Images/FinalSuspect-BG-NewYear-Preview.png new file mode 100644 index 00000000..2594e0c4 Binary files /dev/null and b/Assets/Images/FinalSuspect-BG-NewYear-Preview.png differ diff --git a/Assets/Images/FinalSuspect-BG-Security-Preview.png b/Assets/Images/FinalSuspect-BG-Security-Preview.png new file mode 100644 index 00000000..ffad581a Binary files /dev/null and b/Assets/Images/FinalSuspect-BG-Security-Preview.png differ diff --git a/Assets/Images/FinalSuspect-BG-XtremeWave-Preview.png b/Assets/Images/FinalSuspect-BG-XtremeWave-Preview.png new file mode 100644 index 00000000..ff039ef0 Binary files /dev/null and b/Assets/Images/FinalSuspect-BG-XtremeWave-Preview.png differ diff --git a/Assets/Images/KeyBackground.png b/Assets/Images/KeyBackground.png new file mode 100644 index 00000000..6a5f0482 Binary files /dev/null and b/Assets/Images/KeyBackground.png differ diff --git a/Assets/Images/KeyLeftShift.png b/Assets/Images/KeyLeftShift.png new file mode 100644 index 00000000..8f9b96a3 Binary files /dev/null and b/Assets/Images/KeyLeftShift.png differ diff --git a/Assets/Images/KeyRightShift.png b/Assets/Images/KeyRightShift.png new file mode 100644 index 00000000..901970b1 Binary files /dev/null and b/Assets/Images/KeyRightShift.png differ diff --git a/Assets/Images/Plate_Category.png b/Assets/Images/Plate_Category.png new file mode 100644 index 00000000..2be58815 Binary files /dev/null and b/Assets/Images/Plate_Category.png differ diff --git a/Assets/Images/Plate_Clear.png b/Assets/Images/Plate_Clear.png new file mode 100644 index 00000000..9dc92eca Binary files /dev/null and b/Assets/Images/Plate_Clear.png differ diff --git a/Assets/Images/Plate_Content.png b/Assets/Images/Plate_Content.png new file mode 100644 index 00000000..3d5ce2b9 Binary files /dev/null and b/Assets/Images/Plate_Content.png differ diff --git a/Assets/Languages/Brazilian.yaml b/Assets/Languages/Brazilian.yaml index 1b72353c..e2d5d298 100644 --- a/Assets/Languages/Brazilian.yaml +++ b/Assets/Languages/Brazilian.yaml @@ -1,299 +1,337 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "2" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostores" -TypeCrewmate: "Tripulantes" +RoleType.Imp: "Impostor" +RoleType.Crew: "Tripulante" # 阵营 / Teams -TeamImpostor: "Time-Imostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Time-Tripulante" +Team.Imp: "Equipe-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Equipe-Tripulante" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Há {0} Impostores em nosso time" -ImpostorNumImpOnly: "Só há 1 Impostor entre a multidão" -ImpostorNumCrew: "{0} Impostores entre nós" +ImpostorNum.Imp: "Há {0} impostores em nossa equipe" +ImpostorNum.Imp_Only: "Há apenas 1 impostor entre nós" +ImpostorNum.Crew: "{0} impostores entre nós" # 阵营开场 -ImpostorIntroText: "Deixe o mal envolver o mundo!" -ImpostorIntroTextOnly: "Eu posso estar sozinho, mas possuo força infinita!" -CrewmateIntroText: "Complete suas tarefas, unindo-se para resolver essas situações problemáticas!" - -## 原版职业 / Vanilla -Crewmate: "Tripulante" -Engineer: "Engenheiro" -Scientist: "Cientista" -Tracker: "Rastreador" -Noisemaker: "Criador de Ruído" -GuardianAngel: "Anjo da Guarda" -Impostor: "Impostor" -Shapeshifter: "Mudança de Forma" -Phantom: "Fantasma" -CrewmateGhost: "Fantasma de Tripulante" -ImpostorGhost: "Fantasma de Impostor" -CrewmateGhostBlurb: "Complete suas tarefas" -ImpostorGhostBlurb: "Continue sabotando as instalações" -CrewmateGhostBlurbLong: "Complete as tarefas, não fique para trás!" -ImpostorGhostBlurbLong: "Sabotar instalações, ajudar impostores sobreviventes a alcançar a vitória." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Sobreviva até o final!" -HnSEngineerBlurbLong: "Permaneça vivo até que o tempo acabe; completar tarefas irá estender o tempo. \nUse ventos e indicadores de ameaça para se esconder! \nQuando o cronômetro começar, o Impostor começará a caçar você!" -HnSImpostorBlurb: "Elimine todos!" -HnSImpostorBlurbLong: "Mate todos os tripulantes dentro do limite de tempo! \nVocê deve agir rapidamente! Os ventos não estão acessíveis. \nNo último momento, você receberá um aumento de velocidade e dicas de rastreamento!" -HnSCrewmateGhostBlurb: "Torça por seus companheiros" -HnSCrewmateGhostBlurbLong: "Vá! Torça! Por! Seus! Companheiros!" +IntroText.Imp: "Deixe os malvados DOMINAREM O MUNDO!" +IntroText.Imp_Only: "Embora sozinho, você tem PODER INFINITO!" +IntroText.Crewmate: "Complete suas tarefas e encontre os impostores!" + +# 原版职业 / Vanilla +Role.Crewmate: "Tripulante" +Role.Engineer: "Engenheiro" +Role.Scientist: "Cientista" +Role.Tracker: "Rastreador" +Role.Noisemaker: "Criador de Ruído" +Role.GuardianAngel: "Anjo da Guarda" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Cambiante" +Role.Phantom: "Fantasma" +Role.CrewmateGhost: "Fantasma de Tripulante" +Role.ImpostorGhost: "Fantasma de Impostor" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Complete tarefas" +ImpostorGhostBlurb: "Continue sabotando" +CrewmateGhostBlurbLong: "Complete tarefas, não atrapalhe o time!\nUse 「Assombrar」 para ver Impostores e ajude Anjos da Guarda a protegerem Tripulantes!" +ImpostorGhostBlurbLong: "Sabotagem instalações, ajude impostores sobreviventes a vencer!" +HnSEngineerBlurb: "Complete tarefas e sobreviva até o fim!" +HnSImpostorBlurb: "Massacre TODOS!" +HnSCrewmateGhostBlurb: "Anime seus companheiros" +HnSCrewmateGhostBlurbLong: "Morto? O que está olhando? Pode fazer tarefas? Já que não...\nVÁ! ANIMAR! SEUS! COMPANHEIROS!" # 死因 / Death Reason -DeathReason.Kill: "Morto" +DeathReason.Kill: "Assassinado" DeathReason.Exile: "Exilado" -DeathReason.Disconnect: "Desconectado" +DeathReason.Disconnect: "D/C" # 客户端选项 / Client Options FinalSuspectOptions: "Opções do Final Suspect" -Back: "Voltar" -Yes: "Sim" -No: "Não" -UnlockFPS: "Desbloquear FPS" -ChangeOutfit: "Mudar Roupa" -BeanMode: "Modo Clássico de Feijão" -HorseMode: "Modo de Abril de Cavalo" -LongMode: "Modo de Abril Longo" -KickPlayerFriendCodeNotExist: "Expulsar Jogadores que Não estão Logados." -KickPlayerWithDenyName: "Expulsar Jogadores com Apelidos Inapropriados" -KickPlayerInBanList: "Expulsar Jogadores Banidos" -SpamDenyWord: "Bloquear Palavras Inapropriadas" -AutoStartGame: "Iniciar Automaticamente com Lobby Cheio" -AutoEndGame: "Retornar Automaticamente ao Lobby no Fim" -SwitchVanilla: "Alternar para Versão Original" -DisableVanillaSound: "Desabilitar Músicas do Among Us" -DisableFAC: "Desabilitar Anti-Cheat" -ShowPlayerInfo: "Exibir Informações de Plataforma e Cliente do Jogador" -UseModCursor: "Usar Cursor do Mod" -FastBoot: "Modo de Inicialização Rápida" -PrunkMode: "Modo de Prank" -VersionCheat: "Ignorar Verificação de Versão do Mod" -GodMode: "Modo Deus" -NoGameEnd: "Sem Fim de Jogo" -EnableFinalSuspect: "Habilitar「Final Suspect」" +ClientOption.UnlockFPS: "Desbloquear FPS" +ClientOption.SwitchOutfitType: "Trocar Tipo de Traje" +ClientOption.KickPlayerWithAbnormalFriendCode: "Expulsar jogadores com Friend Codes anormais" +ClientOption.KickPlayerWithDenyName: "Expulsar jogadores usando apelidos banidos" +ClientOption.KickPlayerInBanList: "Expulsar jogadores banidos" +ClientOption.SpamDenyWord: "Bloquear palavras banidas" +ClientOption.AutoStartGame: "Iniciar jogo automaticamente quando lotado" +ClientOption.AutoEndGame: "Retornar automaticamente ao lobby ao final do jogo" +ClientOption.SwitchVanilla: "Mudar para Vanilla" +ClientOption.DisableVanillaSound: "Desativar música do jogo Vanilla" +ClientOption.EnableFAC: "Ativar Anti-Cheat" +ClientOption.EnableGuardian: "Ativar Guardião do Cliente (Experimental)" +ClientOption.ShowPlayerInfo: "Mostrar plataforma e informações do cliente" +ClientOption.UseModCursor: "Usar cursor do mod" +ClientOption.FastLaunchMode: "Modo de Inicialização Rápida" +ClientOption.OfflineMode: "Modo Offline (Experimental)" +ClientOption.VersionCheat: "Ignorar verificação de sincronização de versão" +ClientOption.GodMode: "Modo Deus" +ClientOption.NoGameEnd: "Sem Fim de Jogo" +ClientOption.EnableFinalSuspect: "Ativar 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Modo Clássico" +Value.HorseMode: "Modo Cavalo de 1º de Abril" +Value.LongMode: "Modo Longo de 1º de Abril" # 客户端功能 / Client Features FinalSuspectFeatures: "Recursos do Final Suspect" -UnloadMod: "Alternar para Versão Original" -UnloadWarning: "Aviso\nPara reativar o mod, você deve reiniciar o jogo.\nDeseja continuar mesmo assim?" -CannotUnloadDuringGame: "Não é possível alternar para a versão original durante o jogo" -Cancel: "Cancelar" -Unload: "Desativar" -DumpLog: "Gerar Log" -ClearAutoLogs: "Limpar Log Automático" -SoundOptions: "Minhas Músicas" -AudioManagementOptions: "Gerenciamento de Áudio" -OnlyAvailableInMainMenu: "Somente Disponível no Menu Principal" +ClientFeature.UnloadMod: "Mudar para Vanilla" +ClientFeature.DumpLog: "Despejar Log" +ClientFeature.ClearAutoLogs: "Limpar Logs Automáticos" +ClientFeature.MyMusic: "Minha Música" +ClientFeature.ResourceManager: "Gerenciador de Recursos" +ClientFeature.NameTagManager: "Gerenciador de Etiquetas" +ClientFeature.MainMenuStyleManager: "Trocar Estilo do Menu Principal" # 提示 / Tips -updatePleaseWait: "Por favor, aguarde..." -updateInProgress: "Atualização em progresso..." -DownloadingAudios: "Baixando..." -Playing: "Jogando..." -Parsing: "Analisando..." -DownLoadSucceedNotice: "Download bem-sucedido!" -DownLoadFailureNotice: "Download falhou =(" -PleaseWait: "Por favor, aguarde..." -LanguageFilesLoadingComplete: "Carregamento de Traduções Completo!" -CheckingForFiles: "Verificando Integridade dos Arquivos de Recursos..." -DownloadingResources: "Baixando Arquivos de Recursos..." -Loading: "Carregando" -LoadingWithDot: "Carregando..." -LoadingComplete: "Carregamento Completo!" +Tip.Downloading: "Baixando..." +Tip.PleaseWait: "Por favor, aguarde..." +Tip.Playing: "Reproduzindo..." +Tip.Parsing: "Analisando..." +Tip.Updating: "Atualizando..." +Tip.DownLoadFinished: "Download finalizado" +Tip.DownLoadSucceeded: "Download bem-sucedido!" +Tip.DownLoadFailed: "Download falhou=(" +Tip.PackageExists: "Instalado" +Tip.OnlyAvailableInMainMenu: "Disponível apenas no Menu Principal" +Tip.HideSummaryTextToShowWinText: "Ocultar Último Resumo para ver texto de vitória" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Carregamento de arquivos de idioma concluído" +Tip.CheckingForFiles: "Verificando integridade dos arquivos de recurso..." +Tip.DownloadingResources: "Baixando arquivos de recurso..." +Tip.Loading: "Carregando" +Tip.LoadingWithDot: "Carregando..." +Tip.LoadingComplete: "Carregamento completo!" +## 切换原版 +Tip.UnloadWarning: "Aviso!\nVocê deve reiniciar o jogo se quiser jogar na versão com mod.\nTem certeza que deseja mudar para Vanilla?" +Tip.CannotUnloadDuringGame: "Não é possível mudar para Vanilla durante uma partida." +## 资源管理 +Tip.ResourceManager: "Você pode baixar recursos compatíveis e adicionais no 「Gerenciador de Recursos」\nArquivos prefixados com \"Pre-\" são pacotes de recursos pré-baixados" +## 我的音乐 +Tip.MyMusic: "Você pode baixar músicas compatíveis no 「Gerenciador de Recursos」 ou adicionar seus arquivos de áudio favoritos na pasta (Among Us/Final Suspect_Data/Musics). Se o caminho local não existir, será exibido 「Arquivo Ausente」" +Tip.Incomplete_Music: "Arquivo de música incompleto detectado" +Tip.Incomplete_SoundEffect: "Arquivo de efeito sonoro incompleto detectado" +Tip.Incomplete_Image: "Arquivo de imagem incompleto detectado" +Tip.Incomplete: "Para melhorar sua experiência, baixe o pacote de recursos compatíveis em 「Menu Principal - Configurações - Recursos do Cliente - Gerenciador de Recursos」" +## 名称标识管理 +Tip.TextContent: "Conteúdo do Texto" +Tip.TextSizeDescription: "Tamanho do Texto (Padrão 100%)" +Tip.TextColorDescription: "Cor do Texto (Código Hex)\nDeixe em branco se não necessário, preencha múltiplos para gradiente automático\n" +Tip.PleaseEnterFriendCode: "Insira o Friend Code para vincular a nova etiqueta" +Tip.FriendCodeAlreadyExist: "Este Friend Code já possui uma etiqueta" +Tip.FriendCodeIncorrect: "Insira um Friend Code válido" +Tip.CustomNameTagHelp: "Você pode adicionar etiquetas para qualquer jogador. As etiquetas serão atribuídas automaticamente quando o jogador vinculado ao Friend Code entrar. Não é possível editar etiquetas após entrar em um lobby.\nNão-VIPs só podem adicionar observações (funções VIP ainda não desenvolvidas)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Você pode baixar 「Pacotes de Estilo de Menu Principal」 no 「Gerenciador de Recursos」 e escolher seu estilo favorito aqui" + +# 更新结果 +UpdateResult.Succeed_Title: "Atualização Bem-sucedida" +UpdateResult.Succeed_Text: "Será ativado após reiniciar o jogo :)" +UpdateResult.Failed_Title: "Atualização Falhou" +UpdateResult.Failed_Reason_NotFound: "Motivo: {0}\nEsta fonte pode estar temporariamente indisponível, troque a fonte e tente novamente" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Motivo: Erro de verificação de arquivo\nA versão desta fonte não é a mais recente, troque a fonte e tente novamente" +UpdateResult.Failed_Reason_Ping: "Motivo: Tempo esgotado ou interrupção no download\nVerifique sua rede e tente novamente ou atualize manualmente" # 更新检查 / Update Checker -Retry: "Tentar Novamente" -updateCheckPopupTitle: "Verificação de Atualização" -updateCheckFailedRetry: "Falha ao verificar atualização :(\nTentar novamente?" -updateCheckFailedExit: "Falha ao verificar atualização :(\nPor favor, verifique sua conexão de rede e tente novamente." +UpdateCheck.Popup_Title: "Verificar Atualizações" +UpdateCheck.Failed_Retry: "Falha ao verificar atualizações :(\nTentar novamente?" +UpdateCheck.Failed_Exit: "Falha ao verificar atualizações :(\nVerifique sua rede e tente novamente!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Lembrete de Atualização" -updateNotice: "Lembrete de Atualização" -UpdateBySelfText: "Esta versão não suporta atualizações automáticas. Por favor, atualize manualmente." -updateButton: "Atualizar Agora" -updatePopupTitle: "Atualizar Agora" -updatePopupTitleFailed: "Falha ao Atualizar" -updatePopupTitleDone: "Atualização Concluída" +UpdateRemind.updatePopup: "Atualizar Agora" +UpdateRemind.updateNotice: "Lembrete de Atualização" +UpdateRemind.BySelf_Title: "Dica de Atualização" +UpdateRemind.BySelf_Text: "Esta versão não suporta atualização com um clique, atualize manualmente" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Por favor, selecione um canal para atualizar\nse você não sabe o que selecionar, por favor, selecione o [Github]\nSe a atualização falhar, selecione [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Reinicie o jogo para aplicar as alterações :)" -updatePingFialed: "Razão: {0}\nO canal selecionado pode estar temporariamente indisponível. Por favor, tente alternar o canal de download." -updateFileMd5Incorrect: "Razão: Erro no checksum do arquivo\nA versão do arquivo desse canal não é a mais recente. Por favor, tente alternar o canal de download." -downloadFailed: "Razão: Tempo limite de download ou interrupção\nPor favor, tente novamente após alterar sua rede ou atualizar manualmente." +UpdateSource.Choose: "Escolha a fonte de atualização\nSe não souber, escolha [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Temos uma atualização importante. Por favor, atualize este mod.\nCaso contrário, você não poderá entrar em salas públicas." -CanNotJoinPublicRoomNoLatest: "Temos uma atualização importante. Por favor, atualize este mod.\nCaso contrário, você não poderá entrar em salas públicas." -ModBrokenMessage: "Os arquivos do mod estão corrompidos. Por favor, reinicie o jogo ou reinstale este mod." -UnsupportedVersion: "Sua versão do Among Us é incompatível com o FinalSuspect.\nPor favor, atualize seu jogo." +CanNotJoinPublicRoomNoLatest: "Temos uma atualização importante, atualize este mod\nCaso contrário não poderá entrar em salas públicas" +ModBrokenMessage: "Arquivos do mod corrompidos, reinicie o jogo ou reinstale o mod" +UnsupportedVersion: "Sua versão do Among Us é incompatível com FinalSuspect\nAtualize o jogo" + +# 名称标识 +NameTag.DisplayName: "Observação" +NameTag.Title: "Título" +NameTag.Prefix: "Prefixo" +NameTag.Suffix: "Sufixo" +NameTag.Name: "Nome" +NameTag.LastTag: "Sufixo Adicional" +NameTag.PreviewNotAvailable: " (Prévia não suportada) " +NameTag.CanNotEdit: " (Não editável) " +NameTag.RefreshPreview: "Atualizar Prévia" +NameTag.SaveAndClose: "Salvar e Sair" +NameTag.NewNameTag: "Novo" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Perseguindo o Amanhecer(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Com o tempo, o inverno setentrional termina e a primavera renasce.\nContemplando a paisagem nevada de Mira, memórias são despertadas? Córregos quentes fluem no coração.\nNão importa quem seja o suspeito final, não importa quem frature a verdade,\nEm uma nova experiência refrescante, raciocine e deixe novas memórias felizes.\nJogar, se divertir é o mais importante!!!\n\nNomeado por: 一念旧情丶" +MainMenuStyle.Title_Security: "Coração Vermelho, Amor Feroz (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Não cairemos, não desistiremos, não decepcionaremos jogadores e nunca pararemos de progredir.\nSe encontrarmos olhares frios, provaremos a todos!\nCom paixão, zarparamos novamente\n\nNomeado por: Slok" +MainMenuStyle.Title_NewYear: "Afinidade (Ano Novo)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortuna: Desejos concedidosVínculo: Tecido nas batidas do coração\nHarmonia: Fios de afeiçãoBem-aventurança: Paz em cada alma\nVínculos forjados compartilham alegriaViaje longe, encontre alegria na unidade\nA melhor era acolhe todos com alegriaNossa oração - alegria para cada ser\nQue cada alma encontre seu caminho no próximo ano\nCom nossas bênçãos iluminando seu caminho, embarque na jornada para forjar sua própria lenda!\n\nNomeado por: Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Ano Novo)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Flores desabrocham como ondas, suaves como o vento, desejando que o coração de todos seja vasto como o vento, tudo corra bem\"\n\"—Ventos primaveris rugem poderosos, ondas avançam por milhas, desejando que todos estejam corajosamente na frente, prosperando\"\n\"—Bem-vindo ao XtremeWave — Programa Especial de Ano Novo!\"\n\nNomeado por: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final Buscando a Excelência, Sonhos Comandando Ondas Crescentes!」\n\nNomeado por: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Quando Olhando Para Trás no Fim (Colaboração)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Olhando para trás no fim, tudo termina no fim」\n\nNomeado por: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Não Baixado" +MainMenuStyle.NotApply: "Aplicar" +MainMenuStyle.Applied: "Aplicado" + +# 资源包 +Package.MainMenuStyle: "Pacote de Estilo de Menu Principal" # 音频播放 / Audio Playback -PlayMode0: "Tocar uma vez" -PlayMode1: "Repetir uma única" -PlayMode2: "Aleatório" -PlayMode3: "Sequencial" -Stop: "Parar" -CanPlay: "← Clique para Tocar" -NoFound: "[Arquivo Ausente]" -NextPage: "Próxima Página" -PreviousPage: "Página Anterior" - -# 音频添加 / Audio Addition -download: "Baixar" -delete: "Excluir" -NewSound: "Adicionar Nova Música" -PleaseEnterMusic: "Por favor, insira o nome da música" -AudioManagementAlreadyExists: "Este nome de música já existe" -NotAllowedMusic: "O formato do nome da música não é permitido" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Você pode baixar músicas suportadas pelo XtremeWave ou adicionar suas próprias músicas em 「Gerenciamento de Áudio」. Ao adicionar suas próprias músicas, certifique-se de adicionar o nome da música em 「Gerenciamento de Áudio」 e colocar o arquivo de áudio correspondente na pasta 「Among Us/Final Suspect_Data/Resources/Sounds」 (formatos suportados: .wav). As músicas podem ser tocadas em 「Minhas Músicas」." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Você pode baixar músicas suportadas pelo XtremeWave ou adicionar suas próprias músicas em 「Gerenciamento de Áudio」. Se o caminho do recurso de música local estiver ausente, ele exibirá '[Arquivo Ausente]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "O arquivo de música atual foi detectado como incompleto" -AudioNYPro: "Para uma experiência de jogo aprimorada, baixe nossas músicas em 「Inicial-Configurações-Mais Recursos-Gerenciamento de Áudio」" +MusPlay.Mode0: "Reproduzir Uma Vez" +MusPlay.Mode1: "Repetição Única" +MusPlay.Mode2: "Lista Aleatória" +MusPlay.Mode3: "Reprodução Sequencial" +MusPlay.Stop: "Parar Reprodução" +MusPlay.CanPlay: "Clique para Reproduzir" +MusPlay.NoFound: "Arquivo Ausente" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(Feliz Ano Novo Chinês)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Onda de Maré" +Mus.TrailOfTruth: "Trilha da Verdade" +Mus.Interlude: "Interlúdio" +Mus.Fractured: "Fraturas" +Mus.ElegyOfFracturedVow: "Elegia do Voto Fraturado" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] foi expulso porque seu nome corresponde a [{1}]" -Message.BanedByBanList: "[{0}] foi banido porque foi banido anteriormente." -Message.BanedByFACList: "[{0}] foi banido porque está na lista de banidos do FAC." -Message.DumpfileSaved: "O arquivo de log foi salvo com sucesso na área de trabalho, nome do arquivo: {0}" -Message.KickedByNoFriendCode: "[{0}] foi expulso porque seu código de amigo não existe." -Message.AddedPlayerToBanList: "Adicionado [{0}] à lista de banidos" -Message.KickedByFAC: "[{0}] foi expulso pelo FAC, motivo: {1}" -Message.BanedByFAC: "[{0}] foi banido pelo FAC, motivo:{1}" +Mus.ReturnToSimplicity: "Retorno à Simplicidade" +Mus.ReturnToSimplicity2: "Retorno à Simplicidade (Versão Completa)" +Mus.ChasingDawn: "Perseguindo o Amanhecer" +Mus.StruggleAgainstFadingFlame: "Luta Contra a Chama que Desaparece" +Mus.Affinity: "Afinidade" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Função" +DisplayedRoleTag.PlayerIdentityTag: "Marcação de Identidade" +DisplayedRoleTag.Room: "Sala" + +PlayerIdentityTag.Hard_Cleared: "Confirmado" +PlayerIdentityTag.Silver_Clear: "Semi-Livre" +PlayerIdentityTag.Wolf_Bucket: "Poço de Lobos" +PlayerIdentityTag.No_Kill: "Sem Facada" +PlayerIdentityTag.Outside_Position: "Posição Externa" +PlayerIdentityTag.Inside_Position: "Posição Interna" # 通知 / Notifications -PlayerLeft: "[{0}] saiu do jogo" -PlayerLeftCuzTimeout: "[{0}] saiu do jogo devido a timeout de conexão" -PlayerKickByHost: "[{0}] foi expulso pelo anfitrião" -PlayerBanByHost: "[{0}] foi banido pelo anfitrião" -PlayerLeftCuzError: "[{0}] saiu do jogo devido a um erro" -PlayerLeftByAU-Anticheat: "[{0}] foi expulso pelos antitrapaças oficiais do AmongU (não relacionado ao FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] foi expulso porque tinha uma versão diferente do mod" +## 原版 +Notification.PlayerLeft: "[{0}] saiu do jogo" +Notification.PlayerLeftCuzTimeout: "[{0}] saiu do jogo por tempo limite de conexão" +Notification.PlayerKickByHost: "[{0}] foi expulso pelo anfitrião" +Notification.PlayerBanByHost: "[{0}] foi banido pelo anfitrião" +Notification.PlayerLeftCuzError: "[{0}] saiu do jogo devido a um erro" +Notification.PlayerLeftByAU-Anticheat: "[{0}] foi expulso pelo Anti-Cheat do Among Us (sem relação com FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] foi expulso por ter versão/mod diferente" +## 模组 +Notification.KickedByDenyName: "[{0}] foi expulso por usar apelido com palavras banidas" +Notification.DumpfileSaved: "Arquivo de log salvo na área de trabalho: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] foi removido pois esta sala proíbe Friend Codes anormais" +Notification.AddedPlayerToBanList: "Adicionar [{0}] à lista de banidos" +Notification.FPSSetTo: "Limite de FPS definido para: {0}" # 警告 / Warnings -Warning: "Aviso!" -Warning.MismatchedVersion: "{0}\ntem uma versão diferente de {1}" -Warning.AutoExitAtMismatchedVersion: "O anfitrião não tem ou tem uma versão diferente de {0}\nVocê será expulso em {1}" -Warning.InvalidRpc: "{0} foi expulso porque um RPC inválido foi recebido." -Warning.InvalidRpc_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (RPC Inválido:{1})" -Warning.SetName: "{0} foi expulso porque definiu o nome várias vezes." -Warning.SetName_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (Definir Nome Várias Vezes)" -Warning.SendQuickChat: "{0} foi expulso porque enviou várias mensagens rápidas em 3s" -Warning.SendQuickChat_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (Enviar Várias Mensagens Rápidas em 3s)" -Warning.InvalidSlothRPC: "Expulsado {0} porque um RPC ilegal foi recebido (Enviando ilegalmente o Rpc oficial: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (Enviando oficialmente RPC ilegal: {1})" -Warning.Cheater: "Expulsado {0} porque é suspeito de uso de trapaças" -Warning.Cheater_NotHost: "{0} é suspeito de uso de trapaças, Por favor, lembre o anfitrião de expulsá-lo" -Warning.CantKickDev: "Desculpe, você não pode expulsar o desenvolvedor" -Warning.RoomBroken: "Desculpe, esta sala foi comprometida. Por favor, vá para outra sala para continuar o jogo." +Warning: "Aviso" +Warning.MismatchedVersion: "[{0}]\npossui versão diferente de [{1}] instalada" +Warning.AutoExitAtMismatchedVersion: "Sua versão de [{0}] é diferente do anfitrião\nVocê será expulso em {1} segundos" +Warning.CantKickDev: "Desculpe, você não pode expulsar desenvolvedores" +Warning.RoomBroken: "Desculpe, esta sala sofreu ataque hacker, jogue em outra sala" +Warning.InvalidColor: "Jogador com cor inválida detectado" ## 错误等级 / Error Levels -ErrorLevel1: "Pode ocorrer bugs." -ErrorLevel2: "Isso pode ser um bug." -ErrorLevel3: "Esta versão não deveria ter sido lançada." +ErrorLevel1: "Pode causar múltiplos bugs simultaneamente" +ErrorLevel2: "Bugs podem ocorrer" +ErrorLevel3: "Versão não lançada" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Aviso: FAC detectou um alto nível de trapaças." -FAC.CheatDetected.LowLevel: "Aviso: FAC detectou um baixo nível de trapaças. Um dos jogadores está hackeando." -FAC.CheatDetected.FAC: "Usando programas de trapaça (por exemplo, AUM, YuMenu, SM, etc.)" +CheatDetected.HighLevel: "Aviso: FAC está defendendo contra trapaças de bombardeio" +CheatDetected.LowLevel: "Aviso: FAC detectou possível trapaceiro" +CheatDetected.UseCheat: "{0} usou programa de trapaça [{1}]" +CheatDetected.MayUseCheat: "{0} suspeito de usar mod [{1}] ou programa de trapaça" +CheatDetected.InvalidRpc: "[{0}] foi expulso por enviar dados inválidos (Rpc inválido: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] suspeito de trapaça, lembre o anfitrião de expulsá-lo (Rpc inválido: {1})" +CheatDetected.SetName: "[{0}] foi expulso por definir nome múltiplas vezes" +CheatDetected.SetName_NotHost: "[{0}] suspeito de trapaça, lembre o anfitrião de expulsá-lo (Definiu nome múltiplas vezes)" +CheatDetected.SendQuickChat: "[{0}] foi expulso por enviar múltiplas mensagens rápidas em 3 segundos" +CheatDetected.SendQuickChat_NotHost: "[{0}] suspeito de trapaça, lembre o anfitrião de expulsá-lo (Enviou múltiplas mensagens rápidas em 3 segundos)" +CheatDetected.InvalidSlothRPC: "[{0}] foi expulso por enviar dados ilegais (Rpc oficial enviado ilegalmente: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] suspeito de trapaça, lembre o anfitrião de expulsá-lo (Rpc oficial enviado ilegalmente: {1})" +CheatDetected.Overload: "[{0}] foi expulso por iniciar ataque de sobrecarga" +CheatDetected.Overload_NotHost: "[{0}] iniciou ataque de sobrecarga, esta sala está danificada, jogue em outra sala" +CheatDetected.Cheater: "[{0}] foi expulso por suspeita de trapaça" +CheatDetected.Cheater_NotHost: "[{0}] suspeito de trapaça, lembre o anfitrião de expulsá-lo" +CheatDetected.BanedByBanList: "[{0}] foi expulso por estar na lista de banidos" +CheatDetected.BanedByFACList: "[{0}] foi expulso por estar na lista de banidos do FAC" # 模组信息 / Mod Infos -Contributors: "Colaboradores" -Acknowledgement: "Agradecimentos" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Você foi expulso pelo antitrapaças.\n (o uso de módulos pode ser mal interpretado como trapaça)" -DCNotify.Banned: "Você não está autorizado a entrar nesta sala" -DCNotify.Kicked: "Você foi expulso da sala" -DCNotify.DCFromServer: "Você se desconectou do servidor.\nIsso pode ser devido à instabilidade em sua rede.\nTambém pode ser devido à instabilidade do servidor." -DCNotify.GameNotFound: "A sala atribuída não foi encontrada, a sala pode ter sido dissolvida\n ou verifique se você selecionou um servidor diferente da sala" -DCNotify.GameStarted: "O jogo já começou, por favor, espere até que ele termine" -DCNotify.GameFull: "A sala está cheia, por favor, tente novamente mais tarde" -DCNotify.IncorrectVersion: "Sua versão do Among Us é diferente desta sala" -DCNotify.Description: "Você foi expulso do jogo.\nMotivo: {0}" -DCNotify.DenyName: "Seu apelido contém caracteres irregulares" -DCNotify.BanList: "Você foi banido pelo anfitrião" -DCNotify.FACList: "Você foi banido pelo FAC" -DCNotify.CheatDetected: "Você foi detectado como suspeito de trapaça pelo FAC" -DCNotify.InvalidRPC: "Você pode ter instalado um mod diferente do anfitrião ou seu mod foi modificado maliciosamente" -DCNotify.ModVersionIncorrect: "Sua versão do mod é diferente do anfitrião" -DCNotify.LowLevel: "Seu nível não atinge o requisito desta sala" -DCNotify.NotLogin: "Jogadores não logados não são permitidos nesta sala" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Pressione F1 para visualizar a descrição de seu papel" +ModInfo.Contributors: "Contribuidores" +ModInfo.Acknowledgement: "Agradecimentos Especiais" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Você foi expulso pelo anti-cheat da InnerSloth" +DCNotify.Banned: "Você foi banido desta sala" +DCNotify.Kicked: "Você foi expulso desta sala" +DCNotify.DCFromServer: "Sua conexão com o servidor foi interrompida\nPode ser devido a rede instável\nou instabilidade/recusa de acesso do servidor" +DCNotify.GameNotFound: "Sala especificada não encontrada, pode ter fechado\nou verifique se selecionou servidor diferente da sala" +DCNotify.GameStarted: "Este jogo já começou, aguarde o término" +DCNotify.GameFull: "Esta sala está lotada, tente novamente mais tarde" +DCNotify.IncorrectVersion: "Sua versão do Among Us difere da sala" +DCNotify.Description: "Você foi expulso da sala\nMotivo: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Pressione F1 para ver descrição da função" +PressF2ToHidePane: "Pressione F2 para mostrar/ocultar painel" FakeTask: "Tarefa Falsa:" KillCount: "Assassinatos" # 复盘信息 / Last Results -RoleSummaryText: "Últimos Resultados:" -ShowResults: "Mostrar Últimos Resultados" -HideResults: "Ocultar Últimos Resultados" -NoInfoExists: "Nenhum último resultado disponível" -CrewsWin: "Time-Tripulante Vence" -CrewmatesWin: "Tripulantes Vencem" -CrewmatesWinBlurb: "A luz da verdade brilha na esperança!" -ImpsWin: "Time-Impostor Vence" -ImpostorsWin: "Impostores Vencem" -ImpostorsWinBlurb: "O mal transforma a verdade em cinzas" -HideSummaryTextToShowWinText: "Ocultar último resultado para visualizar texto de vitória" - -# 禁用公开 -DisabledByProgram: "Operações de sala pública foram desabilitadas pelo programa" -PublicNotAvailableOnThisVersion: "Salas públicas não estão disponíveis nesta versão do FinalSuspect" +Summary.Text: "Último Resumo:" +Summary.ShowResults: "Mostrar Último Resumo" +Summary.HideResults: "Ocultar Último Resumo" +Summary.NoInfoExists: "Nenhum Último Resumo válido existente" +Summary.CrewsWin: "Equipe-Tripulante Venceu" +Summary.ImpsWin: "Equipe-Impostor Venceu" +Outro.Crews_Win: "Vitória dos Tripulantes" +Outro.Crews_WinBlurb: "A luz da verdade brilha na esperança!" +Outro.Imps_Win: "Vitória dos Impostores" +Outro.Imps_WinBlurb: "O mal reduziu a verdade a cinzas" # 主页 / Main UI -FinalSuspectWelcomeText: "Desejando uma agradável experiência de jogo!" -ConnectToFinalSuspectServerFailed: "Falha ao conectar ao servidor do FinalSuspect" +FinalSuspectWelcomeText: "Desejamos uma agradável experiência de jogo!" +RetrieveVersionInfoFailed: "Falha ao obter informações do FinalSuspect" Website: "Site Oficial" MainMenuCredential: "{0} © 2025" -LShift: "Pressione LShift para voltar ao quarto anterior" -RShift: "Pressione RShift para entrar no quarto da área de transferência" -LobbyTimeDisplayText: "Tempo decorrido de existência" +LShift: "Lobby Anterior" +RShift: "Lobby da Área de Transferência" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" @@ -302,11 +340,26 @@ Server: "Servidor" Local: "Local" # 其他 / Other -HongKong: "HongKong" -FPSSetTo: "Taxa de quadros limitada definida para: {0}" +HongKong: "Hong Kong" BrowsingMode: "Modo de Navegação" Broken: "Quebrado" +Unknown: "Desconhecido" +Back: "Voltar" +Yes: "Sim" +No: "Não" +Cancel: "Cancelar" +Unload: "Trocar" +Retry: "Tentar Novamente" +PreviousPage: "Página Anterior" +NextPage: "Próxima Página" +Download: "Baixar" +Disable: "Desativar" +Delete: "Excluir" +Author: "Autor" +Close: "Fechar" # 身份 / Identity -Host: "Anfitrião" -Cheater: "Trapaceiro" +Id.Host: "Anfitrião" +Id.Cheater: "Trapaceiro" +Id.Developer: "Desenvolvedor" +Id.Contributor: "Contribuidor" \ No newline at end of file diff --git a/Assets/Languages/Dutch.yaml b/Assets/Languages/Dutch.yaml index 5f296254..1b0db9f1 100644 --- a/Assets/Languages/Dutch.yaml +++ b/Assets/Languages/Dutch.yaml @@ -1,293 +1,337 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "6" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostors" -TypeCrewmate: "Crewmates" +RoleType.Imp: "Impostor" +RoleType.Crew: "Bemanningslid" # 阵营 / Teams -TeamImpostor: "Team-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Team-Crewmate" +Team.Imp: "Team-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Team-Bemanningslid" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Er zijn {0} Impostors in ons team" -ImpostorNumImpOnly: "Er is maar één Impostor tussen de menigte" -ImpostorNumCrew: "Er zijn {0} Impostors onder ons" +ImpostorNum.Imp: "Er zijn {0} impostors in ons team" +ImpostorNum.Imp_Only: "Er is maar 1 impostor onder ons" +ImpostorNum.Crew: "Er zijn {0} impostors onder ons" # 阵营开场 -ImpostorIntroText: "Laat het kwaad de wereld omhullen!" -ImpostorIntroTextOnly: "Ik ben misschien alleen, maar ik bezit oneindige kracht!" -CrewmateIntroText: "Voltooien je taken, verenigen om die lastige situaties op te lossen!" - -## 原版职业 / Vanilla -Crewmate: "Crewmate" -Engineer: "Ingenieur" -Scientist: "Wetenschapper" -Tracker: "Tracker" -Noisemaker: "Geluidsmaker" -GuardianAngel: "Bewaker" -Impostor: "Impostor" -Shapeshifter: "Vormveranderaar" -Phantom: "Spook" -CrewmateGhost: "Crewmate Geest" -ImpostorGhost: "Impostor Geest" -CrewmateGhostBlurb: "Voltooien je taken" -ImpostorGhostBlurb: "Blijf sabotage plegen" -CrewmateGhostBlurbLong: "Voltooien taken, niet achterblijven!" -ImpostorGhostBlurbLong: "Sabotage faciliteiten, help overlevende impostors bij het behalen van de overwinning." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Overleef tot het einde!" -HnSEngineerBlurbLong: "Blijf in leven tot de tijd om is; taken voltooien verlengt de tijd. \nGebruik ventilaties en dreigingindicatoren om je te verbergen! \nAls de timer start, begint de Impostor met jagen!" -HnSImpostorBlurb: "Iedereen elimineren!" -HnSImpostorBlurbLong: "Vermoord alle bemanningsleden binnen de tijdslimiet! \nJe moet snel handelen! Ventilaties zijn niet toegankelijk. \nOp het laatste moment krijg je een snelheidsboost en trackingsuggesties!" -HnSCrewmateGhostBlurb: "Steun je teamgenoten" -HnSCrewmateGhostBlurbLong: "Ga! Steun! Voor! Je! Teamgenoten!" +IntroText.Imp: "Laat het kwaad DE WERELD DOMINEREN!" +IntroText.Imp_Only: "Alleen, maar met EINDELOZE KRACHT!" +IntroText.Crewmate: "Voltooi je taken en ontmasker de impostors!" + +# 原版职业 / Vanilla +Role.Crewmate: "Bemanningslid" +Role.Engineer: "Ingenieur" +Role.Scientist: "Wetenschapper" +Role.Tracker: "Tracker" +Role.Noisemaker: "Lawaaimaker" +Role.GuardianAngel: "Beschermengel" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Vormenwisselaar" +Role.Phantom: "Fantoom" +Role.CrewmateGhost: "Bemanningslid Spook" +Role.ImpostorGhost: "Impostor Spook" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Voltooi taken" +ImpostorGhostBlurb: "Blijf saboteren" +CrewmateGhostBlurbLong: "Voltooi taken, laat je team niet in de steek!\nGebruik 「Spoken」 om Impostors te zien en help Beschermengels Bemanningsleden beschermen!" +ImpostorGhostBlurbLong: "Saboteer faciliteiten, help overlevende impostors te winnen!" +HnSEngineerBlurb: "Voltooi taken en overleef tot het einde!" +HnSImpostorBlurb: "Slacht ze ALLEMAAL af!" +HnSCrewmateGhostBlurb: "Moedig je teamgenoten aan" +HnSCrewmateGhostBlurbLong: "Dood? Waar kijk je naar? Kun je taken doen? Zo niet...\nGA! JE! TEAMGENOTEN! AANMOEDIGEN!" # 死因 / Death Reason DeathReason.Kill: "Vermoord" DeathReason.Exile: "Verbannen" -DeathReason.Disconnect: "Verbroken verbinding" +DeathReason.Disconnect: "D/C" # 客户端选项 / Client Options FinalSuspectOptions: "Final Suspect Opties" -Back: "Terug" -Yes: "Ja" -No: "Nee" -UnlockFPS: "FPS Ontgrendelen" -ChangeOutfit: "Kleding Veranderen" -BeanMode: "Klassieke Boontje Modus" -HorseMode: "April Fool Paard Modus" -LongMode: "April Fool Lange Modus" -KickPlayerFriendCodeNotExist: "Spelers die niet zijn ingelogd uitschoppen." -KickPlayerWithDenyName: "Spelers met ongepaste bijnamen uitschoppen" -KickPlayerInBanList: "Gebannede spelers uitschoppen" -SpamDenyWord: "Ongepaste woorden blokkeren" -AutoStartGame: "Automatisch starten bij volle lobby" -AutoEndGame: "Na afloop automatisch terugkeren naar lobby" -SwitchVanilla: "Oorspronkelijke versie inschakelen" -DisableVanillaSound: "Among Us muziek uitschakelen" -DisableFAC: "Anti-cheat uitschakelen" -ShowPlayerInfo: "Spelerplatform en clientinformatie weergeven" -UseModCursor: "Gebruik Mod cursor" -FastBoot: "Modo de Inicio Rápido" -PrunkMode: "Prank Modus" -VersionCheat: "Mod-versie synchronisatiecontrole omzeilen" -GodMode: "God Modus" -NoGameEnd: "Geen eind van het spel" -EnableFinalSuspect: "「Final Suspect」 inschakelen" +ClientOption.UnlockFPS: "FPS Ontgrendelen" +ClientOption.SwitchOutfitType: "Outfittype Wijzigen" +ClientOption.KickPlayerWithAbnormalFriendCode: "Spelers met abnormale Friend Codes eruit gooien" +ClientOption.KickPlayerWithDenyName: "Spelers met verboden namen eruit gooien" +ClientOption.KickPlayerInBanList: "Verbannen spelers eruit gooien" +ClientOption.SpamDenyWord: "Verboden woorden blokkeren" +ClientOption.AutoStartGame: "Spel automatisch starten wanneer vol" +ClientOption.AutoEndGame: "Automatisch terugkeren naar lobby na spel" +ClientOption.SwitchVanilla: "Overschakelen naar Vanilla" +ClientOption.DisableVanillaSound: "Vanilla-spelmuziek uitschakelen" +ClientOption.EnableFAC: "Anti-Cheat inschakelen" +ClientOption.EnableGuardian: "Client Guardian inschakelen (Experimenteel)" +ClientOption.ShowPlayerInfo: "Spelerplatform & clientinfo tonen" +ClientOption.UseModCursor: "Mod-cursor gebruiken" +ClientOption.FastLaunchMode: "Snelstartmodus" +ClientOption.OfflineMode: "Offline-modus (Experimenteel)" +ClientOption.VersionCheat: "Versiesynchronisatiecontrole omzeilen" +ClientOption.GodMode: "God-modus" +ClientOption.NoGameEnd: "Geen Spel Einde" +ClientOption.EnableFinalSuspect: "「Final Suspect」 inschakelen" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Klassieke Modus" +Value.HorseMode: "1 April Paardenmodus" +Value.LongMode: "1 April Lange Modus" # 客户端功能 / Client Features FinalSuspectFeatures: "Final Suspect Functies" -UnloadMod: "Oorspronkelijke versie inschakelen" -UnloadWarning: "Waarschuwing\nOm het mod opnieuw in te schakelen, moet je het spel opnieuw opstarten.\nWil je toch doorgaan?" -CannotUnloadDuringGame: "Kan niet overschakelen naar oorspronkelijke versie tijdens het spel" -Cancel: "Annuleren" -Unload: "Ontladen" -DumpLog: "Log wegschrijven" -ClearAutoLogs: "Automatische log wissen" -SoundOptions: "Mijn Muziek" -AudioManagementOptions: "Geluidbeheer" -OnlyAvailableInMainMenu: "Alleen beschikbaar in hoofdmenu" +ClientFeature.UnloadMod: "Overschakelen naar Vanilla" +ClientFeature.DumpLog: "Log Dumpen" +ClientFeature.ClearAutoLogs: "Automatische Logs Wissen" +ClientFeature.MyMusic: "Mijn Muziek" +ClientFeature.ResourceManager: "Resourcebeheer" +ClientFeature.NameTagManager: "Naamlabelbeheer" +ClientFeature.MainMenuStyleManager: "Hoofdmenustijl Wijzigen" # 提示 / Tips -updatePleaseWait: "Even geduld aub..." -updateInProgress: "Bezig met bijwerken..." -DownloadingAudios: "Bezig met downloaden..." -Playing: "Aan het spelen..." -Parsing: "Analyseren..." -DownLoadSucceedNotice: "Download geslaagd!" -DownLoadFailureNotice: "Download mislukt =(" -PleaseWait: "Even geduld aub..." +Tip.Downloading: "Downloaden..." +Tip.PleaseWait: "Even geduld..." +Tip.Playing: "Afspelen..." +Tip.Parsing: "Verwerken..." +Tip.Updating: "Updaten..." +Tip.DownLoadFinished: "Download voltooid" +Tip.DownLoadSucceeded: "Download geslaagd!" +Tip.DownLoadFailed: "Download mislukt=(" +Tip.PackageExists: "Geïnstalleerd" +Tip.OnlyAvailableInMainMenu: "Alleen beschikbaar in Hoofdmenu" +Tip.HideSummaryTextToShowWinText: "Verberg Laatste Samenvatting om overwinningsbericht te zien" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Taalbestanden laden voltooid" +Tip.CheckingForFiles: "Bestandsintegriteit controleren..." +Tip.DownloadingResources: "Resourcebestanden downloaden..." +Tip.Loading: "Laden" +Tip.LoadingWithDot: "Laden..." +Tip.LoadingComplete: "Laden voltooid!" +## 切换原版 +Tip.UnloadWarning: "Waarschuwing!\nJe moet het spel herstarten om de gemodde versie te spelen.\nWeet je zeker dat je naar Vanilla wilt overschakelen?" +Tip.CannotUnloadDuringGame: "Kan niet overschakelen tijdens een spel." +## 资源管理 +Tip.ResourceManager: "Je kunt mod-compatibele en extra resources downloaden in 「Resourcebeheer」\nBestanden met \"Pre-\" zijn vooraf gedownloade resourcepakketten" +## 我的音乐 +Tip.MyMusic: "Je kunt mod-ondersteunde muziek downloaden in 「Resourcebeheer」 of eigen audiobestanden toevoegen in (Among Us/Final Suspect_Data/Musics). Als het lokale pad niet bestaat, wordt 「Bestand Ontbreekt」 weergegeven" +Tip.Incomplete_Music: "Onvolledig muziekbestand gedetecteerd" +Tip.Incomplete_SoundEffect: "Onvolledig geluidseffectbestand gedetecteerd" +Tip.Incomplete_Image: "Onvolledig afbeeldingsbestand gedetecteerd" +Tip.Incomplete: "Download het mod-compatibele resourcepakket in 「Hoofdmenu - Instellingen - Clientfuncties - Resourcebeheer」 voor betere ervaring" +## 名称标识管理 +Tip.TextContent: "Tekstinhoud" +Tip.TextSizeDescription: "Tekstgrootte (Standaard 100%)" +Tip.TextColorDescription: "Tekstkleur (Hex Kleurcode)\nLaat leeg indien niet nodig, vul meerdere in voor automatisch verloop\n" +Tip.PleaseEnterFriendCode: "Voer Friend Code in om nieuw naamlabel aan te koppelen" +Tip.FriendCodeAlreadyExist: "Deze Friend Code heeft al een naamlabel" +Tip.FriendCodeIncorrect: "Voer een geldige Friend Code in" +Tip.CustomNameTagHelp: "Je kunt naamlabels voor spelers toevoegen. Labels worden automatisch toegewezen wanneer de speler met de Friend Code join. Bewerken in een lobby is niet mogelijk.\nNiet-VIP's kunnen alleen opmerkingen toevoegen (VIP-functies nog in ontwikkeling)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Download 「Hoofdmenustijl Pakketten」 in 「Resourcebeheer」 en kies hier je favoriete stijl" + +# 更新结果 +UpdateResult.Succeed_Title: "Update Geslaagd" +UpdateResult.Succeed_Text: "Wordt geactiveerd na herstarten :)" +UpdateResult.Failed_Title: "Update Mislukt" +UpdateResult.Failed_Reason_NotFound: "Reden: {0}\nDeze bron is mogelijk tijdelijk onbeschikbaar, kies een andere bron" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Reden: Bestandsverificatiefout\nDe versie van deze bron is niet de nieuwste, kies een andere bron" +UpdateResult.Failed_Reason_Ping: "Reden: Downloadtime-out of onderbroken\nControleer je netwerk of update handmatig" # 更新检查 / Update Checker -Retry: "Opnieuw proberen" -updateCheckPopupTitle: "Controle op bijwerkingen" -updateCheckFailedRetry: "Controle op bijwerkingen mislukt :(\nOpnieuw proberen?" -updateCheckFailedExit: "Controle op bijwerkingen mislukt :(\nControleer je internetverbinding en probeer het opnieuw." +UpdateCheck.Popup_Title: "Updatecontrole" +UpdateCheck.Failed_Retry: "Updatecontrole mislukt :(\nOpnieuw proberen?" +UpdateCheck.Failed_Exit: "Updatecontrole mislukt :(\nControleer je netwerk!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Bijwerken" -updateNotice: "Bijwerken" -UpdateBySelfText: "Deze versie ondersteunt geen automatische bijwerkingen. Gelieve handmatig bij te werken." -updateButton: "Nu bijwerken" -updatePopupTitle: "Nu bijwerken" -updatePopupTitleFailed: "Bijwerken mislukt" -updatePopupTitleDone: "Bijwerken voltooid" +UpdateRemind.updatePopup: "Nu Updaten" +UpdateRemind.updateNotice: "Updateherinnering" +UpdateRemind.BySelf_Title: "Updatehint" +UpdateRemind.BySelf_Text: "Deze versie ondersteunt geen one-click update, update handmatig" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Selecteer een bron voor het bijwerken\nals je niet weet wat te selecteren, kies dan [Github]\nAls het bijwerken mislukt, selecteer [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Herstart het spel om de wijzigingen toe te passen :)" -updatePingFialed: "Reden: {0}\nDe geselecteerde bron kan tijdelijk niet beschikbaar zijn. Probeer een andere downloadbron." -updateFileMd5Incorrect: "Reden: Fout in bestandssom\nDe versie van dit kanaal is niet de nieuwste. Probeer een andere downloadbron." -downloadFailed: "Reden: Download tijd uitgelopen of onderbroken\nProbeer opnieuw na het wijzigen van uw netwerk of handmatig bij te werken." +UpdateSource.Choose: "Kies updatebron\nKies [Github] bij twijfel" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "We hebben een belangrijke update. Gelieve dit mod bij te werken.\nAnders kun je geen publieke kamers betreden." -CanNotJoinPublicRoomNoLatest: "We hebben een belangrijke update. Gelieve dit mod bij te werken.\nAnders kun je geen publieke kamers betreden." -ModBrokenMessage: "Mod-bestanden zijn beschadigd. Start het spel opnieuw of installeer dit mod opnieuw." -UnsupportedVersion: "Je versie van Among Us is niet compatibel met FinalSuspect.\nGelieve je spel bij te werken." +CanNotJoinPublicRoomNoLatest: "Belangrijke update beschikbaar, update deze mod\nAnders kun je geen openbare rooms joinen" +ModBrokenMessage: "Mod-bestanden gecrasht, herstart het spel of installeer opnieuw" +UnsupportedVersion: "Je Among Us-versie is incompatibel met FinalSuspect\nUpdate het spel" + +# 名称标识 +NameTag.DisplayName: "Opmerking" +NameTag.Title: "Titel" +NameTag.Prefix: "Voorvoegsel" +NameTag.Suffix: "Achtervoegsel" +NameTag.Name: "Naam" +NameTag.LastTag: "Extra Achtervoegsel" +NameTag.PreviewNotAvailable: " (Voorbeeld niet ondersteund) " +NameTag.CanNotEdit: " (Niet bewerkbaar) " +NameTag.RefreshPreview: "Voorbeeld Vernieuwen" +NameTag.SaveAndClose: "Opslaan en Sluiten" +NameTag.NewNameTag: "Nieuw" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Dageraad Jagen(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Tijd verstrijkt, noordelijke winter eindigt, lente herleeft.\nKijkend naar Mira's besneeuwde landschap, herleven herinneringen? Warme stromingen in het hart.\nWie de eindverdachte ook is, wiens handen de waarheid ook breken,\nIn een verfrissende ervaring, denk na en creëer nieuwe herinneringen.\nSpelen, plezier hebben staat voorop!!!\n\nGenoemd door: 一念旧情丶" +MainMenuStyle.Title_Security: "Rood Hart, Felle Liefde (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "We vallen niet, geven niet op, teleurstellen spelers niet, stoppen nooit met vooruitgang.\nBij koude blikken bewijzen we het aan iedereen!\nMet passie varen we opnieuw\n\nGenoemd door: Slok" +MainMenuStyle.Title_NewYear: "Verwantschap (Nieuwjaar)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortuin: Wensen verleendBand: Geweven in hartslagen\nHarmonie: Draden van genegenheidZaligheid: Vrede in elke ziel\nVoorbestemde banden smeden gedeelde vreugdeVer reizen, vreugde in eenheid vinden\nTopera koestert allen in vreugdeOns gebed—vreugde voor elk wezen\nMoge elke ziel zijn pad vinden in het nieuwe jaar\nMet onze zegeningen begin je aan je reis om je eigen legende te smeden!\n\nGenoemd door: Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Nieuwjaar)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Bloesems golven, zacht als wind, moge ieders hart zo wijd zijn, alles voorspoedig\"\n\"—Lentewinden krachtig, golven over mijlen, moge allen moedig vooraan staan, bloeiend\"\n\"—Welkom bij XtremeWave — Nieuwjaarsspecial!\"\n\nGenoemd door: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final Streeft naar Excellentie, Droom Beheerst Opkomende Golven!」\n\nGenoemd door: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Terugkijkend Aan Het Einde (Samenwerking)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Terugkijkend aan het einde, eindigt alles aan het einde」\n\nGenoemd door: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Niet Gedownload" +MainMenuStyle.NotApply: "Toepassen" +MainMenuStyle.Applied: "Toegepast" + +# 资源包 +Package.MainMenuStyle: "Hoofdmenustijl Pakket" # 音频播放 / Audio Playback -PlayMode0: "Een keer afspelen" -PlayMode1: "Eenmalig herhalen" -PlayMode2: "Willekeurig" -PlayMode3: "Aaneenvolgend" -Stop: "Stoppen" -CanPlay: "← Klik om af te spelen" -NoFound: "[Bestand ontbreekt]" -NextPage: "Volgende pagina" -PreviousPage: "Vorige pagina" - -# 音频添加 / Audio Addition -download: "Downloaden" -delete: "Verwijderen" -NewSound: "Nieuwe muziek toevoegen" -PleaseEnterMusic: "Geef de naam van de muziek op" -AudioManagementAlreadyExists: "Deze muzieknaam bestaat al" -NotAllowedMusic: "Het formaat van de muzieknaam is niet toegestaan" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Je kunt muziek downloaden die wordt ondersteund door XtremeWave of je eigen muziek toevoegen in 「Geluidbeheer」. Als je je eigen muziek toevoegt, zorg er dan voor dat je de muzieknaam invoert in 「Geluidbeheer」 en het bijbehorende audio-bestand plaats in de map 「Among Us/Final Suspect_Data/Resources/Sounds」 (ondersteunde formaten: .wav). Muziek kan worden afgespeeld in 「Mijn Muziek」." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Je kunt muziek downloaden die wordt ondersteund door XtremeWave of je eigen muziek toevoegen in 「Geluidbeheer」. Als het lokale muziekbestand ontbreekt, zal het weergeven '[Bestand ontbreekt]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Het huidige muziekbestand wordt gedetecteerd als incompleet" -AudioNYPro: "Voor een betere gaming ervaring, download onze muziek in 「Start-Instellingen-Meer Functies-Geluidbeheer」" +MusPlay.Mode0: "Eenmaal Afspelen" +MusPlay.Mode1: "Enkele Herhaling" +MusPlay.Mode2: "Willekeurige Afspeellijst" +MusPlay.Mode3: "Opeenvolgend Afspelen" +MusPlay.Stop: "Afspelen Stoppen" +MusPlay.CanPlay: "Klik om Af te spelen" +MusPlay.NoFound: "Bestand Ontbreekt" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(Gelukkig Chinees Nieuwjaar)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Vloedgolf" +Mus.TrailOfTruth: "Spoor van Waarheid" +Mus.Interlude: "Tussenspel" +Mus.Fractured: "Gebroken" +Mus.ElegyOfFracturedVow: "Elegie van Gebroken Gelofte" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] is verwijderd omdat zijn naam overeenkomt met [{1}]" -Message.BanedByBanList: "[{0}] is verbannen omdat hij eerder was verbannen." -Message.BanedByFACList: "[{0}] is verbannen omdat hij op de FAC-lijst van verbannen personen staat." -Message.DumpfileSaved: "Het logbestand is succesvol opgeslagen op het bureaublad, bestandsnaam: {0}" -Message.KickedByNoFriendCode: "[{0}] is verwijderd omdat zijn vriendencode niet bestaat." -Message.AddedPlayerToBanList: "Toegevoegd [{0}] aan de banlijst" -Message.KickedByFAC: "[{0}] is verwijderd door FAC, reden: {1}" -Message.BanedByFAC: "[{0}] is verwijderd door FAC, reden:{1}" +Mus.ReturnToSimplicity: "Terug naar Eenvoud" +Mus.ReturnToSimplicity2: "Terug naar Eenvoud (Volledige Ver.)" +Mus.ChasingDawn: "Dageraad Jagen" +Mus.StruggleAgainstFadingFlame: "Strijd Tegen Vervagende Vlam" +Mus.Affinity: "Verwantschap" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Rol" +DisplayedRoleTag.PlayerIdentityTag: "Identiteitstag" +DisplayedRoleTag.Room: "Kamer" + +PlayerIdentityTag.Hard_Cleared: "Bevestigd burger" +PlayerIdentityTag.Silver_Clear: "Waarschijnlijk burger" +PlayerIdentityTag.Wolf_Bucket: "Wolvenhol" +PlayerIdentityTag.No_Kill: "Geen moord" +PlayerIdentityTag.Outside_Position: "Buitenpositie" +PlayerIdentityTag.Inside_Position: "Binnenpositie" # 通知 / Notifications -PlayerLeft: "[{0}] heeft het spel verlaten" -PlayerLeftCuzTimeout: "[{0}] heeft het spel verlaten vanwege verbindingstimeout" -PlayerKickByHost: "[{0}] is verwijderd door de gastheer" -PlayerBanByHost: "[{0}] is verbannen door de gastheer" -PlayerLeftCuzError: "[{0}] heeft het spel verlaten vanwege een fout" -PlayerLeftByAU-Anticheat: "[{0}] is verwijderd door de officiële anti-cheat van AmongU (niet gerelateerd aan FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] is verwijderd omdat hij een andere versie van de mod had" +## 原版 +Notification.PlayerLeft: "[{0}] verliet het spel" +Notification.PlayerLeftCuzTimeout: "[{0}] verliet wegens verbindingstime-out" +Notification.PlayerKickByHost: "[{0}] eruit gegooid door host" +Notification.PlayerBanByHost: "[{0}] verbannen door host" +Notification.PlayerLeftCuzError: "[{0}] verliet wegens fout" +Notification.PlayerLeftByAU-Anticheat: "[{0}] eruit gegooid door Among Us Anti-Cheat (onafhankelijk van FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] eruit gegooid wegens verschillende versie/mod" +## 模组 +Notification.KickedByDenyName: "[{0}] eruit gegooid wegens verboden naam" +Notification.DumpfileSaved: "Logbestand opgeslagen op bureaublad: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] verwijderd wegens abnormale Friend Code" +Notification.AddedPlayerToBanList: "[{0}] aan banlijst toegevoegd" +Notification.FPSSetTo: "FPS-limiet ingesteld op: {0}" # 警告 / Warnings -Warning: "Waarschuwing!" -Warning.MismatchedVersion: "{0}\nheeft een andere versie van {1}" -Warning.AutoExitAtMismatchedVersion: "De gastheer heeft geen of een andere versie van {0}\nJe wordt over {1} verwijderd" -Warning.InvalidRpc: "{0} is verwijderd omdat een ongeldig RPC is ontvangen." -Warning.InvalidRpc_NotHost: "{0} wordt verdacht van het gebruik van cheats. Vraag de gastheer om hem te verwijderen (Ongeldige Rpc:{1})" -Warning.SetName: "{0} is verwijderd omdat hij meerdere keren een naam heeft ingesteld." -Warning.SetName_NotHost: "{0} wordt verdacht van het gebruik van cheats. Vraag de gastheer om hem te verwijderen (Meerdere keren naam instellen)" -Warning.SendQuickChat: "{0} is verwijderd omdat hij binnen 3 seconden meerdere snelberichten heeft verstuurd" -Warning.SendQuickChat_NotHost: "{0} wordt verdacht van het gebruik van cheats. Vraag de gastheer om hem te verwijderen (Meerdere snelberichten binnen 3 seconden versturen)" -Warning.InvalidSlothRPC: "{0} is verwijderd omdat een ongeldige RPC is ontvangen (Onrechtmatig verzenden van officiële Rpc: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} wordt verdacht van het gebruik van cheats. Vraag de gastheer om hem te verwijderen (Onrechtmatig verzenden van officiële Rpc: {1})" -Warning.Cheater: "{0} is verwijderd omdat er wordt vermoed dat ze弊用作弊手段" -Warning.Cheater_NotHost: "{0} wordt verdacht van het gebruik van cheats. Verzoek de host hen te verwijderen" -Warning.CantKickDev: "Sorry, je kunt de ontwikkelaar niet verwijderen" -Warning.RoomBroken: "Sorry, deze kamer is gehackt. Ga naar een andere kamer om verder te spelen." +Warning: "Waarschuwing" +Warning.MismatchedVersion: "[{0}]\nheeft andere versie [{1}] geïnstalleerd" +Warning.AutoExitAtMismatchedVersion: "Je versie van [{0}] verschilt van de host\nJe wordt eruit gegooid over {1} seconden" +Warning.CantKickDev: "Je kunt ontwikkelaars niet eruit gooien" +Warning.RoomBroken: "Deze room is gehackt, speel in een andere room" +Warning.InvalidColor: "Speler met ongeldige kleur gedetecteerd" ## 错误等级 / Error Levels -ErrorLevel1: "Er kunnen bugs optreden." -ErrorLevel2: "Dit kan een bug zijn." -ErrorLevel3: "Deze versie had niet uitgebracht moeten worden." +ErrorLevel1: "Kan meerdere bugs veroorzaken" +ErrorLevel2: "Bugs mogelijk" +ErrorLevel3: "Niet-uitgegeven versie" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Waarschuwing: FAC heeft een hoog niveau van cheats gedetecteerd." -FAC.CheatDetected.LowLevel: "Waarschuwing: FAC heeft een laag niveau van cheats gedetecteerd. Een van de spelers hackt." -FAC.CheatDetected.FAC: "Het gebruik van cheatprogramma's (bijvoorbeeld AUM, YuMenu, SM, enz.)" +CheatDetected.HighLevel: "Waarschuwing: FAC verdedigt tegen bombcheats" +CheatDetected.LowLevel: "Waarschuwing: FAC detecteerde mogelijke cheater" +CheatDetected.UseCheat: "{0} gebruikte cheatprogramma [{1}]" +CheatDetected.MayUseCheat: "{0} verdacht van mod [{1}] of cheatprogramma" +CheatDetected.InvalidRpc: "[{0}] eruit gegooid wegens ongeldige data (Ongeldige Rpc: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] cheatverdacht, vraag host om eruit te gooien (Ongeldige Rpc: {1})" +CheatDetected.SetName: "[{0}] eruit gegooid wegens meerdere naamsets" +CheatDetected.SetName_NotHost: "[{0}] cheatverdacht, vraag host om eruit te gooien (Meerdere naamsets)" +CheatDetected.SendQuickChat: "[{0}] eruit gegooid wegens snelle berichten" +CheatDetected.SendQuickChat_NotHost: "[{0}] cheatverdacht, vraag host om eruit te gooien (Snelle berichten binnen 3s)" +CheatDetected.InvalidSlothRPC: "[{0}] eruit gegooid wegens illegale data (Illegale Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] cheatverdacht, vraag host om eruit te gooien (Illegale Rpc: {1})" +CheatDetected.Overload: "[{0}] eruit gegooid wegens overloadaanval" +CheatDetected.Overload_NotHost: "[{0]} startte overloadaanval, room beschadigd - speel elders" +CheatDetected.Cheater: "[{0}] eruit gegooid wegens cheatverdacht" +CheatDetected.Cheater_NotHost: "[{0}] cheatverdacht, vraag host om eruit te gooien" +CheatDetected.BanedByBanList: "[{0}] eruit gegooid wegens banlijst" +CheatDetected.BanedByFACList: "[{0}] eruit gegooid wegens FAC-banlijst" # 模组信息 / Mod Infos -Contributors: "Bijdragers" -Acknowledgement: "Erkenning" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Je bent verwijderd door de anti-cheat.\n (het gebruik van mods kan worden geïnterpreteerd als cheating)" -DCNotify.Banned: "Je bent niet toegestaan om deze kamer te betreden" -DCNotify.Kicked: "Je bent verwijderd uit de kamer" -DCNotify.DCFromServer: "Je bent verbonden met de server verbroken.\n Dit kan worden veroorzaakt door instabiliteit in je netwerk.\n Dit kan ook worden veroorzaakt door serverinstabiliteit." -DCNotify.GameNotFound: "De toegewezen kamer is niet gevonden, de kamer kan zijn opgeheven\n of controleer of je een andere server hebt geselecteerd dan de kamer" -DCNotify.GameStarted: "Het spel is al begonnen, wacht tot het afgelopen is" -DCNotify.GameFull: "De kamer is vol, probeer het later opnieuw" -DCNotify.IncorrectVersion: "Je versie van Among Us is anders dan deze kamer" -DCNotify.Description: "Je bent verwijderd uit het spel.\nReden: {0}" -DCNotify.DenyName: "Je bijnaam bevat onregelmatige tekens" -DCNotify.BanList: "Je bent verbannen door de gastheer" -DCNotify.FACList: "Je bent verbannen door FAC" -DCNotify.CheatDetected: "Je bent verdacht van cheating gedetecteerd door FAC" -DCNotify.InvalidRPC: "Je hebt mogelijk een andere mod geïnstalleerd dan de gastheer of je mod is kwaadaardig gewijzigd" -DCNotify.ModVersionIncorrect: "Je mod-versie is anders dan die van de gastheer" -DCNotify.LowLevel: "Je niveau voldoet niet aan de eisen van deze kamer" -DCNotify.NotLogin: "Niet-ingevoerde spelers zijn niet toegestaan in deze kamer" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Druk op F1 om je rolbeschrijving te bekijken" -FakeTask: "Valse taak:" +ModInfo.Contributors: "Bijdragers" +ModInfo.Acknowledgement: "Speciale Dank" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Je werd eruit gegooid door InnerSloth's anti-cheat" +DCNotify.Banned: "Je bent verbannen uit deze room" +DCNotify.Kicked: "Je werd eruit gegooid uit deze room" +DCNotify.DCFromServer: "Verbinding met server verbroken\nMogelijk door onstabiel netwerk\nof serverproblemen" +DCNotify.GameNotFound: "Room niet gevonden, mogelijk gesloten\nof verkeerde server geselecteerd" +DCNotify.GameStarted: "Spel is al begonnen, wacht op einde" +DCNotify.GameFull: "Room vol, probeer later opnieuw" +DCNotify.IncorrectVersion: "Je Among Us-versie verschilt van de room" +DCNotify.Description: "Je werd uit de room gegooid\nReden: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Druk F1 voor rolomschrijving" +PressF2ToHidePane: "Druk F2 om paneel te tonen/verbergen" +FakeTask: "Nep-taak:" KillCount: "Moorden" # 复盘信息 / Last Results -RoleSummaryText: "Laatste resultaten:" -ShowResults: "Laatste resultaten weergeven" -HideResults: "Laatste resultaten verbergen" -NoInfoExists: "Geen laatste resultaten beschikbaar" -CrewsWin: "Team-Crewmate Wint" -CrewmatesWin: "Crewmates Winnen" -CrewmatesWinBlurb: "Het licht van de waarheid schijnt in de hoop!" -ImpsWin: "Team-Impostor Wint" -ImpostorsWin: "Impostors Winnen" -ImpostorsWinBlurb: "Het kwaad verandert de waarheid in as" -HideSummaryTextToShowWinText: "Verberg laatste resultaat om overwinningstekst te bekijken" - -# 禁用公开 -DisabledByProgram: "Openbare kameroperaties zijn uitgeschakeld door het programma" -PublicNotAvailableOnThisVersion: "Openbare kamers zijn niet beschikbaar in deze versie van FinalSuspect" +Summary.Text: "Laatste Samenvatting:" +Summary.ShowResults: "Toon Laatste Samenvatting" +Summary.HideResults: "Verberg Laatste Samenvatting" +Summary.NoInfoExists: "Geen geldige samenvatting" +Summary.CrewsWin: "Team-Bemanningslid Wint" +Summary.ImpsWin: "Team-Impostor Wint" +Outro.Crews_Win: "Bemanningsleden Winnen" +Outro.Crews_WinBlurb: "Het licht der waarheid schijnt in hoop!" +Outro.Imps_Win: "Impostors Winnen" +Outro.Imps_WinBlurb: "Het kwaad verpulverde de waarheid" # 主页 / Main UI -FinalSuspectWelcomeText: "Wens je een aangename gaming ervaring!" -ConnectToFinalSuspectServerFailed: "Verbinding met FinalSuspect-server mislukt" -Website: "Officiële website" +FinalSuspectWelcomeText: "Een prettige speelervaring gewenst!" +RetrieveVersionInfoFailed: "FinalSuspect-info ophalen mislukt" +Website: "Officiële Website" MainMenuCredential: "{0} © 2025" -LShift: "Drücke LShift um in den vorherigen Raum zurückzukehren" -RShift: "Drücke RShift um in den Zwischenablage-Raum zu gehen" -LobbyTimeDisplayText: "Verstreken tijd van bestaan" +LShift: "Vorige Lobby" +RShift: "Klembord Lobby" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" @@ -297,18 +341,25 @@ Local: "Lokaal" # 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Framerate beperking ingesteld op: {0}" -BrowsingMode: "Browsmodus" +BrowsingMode: "Blader Modus" Broken: "Gebroken" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Vertalingen laden voltooid!" -CheckingForFiles: "Controleren op integriteit van bronbestanden..." -DownloadingResources: "Bronbestanden downloaden..." -Loading: "Laden" -LoadingWithDot: "Laden..." -LoadingComplete: "Laden voltooid!" +Unknown: "Onbekend" +Back: "Terug" +Yes: "Ja" +No: "Nee" +Cancel: "Annuleren" +Unload: "Wisselen" +Retry: "Opnieuw Proberen" +PreviousPage: "Vorige Pagina" +NextPage: "Volgende Pagina" +Download: "Downloaden" +Disable: "Uitschakelen" +Delete: "Verwijderen" +Author: "Auteur" +Close: "Sluiten" # 身份 / Identity -Host: "Gastheer" -Cheater: "Cheater" +Id.Host: "Host" +Id.Cheater: "Cheater" +Id.Developer: "Ontwikkelaar" +Id.Contributor: "Bijdrager" \ No newline at end of file diff --git a/Assets/Languages/English.yaml b/Assets/Languages/English.yaml index 8b84b33a..768213f8 100644 --- a/Assets/Languages/English.yaml +++ b/Assets/Languages/English.yaml @@ -1,56 +1,51 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "0" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostors" -TypeCrewmate: "Crewmates" +RoleType.Imp: "Impostor" +RoleType.Crew: "Crewmate" # 阵营 / Teams -TeamImpostor: "Team-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Team-Crewmate" +Team.Imp: "Team-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Team-Crewmate" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "There Are {0} Impostors In Our Team" -ImpostorNumImpOnly: "There Is Only 1 Impostor Among The Crowd" -ImpostorNumCrew: "There Are {0} Impostors Among Us" +ImpostorNum.Imp: "There are {0} impostors in our team" +ImpostorNum.Imp_Only: "There is only 1 impostor among the crowd" +ImpostorNum.Crew: "There are {0} impostors among us" # 阵营开场 -ImpostorIntroText: "Let Evil Envelop the World!" -ImpostorIntroTextOnly: "I May Be Alone, But I Possess Infinite Strength!" -CrewmateIntroText: "Complete Your Tasks, Unite to Solve Those Troublesome Situations!" - -## 原版职业 / Vanilla -Crewmate: "Crewmate" -Engineer: "Engineer" -Scientist: "Scientist" -Tracker: "Tracker" -Noisemaker: "Noisemaker" -GuardianAngel: "Guardian Angel" -Impostor: "Impostor" -Shapeshifter: "Shapeshifter" -Phantom: "Phantom" -CrewmateGhost: "Crewmate Ghost" -ImpostorGhost: "Impostor Ghost" -CrewmateGhostBlurb: "Complete your tasks" -ImpostorGhostBlurb: "Continue sabotaging facilities" -CrewmateGhostBlurbLong: "Complete tasks, don't lag behind!" -ImpostorGhostBlurbLong: "Sabotage facilities, assist surviving impostors in achieving victory." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Survive until the end!" -HnSEngineerBlurbLong: "Stay alive until time runs out; completing tasks will extend the time. \nUse vents and threat indicators to hide! \nOnce the timer starts, the Impostor will begin hunting you!" -HnSImpostorBlurb: "Eliminate everyone!" -HnSImpostorBlurbLong: "Kill all crew members within the time limit! \nYou must act quickly! Vents are not accessible. \nAt the last moment, you'll receive a speed boost and tracking hints!" -HnSCrewmateGhostBlurb: "Cheer on your teammates" -HnSCrewmateGhostBlurbLong: "Go! Cheer! For! Your! Teammates!" +IntroText.Imp: "Let evils DOMINATE THE WORLD!" +IntroText.Imp_Only: "Though alone, you have ENDLESS POWER!" +IntroText.Crewmate: "Complete your tasks and find out the impostors!" + +# 原版职业 / Vanilla +Role.Crewmate: "Crewmate" +Role.Engineer: "Engineer" +Role.Scientist: "Scientist" +Role.Tracker: "Tracker" +Role.Noisemaker: "Noisemaker" +Role.GuardianAngel: "Guardian Angel" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Shapeshifter" +Role.Phantom: "Phantom" +Role.CrewmateGhost: "Crewmate Ghost" +Role.ImpostorGhost: "Impostor Ghost" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Complete tasks" +ImpostorGhostBlurb: "Continue sabotaging" +CrewmateGhostBlurbLong: "Complete tasks, don't drag the team down!\nUse 「Haunting」 to see Impostors,and help Guardian Angels protect Crewmates!" +ImpostorGhostBlurbLong: "Sabotage facilities, assist surviving impostors to win!" +HnSEngineerBlurb: "Complete tasks and survive to the end!" +HnSImpostorBlurb: "Slaughter them ALL!" +HnSCrewmateGhostBlurb: "Cheer up your teammates" +HnSCrewmateGhostBlurbLong: "Dead? What are you looking at? Can you do tasks? Since not...\nGO! CHEER! FOR! YOUR! TEAMMATES!" # 死因 / Death Reason DeathReason.Kill: "Killed" @@ -59,235 +54,284 @@ DeathReason.Disconnect: "D/C" # 客户端选项 / Client Options FinalSuspectOptions: "Final Suspect Options" -Back: "Back" -Yes: "Yes" -No: "No" -UnlockFPS: "Unlock FPS" -ChangeOutfit: "Change Outfit" -BeanMode: "Classic Bean Mode" -HorseMode: "April Fools Horse Mode" -LongMode: "April Fools Long Mode" -KickPlayerFriendCodeNotExist: "Kick Players Who Are Not Logged In." -KickPlayerWithDenyName: "Kick Players Using Inappropriate Nicknames" -KickPlayerInBanList: "Kick Banned Players" -SpamDenyWord: "Block Inappropriate Words" -AutoStartGame: "Auto Start at Full Lobby" -AutoEndGame: "Auto-return to Lobby at End" -SwitchVanilla: "Switch Vanilla" -DisableVanillaSound: "Disable Among U's Musics" -DisableFAC: "Disable Anti-Cheat" -ShowPlayerInfo: "Display Player Platform and Client Information" -UseModCursor: "Use Mod Cursor" -FastBoot: "Fast Launch Mode" -PrunkMode: "Prank Mode" -VersionCheat: "Bypass Mod Version Sync Check" -GodMode: "God Mode" -NoGameEnd: "No Game End" -EnableFinalSuspect: "Enable「Final Suspect」" +ClientOption.UnlockFPS: "Unlock FPS" +ClientOption.SwitchOutfitType: "Switch Outfit Type" +ClientOption.KickPlayerWithAbnormalFriendCode: "Kick players with abnormal Friend Codes" +ClientOption.KickPlayerWithDenyName: "Kick players using banned nicknames" +ClientOption.KickPlayerInBanList: "Kick banned players" +ClientOption.SpamDenyWord: "Block banned words" +ClientOption.AutoStartGame: "Auto-start the game when full" +ClientOption.AutoEndGame: "Auto-return to lobby when game ends" +ClientOption.SwitchVanilla: "Switch to Vanilla" +ClientOption.DisableVanillaSound: "Disable Vanilla game music" +ClientOption.EnableFAC: "Enable Anti-Cheat" +ClientOption.EnableGuardian: "Enable Client Guardian (Experimental)" +ClientOption.ShowPlayerInfo: "Show player platform & client info" +ClientOption.UseModCursor: "Use mod cursor" +ClientOption.FastLaunchMode: "Fast Launch Mode" +ClientOption.OfflineMode: "Offline Mode (Experimental)" +ClientOption.VersionCheat: "Bypass version sync check" +ClientOption.GodMode: "God Mode" +ClientOption.NoGameEnd: "No Game End" +ClientOption.EnableFinalSuspect: "Enable 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Classic Mode" +Value.HorseMode: "April Fools' Horse Mode" +Value.LongMode: "April Fools' Long Mode" # 客户端功能 / Client Features FinalSuspectFeatures: "Final Suspect Features" -UnloadMod: "Switch Vanilla" -UnloadWarning: "Warning\nTo re-enable the mod, you must restart the game.\nWould you like to continue anyway?" -CannotUnloadDuringGame: "Cannot switch to vanilla in game" -Cancel: "Cancel" -Unload: "Unload" -DumpLog: "Output Log" -ClearAutoLogs: "Clear Auto Log" -SoundOptions: "My Musics" -AudioManagementOptions: "Audio Management" -OnlyAvailableInMainMenu: "Only Available In Main Menu" +ClientFeature.UnloadMod: "Switch to Vanilla" +ClientFeature.DumpLog: "Dump Log" +ClientFeature.ClearAutoLogs: "Clear Auto Logs" +ClientFeature.MyMusic: "My Music" +ClientFeature.ResourceManager: "Resource Manager" +ClientFeature.NameTagManager: "Name Tag Manager" +ClientFeature.MainMenuStyleManager: "Switch Main Menu Style" # 提示 / Tips -updatePleaseWait: "Please wait..." -updateInProgress: "Update in progress..." -DownloadingAudios: "Downloading..." -Playing: "Playing..." -Parsing: "Analyzing..." -DownLoadSucceedNotice: "Download successful!" -DownLoadFailureNotice: "Download failed =(" -PleaseWait: "Please wait..." +Tip.Downloading: "Downloading..." +Tip.PleaseWait: "Please wait..." +Tip.Playing: "Playing..." +Tip.Parsing: "Parsing..." +Tip.Updating: "Updating..." +Tip.DownLoadFinished: "Download finished" +Tip.DownLoadSucceeded: "Download succeeded!" +Tip.DownLoadFailed: "Download failed=(" +Tip.PackageExists: "Installed" +Tip.OnlyAvailableInMainMenu: "Only available on Main Menu" +Tip.HideSummaryTextToShowWinText: "Hide Last Summary to view victory text" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Language files loading completes" +Tip.CheckingForFiles: "Verifying resource file integrity..." +Tip.DownloadingResources: "Downloading resource files..." +Tip.Loading: "Loading" +Tip.LoadingWithDot: "Loading..." +Tip.LoadingComplete: "Loading complete!" +## 切换原版 +Tip.UnloadWarning: "Warning!\nYou must restart the game if you want to play modded version.\nAre you sure that you want to switch to Vanilla?" +Tip.CannotUnloadDuringGame: "Cannot switch to Vanilla when a game starts." +## 资源管理 +Tip.ResourceManager: "You can download mod-compatible and additional resources in 「Resource Manager」\nFiles prefixed with \"Pre-\" are pre-download resource packages" +## 我的音乐 +Tip.MyMusic: "You can download mod-supported music in 「Resource Manager」 or add your favorite audio files in the folder (Among Us/Final Suspect_Data/Musics). If the local resource path for music does not exist, 「File Missing」 will be displayed" +Tip.Incomplete_Music: "Incomplete music file detected" +Tip.Incomplete_SoundEffect: "Incomplete sound effect file detected" +Tip.Incomplete_Image: "Incomplete image file detected" +Tip.Incomplete: "To improve your gaming experience, please download the mod-compatible resource package in 「Main Menu - Settings - Client Features - Resource Manager」" +## 名称标识管理 +Tip.TextContent: "Text Content" +Tip.TextSizeDescription: "Text Size (Default 100%)" +Tip.TextColorDescription: "Text Color (Hex Color Code)\nLeave blank if not required, fill multiple for automatic gradient\n" +Tip.PleaseEnterFriendCode: "Please enter the Friend Code to bind the new name tag to" +Tip.FriendCodeAlreadyExist: "This Friend Code already has a name tag" +Tip.FriendCodeIncorrect: "Please enter a valid Friend Code" +Tip.CustomNameTagHelp: "You can add name tags for any player. Tags will be automatically assigned when the player bind to the Friend Code joins. But you cannot edit tags after entering a lobby.\nNon-VIPs can only add remarks (VIP functions have not developed yet)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "You can download 「Main Menu Style Packs」 in 「Resource Manager」 to get main menu resources, and choose your favorite main menu style here" + +# 更新结果 +UpdateResult.Succeed_Title: "Update Succeeded" +UpdateResult.Succeed_Text: "It will be activated after restarting the game :)" +UpdateResult.Failed_Title: "Update Failed" +UpdateResult.Failed_Reason_NotFound: "Reason: {0}\nThis source may be temporarily unavailable, please switch download source and retry" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Reason: File verification error\nThe file version from this source is not the latest, please switch download source and retry" +UpdateResult.Failed_Reason_Ping: "Reason: Download update timed out or interrupted\nPlease check your network and retry or update manually" # 更新检查 / Update Checker -Retry: "Retry" -updateCheckPopupTitle: "Update Check" -updateCheckFailedRetry: "Update check failed :(\nRetry?" -updateCheckFailedExit: "Update check failed :(\nPlease check your network connection and try again." +UpdateCheck.Popup_Title: "Update Check" +UpdateCheck.Failed_Retry: "Failed to check for updates :(\nRetry?" +UpdateCheck.Failed_Exit: "Failed to check for updates :(\nPlease check your network and retry!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Update Reminder" -updateNotice: "Update Reminder" -UpdateBySelfText: "This version does not support one-click updates. Please update manually." -updateButton: "Update Now" -updatePopupTitle: "Update Now" -updatePopupTitleFailed: "Update Failed" -updatePopupTitleDone: "Update Completed" +UpdateRemind.updatePopup: "Update Now" +UpdateRemind.updateNotice: "Update Reminder" +UpdateRemind.BySelf_Title: "Update Hint" +UpdateRemind.BySelf_Text: "This version does not support one-click update, please update manually" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Please select a channel to update\nif you do not know what to select, please select the [Github]\nIf the update fails, select [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Restart the game to apply changes :)" -updatePingFialed: "Reason: {0}\nThe selected channel may be temporarily unavailable. Please try switching download channels." -updateFileMd5Incorrect: "Reason: File checksum error\nThe file version from this channel is not the latest. Please try switching download channels." -downloadFailed: "Reason: Download timeout or interrupted\nPlease retry after changing your network or update manually." +UpdateSource.Choose: "Please choose update source\nIf unsure, choose [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "We have an important update. Please update this mod.\nOtherwise, you cannot join public rooms." -CanNotJoinPublicRoomNoLatest: "We have an important update. Please update this mod.\nOtherwise, you cannot join public rooms." -ModBrokenMessage: "Mod files are corrupted. Please restart the game or reinstall this mod." -UnsupportedVersion: "Your Among Us version is incompatible with FinalSuspect.\nPlease update your game." +CanNotJoinPublicRoomNoLatest: "We have an important update, please update this mod\nOtherwise you cannot join public rooms" +ModBrokenMessage: "Mod files crashed, please restart the game or reinstall this mod" +UnsupportedVersion: "Your Among Us version is incompatible with FinalSuspect\nPlease update the game" + +# 名称标识 +NameTag.DisplayName: "Remark" +NameTag.Title: "Title" +NameTag.Prefix: "Prefix" +NameTag.Suffix: "Suffix" +NameTag.Name: "Name" +NameTag.LastTag: "Additional Suffix" +NameTag.PreviewNotAvailable: " (Preview not supported) " +NameTag.CanNotEdit: " (Not editable) " +NameTag.RefreshPreview: "Refresh Preview" +NameTag.SaveAndClose: "Save and Exit" +NameTag.NewNameTag: "New" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Chasing Dawn(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "As time passes,northern winter ends, and spring revives.\nGazing at Mira's snowy scenery, are memories awakened? Warm streams flow in the heart.\nNo matter who is the final suspect, no matter whose hands fracture the truth,\nIn a refreshing new experience, brainstorm and leave behind new happy memories.\nPlaying games, having fun is the most important!!!\n\nNamed by: 一念旧情丶" +MainMenuStyle.Title_Security: "Red Heart, Fierce Love (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "We will not fall, not give up, not disappoint players, and never stop progressing.\nIf met with cold stares, we will prove it to everyone!\nWith passion, we set sail again\n\nNamed by: Slok" +MainMenuStyle.Title_NewYear: "Affinity (Spring Festival)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortune: Wishes bestowed Bond: Woven in heartbeats\nHarmony: Threads of affection Bliss: Peace in every soul\nFated bonds forge shared joy Journey far, find joy in unity\nPrime era cradles all in mirth Our prayer—joy for every being\nMay each soul find their destined path in the coming year\nWith our blessings lighting your way, embark on your journey to forge your own legend!\n\nNamed by: Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Spring Festival)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Flowers bloom like waves, gentle as the wind, wishing everyone's heart as vast as the wind, all things go smoothly\"\n\"—Spring winds surge mightily, waves surge for miles, wishing everyone stands bravely at the forefront, thriving and prospering\"\n\"—Welcome to XtremeWave — Spring Festival Special Program!\"\n\nNamed by: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final Striving Toward Excellence, Dream Commanding Surging Waves!」\n\nNamed by: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "When Looking Back At The End (Collaboration)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Looking back at the end, it all ends at the end」\n\nNamed by: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Not Downloaded" +MainMenuStyle.NotApply: "Apply" +MainMenuStyle.Applied: "Applied" + +# 资源包 +Package.MainMenuStyle: "Main Menu Style Pack" # 音频播放 / Audio Playback -PlayMode0: "Play Once" -PlayMode1: "Repeat Single" -PlayMode2: "Shuffle" -PlayMode3: "Sequential" -Stop: "Stop" -CanPlay: "← Click to Play" -NoFound: "[File Missing]" -NextPage: "Next Page" -PreviousPage: "Previous Page" - -# 音频添加 / Audio Addition -download: "Download" -delete: "Delete" -NewSound: "Add New Music" -PleaseEnterMusic: "Please Enter Music Name" -AudioManagementAlreadyExists: "This music name already exists" -NotAllowedMusic: "Music name format is not allowed" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "You can download XtremeWave supported music or add your own music in 「Audio Management」. When adding your own music, ensure you add the music name in 「Audio Management」 and place the corresponding audio file in the 「Among Us/Final Suspect_Data/Resources/Sounds」 folder (supported formats: .wav). Music can be played in 「My Music」." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "You can download XtremeWave supported music or add your own music in 「Audio Management」. If the local music resource path is missing, it will display '[File Missing]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Current music file detected as incomplete" -AudioNYPro: "For an enhanced gaming experience, download our audio in 「Home-Settings-More Features-Audio Management」" +MusPlay.Mode0: "Play Once" +MusPlay.Mode1: "Single Loop" +MusPlay.Mode2: "List Random" +MusPlay.Mode3: "Sequential Play" +MusPlay.Stop: "Stop Playback" +MusPlay.CanPlay: "Click to Play" +MusPlay.NoFound: "File Missing" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(Happy Chinese New Year)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" +Mus.TrailOfTruth: "Trail of Truth" Mus.Interlude: "Interlude" Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.ElegyOfFracturedVow: "Elegy of Fractured Vow" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" +Mus.ReturnToSimplicity: "return to simplicity" +Mus.ReturnToSimplicity2: "return to simplicity (Full Ver.)" +Mus.ChasingDawn: "Chasing Dawn" +Mus.StruggleAgainstFadingFlame: "Struggle Against Fading Flame" Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] was kicked because its name matched [{1}]" -Message.BanedByBanList: "[{0}] was banned because they were banned in the past." -Message.BanedByFACList: "[{0}] has been banned because he is in FAC list of Banned people." -Message.DumpfileSaved: "The log file was successfully saved to the desktop, filename: {0}" -Message.KickedByNoFriendCode: "[{0}] was kicked because their friend code does not exist." -Message.AddedPlayerToBanList: "Added [{0}] to the ban list" -Message.KickedByFAC: "[{0}]Kicked by FAC, reason: {1}" -Message.BanedByFAC: "[{0}]Kicked by FAC, reason:{1}" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Role" +DisplayedRoleTag.PlayerIdentityTag: "Player Identity Tag" +DisplayedRoleTag.Room: "Room" + +PlayerIdentityTag.Hard_Cleared: "Confirmed Good" +PlayerIdentityTag.Silver_Clear: "Soft Cleared" +PlayerIdentityTag.Wolf_Bucket: "Wolf Pool" +PlayerIdentityTag.No_Kill: "No Kill" +PlayerIdentityTag.Outside_Position: "Outside Position" +PlayerIdentityTag.Inside_Position: "Inside Position" # 通知 / Notifications -PlayerLeft: "[{0}] left the game" -PlayerLeftCuzTimeout: "[{0}]left the game due to connection timeout" -PlayerKickByHost: "[{0}] kicked by host" -PlayerBanByHost: "[{0}] banned by host" -PlayerLeftCuzError: "[{0}] left the game due to an error" -PlayerLeftByAU-Anticheat: "[{0}] was kicked by AmongU's official anti-cheats (Not related to FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] was kicked because they had a different version of the mod" +## 原版 +Notification.PlayerLeft: "[{0}] left the game" +Notification.PlayerLeftCuzTimeout: "[{0}] left the game due to connection timeout" +Notification.PlayerKickByHost: "[{0}] was kicked by the host" +Notification.PlayerBanByHost: "[{0}] was banned by the host" +Notification.PlayerLeftCuzError: "[{0}] left the game due to an error" +Notification.PlayerLeftByAU-Anticheat: "[{0}] was kicked by Among Us Anti-Cheat (unrelated to FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] was kicked because they have a different version/mod installed" +## 模组 +Notification.KickedByDenyName: "[{0}] was kicked for having a nickname containing banned words" +Notification.DumpfileSaved: "Log file successfully saved to desktop, filename: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] was removed as this room prohibits players with abnormal Friend Codes" +Notification.AddedPlayerToBanList: "Add [{0}] to the ban list" +Notification.FPSSetTo: "FPS cap set to: {0}" # 警告 / Warnings -Warning: "Warning!" -Warning.MismatchedVersion: "{0}\nhave a different version of {1}" -Warning.AutoExitAtMismatchedVersion: "The host has no or a different version of {0}\nYou will be kicked in {1}" -Warning.InvalidRpc: "Kicked {0} because an invalid RPC was received." -Warning.InvalidRpc_NotHost: "{0} is suspected of using cheats. Please remind the host to kick them out (Invalid Rpc:{1})" -Warning.SetName: "Kicked {0} because they set name set name for multiple times." -Warning.SetName_NotHost: "{0} is suspected of using cheats. Please remind the host to kick them out (Set Name For Multiple Times)" -Warning.SendQuickChat: "Kicked {0} because they send multiple quick messages within 3s" -Warning.SendQuickChat_NotHost: "0} is suspected of using cheats. Please remind the host to kick them out (Send Multiple Quick Messages Within 3s)" -Warning.InvalidSlothRPC: "Kicked {0} because an illegal RPC was received (Illegally sending official Rpc: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} is suspected of using cheats. Please remind the host to kick them out (Illegally sending official Rpc: {1})" -Warning.Cheater: "Kicked {0} they're suspected of using cheats'" -Warning.Cheater_NotHost: "{0} is suspected of using cheats,Please remind the host to kick them out" -Warning.CantKickDev: "Sorry, you cannot kick the developer" -Warning.RoomBroken: "Sorry, this room has been bombed. Please proceed to another room to continue your game." +Warning: "Warning" +Warning.MismatchedVersion: "[{0}]\nhas a different version of [{1}] installed" +Warning.AutoExitAtMismatchedVersion: "Your version of [{0}] is different from the host's\nYou will be kicked in {1} seconds" +Warning.CantKickDev: "Sorry, you cannot kick developers" +Warning.RoomBroken: "Sorry, this room has suffered a hacking attack, please play in another room" +Warning.InvalidColor: "Player with invalid color detected" ## 错误等级 / Error Levels -ErrorLevel1: "Bugs may occur." -ErrorLevel2: "This may be a bug." -ErrorLevel3: "This version shouldn't have been released." +ErrorLevel1: "May cause multiple bugs simultaneously" +ErrorLevel2: "Bugs may occur" +ErrorLevel3: "Unreleased version" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Warning: FAC detected High Level of cheats." -FAC.CheatDetected.LowLevel: "Warning: FAC detected Low Level of cheats. One of the players is hacking." -FAC.CheatDetected.FAC: "Using cheat programs (e.g., AUM, YuMenu, SM, etc.)" +CheatDetected.HighLevel: "Warning: FAC is defending against bombing cheats" +CheatDetected.LowLevel: "Warning: FAC detected possible cheater" +CheatDetected.UseCheat: "{0} used cheat program [{1}]" +CheatDetected.MayUseCheat: "{0} suspected of using mod [{1}] or cheat program" +CheatDetected.InvalidRpc: "[{0}] was kicked for sending invalid data (Invalid Rpc: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] suspected of cheating, please remind the host to kick them promptly (Invalid Rpc: {1})" +CheatDetected.SetName: "[{0}] was kicked for setting name multiple times" +CheatDetected.SetName_NotHost: "[{0}] suspected of cheating, please remind the host to kick them promptly (Set name multiple times)" +CheatDetected.SendQuickChat: "[{0}] was kicked for sending multiple quick messages within 3 seconds" +CheatDetected.SendQuickChat_NotHost: "[{0}] suspected of cheating, please remind the host to kick them promptly (Sent multiple quick messages within 3 seconds)" +CheatDetected.InvalidSlothRPC: "[{0}] was kicked for sending illegal data (Illegally sent official Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] suspected of cheating, please remind the host to kick them promptly (Illegally sent official Rpc: {1})" +CheatDetected.Overload: "[{0}] was kicked for initiating an overload attack" +CheatDetected.Overload_NotHost: "[{0}] initiated an overload attack, this room is damaged, please play in another room" +CheatDetected.Cheater: "[{0}] was kicked for suspected cheating" +CheatDetected.Cheater_NotHost: "[{0}] suspected of cheating, please remind the host to kick them promptly" +CheatDetected.BanedByBanList: "[{0}] was kicked for being on the ban list" +CheatDetected.BanedByFACList: "[{0}] was kicked for being on the FAC ban list" # 模组信息 / Mod Infos -Contributors: "Contributors" -Acknowledgement: "Acknowledgement" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "You have been kicked out by the anti-cheat.\n (module use may be mistaken for cheats)" -DCNotify.Banned: "You are not allowed to enter this room" -DCNotify.Kicked: "You were kicked out of the room" -DCNotify.DCFromServer: "You disconnected from the server.\nThis may be due to instability in your network.\nThis may also be due to server instability." -DCNotify.GameNotFound: "The assigned room was not found, the room may have been disbanded\n or check if you have selected a different server from the room" -DCNotify.GameStarted: "The game has already started, please wait until it ends" -DCNotify.GameFull: "The room is full, please try again later" -DCNotify.IncorrectVersion: "Your version of Among Us is different from this room" -DCNotify.Description: "You were kicked out of game.\nBecause: {0}" -DCNotify.DenyName: "Your nickname contains irregular characters" -DCNotify.BanList: "You have been banned by host" -DCNotify.FACList: "You have been banned by FAC" -DCNotify.CheatDetected: "You have been detected as suspected of cheating by FAC" -DCNotify.InvalidRPC: "You may have installed a different mod than host or your mod has been maliciously modified" -DCNotify.ModVersionIncorrect: "Your mod version is different from the host" -DCNotify.LowLevel: "Your lv. does not meet to the requirement of this room" -DCNotify.NotLogin: "Non-logged players are not allowed in this room" - -# 任务栏 / Task Panel +ModInfo.Contributors: "Contributors" +ModInfo.Acknowledgement: "Special Thanks" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "You were kicked from the room by InnerSloth's anti-cheat system" +DCNotify.Banned: "You were banned from this room" +DCNotify.Kicked: "You were kicked from this room" +DCNotify.DCFromServer: "Your connection to the server was interrupted\nThis may be due to unstable network\nor server instability/refusal of access" +DCNotify.GameNotFound: "Specified room not found, may have closed\nor check if you selected a different server than the room" +DCNotify.GameStarted: "This game has already started, please wait for the game to end" +DCNotify.GameFull: "This room is full, please try again later" +DCNotify.IncorrectVersion: "Your Among Us version differs from the room" +DCNotify.Description: "You were kicked from the room\nReason: {0}" + +# 任务栏相关 / Task Panel PressF1ShowRoleDescription: "Press F1 to view your role description" +PressF2ToHidePane: "Press F2 to show/hide pane" FakeTask: "Fake Task:" KillCount: "Kills" # 复盘信息 / Last Results -RoleSummaryText: "Last Results:" -ShowResults: "Show Last Results" -HideResults: "Hide Last Results" -NoInfoExists: "No last results available" -CrewsWin: "Team-Crewmate Win" -CrewmatesWin: "Crewmates Win" -CrewmatesWinBlurb: "The light of truth shines in hope!" -ImpsWin: "Team-Impostor Win" -ImpostorsWin: "Impostors Win" -ImpostorsWinBlurb: "Evil turns truth into ashes" -HideSummaryTextToShowWinText: "Hide last result to view victory text" - -# 禁用公开 -DisabledByProgram: "Public room operations have been disabled by the program" -PublicNotAvailableOnThisVersion: "Public rooms are not available on this version of FinalSuspect" +Summary.Text: "Last Summary:" +Summary.ShowResults: "Show Last Summary" +Summary.HideResults: "Hide Last Summary" +Summary.NoInfoExists: "No valid Last Summary exists" +Summary.CrewsWin: "Team-Crewmate Win" +Summary.ImpsWin: "Team-Impostor Win" +Outro.Crews_Win: "Crewmates Victory" +Outro.Crews_WinBlurb: "The light of truth shines in hope!" +Outro.Imps_Win: "Impostors Victory" +Outro.Imps_WinBlurb: "Evil destroyed the truth to ashes" # 主页 / Main UI FinalSuspectWelcomeText: "Wishing you a pleasant gaming experience!" -ConnectToFinalSuspectServerFailed: "Failed to connect to FinalSuspect server" +RetrieveVersionInfoFailed: "Failed to retrieve FinalSuspect info" Website: "Official Website" MainMenuCredential: "{0} © 2025" -LShift: "Press LShift to join to the last lobby" -RShift: "Press RShift to join the clipboard lobby" -LobbyTimeDisplayText: "LobbyTimeDisplay" +LShift: "Previous Lobby" +RShift: "Clipboard Lobby" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" @@ -296,19 +340,26 @@ Server: "Server" Local: "Local" # 其他 / Other -HongKong: "HongKong" -FPSSetTo: "Frame rate cap set to: {0}" +HongKong: "Hong Kong" BrowsingMode: "Browsing Mode" Broken: "Broken" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Translation Loading Complete!" -CheckingForFiles: "Verifying Resource File Integrity..." -DownloadingResources: "Downloading Resource Files..." -Loading: "Loading" -LoadingWithDot: "Loading..." -LoadingComplete: "Loading Complete!" +Unknown: "Unknown" +Back: "Back" +Yes: "Yes" +No: "No" +Cancel: "Cancel" +Unload: "Switch" +Retry: "Retry" +PreviousPage: "Previous Page" +NextPage: "Next Page" +Download: "Download" +Disable: "Disable" +Delete: "Delete" +Author: "Author" +Close: "Close" # 身份 / Identity -Host: "Host" -Cheater: "Cheater" +Id.Host: "Host" +Id.Cheater: "Cheater" +Id.Developer: "Developer" +Id.Contributor: "Contributor" \ No newline at end of file diff --git a/Assets/Languages/Filipino.yaml b/Assets/Languages/Filipino.yaml index 672a146f..c8eae911 100644 --- a/Assets/Languages/Filipino.yaml +++ b/Assets/Languages/Filipino.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "7" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Mga Impostor" -TypeCrewmate: "Mga Crewmate" +RoleType.Imp: "Impostor" +RoleType.Crew: "Crewmate" # 阵营 / Teams -TeamImpostor: "Team-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Team-Crewmate" +Team.Imp: "Team-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Team-Crewmate" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "May {0} Impostor sa aming koponan" -ImpostorNumImpOnly: "May 1 lamang Impostor sa multud" -ImpostorNumCrew: "May {0} Impostor sa atin" +ImpostorNum.Imp: "May {0} impostor sa aming team" +ImpostorNum.Imp_Only: "Isang impostor lang sa grupo" +ImpostorNum.Crew: "May {0} impostor sa amin" # 阵营开场 -ImpostorIntroText: "Ipaslang ng kasamaan ang mundo!" -ImpostorIntroTextOnly: "Baka ako nag-isa, ngunit may walang hanggang lakas ako!" -CrewmateIntroText: "Kumpleto ang iyong mga gawain, magkaisa upang malutas ang mga nakakabuluhang sitwasyon!" - -## 原版职业 / Vanilla -Crewmate: "Crewmate" -Engineer: "Inhinyero" -Scientist: "Agham-tao" -Tracker: "Taga-sunod" -Noisemaker: "Taga-ingay" -GuardianAngel: "Tagapangalaga" -Impostor: "Impostor" -Shapeshifter: "Taga-bagong anyo" -Phantom: "Multo" -CrewmateGhost: "Crewmate Ghost" -ImpostorGhost: "Impostor Ghost" -CrewmateGhostBlurb: "Kumpleto ang iyong mga gawain" -ImpostorGhostBlurb: "Tuloy ang pag-sabotage ng mga facilidades" -CrewmateGhostBlurbLong: "Kumpleto ang mga gawain, huwag maging malapit!" -ImpostorGhostBlurbLong: "Sabotage ang mga facilidades, tulungan ang mga nabubuhay na impostors sa pagkakakuha ng tagumpay." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Mabuhay hanggang sa wakas!" -HnSEngineerBlurbLong: "Mananatili kang buhay hanggang mag-full ang oras; ang pagkumpleto ng mga gawain ay magpapahaba ng oras. \nGamitin ang mga bente at mga indikador ng banta upang magtago! \nPagka-nag-start na ang timer, maghahabol na ang Impostor sa'yo!" -HnSImpostorBlurb: "Eliminate ang lahat!" -HnSImpostorBlurbLong: "Patayin ang lahat ng mga miyembro ng crew sa loob ng limitasyon ng oras! \nKailangan mong mabilis ang aksyon! Hindi maaaring gamitin ang mga bente. \nSa huling sandali, makakatanggap ka ng boost sa bilis at mga hint ng tracking!" -HnSCrewmateGhostBlurb: "Cheer para sa iyong mga kasamahan" -HnSCrewmateGhostBlurbLong: "Go! Cheer! For! Your! Teammates!" +IntroText.Imp: "Hayaan ang masama na DOMINAHIN ANG MUNDO!" +IntroText.Imp_Only: "Bagamat nag-iisa, may WALANG HANGGANG KAPANGYARIHAN ka!" +IntroText.Crewmate: "Kumpletuhin ang mga task at hanapin ang impostor!" + +# 原版职业 / Vanilla +Role.Crewmate: "Crewmate" +Role.Engineer: "Engineer" +Role.Scientist: "Scientist" +Role.Tracker: "Tracker" +Role.Noisemaker: "Noisemaker" +Role.GuardianAngel: "Guardian Angel" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Shapeshifter" +Role.Phantom: "Multo" +Role.CrewmateGhost: "Multo ng Crewmate" +Role.ImpostorGhost: "Multo ng Impostor" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Kumpletuhin ang mga task" +ImpostorGhostBlurb: "Magpatuloy sa pagsasabotahe" +CrewmateGhostBlurbLong: "Kumpletuhin ang mga task, huwag pabigat sa team!\nGamitin ang 「Pagpaparamdam」 para makita ang Impostor, at tulungan ang Guardian Angel na protektahan ang Crewmates!" +ImpostorGhostBlurbLong: "Sabotahe ang mga pasilidad, tulungan ang nabubuhay na impostor na manalo!" +HnSEngineerBlurb: "Kumpletuhin ang mga task at mabuhay hanggang wakas!" +HnSImpostorBlurb: "Patayin silang LAHAT!" +HnSCrewmateGhostBlurb: "Pasayahin ang iyong mga kasamahan" +HnSCrewmateGhostBlurbLong: "Patay? Ano ang tinitingnan mo? Maaari ka bang gumawa ng task? Dahil hindi...\nPUMUNTA! PASAYAHIN! ANG! IYONG! MGA KASAMAHAN!" # 死因 / Death Reason DeathReason.Kill: "Pinatay" -DeathReason.Exile: "Pinag-iwan" -DeathReason.Disconnect: "Nakatanggal ng koneksyon" +DeathReason.Exile: "Itinapon" +DeathReason.Disconnect: "D/C" # 客户端选项 / Client Options -FinalSuspectOptions: "Mga Opsiyon ng Final Suspect" -Back: "Bumalik" -Yes: "Opo" -No: "Hindi" -UnlockFPS: "I-unlock ang FPS" -ChangeOutfit: "Baguhin ang Outfit" -BeanMode: "Klasikong Mode ng Bean" -HorseMode: "Easter Egg na Mode ng Kabayo" -LongMode: "Easter Egg na Long Mode" -KickPlayerFriendCodeNotExist: "Tanggihan ang mga Player na Hindi Nakalogin." -KickPlayerWithDenyName: "Tanggihan ang mga Player na Gumagamit ng Hindi Papatibayan na Nicknames" -KickPlayerInBanList: "Tanggihan ang mga Banned na Player" -SpamDenyWord: "Blokear ang mga Hindi Papatibayan na Salita" -AutoStartGame: "Auto Start kapag Full ang Lobby" -AutoEndGame: "Auto-balik sa Lobby sa End" -SwitchVanilla: "Lumipat sa Vanilla" -DisableVanillaSound: "I-disable ang Among U's Musics" -DisableFAC: "I-disable ang Anti-Cheat" -ShowPlayerInfo: "Ipakita ang Player Platform at Client Information" -UseModCursor: "Gamitin ang Mod Cursor" -FastBoot: "Maingay na Mode" -PrunkMode: "Prank Mode" -VersionCheat: "Bypass Mod Version Sync Check" -GodMode: "God Mode" -NoGameEnd: "Walang Game End" -EnableFinalSuspect: "Paganahin ang 「Final Suspect」" +FinalSuspectOptions: "Mga Opsyon ng Final Suspect" +ClientOption.UnlockFPS: "I-unlock ang FPS" +ClientOption.SwitchOutfitType: "Palitan ang Uri ng Kasuotan" +ClientOption.KickPlayerWithAbnormalFriendCode: "I-kick ang player na may abnormal na Friend Code" +ClientOption.KickPlayerWithDenyName: "I-kick ang player na gumagamit ng banned na pangalan" +ClientOption.KickPlayerInBanList: "I-kick ang banned na player" +ClientOption.SpamDenyWord: "I-block ang mga banned na salita" +ClientOption.AutoStartGame: "Awtomatikong simulan ang laro kapag puno" +ClientOption.AutoEndGame: "Awtomatikong bumalik sa lobby pagkatapos ng laro" +ClientOption.SwitchVanilla: "Lumipat sa Vanilla" +ClientOption.DisableVanillaSound: "I-disable ang Vanilla game music" +ClientOption.EnableFAC: "I-enable ang Anti-Cheat" +ClientOption.EnableGuardian: "I-enable ang Client Guardian (Experimental)" +ClientOption.ShowPlayerInfo: "Ipakita ang platform at client info" +ClientOption.UseModCursor: "Gumamit ng mod cursor" +ClientOption.FastLaunchMode: "Mabilisang Paglulunsad" +ClientOption.OfflineMode: "Offline na Mode (Experimental)" +ClientOption.VersionCheat: "Lampasan ang version sync check" +ClientOption.GodMode: "God Mode" +ClientOption.NoGameEnd: "Walang Katapusan ng Laro" +ClientOption.EnableFinalSuspect: "I-enable ang 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Classic Mode" +Value.HorseMode: "April Fools' Horse Mode" +Value.LongMode: "April Fools' Long Mode" # 客户端功能 / Client Features -FinalSuspectFeatures: "Mga Feature ng Final Suspect" -UnloadMod: "Lumipat sa Vanilla" -UnloadWarning: "Babala\nUpang muli ayusin ang mod, kailangan mong muli simulan ang laro.\nGusto mo pa rin magpatuloy?" -CannotUnloadDuringGame: "Hindi maaaring ilipat sa vanilla sa gitna ng laro" -Cancel: "Kanselahin" -Unload: "I-unload" -DumpLog: "I-output ang Log" -ClearAutoLogs: "Alisin ang awtomatikong log" -SoundOptions: "Mga Musika Ko" -AudioManagementOptions: "Audio Management" -OnlyAvailableInMainMenu: "Available Only In Main Menu" +FinalSuspectFeatures: "Mga Tampok ng Final Suspect" +ClientFeature.UnloadMod: "Lumipat sa Vanilla" +ClientFeature.DumpLog: "I-dump ang Log" +ClientFeature.ClearAutoLogs: "I-clear ang Auto Logs" +ClientFeature.MyMusic: "Aking Musika" +ClientFeature.ResourceManager: "Tagapamahala ng Resource" +ClientFeature.NameTagManager: "Tagapamahala ng Name Tag" +ClientFeature.MainMenuStyleManager: "Palitan ang Estilo ng Main Menu" # 提示 / Tips -updatePleaseWait: "Maghintay sandali..." -updateInProgress: "Nagpapa-update..." -DownloadingAudios: "Nag-download..." -Playing: "Naglalaro..." -Parsing: "Nag-aanalyze..." -DownLoadSucceedNotice: "Matagumpay na nai-download!" -DownLoadFailureNotice: "Nabigo ang download =(" -PleaseWait: "Maghintay sandali..." +Tip.Downloading: "Nagda-download..." +Tip.PleaseWait: "Maghintay..." +Tip.Playing: "Nagpe-play..." +Tip.Parsing: "Nagpa-parse..." +Tip.Updating: "Nag-u-update..." +Tip.DownLoadFinished: "Tapos na ang download" +Tip.DownLoadSucceeded: "Matagumpay ang download!" +Tip.DownLoadFailed: "Nabigo ang download=(" +Tip.PackageExists: "Naka-install" +Tip.OnlyAvailableInMainMenu: "Magagamit lamang sa Main Menu" +Tip.HideSummaryTextToShowWinText: "Itago ang Huling Buod para makita ang victory text" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Kumpleto na ang paglo-load ng mga language file" +Tip.CheckingForFiles: "Sinusuri ang integridad ng resource file..." +Tip.DownloadingResources: "Nagda-download ng mga resource file..." +Tip.Loading: "Naglo-load" +Tip.LoadingWithDot: "Naglo-load..." +Tip.LoadingComplete: "Kumpleto na ang paglo-load!" +## 切换原版 +Tip.UnloadWarning: "Babala!\nKailangan mong i-restart ang laro para makapaglaro ng modded version.\nSigurado ka bang gusto mong lumipat sa Vanilla?" +Tip.CannotUnloadDuringGame: "Hindi maaaring lumipat sa Vanilla kapag nagsimula na ang laro." +## 资源管理 +Tip.ResourceManager: "Maaari kang mag-download ng mga mod-compatible at karagdagang resource sa 「Resource Manager」\nAng mga file na may \"Pre-\" ay pre-download resource packages" +## 我的音乐 +Tip.MyMusic: "Maaari kang mag-download ng mga mod-supported na musika sa 「Resource Manager」 o magdagdag ng mga audio file sa folder (Among Us/Final Suspect_Data/Musics). Kung hindi umiiral ang local resource path, ipapakita ang 「File Missing」" +Tip.Incomplete_Music: "Natukoy ang hindi kumpletong music file" +Tip.Incomplete_SoundEffect: "Natukoy ang hindi kumpletong sound effect file" +Tip.Incomplete_Image: "Natukoy ang hindi kumpletong image file" +Tip.Incomplete: "Para mapabuti ang gaming experience, mag-download ng mod-compatible resource package sa 「Main Menu - Settings - Client Features - Resource Manager」" +## 名称标识管理 +Tip.TextContent: "Nilalaman ng Teksto" +Tip.TextSizeDescription: "Laki ng Teksto (Default 100%)" +Tip.TextColorDescription: "Kulay ng Teksto (Hex Color Code)\nIwanang blangko kung hindi kailangan, lagyan ng marami para sa automatic gradient\n" +Tip.PleaseEnterFriendCode: "Pakilagay ang Friend Code para i-bind ang bagong name tag" +Tip.FriendCodeAlreadyExist: "Mayroon nang name tag ang Friend Code na ito" +Tip.FriendCodeIncorrect: "Pakilagay ng wastong Friend Code" +Tip.CustomNameTagHelp: "Maaari kang magdagdag ng name tag para sa kahit anong player. Ang mga tag ay awtomatikong itatalaga kapag sumali ang player na naka-bind sa Friend Code. Ngunit hindi mo mae-edit ang mga tag pagkatapos pumasok sa lobby.\nAng mga non-VIP ay maaari lamang magdagdag ng remark (hindi pa na-debelop ang VIP functions)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Maaari kang mag-download ng 「Main Menu Style Packs」 sa 「Resource Manager」 para makakuha ng main menu resources, at pumili ng iyong paboritong main menu style dito" + +# 更新结果 +UpdateResult.Succeed_Title: "Tagumpay ang Update" +UpdateResult.Succeed_Text: "Ma-aaactivate ito pagkatapos i-restart ang laro :)" +UpdateResult.Failed_Title: "Nabigo ang Update" +UpdateResult.Failed_Reason_NotFound: "Dahilan: {0}\nMaaaring pansamantalang hindi available ang source na ito, palitan ang download source at subukan muli" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Dahilan: File verification error\nHindi pinakabago ang file version mula sa source na ito, palitan ang download source at subukan muli" +UpdateResult.Failed_Reason_Ping: "Dahilan: Nag-time out o na-interrupt ang download\nSuriin ang iyong network at subukan muli o i-update nang mano-mano" # 更新检查 / Update Checker -Retry: "Subukan muli" -updateCheckPopupTitle: "Update Checker" -updateCheckFailedRetry: "Nabigo ang update check :(\nSubukan muli?" -updateCheckFailedExit: "Nabigo ang update check :(\nMangyaring suriin ang iyong koneksyon sa internet at subukan muli." +UpdateCheck.Popup_Title: "Update Check" +UpdateCheck.Failed_Retry: "Nabigong mag-check ng update :(\nSubukan muli?" +UpdateCheck.Failed_Exit: "Nabigong mag-check ng update :(\nSuriin ang iyong network at subukan muli!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Update Reminder" -updateNotice: "Update Reminder" -UpdateBySelfText: "Ang bersyon na ito ay hindi sumusuporta sa one-click update. Mangyaring mag-update nang manu-manu." -updateButton: "Update Now" -updatePopupTitle: "Update Now" -updatePopupTitleFailed: "Update Failed" -updatePopupTitleDone: "Update Completed" +UpdateRemind.updatePopup: "I-update Ngayon" +UpdateRemind.updateNotice: "Paalala ng Update" +UpdateRemind.BySelf_Title: "Hint sa Update" +UpdateRemind.BySelf_Text: "Hindi suportado ng version na ito ang one-click update, mangyaring i-update nang mano-mano" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Mangyaring piliin ang channel para mag-update\nkung hindi mo alam ano ang piliin, piliin ang [Github]\nKung nabigo ang update, piliin ang [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "I-restart ang laro para mapatibayan ang mga pagbabago :)" -updatePingFialed: "Rason: {0}\nMaaaring hindi magamit ngayon ang piniling channel. Subukan mo ang pagbabago ng download channel." -updateFileMd5Incorrect: "Rason: File checksum error\nHindi ito ang pinakabagong bersyon ng file mula sa channel na ito. Subukan mo ang pagbabago ng download channel." -downloadFailed: "Rason: Download timeout o interrupted\nSubukan mo muli pagkatapos ng pagbabago ng iyong network o mag-update nang manu-manu." +UpdateSource.Choose: "Mangyaring pumili ng update source\nKung hindi sigurado, piliin ang [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Mayroon kaming mahahalagang update. Mangyaring mag-update ang mod na ito.\nKung hindi, hindi ka maaaring sumali sa mga public rooms." -CanNotJoinPublicRoomNoLatest: "Mayroon kaming mahahalagang update. Mangyaring mag-update ang mod na ito.\nKung hindi, hindi ka maaaring sumali sa mga public rooms." -ModBrokenMessage: "Ang mga file ng mod ay nasira. Mangyaring muli simulan ang laro o muli i-install ang mod na ito." -UnsupportedVersion: "Hindi compatible ang iyong bersyon ng Among Us sa FinalSuspect.\nMangyaring i-update ang iyong laro." +CanNotJoinPublicRoomNoLatest: "Mayroon kaming mahalagang update, mangyaring i-update ang mod na ito\nKung hindi, hindi ka makakasali sa mga public room" +ModBrokenMessage: "Nasira ang mga mod file, mangyaring i-restart ang laro o i-reinstall ang mod na ito" +UnsupportedVersion: "Hindi tugma ang iyong Among Us version sa FinalSuspect\nMangyaring i-update ang laro" + +# 名称标识 +NameTag.DisplayName: "Remark" +NameTag.Title: "Title" +NameTag.Prefix: "Prefix" +NameTag.Suffix: "Suffix" +NameTag.Name: "Pangalan" +NameTag.LastTag: "Karagdagang Suffix" +NameTag.PreviewNotAvailable: " (Hindi suportado ang preview) " +NameTag.CanNotEdit: " (Hindi mae-edit) " +NameTag.RefreshPreview: "I-refresh ang Preview" +NameTag.SaveAndClose: "I-save at Umalis" +NameTag.NewNameTag: "Bago" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Chasing Dawn(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Habang lumilipas ang panahon, nagtatapos ang taglamig sa hilaga, at muling binubuhay ang tagsibol.\nHabang minamasdan ang niyebe ng Mira, naalala ba ang mga alaala? Umaagos ang mainit na mga ilog sa puso.\nSino man ang huling suspek, kaninuman man ang mga kamay na nagbasag sa katotohanan,\nSa isang nakakapreskong bagong karanasan, mag-isip nang mabuti at mag-iwan ng mga bagong masasayang alaala.\nAng paglalaro, ang pagkakaroon ng kasiyahan ang pinakamahalaga!!!\n\nPinangalanan ni: 一念旧情丶" +MainMenuStyle.Title_Security: "Red Heart, Fierce Love (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Hindi kami babagsak, hindi susuko, hindi bibiguin ang mga manlalaro, at hindi titigil sa pag-unlad.\nKung nakatagpo ng malamig na tingin, patutunayan namin sa lahat!\nSa pagnanasa, muling naglayag kami\n\nPinangalanan ni: Slok" +MainMenuStyle.Title_NewYear: "Affinity (Spring Festival)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Good Fortune: Ipinagkaloob na mga hangarin Bond: Hinabi sa tibok ng puso\nHarmony: Mga sinulid ng pagmamahal Bliss: Kapayapaan sa bawat kaluluwa\nAng mga kapalaran na bono ay bumubuo ng ibinahaging kasiyahan Maglakbay nang malayo, hanapin ang kasiyahan sa pagkakaisa\nAng pangunahing panahon ay kumakalinga sa lahat sa katuwaanAng aming panalangin—kasiyahan para sa bawat nilalang\nNawa'y ang bawat kaluluwa ay makahanap ng kanilang itinakdang landas sa darating na taon\nSa aming mga pagpapala na nagliliwanag sa iyong daan, simulan ang iyong paglalakbay upang makalikha ng sariling alamat!\n\nPinangalanan ni: Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Spring Festival)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Ang mga bulaklak ay namumukadkad na parang mga alon, banayad na parang hangin, na nagnanais na ang puso ng lahat ay kasing lawak ng hangin, na ang lahat ng bagay ay maayos\"\n\"—Ang mga hanging tagsibol ay sumasalpok nang malakas, ang mga alon ay sumasalpok para sa mga milya, na nagnanais na ang lahat ay matatagong nakatayo sa unahan, umuunlad at sumusulong\"\n\"—Maligayang pagdating sa XtremeWave — Espesyal na Programa ng Spring Festival!\"\n\nPinangalanan ni: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final Striving Toward Excellence, Dream Commanding Surging Waves!」\n\nPinangalanan ni: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "When Looking Back At The End (Collaboration)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Looking back at the end, it all ends at the end」\n\nPinangalanan ni: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Hindi Na-download" +MainMenuStyle.NotApply: "Ilapat" +MainMenuStyle.Applied: "Na-apply" + +# 资源包 +Package.MainMenuStyle: "Main Menu Style Pack" # 音频播放 / Audio Playback -PlayMode0: "Maglaro ng Isang Beses" -PlayMode1: "Ulitin ang Isang Kanta" -PlayMode2: "Maglaro ng Random" -PlayMode3: "Maglaro ng Sekwensyal" -Stop: "Tigilin" -CanPlay: "← Click para maglaro" -NoFound: "[File Missing]" -NextPage: "Susunod na Pahina" -PreviousPage: "Bumalik sa Pahina" - -# 音频添加 / Audio Addition -download: "Download" -delete: "Burahin" -NewSound: "Magdagdag ng Bagong Musik" -PleaseEnterMusic: "Mangyaring Ilagay ang Pangalan ng Musik" -AudioManagementAlreadyExists: "Ang pangalan ng musikang ito ay umiiral na" -NotAllowedMusic: "Hindi payagan ang format ng pangalan ng musika" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Maaari mong i-download ang musika na suportado ng XtremeWave o magdagdag ng iyong sariling musika sa 「Audio Management」. Kapag nagdaragdag ng iyong sariling musika, siguraduhing ilagay ang pangalan ng musika sa 「Audio Management」 at ilagay ang mga korespondente na audio file sa folder na 「Among Us/Final Suspect_Data/Resources/Sounds」 (suportado ang mga formatong .wav). Maaari mong maglaro ng musika sa 「Mga Musika Ko」." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Maaari mong i-download ang musika na suportado ng XtremeWave o magdagdag ng iyong sariling musika sa 「Audio Management」. Kung nawawala ang lokal na resource path ng musika, papayagang ipakita ang '[File Missing]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Detected na incomplete ang kasalukuyang musika file" -AudioNYPro: "Para sa mas magandang karanasan sa laro, i-download ang aming musika sa 「Home-Settings-More Features-Audio Management」" +MusPlay.Mode0: "Play Once" +MusPlay.Mode1: "Single Loop" +MusPlay.Mode2: "List Random" +MusPlay.Mode3: "Sequential Play" +MusPlay.Stop: "I-stop ang Playback" +MusPlay.CanPlay: "I-click para I-play" +MusPlay.NoFound: "Nawawalang File" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(Happy Chinese New Year)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" +Mus.TrailOfTruth: "Trail of Truth" Mus.Interlude: "Interlude" Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.ElegyOfFracturedVow: "Elegy of Fractured Vow" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" +Mus.ReturnToSimplicity: "return to simplicity" +Mus.ReturnToSimplicity2: "return to simplicity (Full Ver.)" +Mus.ChasingDawn: "Chasing Dawn" +Mus.StruggleAgainstFadingFlame: "Struggle Against Fading Flame" Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] ay tinanggal dahil ang pangalan ay nagkakasundo sa [{1}]" -Message.BanedByBanList: "[{0}] ay binan ng dahil sa nakaraang pagban" -Message.BanedByFACList: "[{0}] ay binan dahil nasa FAC list of Banned people" -Message.DumpfileSaved: "Matagumpay na itinago ang log file sa desktop, file name: {0}" -Message.KickedByNoFriendCode: "[{0}] ay tinanggal dahil ang friend code ay walang eksistensya" -Message.AddedPlayerToBanList: "Nilagay [{0}] sa ban list" -Message.KickedByFAC: "[{0}]Tinanggal ni FAC, dahilan: {1}" -Message.BanedByFAC: "[{0}] Tinanggal ni FAC, dahilan:{1}" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Tungkulin" +DisplayedRoleTag.PlayerIdentityTag: "Tag ng Pagkakakilanlan" +DisplayedRoleTag.Room: "Silid" + +PlayerIdentityTag.Hard_Cleared: "Tiyak na Mabuting Tao" +PlayerIdentityTag.Silver_Clear: "Malamang na Mabuting Tao" +PlayerIdentityTag.Wolf_Bucket: "Balon ng mga Lobo" +PlayerIdentityTag.No_Kill: "Walang Patay" +PlayerIdentityTag.Outside_Position: "Posisyon sa Labas" +PlayerIdentityTag.Inside_Position: "Posisyon sa Loob" # 通知 / Notifications -PlayerLeft: "[{0}] umalis sa laro" -PlayerLeftCuzTimeout: "[{0}] umalis sa laro dahil sa connection timeout" -PlayerKickByHost: "[{0}] tinanggal ng host" -PlayerBanByHost: "[{0}] binan ng host" -PlayerLeftCuzError: "[{0}] umalis sa laro dahil sa isang error" -PlayerLeftByAU-Anticheat: "[{0}] ay tinanggal ng AmongU's official anti-cheats (Hindi kaugnay ng FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] ay tinanggal dahil sa magkakaibang bersyon ng mod" +## 原版 +Notification.PlayerLeft: "[{0}] umalis sa laro" +Notification.PlayerLeftCuzTimeout: "[{0}] umalis sa laro dahil sa connection timeout" +Notification.PlayerKickByHost: "[{0}] na-kick ng host" +Notification.PlayerBanByHost: "[{0}] na-ban ng host" +Notification.PlayerLeftCuzError: "[{0}] umalis sa laro dahil sa error" +Notification.PlayerLeftByAU-Anticheat: "[{0}] na-kick ng Among Us Anti-Cheat (walang kinalaman sa FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] na-kick dahil may ibang version/mod" +## 模组 +Notification.KickedByDenyName: "[{0}] na-kick dahil may nickname na naglalaman ng banned words" +Notification.DumpfileSaved: "Matagumpay na na-save ang log file sa desktop, filename: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] tinanggal dahil ipinagbabawal ang abnormal Friend Code sa room na ito" +Notification.AddedPlayerToBanList: "Idagdag si [{0}] sa ban list" +Notification.FPSSetTo: "Nakapirmi ang FPS sa: {0}" # 警告 / Warnings -Warning: "Babala!" -Warning.MismatchedVersion: "{0}\nmay magkakaibang bersyon ng {1}" -Warning.AutoExitAtMismatchedVersion: "Ang host ay walang o magkakaibang bersyon ng {0}\nIkaw ay itatanggal sa {1}" -Warning.InvalidRpc: "{0} ay tinanggal dahil natanggap ang isang invalid RPC." -Warning.InvalidRpc_NotHost: "{0} ay suspek ng paggamit ng cheats. Paki-reminder sa host na ito ay itatanggal (Invalid Rpc:{1})" -Warning.SetName: "{0} ay tinanggal dahil nagsiset ng pangalan para sa maraming beses." -Warning.SetName_NotHost: "{0} ay suspek ng paggamit ng cheats. Paki-reminder sa host na ito ay itatanggal (Set Name For Multiple Times)" -Warning.SendQuickChat: "{0} ay tinanggal dahil nagpadala ng maraming quick messages sa loob ng 3s" -Warning.SendQuickChat_NotHost: "{0} ay suspek ng paggamit ng cheats. Paki-reminder sa host na ito ay itatanggal (Send Multiple Quick Messages Within 3s)" -Warning.InvalidSlothRPC: "Tayo {0} dahil natanggap ang isang illegal na RPC (Iligally nagpadala ng opisyal na Rpc: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} ay suspek ng paggamit ng cheats. Paki-reminder sa host na ito ay itatanggal (Illegally sending official Rpc: {1})" -Warning.Cheater: "Tayo {0} dahil suspek nila ang paggamit ng mga cheat" -Warning.Cheater_NotHost: "{0} ay suspek ng paggamit ng mga cheat, Pakiingay sa host na ilabas sila" -Warning.CantKickDev: "Sorry, hindi maaaring itatanggal ang developer" -Warning.RoomBroken: "Sorry, ito ay binomba na ang kwarto. Paki-proceed sa ibang kwarto upang magpatuloy sa iyong laro." +Warning: "Babala" +Warning.MismatchedVersion: "[{0}]\nmay ibang version ng [{1}] na naka-install" +Warning.AutoExitAtMismatchedVersion: "Ang iyong version ng [{0}] ay iba sa host\nMa-kick ka sa loob ng {1} segundo" +Warning.CantKickDev: "Paumanhin, hindi mo maaaring i-kick ang mga developer" +Warning.RoomBroken: "Paumanhin, na-hack ang room na ito, mangyaring maglaro sa ibang room" +Warning.InvalidColor: "Natukoy ang player na may invalid na kulay" ## 错误等级 / Error Levels -ErrorLevel1: "Maaaring magkaroon ng mga bugs." -ErrorLevel2: "Itinuturing na bug ito." -ErrorLevel3: "Hindi dapat ilabas ang bersyon na ito." +ErrorLevel1: "Maaaring magdulot ng maraming bug nang sabay" +ErrorLevel2: "Maaaring magkaroon ng bug" +ErrorLevel3: "Hindi pa nailalabas na version" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Babala: Naidetect ng FAC na mataas na antas ng mga cheats." -FAC.CheatDetected.LowLevel: "Babala: Naidetect ng FAC na mababang antas ng mga cheats. Ang isa sa mga players ay nag-cheat." -FAC.CheatDetected.FAC: "Gamit ang mga cheat program (hal., AUM, YuMenu, SM, etc.)" +CheatDetected.HighLevel: "Babala: Pinoprotektahan ng FAC laban sa bombing cheats" +CheatDetected.LowLevel: "Babala: Nakadetect ang FAC ng posibleng cheater" +CheatDetected.UseCheat: "Gumamit si {0} ng cheat program [{1}]" +CheatDetected.MayUseCheat: "Pinaghihinalaang gumamit si {0} ng mod [{1}] o cheat program" +CheatDetected.InvalidRpc: "Na-kick si [{0}] dahil sa pagpapadala ng invalid na data (Invalid Rpc: {1})" +CheatDetected.InvalidRpc_NotHost: "Pinaghihinalaang cheater si [{0}], pakipaalala sa host na i-kick agad (Invalid Rpc: {1})" +CheatDetected.SetName: "Na-kick si [{0}] dahil sa paulit-ulit na pag-set ng pangalan" +CheatDetected.SetName_NotHost: "Pinaghihinalaang cheater si [{0}], pakipaalala sa host na i-kick agad (Paulit-ulit na pag-set ng pangalan)" +CheatDetected.SendQuickChat: "Na-kick si [{0}] dahil sa pagpapadala ng maraming quick message sa loob ng 3 segundo" +CheatDetected.SendQuickChat_NotHost: "Pinaghihinalaang cheater si [{0}], pakipaalala sa host na i-kick agad (Nagpadala ng maraming quick message sa loob ng 3 segundo)" +CheatDetected.InvalidSlothRPC: "Na-kick si [{0}] dahil sa pagpapadala ng ilegal na data (Ilegal na official Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "Pinaghihinalaang cheater si [{0}], pakipaalala sa host na i-kick agad (Ilegal na official Rpc: {1})" +CheatDetected.Overload: "Na-kick si [{0}] dahil sa pag-initiate ng overload attack" +CheatDetected.Overload_NotHost: "Nag-initiate ng overload attack si [{0}], nasira ang room na ito, mangyaring maglaro sa ibang room" +CheatDetected.Cheater: "Na-kick si [{0}] dahil sa pinaghihinalaang pagcheat" +CheatDetected.Cheater_NotHost: "Pinaghihinalaang cheater si [{0}], pakipaalala sa host na i-kick agad" +CheatDetected.BanedByBanList: "Na-kick si [{0}] dahil nasa ban list" +CheatDetected.BanedByFACList: "Na-kick si [{0}] dahil nasa FAC ban list" # 模组信息 / Mod Infos -Contributors: "Mga Nagtribute" -Acknowledgement: "Pagkilala" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Ikaw ay tinanggal ng anti-cheat.\n(maaaring maling interpretasyon ang paggamit ng module bilang cheat)" -DCNotify.Banned: "Hindi ka pinapayagan na pumasok sa kwarto na ito" -DCNotify.Kicked: "Ikaw ay tinanggal sa kwarto" -DCNotify.DCFromServer: "Ikaw ay natanggal mula sa server.\nMaaari ito na dahil sa instability sa iyong network.\nMaaari ito din dahil sa instability ng server." -DCNotify.GameNotFound: "Hindi natagpuan ang pinagkaloob na kwarto, ang kwarto ay maaaring natanggal\n o suriin kung mayroon kang napiling ibang server mula sa kwarto" -DCNotify.GameStarted: "Ang laro ay nagsimula na, maghintay hanggang matapos" -DCNotify.GameFull: "Ang kwarto ay puno, subukan muli mamaya" -DCNotify.IncorrectVersion: "Ang iyong bersyon ng Among Us ay nakaka-ibang sa kwarto na ito" -DCNotify.Description: "Ikaw ay tinanggal sa laro.\nDahilan: {0}" -DCNotify.DenyName: "Ang iyong nickname ay naglalaman ng mga irregular na characters" -DCNotify.BanList: "Ikaw ay binan ng host" -DCNotify.FACList: "Ikaw ay binan ng FAC" -DCNotify.CheatDetected: "Ikaw ay naidetect na suspek ng cheating ng FAC" -DCNotify.InvalidRPC: "Maaaring mayroon kang magkakaibang mod na naka-install kaysa sa host o ang iyong mod ay nakakalantad sa malicious na pagbabago" -DCNotify.ModVersionIncorrect: "Ang iyong bersyon ng mod ay nakaka-ibang sa host" -DCNotify.LowLevel: "Ang iyong antas ay hindi nakakatugma sa requirement ng kwarto na ito" -DCNotify.NotLogin: "Ang mga hindi nakalogin na mga players ay hindi pinapayagan sa kwarto na ito" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Pindutin ang F1 para makita ang iyong role description" -FakeTask: "Fake Task:" -KillCount: "Kills" +ModInfo.Contributors: "Mga Kontribyutor" +ModInfo.Acknowledgement: "Espesyal na Pasasalamat" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Na-kick ka ng anti-cheat system ng InnerSloth" +DCNotify.Banned: "Na-ban ka sa room na ito" +DCNotify.Kicked: "Na-kick ka sa room na ito" +DCNotify.DCFromServer: "Na-interrupt ang iyong koneksyon sa server\nMaaaring dahil sa unstable na network\no server instability/pagtanggi ng access" +DCNotify.GameNotFound: "Hindi mahanap ang tinukoy na room, maaaring sarado na\no tingnan kung naka-select ka ng ibang server kaysa sa room" +DCNotify.GameStarted: "Nagsimula na ang larong ito, maghintay hanggang matapos" +DCNotify.GameFull: "Puno na ang room na ito, subukan muli mamaya" +DCNotify.IncorrectVersion: "Iba ang iyong Among Us version sa room" +DCNotify.Description: "Na-kick ka sa room\nDahilan: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Pindutin ang F1 para makita ang role description" +PressF2ToHidePane: "Pindutin ang F2 para ipakita/itago ang pane" +FakeTask: "Pekeng Task:" +KillCount: "Mga Pagpatay" # 复盘信息 / Last Results -RoleSummaryText: "Last Results:" -ShowResults: "Show Last Results" -HideResults: "Hide Last Results" -NoInfoExists: "Walang available na last results" -CrewsWin: "Team-Crewmate Win" -CrewmatesWin: "Crewmates Win" -CrewmatesWinBlurb: "Ang liwanag ng katotohanan ay nagmumulay sa pag-asa!" -ImpsWin: "Team-Impostor Win" -ImpostorsWin: "Impostors Win" -ImpostorsWinBlurb: "Ang kasamaan ay nagbabago ng katotohanan sa abo" -HideSummaryTextToShowWinText: "Hide last result to view victory text" - -# 禁用公开 -DisabledByProgram: "Ang mga operasyon ng public room ay hindi pinapayagan ng programa" -PublicNotAvailableOnThis Version: "Ang mga public rooms ay hindi available sa bersyon na ito ng FinalSuspect" +Summary.Text: "Huling Buod:" +Summary.ShowResults: "Ipakita ang Huling Buod" +Summary.HideResults: "Itago ang Huling Buod" +Summary.NoInfoExists: "Walang valid na Huling Buod" +Summary.CrewsWin: "Panalo ang Team-Crewmate" +Summary.ImpsWin: "Panalo ang Team-Impostor" +Outro.Crews_Win: "Tagumpay ng Crewmates" +Outro.Crews_WinBlurb: "Nagniningning ang liwanag ng katotohanan sa pag-asa!" +Outro.Imps_Win: "Tagumpay ng Impostors" +Outro.Imps_WinBlurb: "Winasak ng kasamaan ang katotohanan hanggang maging abo" # 主页 / Main UI -FinalSuspectWelcomeText: "Wishing you a pleasant gaming experience!" -ConnectToFinalSuspectServerFailed: "Failed to connect to FinalSuspect server" -Website: "Official Website" +FinalSuspectWelcomeText: "Nais naming sa iyo ang isang kasiya-siyang gaming experience!" +RetrieveVersionInfoFailed: "Nabigong makuha ang FinalSuspect info" +Website: "Opisyal na Website" MainMenuCredential: "{0} © 2025" -LShift: "Pindutin ang LShift upang muli pumunta sa nakaraang kuwarto" -RShift: "Pindutin ang RShift upang pumasok sa clipboard room" -LobbyTimeDisplayText: "Panahon ng pag iral" +LShift: "Nakaraang Lobby" +RShift: "Clipboard Lobby" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" FrameRate: "Frame Rate" Server: "Server" -Local: "Local" +Local: "Lokal" # 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Frame rate cap set to: {0}" BrowsingMode: "Browsing Mode" -Broken: "Broken" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Translation Loading Complete!" -CheckingForFiles: "Verifying Resource File Integrity..." -DownloadingResources: "Downloading Resource Files..." -Loading: "Loading" -LoadingWithDot: "Loading..." -LoadingComplete: "Loading Complete!" +Broken: "Sira" +Unknown: "Hindi Kilala" +Back: "Bumalik" +Yes: "Oo" +No: "Hindi" +Cancel: "Kanselahin" +Unload: "Palitan" +Retry: "Subukan Muli" +PreviousPage: "Nakaraang Pahina" +NextPage: "Susunod na Pahina" +Download: "I-download" +Disable: "I-disable" +Delete: "Burahin" +Author: "May-akda" +Close: "Isara" # 身份 / Identity -Host: "Host" -Cheater: "Cheater" +Id.Host: "Host" +Id.Cheater: "Cheater" +Id.Developer: "Developer" +Id.Contributor: "Kontribyutor" \ No newline at end of file diff --git a/Assets/Languages/French.yaml b/Assets/Languages/French.yaml index 83e07c6d..c423067e 100644 --- a/Assets/Languages/French.yaml +++ b/Assets/Languages/French.yaml @@ -1,314 +1,365 @@ -# FinalSuspect 的语言文件 / Fichier de traduction de FinalSuspect +# FinalSuspect 的语言文件 / Translation file of FinalSuspect -#当前翻译文件语言的ID / Language ID of current file -LangID: "8" - -# 作者署名(不需要请留空)/ Signature de l'auteur (laisser vide si non nécessaire) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" -# 职业类型 / Types de rôle -TypeImpostor: "Imposteurs" -TypeCrewmate: "Membres d'équipage" +# 职业类型 / Role Type +RoleType.Imp: "Imposteur" +RoleType.Crew: "Équipier" -# 阵营 / Équipes -TeamImpostor: "Équipe-Imposteur" -TeamImpostorOnly: "Imposteur" -TeamCrewmate: "Équipe-Membre d'équipage" +# 阵营 / Teams +Team.Imp: "Team-Imposteur" +Team.Imp_Only: "Imposteur" +Team.Crew: "Team-Équipier" -# 伪装者数量文字 / Texte du nombre d'imposteurs -ImpostorNumImp: "Il y a {0} imposteurs dans notre équipe" -ImpostorNumImpOnly: "Il n'y a qu'un imposteur dans la foule" -ImpostorNumCrew: "Il y a {0} imposteurs parmi nous" +# 伪装者数量文字 / Impostor Text +ImpostorNum.Imp: "Il y a {0} imposteurs dans notre équipe" +ImpostorNum.Imp_Only: "Il n'y a qu'un seul imposteur parmi nous" +ImpostorNum.Crew: "Il y a {0} imposteurs parmi nous" # 阵营开场 -ImpostorIntroText: "Laissez le mal envelopper le monde !" -ImpostorIntroTextOnly: "Je peux être seul, mais je possède une force infinie !" -CrewmateIntroText: "Terminez vos tâches, unissez-vous pour résoudre ces situations délicates !" - -## 原版职业 / Rôles originaux -Crewmate: "Membre d'équipage" -Engineer: "Ingénieur" -Scientist: "Scientifique" -Tracker: "Pisteur" -Noisemaker: "Bruiteur" -GuardianAngel: "Gardien ange" -Impostor: "Imposteur" -Shapeshifter: "Changé-forme" -Phantom: "Fantôme" -CrewmateGhost: "Fantôme de membre d'équipage" -ImpostorGhost: "Fantôme d'imposteur" -CrewmateGhostBlurb: "Terminez vos tâches" -ImpostorGhostBlurb: "Continuez à saboter les installations" -CrewmateGhostBlurbLong: "Terminez les tâches, ne traînez pas !" -ImpostorGhostBlurbLong: "Sabotez les installations, aidez les imposteurs survivants à remporter la victoire." - -## 捉迷藏 / Chasse au fantôme -HnSEngineerBlurb: "Survivre jusqu'à la fin !" -HnSEngineerBlurbLong: "Restez en vie jusqu'à ce que le temps soit écoulé ; terminer les tâches prolongera le temps. \nUtilisez les vents et les indicateurs de menace pour vous cacher ! \nUne fois que le minuteur commence, l'imposteur commencera à vous chasser !" -HnSImpostorBlurb: "Éliminer tout le monde !" -HnSImpostorBlurbLong: "Tuez tous les membres d'équipage dans le délai ! \nVous devez agir rapidement ! Les vents ne sont pas accessibles. \nÀ la dernière minute, vous recevrez un boost de vitesse et des indices de traque !" +IntroText.Imp: "Que le mal DOMINE LE MONDE !" +IntroText.Imp_Only: "Seul mais avec un POUVOIR INFINI !" +IntroText.Crewmate: "Complétez vos tâches et démasquez les imposteurs !" + +# 原版职业 / Vanilla +Role.Crewmate: "Équipier" +Role.Engineer: "Ingénieur" +Role.Scientist: "Scientifique" +Role.Tracker: "Traqueur" +Role.Noisemaker: "Bruiteur" +Role.GuardianAngel: "Ange Gardien" +Role.Impostor: "Imposteur" +Role.Shapeshifter: "Métamorphe" +Role.Phantom: "Fantôme" +Role.CrewmateGhost: "Fantôme d'Équipier" +Role.ImpostorGhost: "Fantôme d'Imposteur" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Complétez les tâches" +ImpostorGhostBlurb: "Continuez à saboter" +CrewmateGhostBlurbLong: "Complétez les tâches, ne freinez pas l'équipe !\nUtilisez 「Hantise」 pour voir les Imposteurs et aidez les Anges Gardiens à protéger les Équipiers !" +ImpostorGhostBlurbLong: "Sabotez les installations, aidez les imposteurs survivants à gagner !" +HnSEngineerBlurb: "Complétez les tâches et survivez jusqu'à la fin !" +HnSImpostorBlurb: "MASSACREZ-LES TOUS !" HnSCrewmateGhostBlurb: "Encouragez vos coéquipiers" -HnSCrewmateGhostBlurbLong: "Allez ! Encouragez ! Pour ! Vos ! Coéquipiers !" +HnSCrewmateGhostBlurbLong: "Mort ? Qu'est-ce que tu regardes ? Tu peux faire des tâches ? Non ?\nALLEZ ! ENCOURAGEZ ! VOS ! COÉQUIPIERS !" -# 死因 / Causes de décès +# 死因 / Death Reason DeathReason.Kill: "Tué" -DeathReason.Exile: "Exilé" -DeathReason.Disconnect: "Déconnecté" - -# 客户端选项 / Options du client -FinalSuspectOptions: "Options de Final Suspect" -Back: "Retour" -Yes: "Oui" -No: "Non" -UnlockFPS: "Débloquer les FPS" -ChangeOutfit: "Changer de tenue" -BeanMode: "Mode haricot classique" -HorseMode: "Mode cheval d'avril" -LongMode: "Mode long d'avril" -KickPlayerFriendCodeNotExist: "Expulser les joueurs qui ne sont pas connectés." -KickPlayerWithDenyName: "Expulser les joueurs avec des pseudonymes inappropriés" -KickPlayerInBanList: "Expulser les joueurs bannis" -SpamDenyWord: "Bloquer les mots inappropriés" -AutoStartGame: "Démarrer automatiquement avec une salle pleine" -AutoEndGame: "Retour automatique au lobby à la fin" -SwitchVanilla: "Basculer en version d'origine" -DisableVanillaSound: "Désactiver les musiques d'Among Us" -DisableFAC: "Désactiver l'anti-triche" -ShowPlayerInfo: "Afficher les informations de la plateforme et du client du joueur" -UseModCursor: "Utiliser le curseur du mod" -FastBoot: "Mode de lancement rapide" -PrunkMode: "Mode plaisanterie" -VersionCheat: "Contourner la vérification de la version du mod" -GodMode: "Mode Dieu" -NoGameEnd: "Pas de fin de partie" -EnableFinalSuspect: "Activer「Final Suspect」" - -# 客户端功能 / Fonctionnalités du client -FinalSuspectFeatures: "Fonctionnalités de Final Suspect" -UnloadMod: "Basculer en version d'origine" -UnloadWarning: "Avertissement\nPour réactiver le mod, vous devez redémarrer le jeu.\nVoulez-vous continuer malgré tout ?" -CannotUnloadDuringGame: "Impossible de basculer en version d'origine pendant le jeu" -Cancel: "Annuler" -Unload: "Décharger" -DumpLog: "Générer un journal" -ClearAutoLogs: "Effacer le log automatique" -SoundOptions: "Mes musiques" -AudioManagementOptions: "Gestion audio" -OnlyAvailableInMainMenu: "Disponible uniquement dans le menu principal" - -# 提示 / Conseils -updatePleaseWait: "Veuillez patienter..." -updateInProgress: "Mise à jour en cours..." -DownloadingAudios: "Téléchargement en cours..." -Playing: "En train de jouer..." -Parsing: "Analyse en cours..." -DownLoadSucceedNotice: "Téléchargement réussi !" -DownLoadFailureNotice: "Échec du téléchargement =(" -PleaseWait: "Veuillez patienter..." - -# 更新检查 / Vérification des mises à jour -Retry: "Réessayer" -updateCheckPopupTitle: "Vérification des mises à jour" -updateCheckFailedRetry: "Échec de la vérification des mises à jour :(\nRéessayer ?" -updateCheckFailedExit: "Échec de la vérification des mises à jour :(\nVeuillez vérifier votre connexion Internet et réessayer." - -# 更新提醒 / Rappel de mise à jour -UpdateBySelfTitle: "Rappel de mise à jour" -updateNotice: "Rappel de mise à jour" -UpdateBySelfText: "Cette version ne prend pas en charge les mises à jour automatiques. Veuillez mettre à jour manuellement." -updateButton: "Mettre à jour maintenant" -updatePopupTitle: "Mettre à jour maintenant" -updatePopupTitleFailed: "Échec de la mise à jour" -updatePopupTitleDone: "Mise à jour terminée" - -# 选择更新渠道 / Sélectionner la source de la mise à jour -updateChoseSource: "Veuillez sélectionner un canal pour la mise à jour\nsi vous ne savez pas quoi sélectionner, veuillez choisir [Github]\nsi la mise à jour échoue, choisissez [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Avis de fin de la mise à jour -updateRestart: "Redémarrez le jeu pour appliquer les modifications :)" -updatePingFialed: "Raison : {0}\nLe canal sélectionné peut être temporairement indisponible. Veuillez essayer de changer de canal de téléchargement." -updateFileMd5Incorrect: "Raison : Erreur de somme de contrôle du fichier\nLa version du fichier de ce canal n'est pas la plus récente. Veuillez essayer de changer de canal de téléchargement." -downloadFailed: "Raison : Téléchargement expiré ou interrompu\nVeuillez réessayer après avoir changé de réseau ou mettre à jour manuellement." - -# 无法加入公开游戏原因 / Raisons d'échec de la connexion à une partie publique -onSetPublicNoLatest: "Nous avons une mise à jour importante. Veuillez mettre à jour ce mod.\nSinon, vous ne pourrez pas rejoindre les salles publiques." -CanNotJoinPublicRoomNoLatest: "Nous avons une mise à jour importante. Veuillez mettre à jour ce mod.\nSinon, vous ne pourrez pas rejoindre les salles publiques." -ModBrokenMessage: "Les fichiers du mod sont corrompus. Veuillez redémarrer le jeu ou réinstaller ce mod." -UnsupportedVersion: "Votre version d'Among Us est incompatible avec FinalSuspect.\nVeuillez mettre à jour votre jeu." - -# 音频播放 / Lecture audio -PlayMode0: "Jouer une fois" -PlayMode1: "Répéter une seule fois" -PlayMode2: "Aléatoire" -PlayMode3: "Séquentiel" -Stop: "Arrêter" -CanPlay: "← Cliquez pour jouer" -NoFound: "[Fichier manquant]" -NextPage: "Page suivante" -PreviousPage: "Page précédente" - -# 音频添加 / Ajout d'audio -download: "Télécharger" -delete: "Supprimer" -NewSound: "Ajouter une nouvelle musique" -PleaseEnterMusic: "Veuillez entrer le nom de la musique" -AudioManagementAlreadyExists: "Ce nom de musique existe déjà" -NotAllowedMusic: "Le format du nom de la musique n'est pas autorisé" - -# 界面提示 / Conseils d'interface -CustomAudioManagementHelp: "Vous pouvez télécharger des musiques prises en charge par XtremeWave ou ajouter vos propres musiques dans le「Gestionnaire de sons」. Pour ajouter vos propres musiques, veillez à ajouter le nom de la musique dans le「Gestionnaire de sons」et à placer le fichier audio correspondant dans le dossier「Among Us/Final Suspect_Data/Resources/Sounds」(formats pris en charge : .wav). Les musiques peuvent être jouées dans「Mes Musiques」." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Vous pouvez télécharger des musiques prises en charge par XtremeWave ou ajouter vos propres musiques dans le「Gestionnaire de sons」. Si le chemin du fichier audio local est manquant, il affichera「[Fichier manquant]」." - -# 主界面音乐提醒 / Rappel de musique du menu principal -MusicNotYet: "Le fichier audio actuel est détecté comme incomplet" -AudioNYPro: "Pour une meilleure expérience de jeu, téléchargez nos musiques dans le「Gestionnaire de sons」dans le menu principal." +DeathReason.Exile: "Banni" +DeathReason.Disconnect: "D/C" + +# 客户端选项 / Client Options +FinalSuspectOptions: "Options Final Suspect" +ClientOption.UnlockFPS: "Déverrouiller les FPS" +ClientOption.SwitchOutfitType: "Changer le type de tenue" +ClientOption.KickPlayerWithAbnormalFriendCode: "Exclure les joueurs avec Friend Code anormal" +ClientOption.KickPlayerWithDenyName: "Exclure les joueurs utilisant des noms interdits" +ClientOption.KickPlayerInBanList: "Exclure les joueurs bannis" +ClientOption.SpamDenyWord: "Bloquer les mots interdits" +ClientOption.AutoStartGame: "Démarrer automatiquement quand plein" +ClientOption.AutoEndGame: "Retour automatique au lobby après partie" +ClientOption.SwitchVanilla: "Passer à Vanilla" +ClientOption.DisableVanillaSound: "Désactiver la musique vanilla" +ClientOption.EnableFAC: "Activer l'Anti-Triche" +ClientOption.EnableGuardian: "Activer le Gardien Client (Expérimental)" +ClientOption.ShowPlayerInfo: "Afficher plateforme & info client" +ClientOption.UseModCursor: "Utiliser curseur du mod" +ClientOption.FastLaunchMode: "Mode Lancement Rapide" +ClientOption.OfflineMode: "Mode hors ligne (Expérimental)" +ClientOption.VersionCheat: "Contourner la vérification de version" +ClientOption.GodMode: "Mode Dieu" +ClientOption.NoGameEnd: "Pas de Fin de Partie" +ClientOption.EnableFinalSuspect: "Activer 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Mode Classique" +Value.HorseMode: "Mode Cheval du 1er Avril" +Value.LongMode: "Mode Long du 1er Avril" + +# 客户端功能 / Client Features +FinalSuspectFeatures: "Fonctionnalités Final Suspect" +ClientFeature.UnloadMod: "Passer à Vanilla" +ClientFeature.DumpLog: "Exporter les Logs" +ClientFeature.ClearAutoLogs: "Effacer les Logs Auto" +ClientFeature.MyMusic: "Ma Musique" +ClientFeature.ResourceManager: "Gestionnaire de Ressources" +ClientFeature.NameTagManager: "Gestionnaire d'Étiquettes" +ClientFeature.MainMenuStyleManager: "Changer le Style du Menu" + +# 提示 / Tips +Tip.Downloading: "Téléchargement..." +Tip.PleaseWait: "Veuillez patienter..." +Tip.Playing: "Lecture..." +Tip.Parsing: "Analyse..." +Tip.Updating: "Mise à jour..." +Tip.DownLoadFinished: "Téléchargement terminé" +Tip.DownLoadSucceeded: "Téléchargement réussi !" +Tip.DownLoadFailed: "Échec du téléchargement=(" +Tip.PackageExists: "Installé" +Tip.OnlyAvailableInMainMenu: "Disponible uniquement au Menu Principal" +Tip.HideSummaryTextToShowWinText: "Masquer le Résumé pour voir le message de victoire" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Chargement des fichiers langue terminé" +Tip.CheckingForFiles: "Vérification des fichiers ressource..." +Tip.DownloadingResources: "Téléchargement des ressources..." +Tip.Loading: "Chargement" +Tip.LoadingWithDot: "Chargement..." +Tip.LoadingComplete: "Chargement terminé !" +## 切换原版 +Tip.UnloadWarning: "Attention !\nRedémarrez le jeu pour la version moddée.\nPasser à Vanilla ?" +Tip.CannotUnloadDuringGame: "Impossible de changer pendant une partie." +## 资源管理 +Tip.ResourceManager: "Téléchargez ressources compatibles mod dans 「Gestionnaire de Ressources」\nFichiers préfixés \"Pre-\" sont des packs pré-téléchargés" +## 我的音乐 +Tip.MyMusic: "Téléchargez musiques compatibles dans 「Gestionnaire de Ressources」 ou ajoutez fichiers audio dans (Among Us/Final Suspect_Data/Musics). Si le chemin n'existe pas, 「Fichier Manquant」 s'affichera" +Tip.Incomplete_Music: "Fichier musique incomplet détecté" +Tip.Incomplete_SoundEffect: "Fichier son incomplet détecté" +Tip.Incomplete_Image: "Fichier image incomplet détecté" +Tip.Incomplete: "Pour améliorer votre expérience, téléchargez le pack de ressources compatible dans 「Menu Principal - Paramètres - Fonctionnalités Client - Gestionnaire de Ressources」" +## 名称标识管理 +Tip.TextContent: "Contenu Texte" +Tip.TextSizeDescription: "Taille Texte (Défaut 100%)" +Tip.TextColorDescription: "Couleur Texte (Code Hexadécimal)\nLaissez vide si non requis, plusieurs valeurs pour dégradé automatique\n" +Tip.PleaseEnterFriendCode: "Entrez le Friend Code pour lier le nouvel étiquette" +Tip.FriendCodeAlreadyExist: "Ce Friend Code a déjà un étiquette" +Tip.FriendCodeIncorrect: "Entrez un Friend Code valide" +Tip.CustomNameTagHelp: "Ajoutez des étiquettes pour tout joueur. Elles seront assignées automatiquement. Édition impossible dans un lobby.\nNon-VIP : uniquement remarques (fonctions VIP non développées)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Téléchargez 「Packs de Style Menu」 dans 「Gestionnaire de Ressources」 et choisissez votre style préféré ici" + +# 更新结果 +UpdateResult.Succeed_Title: "Mise à jour Réussie" +UpdateResult.Succeed_Text: "Actif après redémarrage :)" +UpdateResult.Failed_Title: "Échec Mise à jour" +UpdateResult.Failed_Reason_NotFound: "Raison : {0}\nSource temporairement indisponible, changez de source" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Raison : Erreur vérification fichier\nVersion obsolète, changez de source" +UpdateResult.Failed_Reason_Ping: "Raison : Time-out ou interruption\nVérifiez votre réseau ou mettez à jour manuellement" + +# 更新检查 / Update Checker +UpdateCheck.Popup_Title: "Vérification Mise à jour" +UpdateCheck.Failed_Retry: "Échec vérification mise à jour :(\nRéessayer ?" +UpdateCheck.Failed_Exit: "Échec vérification mise à jour :(\nVérifiez votre réseau !" + +# 更新提醒 / Update Reminder +UpdateRemind.updatePopup: "Mettre à jour Maintenant" +UpdateRemind.updateNotice: "Rappel Mise à jour" +UpdateRemind.BySelf_Title: "Conseil Mise à jour" +UpdateRemind.BySelf_Text: "Cette version ne supporte pas la mise à jour en un clic, faites-la manuellement" + +# 选择更新渠道 / Update Chose Source +UpdateSource.Choose: "Choisissez source de mise à jour\nSi incertain, choisissez [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" + +# 无法加入公开游戏原因 / Unable to join public game reasons +CanNotJoinPublicRoomNoLatest: "Mise à jour importante disponible\nMettez à jour ce mod pour rejoindre les salles publiques" +ModBrokenMessage: "Fichiers mod corrompus, redémarrez ou réinstallez" +UnsupportedVersion: "Votre version Among Us est incompatible avec FinalSuspect\nMettez à jour le jeu" + +# 名称标识 +NameTag.DisplayName: "Remarque" +NameTag.Title: "Titre" +NameTag.Prefix: "Préfixe" +NameTag.Suffix: "Suffixe" +NameTag.Name: "Nom" +NameTag.LastTag: "Suffixe Additionnel" +NameTag.PreviewNotAvailable: " (Aperçu non supporté) " +NameTag.CanNotEdit: " (Non éditable) " +NameTag.RefreshPreview: "Actualiser Aperçu" +NameTag.SaveAndClose: "Sauvegarder et Quitter" +NameTag.NewNameTag: "Nouveau" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Chasse à l'Aube(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Le temps passe, l'hiver nordique se termine, le printemps renaît.\nContemplant les paysages enneigés de Mira, les souvenirs reviennent ? Des ruisseaux chauds coulent dans le cœur.\nPeu importe le suspect final, peu importe qui brise la vérité,\nDans une expérience rafraîchissante, réfléchissez et créez de nouveaux souvenirs heureux.\nJouer, s'amuser est le plus important !!!\n\nNommé par : 一念旧情丶" +MainMenuStyle.Title_Security: "Cœur Rouge, Amour Féroce (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Nous ne tomberons pas, n'abandonnerons pas, ne décevrons pas les joueurs, ne cesserons jamais de progresser.\nFace aux regards froids, nous prouverons à tous !\nAvec passion, nous reprenons la mer\n\nNommé par : Slok" +MainMenuStyle.Title_NewYear: "Affinité (Nouvel An)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortune : Souhaits accordés Lien : Tissé dans les battements\nHarmonie : Fils d'affection Béatitude : Paix en chaque âme\nLiens forgés partagent joie Voyagez loin, trouvez joie dans l'unité\nÂge d'or berce tous dans l'allégresse Notre prière—joie pour chaque être\nQue chaque âme trouve sa voie l'an prochain\nAvec nos bénédictions éclairant votre chemin, forgez votre légende !\n\nNommé par : Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Nouvel An)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Fleurs éclosent comme vagues, douces comme vent, souhaitant les cœurs vastes comme vent, tout s'accomplisse\"\n\"—Vents printaniers puissants, vagues sur des lieues, souhaitant tous debout au front, prospères\"\n\"—Bienvenue à XtremeWave — Programme Spécial Nouvel An !\"\n\nNommé par : Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final Visant l'Excellence, Rêve Commandant les Vagues !」\n\nNommé par : Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "En Regardant en Arrière à la Fin (Collaboration)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Regarder en arrière à la fin, tout finit à la fin」\n\nNommé par : MAMTI.麦麦头" +MainMenuStyle.NotFound: "Non Téléchargé" +MainMenuStyle.NotApply: "Appliquer" +MainMenuStyle.Applied: "Appliqué" + +# 资源包 +Package.MainMenuStyle: "Pack Style Menu Principal" + +# 音频播放 / Audio Playback +MusPlay.Mode0: "Lire Une Fois" +MusPlay.Mode1: "Boucle Unique" +MusPlay.Mode2: "Liste Aléatoire" +MusPlay.Mode3: "Lecture Séquentielle" +MusPlay.Stop: "Arrêter Lecture" +MusPlay.CanPlay: "Cliquer pour Lire" +MusPlay.NoFound: "Fichier Manquant" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(Joyeux Nouvel An Chinois)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" +Mus.TidalSurge: "Vague de Marée" +Mus.TrailOfTruth: "Piste de Vérité" Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.Fractured: "Fracturé" +Mus.ElegyOfFracturedVow: "Élégie du Vœu Fracturé" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] a été expulsé car son nom correspond à [{1}]" -Message.BanedByBanList: "[{0}] a été banni car il a été banni par le passé." -Message.BanedByFACList: "[{0}] a été banni car il est dans la liste des bannis du FAC." -Message.DumpfileSaved: "Le fichier de journal a été enregistré avec succès sur le bureau, nom du fichier : {0}" -Message.KickedByNoFriendCode: "[{0}] a été expulsé car son code d'amitié n'existe pas." -Message.AddedPlayerToBanList: "Ajout de [{0}] à la liste des bannis" -Message.KickedByFAC: "[{0}] a été expulsé par le FAC, raison : {1}" -Message.BanedByFAC: "[{0}] a été banni par le FAC, raison : {1}" +Mus.ReturnToSimplicity: "Retour à la Simplicité" +Mus.ReturnToSimplicity2: "Retour à la Simplicité (Version Complète)" +Mus.ChasingDawn: "Chasse à l'Aube" +Mus.StruggleAgainstFadingFlame: "Lutte Contre la Flamme Faiblissante" +Mus.Affinity: "Affinité" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Rôle" +DisplayedRoleTag.PlayerIdentityTag: "Étiquette d'identité" +DisplayedRoleTag.Room: "Salle" + +PlayerIdentityTag.Hard_Cleared: "Confirmé villageois" +PlayerIdentityTag.Silver_Clear: "Probable villageois" +PlayerIdentityTag.Wolf_Bucket: "Fosse aux loups" +PlayerIdentityTag.No_Kill: "Pas de meurtre" +PlayerIdentityTag.Outside_Position: "Position extérieure" +PlayerIdentityTag.Inside_Position: "Position intérieure" # 通知 / Notifications -PlayerLeft: "[{0}] a quitté le jeu" -PlayerLeftCuzTimeout: "[{0}] a quitté le jeu en raison d'un timeout de connexion" -PlayerKickByHost: "[{0}] a été expulsé par l'hôte" -PlayerBanByHost: "[{0}] a été banni par l'hôte" -PlayerLeftCuzError: "[{0}] a quitté le jeu en raison d'une erreur" -PlayerLeftByAU-Anticheat: "[{0}] a été expulsé par les anti-tricheurs officiels d'Among Us (non lié à FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] a été expulsé car il avait une version différente du mod" - -# 警告 / Avertissements -Warning: "Avertissement !" -Warning.MismatchedVersion: "{0}\npossède une version différente de {1}" -Warning.AutoExitAtMismatchedVersion: "L'hôte n'a pas ou a une version différente de {0}\nVous serez expulsé dans {1}" -Warning.InvalidRpc: "{0} a été expulsé car un RPC invalide a été reçu." -Warning.InvalidRpc_NotHost: "{0} est suspect d'utiliser des tricheurs. Veuillez demander à l'hôte de l'expulser (RPC Invalide : {1})" -Warning.SetName: "{0} a été expulsé car il a défini son nom plusieurs fois." -Warning.SetName_NotHost: "{0} est suspect d'utiliser des tricheurs. Veuillez demander à l'hôte de l'expulser (Définir le nom plusieurs fois)" -Warning.SendQuickChat: "{0} a été expulsé car il a envoyé plusieurs messages rapides en 3 secondes" -Warning.SendQuickChat_NotHost: "{0} est suspect d'utiliser des tricheurs. Veuillez demander à l'hôte de l'expulser (Envoyer plusieurs messages rapides en 3 secondes)" -Warning.InvalidSlothRPC: "{0} a été expulsé car un RPC illégal a été reçu (Envoi illégal d'un Rpc officiel: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} est suspect d'utiliser des tricheurs. Veuillez demander à l'hôte de l'expulser (Envoi illégal d'un RPC officiel : {1})" -Warning.Cheater: "{0} a été expulsé car il est suspecté d'utiliser des tricheries" -Warning.Cheater_NotHost: "{0} est suspecté d'utiliser des tricheries, Veuillez demander à l'hôte de l'expulser" -Warning.CantKickDev: "Désolé, vous ne pouvez pas expulser le développeur" -Warning.RoomBroken: "Désolé, cette salle a été compromises. Veuillez aller dans une autre salle pour continuer votre partie." - -## 错误等级 / Niveaux d'erreur -ErrorLevel1: "Des bugs peuvent survenir." -ErrorLevel2: "Cela peut être un bug." -ErrorLevel3: "Cette version ne devrait pas être publiée." - -# 反作弊 / Anti-triche -FAC.CheatDetected.HighLevel: "Avertissement : le FAC a détecté un niveau élevé de triche." -FAC.CheatDetected.LowLevel: "Avertissement : le FAC a détecté un niveau faible de triche. L'un des joueurs est en train de tricher." -FAC.CheatDetected.FAC: "Utilisation de programmes de triche (par exemple, AUM, YuMenu, SM, etc.)" - -# 模组信息 / Informations sur le mod -Contributors: "Contributeurs" -Acknowledgement: "Remerciements" - -# 断连提示 / Raisons de déconnexion -DCNotify.Hacking: "Vous avez été expulsé par l'anti-triche.\n (l'utilisation d'un module peut être mal interprétée comme une triche)" -DCNotify.Banned: "Vous n'êtes pas autorisé à entrer dans cette salle" -DCNotify.Kicked: "Vous avez été expulsé de la salle" -DCNotify.DCFromServer: "Vous êtes déconnecté du serveur.\nCela peut être dû à une instabilité de votre réseau.\nCela peut également être dû à une instabilité du serveur." -DCNotify.GameNotFound: "La salle assignée n'a pas été trouvée, la salle peut avoir été dissoute\n ou vérifiez si vous avez sélectionné un serveur différent de la salle" -DCNotify.GameStarted: "Le jeu a déjà commencé, veuillez attendre qu'il se termine" -DCNotify.GameFull: "La salle est pleine, veuillez réessayer plus tard" -DCNotify.IncorrectVersion: "Votre version d'Among Us est différente de celle de cette salle" -DCNotify.Description: "Vous avez été expulsé du jeu.\nRaison : {0}" -DCNotify.DenyName: "Votre pseudo contient des caractères irréguliers" -DCNotify.BanList: "Vous avez été banni par l'hôte" -DCNotify.FACList: "Vous avez été banni par le FAC" -DCNotify.CheatDetected: "Vous avez été détecté comme suspect de triche par le FAC" -DCNotify.InvalidRPC: "Vous avez peut-être installé un mod différent de celui de l'hôte ou votre mod a été modifié de manière malveillante" -DCNotify.ModVersionIncorrect: "Votre version du mod est différente de celle de l'hôte" -DCNotify.LowLevel: "Votre niveau ne répond pas aux exigences de cette salle" -DCNotify.NotLogin: "Les joueurs non connectés ne sont pas autorisés dans cette salle" - -# 任务栏 / Barre de tâches -PressF1ShowRoleDescription: "Appuyez sur F1 pour afficher la description de votre rôle" -FakeTask: "Tâche fictive :" -KillCount: "Meurtres" - -# 复盘信息 / Résultats de la partie -RoleSummaryText: "Derniers résultats :" -ShowResults: "Afficher les derniers résultats" -HideResults: "Cacher les derniers résultats" -NoInfoExists: "Aucun dernier résultat disponible" -CrewsWin: "Victoire de l'équipe des membres d'équipage" -CrewmatesWin: "Victoire des membres d'équipage" -CrewmatesWinBlurb: "La lumière de la vérité brille dans l'espoir !" -ImpsWin: "Victoire de l'équipe des imposteurs" -ImpostorsWin: "Victoire des imposteurs" -ImpostorsWinBlurb: "Le mal transforme la vérité en cendres" -HideSummaryTextToShowWinText: "Cacher les derniers résultats pour afficher le texte de victoire" - -# 禁用公开 -DisabledByProgram: "Les opérations de salle publique ont été désactivées par le programme" -PublicNotAvailableOnThis Version: "Les salles publiques ne sont pas disponibles sur cette version de FinalSuspect" - -# 主页 / Interface principale -FinalSuspectWelcomeText: "Souhaitant une agréable expérience de jeu !" -ConnectToFinalSuspectServerFailed: "Échec de la connexion au serveur de FinalSuspect" -Website: "Site officiel" +## 原版 +Notification.PlayerLeft: "[{0}] a quitté la partie" +Notification.PlayerLeftCuzTimeout: "[{0}] a quitté pour time-out connexion" +Notification.PlayerKickByHost: "[{0}] expulsé par l'hôte" +Notification.PlayerBanByHost: "[{0}] banni par l'hôte" +Notification.PlayerLeftCuzError: "[{0}] a quitté pour erreur" +Notification.PlayerLeftByAU-Anticheat: "[{0}] expulsé par l'anti-triche Among Us (sans lien avec FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] expulsé pour version/mod différente" +## 模组 +Notification.KickedByDenyName: "[{0}] expulsé pour nom interdit" +Notification.DumpfileSaved: "Fichier log sauvegardé sur bureau : {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] retiré pour Friend Code anormal" +Notification.AddedPlayerToBanList: "Ajouter [{0}] à la liste des bannis" +Notification.FPSSetTo: "FPS limité à : {0}" + +# 警告 / Warnings +Warning: "Avertissement" +Warning.MismatchedVersion: "[{0}]\na une version différente de [{1}]" +Warning.AutoExitAtMismatchedVersion: "Votre version [{0}] diffère de l'hôte\nExpulsion dans {1} secondes" +Warning.CantKickDev: "Impossible d'expulser les développeurs" +Warning.RoomBroken: "Room compromise par des hackers, changez de room" +Warning.InvalidColor: "Joueur avec couleur invalide détecté" + +## 错误等级 / Error Levels +ErrorLevel1: "Peut causer plusieurs bugs simultanés" +ErrorLevel2: "Des bugs peuvent survenir" +ErrorLevel3: "Version non publiée" + +# 反作弊 / FAC +CheatDetected.HighLevel: "Alerte : FAC défend contre les cheats de bombardement" +CheatDetected.LowLevel: "Alerte : FAC a détecté un tricheur possible" +CheatDetected.UseCheat: "{0} a utilisé un cheat [{1}]" +CheatDetected.MayUseCheat: "{0} suspecté d'utiliser mod [{1}] ou cheat" +CheatDetected.InvalidRpc: "[{0}] expulsé pour données invalides (Rpc invalide : {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] suspect de triche, demandez à l'hôte d'expulser (Rpc invalide : {1})" +CheatDetected.SetName: "[{0}] expulsé pour changements de nom multiples" +CheatDetected.SetName_NotHost: "[{0}] suspect de triche, demandez à l'hôte d'expulser (Changements de nom multiples)" +CheatDetected.SendQuickChat: "[{0}] expulsé pour messages rapides multiples" +CheatDetected.SendQuickChat_NotHost: "[{0}] suspect de triche, demandez à l'hôte d'expulser (Messages rapides en 3s)" +CheatDetected.InvalidSlothRPC: "[{0}] expulsé pour données illégales (Rpc officiel illégal : {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] suspect de triche, demandez à l'hôte d'expulser (Rpc officiel illégal : {1})" +CheatDetected.Overload: "[{0}] expulsé pour attaque de surcharge" +CheatDetected.Overload_NotHost: "[{0}] a lancé une attaque de surcharge, room compromise - jouez ailleurs" +CheatDetected.Cheater: "[{0}] expulsé pour suspicion de triche" +CheatDetected.Cheater_NotHost: "[{0}] suspect de triche, demandez à l'hôte d'expulser" +CheatDetected.BanedByBanList: "[{0}] expulsé pour liste des bannis" +CheatDetected.BanedByFACList: "[{0}] expulsé pour liste FAC des bannis" + +# 模组信息 / Mod Infos +ModInfo.Contributors: "Contributeurs" +ModInfo.Acknowledgement: "Remerciements Spéciaux" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Expulsé par l'anti-triche d'InnerSloth" +DCNotify.Banned: "Banni de cette room" +DCNotify.Kicked: "Expulsé de cette room" +DCNotify.DCFromServer: "Connexion au serveur interrompue\nRéseau instable ou problème serveur" +DCNotify.GameNotFound: "Room introuvable, peut-être fermée\nOu mauvais serveur sélectionné" +DCNotify.GameStarted: "Partie déjà commencée, attendez la fin" +DCNotify.GameFull: "Room pleine, réessayez plus tard" +DCNotify.IncorrectVersion: "Votre version Among Us diffère de la room" +DCNotify.Description: "Expulsé de la room\nRaison : {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Appuyez sur F1 pour la description du rôle" +PressF2ToHidePane: "Appuyez sur F2 pour afficher/cacher" +FakeTask: "Fausse Tâche :" +KillCount: "Morts" + +# 复盘信息 / Last Results +Summary.Text: "Résumé Final :" +Summary.ShowResults: "Afficher Résumé" +Summary.HideResults: "Masquer Résumé" +Summary.NoInfoExists: "Aucun résumé valide" +Summary.CrewsWin: "Victoire des Équipiers" +Summary.ImpsWin: "Victoire des Imposteurs" +Outro.Crews_Win: "Victoire des Équipiers" +Outro.Crews_WinBlurb: "La lumière de la vérité brille dans l'espoir !" +Outro.Imps_Win: "Victoire des Imposteurs" +Outro.Imps_WinBlurb: "Le mal a réduit la vérité en cendres" + +# 主页 / Main UI +FinalSuspectWelcomeText: "Bonne expérience de jeu !" +RetrieveVersionInfoFailed: "Échec récupération infos FinalSuspect" +Website: "Site Officiel" MainMenuCredential: "{0} © 2025" -LShift: "Appuyez sur LShift pour retourner à la pièce précédente" -RShift: "Appuyez sur RShift pour entrer dans la salle du presse-papiers" -LobbyTimeDisplayText: "Temps d’existence" +LShift: "Lobby Précédent" +RShift: "Lobby Presse-papiers" -# 大厅列表显示 / Liste des salles -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +# 客户端平台 / Platform +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" -# 延迟显示 / Suivi du ping +# 延迟显示 / Ping Tracker Ping: "Ping" -FrameRate: "Taux d'images" +FrameRate: "Frame Rate" Server: "Serveur" Local: "Local" -# 其他 / Autres +# 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Le taux d'images est limité à : {0}" -BrowsingMode: "Mode de navigation" +BrowsingMode: "Mode Navigation" Broken: "Cassé" - -# 加载 / Chargement -LanguageFilesLoadingComplete: "Chargement des traductions terminé !" -CheckingForFiles: "Vérification de l'intégrité des fichiers de ressources..." -DownloadingResources: "Téléchargement des fichiers de ressources..." -Loading: "Chargement" -LoadingWithDot: "Chargement..." -LoadingComplete: "Chargement terminé !" - -# 身份 / Identité -Host: "Hôte" -Cheater: "Tricheur" +Unknown: "Inconnu" +Back: "Retour" +Yes: "Oui" +No: "Non" +Cancel: "Annuler" +Unload: "Changer" +Retry: "Réessayer" +PreviousPage: "Page Précédente" +NextPage: "Page Suivante" +Download: "Télécharger" +Disable: "Désactiver" +Delete: "Supprimer" +Author: "Auteur" +Close: "Fermer" + +# 身份 / Identity +Id.Host: "Hôte" +Id.Cheater: "Tricheur" +Id.Developer: "Développeur" +Id.Contributor: "Contributeur" \ No newline at end of file diff --git a/Assets/Languages/German.yaml b/Assets/Languages/German.yaml index be34dd1e..2b3cb2f4 100644 --- a/Assets/Languages/German.yaml +++ b/Assets/Languages/German.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "9" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostor" -TypeCrewmate: "Crewmate" +RoleType.Imp: "Impostor" +RoleType.Crew: "Crewmate" # 阵营 / Teams -TeamImpostor: "Team-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Team-Crewmate" +Team.Imp: "Team-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Team-Crewmate" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Es gibt {0} Impostor in unserem Team" -ImpostorNumImpOnly: "Es gibt nur einen Impostor in der Menge" -ImpostorNumCrew: "Es gibt {0} Impostor unter uns" +ImpostorNum.Imp: "Es gibt {0} Impostoren in unserem Team" +ImpostorNum.Imp_Only: "Es gibt nur 1 Impostor unter uns" +ImpostorNum.Crew: "Es gibt {0} Impostoren unter uns" # 阵营开场 -ImpostorIntroText: "Lass das Böse die Welt umhüllen!" -ImpostorIntroTextOnly: "Ich bin vielleicht allein, aber ich habe unendliche Kraft!" -CrewmateIntroText: "Erledige deine Aufgaben, vereint euch, um diese schwierigen Situationen zu lösen!" - -## 原版职业 / Vanilla -Crewmate: "Crewmate" -Engineer: "Ingenieur" -Scientist: "Wissenschaftler" -Tracker: "Tracker" -Noisemaker: "Störgeräusch" -GuardianAngel: "Schutzengel" -Impostor: "Impostor" -Shapeshifter: "Formwandler" -Phantom: "Phantom" -CrewmateGhost: "Crewmate Geist" -ImpostorGhost: "Impostor Geist" -CrewmateGhostBlurb: "Erledige deine Aufgaben" -ImpostorGhostBlurb: "Sabotiere weiterhin die Einrichtungen" -CrewmateGhostBlurbLong: "Erledige Aufgaben, bleibe nicht zurück!" -ImpostorGhostBlurbLong: "Sabotiere Einrichtungen, unterstütze die überlebenden Impostor bei einem Sieg." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Überlebe bis zum Ende!" -HnSEngineerBlurbLong: "Bleibe am Leben, bis die Zeit abläuft; Aufgaben erledigen verlängert die Zeit. \nNutze Schächte und Bedrohungshinweise, um dich zu verstecken! \nSobald der Timer startet, beginnt der Impostor mit der Jagd auf dich!" -HnSImpostorBlurb: "Eliminiere jeden!" -HnSImpostorBlurbLong: "Töte alle Crewmitglieder innerhalb der Zeitgrenze! \nDu musst schnell handeln! Schächte sind nicht zugänglich. \nZuletzt erhältst du einen Geschwindigkeitsbonus und Ortungshinweise!" -HnSCrewmateGhostBlurb: "Muntere deine Teamkollegen an" -HnSCrewmateGhostBlurbLong: "Los geht's! Muntere an! Für! Deine! Teamkollegen!" +IntroText.Imp: "Lasst das Böse DIE WELT BEHERRSCHEN!" +IntroText.Imp_Only: "Auch allein besitzt du ENDLOSE MACHT!" +IntroText.Crewmate: "Erfülle deine Aufgaben und finde die Impostoren!" + +# 原版职业 / Vanilla +Role.Crewmate: "Crewmate" +Role.Engineer: "Ingenieur" +Role.Scientist: "Wissenschaftler" +Role.Tracker: "Spürhund" +Role.Noisemaker: "Geräuschemacher" +Role.GuardianAngel: "Schutzengel" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Gestaltwandler" +Role.Phantom: "Phantom" +Role.CrewmateGhost: "Crewmate-Geist" +Role.ImpostorGhost: "Impostor-Geist" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Erfülle Aufgaben" +ImpostorGhostBlurb: "Setze Sabotage fort" +CrewmateGhostBlurbLong: "Erfülle Aufgaben, halte das Team nicht auf!\nNutze „Spuken“, um Impostoren zu sehen, und hilf Schutzengeln, Crewmates zu beschützen!" +ImpostorGhostBlurbLong: "Sabotiere Anlagen, unterstütze überlebende Impostoren zum Sieg!" +HnSEngineerBlurb: "Erfülle Aufgaben und überlebe bis zum Ende!" +HnSImpostorBlurb: "Schlachte sie ALLE!" +HnSCrewmateGhostBlurb: "Feuere deine Teamkollegen an" +HnSCrewmateGhostBlurbLong: "Tot? Was siehst du? Kannst du Aufgaben erledigen? Nein?\nLOS! FEUERE! DEINE! TEAMKOLLEGEN! AN!" # 死因 / Death Reason DeathReason.Kill: "Getötet" -DeathReason.Exile: "Verbannt" +DeathReason.Exile: "Exiliert" DeathReason.Disconnect: "Verbindung getrennt" # 客户端选项 / Client Options -FinalSuspectOptions: "Final Suspect Optionen" -Back: "Zurück" -Yes: "Ja" -No: "Nein" -UnlockFPS: "FPS entsperren" -ChangeOutfit: "Outfit ändern" -BeanMode: "Klassischer Bohne-Modus" -HorseMode: "April Fool Pferde-Modus" -LongMode: "April Fool Lang-Modus" -KickPlayerFriendCodeNotExist: "Spieler ohne Freundecode ausschließen." -KickPlayerWithDenyName: "Spieler mit unzulässigen Nicknames ausschließen" -KickPlayerInBanList: "Gesperrte Spieler ausschließen" -SpamDenyWord: "Unzulässige Wörter blockieren" -AutoStartGame: "Automatisch starten, wenn der Lobby voll ist" -AutoEndGame: "Automatisch zur Lobby zurückkehren, wenn das Spiel endet" -SwitchVanilla: "Zurück zur Originalversion wechseln" -DisableVanillaSound: "Among Us Musik deaktivieren" -DisableFAC: "Anti-Cheat deaktivieren" -ShowPlayerInfo: "Spielerplattform und Clientinformationen anzeigen" -UseModCursor: "Mod-Cursor verwenden" -FastBoot: "Schnellstartmodus" -PrunkMode: "Scherzmodus" -VersionCheat: "Mod-Version-Synchronitätsprüfung umgehen" -GodMode: "Gott-Modus" -NoGameEnd: "Kein Spielende" -EnableFinalSuspect: "「Final Suspect」 aktivieren" +FinalSuspectOptions: "Final Suspect-Optionen" +ClientOption.UnlockFPS: "FPS-Limit aufheben" +ClientOption.SwitchOutfitType: "Outfit-Typ wechseln" +ClientOption.KickPlayerWithAbnormalFriendCode: "Spieler mit ungültigem Friend Code kicken" +ClientOption.KickPlayerWithDenyName: "Spieler mit gesperrtem Namen kicken" +ClientOption.KickPlayerInBanList: "Gesperrte Spieler kicken" +ClientOption.SpamDenyWord: "Gesperrte Wörter blockieren" +ClientOption.AutoStartGame: "Spiel automatisch starten, wenn voll" +ClientOption.AutoEndGame: "Automatisch zur Lobby zurückkehren nach Spielende" +ClientOption.SwitchVanilla: "Zur Vanilla-Version wechseln" +ClientOption.DisableVanillaSound: "Vanilla-Spielmusik deaktivieren" +ClientOption.EnableFAC: "Anti-Cheat aktivieren" +ClientOption.EnableGuardian: "Client Guardian aktivieren (experimentell)" +ClientOption.ShowPlayerInfo: "Spieler-Plattform & Client-Info anzeigen" +ClientOption.UseModCursor: "Mod-Cursor verwenden" +ClientOption.FastLaunchMode: "Schnellstart-Modus" +ClientOption.OfflineMode: "Offline-Modus (experimentell)" +ClientOption.VersionCheat: "Versions-Sync-Check umgehen" +ClientOption.GodMode: "God Mode" +ClientOption.NoGameEnd: "Kein Spielende" +ClientOption.EnableFinalSuspect: "„Final Suspect“ aktivieren" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Klassischer Modus" +Value.HorseMode: "Aprilscherz-Pferde-Modus" +Value.LongMode: "Aprilscherz-Langer-Modus" # 客户端功能 / Client Features -FinalSuspectFeatures: "Final Suspect Funktionen" -UnloadMod: "Zurück zur Originalversion wechseln" -UnloadWarning: "Warnung\nUm das Mod erneut zu aktivieren, musst du das Spiel neu starten.\nMöchtest du trotzdem fortfahren?" -CannotUnloadDuringGame: "Während des Spiels kann nicht zur Originalversion gewechselt werden" -Cancel: "Abbrechen" -Unload: "Deaktivieren" -DumpLog: "Log auswerfen" -ClearAutoLogs: "Automatische Protokolle löschen" -SoundOptions: "Meine Musik" -AudioManagementOptions: "Audioverwaltung" -OnlyAvailableInMainMenu: "Nur im Hauptmenü verfügbar" +FinalSuspectFeatures: "Final Suspect-Funktionen" +ClientFeature.UnloadMod: "Zur Vanilla-Version wechseln" +ClientFeature.DumpLog: "Log exportieren" +ClientFeature.ClearAutoLogs: "Auto-Logs löschen" +ClientFeature.MyMusic: "Meine Musik" +ClientFeature.ResourceManager: "Ressourcen-Manager" +ClientFeature.NameTagManager: "Namens-Tag-Manager" +ClientFeature.MainMenuStyleManager: "Hauptmenü-Stil wechseln" # 提示 / Tips -updatePleaseWait: "Bitte warten..." -updateInProgress: "Aktualisierung läuft..." -DownloadingAudios: "Download läuft..." -Playing: "Wird gespielt..." -Parsing: "Analyse..." -DownLoadSucceedNotice: "Download erfolgreich!" -DownLoadFailureNotice: "Download fehlgeschlagen =(" -PleaseWait: "Bitte warten..." +Tip.Downloading: "Herunterladen..." +Tip.PleaseWait: "Bitte warten..." +Tip.Playing: "Spielt..." +Tip.Parsing: "Analysiere..." +Tip.Updating: "Aktualisiere..." +Tip.DownLoadFinished: "Download abgeschlossen" +Tip.DownLoadSucceeded: "Download erfolgreich!" +Tip.DownLoadFailed: "Download fehlgeschlagen =(" +Tip.PackageExists: "Installiert" +Tip.OnlyAvailableInMainMenu: "Nur im Hauptmenü verfügbar" +Tip.HideSummaryTextToShowWinText: "Letzte Zusammenfassung ausblenden, um Siegtext zu sehen" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Sprachdateien geladen" +Tip.CheckingForFiles: "Überprüfe Ressourcen-Dateien..." +Tip.DownloadingResources: "Ressourcen-Dateien herunterladen..." +Tip.Loading: "Laden" +Tip.LoadingWithDot: "Laden..." +Tip.LoadingComplete: "Laden abgeschlossen!" +## 切换原版 +Tip.UnloadWarning: "Warnung!\nDu musst das Spiel neu starten, um wieder mit Mods zu spielen.\nBist du sicher, dass du zur Vanilla-Version wechseln möchtest?" +Tip.CannotUnloadDuringGame: "Kann nicht zur Vanilla-Version wechseln, während ein Spiel läuft." +## 资源管理 +Tip.ResourceManager: "Du kannst mod-kompatible und zusätzliche Ressourcen im „Ressourcen-Manager“ herunterladen\nDateien mit „Pre-“ sind vorab geladene Ressourcen-Pakete" +## 我的音乐 +Tip.MyMusic: "Du kannst mod-unterstützte Musik im „Ressourcen-Manager“ herunterladen oder eigene Audiodateien in den Ordner (Among Us/Final Suspect_Data/Musics) legen. Falls der lokale Musik-Pfad nicht existiert, wird „Datei fehlt“ angezeigt" +Tip.Incomplete_Music: "Unvollständige Musikdatei erkannt" +Tip.Incomplete_SoundEffect: "Unvollständige Soundeffekt-Datei erkannt" +Tip.Incomplete_Image: "Unvollständige Bilddatei erkannt" +Tip.Incomplete: "Für ein besseres Spielerlebnis lade bitte das mod-kompatible Ressourcen-Paket unter „Hauptmenü – Einstellungen – Client-Funktionen – Ressourcen-Manager“ herunter" +## 名称标识管理 +Tip.TextContent: "Textinhalt" +Tip.TextSizeDescription: "Textgröße (Standard 100%)" +Tip.TextColorDescription: "Textfarbe (Hex-Farbcode)\nLeer lassen, falls nicht benötigt; mehrere Angaben für automatischen Farbverlauf\n" +Tip.PleaseEnterFriendCode: "Bitte gib den Friend Code ein, um das neue Namens-Tag zu binden" +Tip.FriendCodeAlreadyExist: "Dieser Friend Code besitzt bereits ein Namens-Tag" +Tip.FriendCodeIncorrect: "Bitte gib einen gültigen Friend Code ein" +Tip.CustomNameTagHelp: "Du kannst Namens-Tags für jeden Spieler hinzufügen. Tags werden automatisch zugewiesen, wenn der Spieler mit dem gebundenen Friend Code beitritt. Tags können nach Betreten einer Lobby nicht mehr bearbeitet werden.\nNicht-VIPs können nur Notizen hinzufügen (VIP-Funktionen noch nicht verfügbar)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Du kannst „Hauptmenü-Stil-Pakete“ im „Ressourcen-Manager“ herunterladen und hier deinen bevorzugten Stil auswählen" + +# 更新结果 +UpdateResult.Succeed_Title: "Update erfolgreich" +UpdateResult.Succeed_Text: "Wird nach Neustart des Spiels aktiviert :)" +UpdateResult.Failed_Title: "Update fehlgeschlagen" +UpdateResult.Failed_Reason_NotFound: "Grund: {0}\nDiese Quelle ist möglicherweise vorübergehend nicht verfügbar, bitte Download-Quelle wechseln und erneut versuchen" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Grund: Datei-Überprüfung fehlgeschlagen\nDie Datei dieser Quelle ist nicht die neueste, bitte Download-Quelle wechseln und erneut versuchen" +UpdateResult.Failed_Reason_Ping: "Grund: Update-Timeout oder -Unterbrechung\nBitte Netzwerk prüfen und erneut versuchen oder manuell updaten" # 更新检查 / Update Checker -Retry: "Wiederholen" -updateCheckPopupTitle: "Aktualisierungsprüfung" -updateCheckFailedRetry: "Aktualisierungsprüfung fehlgeschlagen :(\nWiederholen?" -updateCheckFailedExit: "Aktualisierungsprüfung fehlgeschlagen :(\nBitte überprüfe deine Internetverbindung und versuche es erneut." +UpdateCheck.Popup_Title: "Update prüfen" +UpdateCheck.Failed_Retry: "Update-Prüfung fehlgeschlagen :(\nErneut versuchen?" +UpdateCheck.Failed_Exit: "Update-Prüfung fehlgeschlagen :(\nBitte Netzwerk prüfen und erneut versuchen!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Aktualisierungserinnerung" -updateNotice: "Aktualisierungserinnerung" -UpdateBySelfText: "Diese Version unterstützt keine automatischen Aktualisierungen. Bitte aktualisiere manuell." -updateButton: "Jetzt aktualisieren" -updatePopupTitle: "Jetzt aktualisieren" -updatePopupTitleFailed: "Aktualisierung fehlgeschlagen" -updatePopupTitleDone: "Aktualisierung abgeschlossen" +UpdateRemind.updatePopup: "Jetzt aktualisieren" +UpdateRemind.updateNotice: "Update-Erinnerung" +UpdateRemind.BySelf_Title: "Update-Hinweis" +UpdateRemind.BySelf_Text: "Diese Version unterstützt kein One-Click-Update, bitte manuell updaten" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Bitte wähle eine Quelle für die Aktualisierung\nwenn du nicht weißt, was auswählen, wähle [Github]\nwenn die Aktualisierung fehlgeschlagen ist, wähle [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Starte das Spiel neu, um die Änderungen zu übernehmen :)" -updatePingFialed: "Grund: {0}\nDer gewählte Kanal könnte vorübergehend nicht verfügbar sein. Versuche einen anderen Download-Kanal." -updateFileMd5Incorrect: "Grund: Dateiprüfsumme fehlerhaft\nDie Dateiversion von diesem Kanal ist nicht die neueste. Versuche einen anderen Download-Kanal." -downloadFailed: "Grund: Download abgelaufen oder unterbrochen\nVersuche es nach einem Netzwerkwechsel oder aktualisiere manuell." +UpdateSource.Choose: "Bitte Update-Quelle wählen\nBei Unsicherheit [Github] auswählen" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Wir haben eine wichtige Aktualisierung. Bitte aktualisiere dieses Mod.\nAnsonsten kannst du keine öffentlichen Räume betreten." -CanNotJoinPublicRoomNoLatest: "Wir haben eine wichtige Aktualisierung. Bitte aktualisiere dieses Mod.\nAnsonsten kannst du keine öffentlichen Räume betreten." -ModBrokenMessage: "Die Mod-Dateien sind beschädigt. Bitte starte das Spiel neu oder installiere dieses Mod erneut." -UnsupportedVersion: "Deine Version von Among Us ist nicht kompatibel mit FinalSuspect.\nBitte aktualisiere dein Spiel." +CanNotJoinPublicRoomNoLatest: "Wichtiges Update verfügbar, bitte Mod aktualisieren\nSonst kannst du keine öffentlichen Räume betreten" +ModBrokenMessage: "Mod-Dateien abgestürzt, bitte Spiel neu starten oder Mod neu installieren" +UnsupportedVersion: "Deine Among Us-Version ist inkompatibel mit FinalSuspect\nBitte Spiel aktualisieren" + +# 名称标识 +NameTag.DisplayName: "Notiz" +NameTag.Title: "Titel" +NameTag.Prefix: "Präfix" +NameTag.Suffix: "Suffix" +NameTag.Name: "Name" +NameTag.LastTag: "Zusätzliches Suffix" +NameTag.PreviewNotAvailable: " (Vorschau nicht verfügbar) " +NameTag.CanNotEdit: " (Nicht bearbeitbar) " +NameTag.RefreshPreview: "Vorschau aktualisieren" +NameTag.SaveAndClose: "Speichern & Beenden" +NameTag.NewNameTag: "Neu" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Jag der Morgenröte(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Mit der Zeit endet der nördliche Winter, der Frühling erwacht.\nBeim Anblick von Miras Schneelandschaft – erwachen Erinnerungen? Warme Ströme fließen im Herzen.\nEgal wer der letzte Verdächtige ist, egal wessen Hände die Wahrheit zerbrechen,\nin einer erfrischenden neuen Erfahrung brainstormen und neue glückliche Erinnerungen hinterlassen.\nSpielen und Spaß haben ist das Wichtigste!!!\n\nBenannt von: 一念旧情丶" +MainMenuStyle.Title_Security: "Rotes Herz, heftige Liebe (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Wir werden nicht fallen, nicht aufgeben, Spieler nicht enttäuschen, niemals aufhören uns zu verbessern.\nWenn wir mit kalten Blicken konfrontiert werden, werden wir es allen beweisen!\nMit Leidenschaft starten wir erneut\n\nBenannt von: Slok" +MainMenuStyle.Title_NewYear: "Verbundenheit (Frühlingsfest)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Glück: Segenswünsche ausgesprochen Bindung: Im Herzschlag gewebt\nHarmonie: Fäden der Zuneigung Glückseligkeit: Frieden in jeder Seele\nSchicksalhafte Bande schmieden gemeinsame Freude Reise weit, finde Freude in der Einheit\nGoldene Zeit umarmt alle in Fröhlichkeit Unser Gebet – Freude für jedes Wesen\nMöge jede Seele im kommenden Jahr ihren bestimmten Weg finden\nMit unseren Segnungen, die deinen Weg erleuchten, beginne deine Reise, deine eigene Legende zu schmieden!\n\nBenannt von: Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Frühlingsfest)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Blumen blühen wie Wellen, sanft wie der Wind, wünsche jedem ein Herz so weit wie der Wind, alles geht reibungslos\"\n\"—Frühlingswinde brausen mächtig, Wellen schlagen kilometerweit, wünsche jedem, mutig an der Spitze zu stehen, gedeihend und florierend\"\n\"—Willkommen beim XtremeWave Frühlingsfest-Special!\"\n\nBenannt von: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "„Final strebt nach Exzellenz, Traum kommandiert tosende Wellen!“\n\nBenannt von: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Wenn man am Ende zurückblickt (Kollaboration)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "„Wenn man am Ende zurückblickt, endet alles am Ende“\n\nBenannt von: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Nicht heruntergeladen" +MainMenuStyle.NotApply: "Anwenden" +MainMenuStyle.Applied: "Angewendet" + +# 资源包 +Package.MainMenuStyle: "Hauptmenü-Stil-Paket" # 音频播放 / Audio Playback -PlayMode0: "Einmal abspielen" -PlayMode1: "Einzelnes Lied wiederholen" -PlayMode2: "Zufällig" -PlayMode3: "Sequenziell" -Stop: "Stoppen" -CanPlay: "← Klicke zum Abspielen" -NoFound: "[Datei fehlt]" -NextPage: "Nächste Seite" -PreviousPage: "Vorherige Seite" - -# 音频添加 / Audio Addition -download: "Herunterladen" -delete: "Löschen" -NewSound: "Neue Musik hinzufügen" -PleaseEnterMusic: "Bitte gib den Musiknamen ein" -AudioManagementAlreadyExists: "Dieser Musikname existiert bereits" -NotAllowedMusic: "Das Musiknamensformat ist nicht erlaubt" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Du kannst Musik herunterladen, die von XtremeWave unterstützt wird, oder deine eigene Musik im 「Audio Management」 hinzufügen. Wenn du deine eigene Musik hinzufügst, stelle sicher, dass du den Musiknamen im 「Audio Management」 hinzufügst und die entsprechende Audiodatei im Ordner 「Among Us/Final Suspect_Data/Resources/Sounds」 ablegst (unterstützte Formate: .wav). Musik kann in 「Meine Musik」 abgespielt werden." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Du kannst Musik herunterladen, die von XtremeWave unterstützt wird, oder deine eigene Musik im 「Audio Management」 hinzufügen. Wenn der lokale Musikressourcenpfad fehlt, wird angezeigt: 「[Datei fehlt]」." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Aktuelle Musikdatei wurde als unvollständig erkannt" -AudioNYPro: "Für ein besseres Spielerlebnis kannst du unsere Musik in 「Start-Einstellungen-Mehr Funktionen-Audioverwaltung」 herunterladen." - -# 官方音乐 / Official Musics -Mus.GongXiFaCai: "恭喜发财" +MusPlay.Mode0: "Einmal abspielen" +MusPlay.Mode1: "Endlos einzeln" +MusPlay.Mode2: "Zufällige Liste" +MusPlay.Mode3: "Sequentielle Wiedergabe" +MusPlay.Stop: "Wiedergabe stoppen" +MusPlay.CanPlay: "Klicken zum Abspielen" +MusPlay.NoFound: "Datei fehlt" + +# 官方音乐 / Musics +Mus.GongXiFaCai: "恭喜发财 (Frohes Chinesisches Neujahr)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Flutwelle" +Mus.TrailOfTruth: "Spur der Wahrheit" +Mus.Interlude: "Zwischenspiel" +Mus.Fractured: "Gebrochen" +Mus.ElegyOfFracturedVow: "Elegie des gebrochenen Gelübdes" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] wurde entfernt, weil sein Name mit [{1}] übereinstimmte" -Message.BanedByBanList: "[{0}] wurde gebannt, weil er zuvor gebannt wurde." -Message.BanedByFACList: "[{0}] wurde gebannt, weil er auf der FAC-Banned-Personen-Liste steht." -Message.DumpfileSaved: "Die Logdatei wurde erfolgreich auf dem Desktop gespeichert, Dateiname: {0}" -Message.KickedByNoFriendCode: "[{0}] wurde entfernt, weil sein Freundecode nicht existiert." -Message.AddedPlayerToBanList: "[{0}] wurde zur Banliste hinzugefügt" -Message.KickedByFAC: "[{0}] wurde von FAC entfernt, Grund: {1}" -Message.BanedByFAC: "[{0}] wurde von FAC entfernt, Grund:{1}" +Mus.ReturnToSimplicity: "zurück zur Einfachheit" +Mus.ReturnToSimplicity2: "zurück zur Einfachheit (Volle Version)" +Mus.ChasingDawn: "Jag der Morgenröte" +Mus.StruggleAgainstFadingFlame: "Kampf gegen die verblassende Flamme" +Mus.Affinity: "Verbundenheit" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Rolle" +DisplayedRoleTag.PlayerIdentityTag: "Identität-Tag" +DisplayedRoleTag.Room: "Raum" + +PlayerIdentityTag.Hard_Cleared: "Bestätigter Dorfbewohner" +PlayerIdentityTag.Silver_Clear: "Wahrscheinlicher Dorfbewohner" +PlayerIdentityTag.Wolf_Bucket: "Wolfsgrube" +PlayerIdentityTag.No_Kill: "Kein Mord" +PlayerIdentityTag.Outside_Position: "Außenposition" +PlayerIdentityTag.Inside_Position: "Innenposition" # 通知 / Notifications -PlayerLeft: "[{0}] hat das Spiel verlassen" -PlayerLeftCuzTimeout: "[{0}] hat das Spiel verlassen, weil die Verbindungstimeout abgelaufen ist" -PlayerKickByHost: "[{0}] wurde vom Host entfernt" -PlayerBanByHost: "[{0}] wurde vom Host gebannt" -PlayerLeftCuzError: "[{0}] hat das Spiel verlassen, weil ein Fehler aufgetreten ist" -PlayerLeftByAU-Anticheat: "[{0}] wurde von den offiziellen Anti-Cheat-Systemen von AmongU entfernt (nicht im Zusammenhang mit FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] wurde entfernt, weil er eine andere Version des Mods hatte" +## 原版 +Notification.PlayerLeft: "[{0}] hat das Spiel verlassen" +Notification.PlayerLeftCuzTimeout: "[{0}] hat das Spiel aufgrund von Verbindungs-Timeout verlassen" +Notification.PlayerKickByHost: "[{0}] wurde vom Host gekickt" +Notification.PlayerBanByHost: "[{0}] wurde vom Host gebannt" +Notification.PlayerLeftCuzError: "[{0}] hat das Spiel aufgrund eines Fehlers verlassen" +Notification.PlayerLeftByAU-Anticheat: "[{0}] wurde vom Among Us-Anti-Cheat gekickt (unabhängig von FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] wurde gekickt, da er eine andere Version/Mod installiert hat" +## 模组 +Notification.KickedByDenyName: "[{0}] wurde gekickt, da sein Name gesperrte Wörter enthält" +Notification.DumpfileSaved: "Log-Datei erfolgreich auf dem Desktop gespeichert, Dateiname: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] wurde entfernt, da dieser Raum Spieler mit ungültigen Friend Codes nicht erlaubt" +Notification.AddedPlayerToBanList: "[{0}] zur Bann-Liste hinzugefügt" +Notification.FPSSetTo: "FPS-Limit gesetzt auf: {0}" # 警告 / Warnings -Warning: "Warnung!" -Warning.MismatchedVersion: "{0}\nhat eine andere Version von {1}" -Warning.AutoExitAtMismatchedVersion: "Der Host hat keine oder eine andere Version von {0}\nDu wirst in {1} entfernt" -Warning.InvalidRpc: "{0} wurde entfernt, weil ein ungültiges RPC empfangen wurde." -Warning.InvalidRpc_NotHost: "{0} wird verdächtigt, Cheats zu verwenden. Bitte den Host darum bitten, ihn zu entfernen (Ungültiges Rpc:{1})" -Warning.SetName: "{0} wurde entfernt, weil er mehrmals einen Namen gesetzt hat." -Warning.SetName_NotHost: "{0} wird verdächtigt, Cheats zu verwenden. Bitte den Host darum bitten, ihn zu entfernen (Mehrfach Namenssetzung)" -Warning.SendQuickChat: "{0} wurde entfernt, weil er innerhalb von 3 Sekunden mehrere Quick-Nachrichten gesendet hat" -Warning.SendQuickChat_NotHost: "{0} wird verdächtigt, Cheats zu verwenden. Bitte den Host darum bitten, ihn zu entfernen (Versand mehrerer Quick-Nachrichten innerhalb von 3 Sekunden)" -Warning.InvalidSlothRPC: "{0} wurde entfernt, da ein ungültiges RPC empfangen wurde (Ungültiges Senden des offiziellen Rpc: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} wird verdächtigt, Cheats zu verwenden. Bitte den Host darum bitten, ihn zu entfernen (Ungültiges offizielles Rpc senden: {1})" -Warning.Cheater: "{0} wurde entfernt, da er verdächtigt wird, Cheats zu verwenden" -Warning.Cheater_NotHost: "{0} wird verdächtigt, Cheats zu verwenden. Bitte bitten Sie den Host, ihn zu entfernen" -Warning.CantKickDev: "Entschuldigung, du kannst den Entwickler nicht entfernen" -Warning.RoomBroken: "Entschuldigung, dieses Zimmer wurde sabotiert. Bitte gehe in ein anderes Zimmer, um weiterzuspielen." +Warning: "Warnung" +Warning.MismatchedVersion: "[{0}]\nhat eine andere Version von [{1}] installiert" +Warning.AutoExitAtMismatchedVersion: "Deine Version von [{0}] unterscheidet sich vom Host\nDu wirst in {1} Sekunden gekickt" +Warning.CantKickDev: "Sorry, du kannst keine Entwickler kicken" +Warning.RoomBroken: "Sorry, dieser Raum wurde gehackt, bitte in einem anderen Raum spielen" +Warning.InvalidColor: "Spieler mit ungültiger Farbe erkannt" ## 错误等级 / Error Levels -ErrorLevel1: "Es können Fehler auftreten." -ErrorLevel2: "Dies könnte ein Fehler sein." -ErrorLevel3: "Diese Version hätte nicht veröffentlicht werden sollen." +ErrorLevel1: "Kann mehrere Bugs gleichzeitig verursachen" +ErrorLevel2: "Bugs können auftreten" +ErrorLevel3: "Nicht veröffentlichte Version" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Warnung: FAC hat ein hohes Niveau an Cheats erkannt." -FAC.CheatDetected.LowLevel: "Warnung: FAC hat ein niedriges Niveau an Cheats erkannt. Einer der Spieler hackt." -FAC.CheatDetected.FAC: "Verwendung von Cheat-Programmen (z.B. AUM, YuMenu, SM, usw.)" +CheatDetected.HighLevel: "Warnung: FAC verteidigt gegen Bombing-Cheats" +CheatDetected.LowLevel: "Warnung: FAC hat möglichen Cheater erkannt" +CheatDetected.UseCheat: "{0} hat Cheat-Programm [{1}] verwendet" +CheatDetected.MayUseCheat: "{0} verdächtigt, Mod [{1}] oder Cheat-Programm zu nutzen" +CheatDetected.InvalidRpc: "[{0}] wurde gekickt für ungültige Daten (Ungültiger Rpc: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] verdächtigt zu cheaten, bitte Host darauf hinweisen, ihn zu kicken (Ungültiger Rpc: {1})" +CheatDetected.SetName: "[{0}] wurde gekickt für wiederholtes Setzen des Namens" +CheatDetected.SetName_NotHost: "[{0}] verdächtigt zu cheaten, bitte Host darauf hinweisen, ihn zu kicken (Name mehrfach gesetzt)" +CheatDetected.SendQuickChat: "[{0}] wurde gekickt für mehrere Quick-Chats innerhalb von 3 Sekunden" +CheatDetected.SendQuickChat_NotHost: "[{0}] verdächtigt zu cheaten, bitte Host darauf hinweisen, ihn zu kicken (Mehrere Quick-Chats innerhalb von 3 Sekunden)" +CheatDetected.InvalidSlothRPC: "[{0}] wurde gekickt für illegale Daten (Offizieller Rpc illegal gesendet: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] verdächtigt zu cheaten, bitte Host darauf hinweisen, ihn zu kicken (Offizieller Rpc illegal gesendet: {1})" +CheatDetected.Overload: "[{0}] wurde gekickt für Überlastungs-Angriff" +CheatDetected.Overload_NotHost: "[{0}] hat Überlastungs-Angriff gestartet, dieser Raum ist beschädigt, bitte in einem anderen Raum spielen" +CheatDetected.Cheater: "[{0}] wurde wegen Cheats verdächtigt gekickt" +CheatDetected.Cheater_NotHost: "[{0}] verdächtigt zu cheaten, bitte Host darauf hinweisen, ihn zu kicken" +CheatDetected.BanedByBanList: "[{0}] wurde gekickt, da er auf der Bann-Liste steht" +CheatDetected.BanedByFACList: "[{0}] wurde gekickt, da er auf der FAC-Bann-Liste steht" # 模组信息 / Mod Infos -Contributors: "Mitwirkende" -Acknowledgement: "Danksagung" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Du wurdest von dem Anti-Cheat-System entfernt.\n (Die Verwendung von Mods kann als Cheat missinterpretiert werden)" -DCNotify.Banned: "Du bist nicht erlaubt, diesen Raum zu betreten" -DCNotify.Kicked: "Du wurdest aus dem Raum entfernt" -DCNotify.DCFromServer: "Du wurdest vom Server getrennt.\nDies kann aufgrund von Instabilitäten in deinem Netzwerk oder auf dem Server vorkommen." -DCNotify.GameNotFound: "Der zugewiesene Raum wurde nicht gefunden, der Raum könnte aufgelöst worden sein\n oder überprüfe, ob du einen anderen Server als den Raum ausgewählt hast" -DCNotify.GameStarted: "Das Spiel hat bereits begonnen, bitte warte, bis es zu Ende ist" -DCNotify.GameFull: "Der Raum ist voll, bitte versuche es später noch einmal" -DCNotify.IncorrectVersion: "Deine Version von Among Us ist anders als diese Raum" -DCNotify.Description: "Du wurdest aus dem Spiel entfernt.\nGrund: {0}" -DCNotify.DenyName: "Dein Spitzname enthält unregelmäßige Zeichen" -DCNotify.BanList: "Du wurdest vom Host gebannt" -DCNotify.FACList: "Du wurdest von FAC gebannt" -DCNotify.CheatDetected: "Du wurdest von FAC als verdächtig erkannt" -DCNotify.InvalidRPC: "Du hast möglicherweise einen anderen Mod als der Host installiert oder dein Mod wurde bösartig geändert" -DCNotify.ModVersionIncorrect: "Deine Mod-Version ist anders als die des Hosts" -DCNotify.LowLevel: "Dein Level erfüllt die Anforderungen dieses Raumes nicht" -DCNotify.NotLogin: "Nicht angemeldete Spieler sind in diesem Raum nicht erlaubt" - -# 任务栏 / Task Panel +ModInfo.Contributors: "Mitwirkende" +ModInfo.Acknowledgement: "Besonderer Dank" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Du wurdest vom Raum durch InnerSloths Anti-Cheat-System gekickt" +DCNotify.Banned: "Du wurdest aus diesem Raum gebannt" +DCNotify.Kicked: "Du wurdest aus diesem Raum gekickt" +DCNotify.DCFromServer: "Deine Verbindung zum Server wurde unterbrochen\nGrund könnte instabiles Netzwerk\noder Serverinstabilität/Zugriffsverweigerung sein" +DCNotify.GameNotFound: "Angegebener Raum nicht gefunden, eventuell geschlossen\noder prüfe, ob du einen anderen Server als den Raum gewählt hast" +DCNotify.GameStarted: "Dieses Spiel hat bereits begonnen, bitte warte bis zum Ende" +DCNotify.GameFull: "Dieser Raum ist voll, bitte später erneut versuchen" +DCNotify.IncorrectVersion: "Deine Among Us-Version unterscheidet sich vom Raum" +DCNotify.Description: "Du wurdest aus dem Raum gekickt\nGrund: {0}" + +# 任务栏相关 / Task Panel PressF1ShowRoleDescription: "Drücke F1, um deine Rollenbeschreibung anzuzeigen" -FakeTask: "Falsche Aufgabe:" -KillCount: "Tötungen" +PressF2ToHidePane: "Drücke F2, um Panel ein-/auszublenden" +FakeTask: "Gefälschte Aufgabe:" +KillCount: "Abschüsse" # 复盘信息 / Last Results -RoleSummaryText: "Letzte Ergebnisse:" -ShowResults: "Letzte Ergebnisse anzeigen" -HideResults: "Letzte Ergebnisse verbergen" -NoInfoExists: "Keine letzten Ergebnisse verfügbar" -CrewsWin: "Team-Crewmate Win" -CrewmatesWin: "Crewmates Win" -CrewmatesWinBlurb: "Das Licht der Wahrheit leuchtet in der Hoffnung!" -ImpsWin: "Team-Impostor Win" -ImpostorsWin: "Impostors Win" -ImpostorsWinBlurb: "Das Böse verwandelt die Wahrheit in Asche" -HideSummaryTextToShowWinText: "Verberge letztes Ergebnis, um Siegertext anzuzeigen" - -# 禁用公开 -DisabledByProgram: "Öffentliche Raumoperationen wurden vom Programm deaktiviert" -PublicNotAvailableOnThis Version: "Öffentliche Räume sind in dieser Version von FinalSuspect nicht verfügbar" +Summary.Text: "Letzte Zusammenfassung:" +Summary.ShowResults: "Letzte Zusammenfassung anzeigen" +Summary.HideResults: "Letzte Zusammenfassung ausblenden" +Summary.NoInfoExists: "Keine gültige Letzte Zusammenfassung vorhanden" +Summary.CrewsWin: "Sieg der Crewmate-Team" +Summary.ImpsWin: "Sieg der Impostor-Team" +Outro.Crews_Win: "Crewmates-Sieg" +Outro.Crews_WinBlurb: "Das Licht der Wahrheit strahlt in der Hoffnung!" +Outro.Imps_Win: "Impostors-Sieg" +Outro.Imps_WinBlurb: "Das Böse hat die Wahrheit zu Asche zerstört" # 主页 / Main UI -FinalSuspectWelcomeText: "Wünsche dir eine angenehme Spielserien!" -ConnectToFinalSuspectServerFailed: "Verbindung zum FinalSuspect-Server fehlgeschlagen" -LShift: "LShift: Drücke LShift, um zurückzukehren" -RShift: "RShift: Drücke RShift, um dorthin zu gelangen" -Website: "Offizielle Website" +FinalSuspectWelcomeText: "Wir wünschen dir ein angenehmes Spielerlebnis!" +RetrieveVersionInfoFailed: "FinalSuspect-Info konnte nicht abgerufen werden" +Website: "Offizielle Webseite" MainMenuCredential: "{0} © 2025" -LobbyTimeDisplayText: "Zeit des Bestehens" +LShift: "Vorherige Lobby" +RShift: "Zwischenablage-Lobby" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" -FrameRate: "Framerate" +FrameRate: "Bildrate" Server: "Server" Local: "Lokal" # 其他 / Other HongKong: "Hongkong" -FPSSetTo: "Framerate-Begrenzung auf: {0} gesetzt" -BrowsingMode: "Durchsuchungsmodus" +BrowsingMode: "Durchsuch-Modus" Broken: "Defekt" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Übersetzungen wurden erfolgreich geladen!" -CheckingForFiles: "Überprüfe die Integrität der Ressourcendateien..." -DownloadingResources: "Ressourcendateien werden heruntergeladen..." -Loading: "Wird geladen" -LoadingWithDot: "Wird geladen..." -LoadingComplete: "Laden abgeschlossen!" +Unknown: "Unbekannt" +Back: "Zurück" +Yes: "Ja" +No: "Nein" +Cancel: "Abbrechen" +Unload: "Wechseln" +Retry: "Erneut versuchen" +PreviousPage: "Vorherige Seite" +NextPage: "Nächste Seite" +Download: "Herunterladen" +Disable: "Deaktivieren" +Delete: "Löschen" +Author: "Autor" +Close: "Schließen" # 身份 / Identity -Host: "Gastgeber" -Cheater: "Cheater" +Id.Host: "Host" +Id.Cheater: "Cheater" +Id.Developer: "Entwickler" +Id.Contributor: "Mitwirkender" \ No newline at end of file diff --git a/Assets/Languages/Irish.yaml b/Assets/Languages/Irish.yaml index cdf79b43..0909b9a3 100644 --- a/Assets/Languages/Irish.yaml +++ b/Assets/Languages/Irish.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "15" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostor" -TypeCrewmate: "Crewmate" +RoleType.Imp: "Fealltóir" +RoleType.Crew: "Foireann" # 阵营 / Teams -TeamImpostor: "Team-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Team-Crewmate" +Team.Imp: "Foireann-Fealltóir" +Team.Imp_Only: "Fealltóir" +Team.Crew: "Foireann-Fhoireann" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Tá {0} Impostor i do chumann" -ImpostorNumImpOnly: "Tá ach impastor amháin i measc an phobail" -ImpostorNumCrew: "{0} Impostor i measc dúinn" +ImpostorNum.Imp: "Tá {0} fhealltóir inár bhfoireann" +ImpostorNum.Imp_Only: "Níl ach 1 fhealltóir i measc an slua" +ImpostorNum.Crew: "{0} fhealltóir i meascainn" # 阵营开场 -ImpostorIntroText: "Tabhair cuairt don domhan!" -ImpostorIntroTextOnly: "B'fhéidir go bhfuil mé ar meáin, ach tá láimh mhór orm!" -CrewmateIntroText: "Críochnaigh do chuid tascanna, aontaithe chun na suíomhanna difhicil seo a réiteach!" - -## 原版职业 / Vanilla -Crewmate: "Crewmate" -Engineer: "Innealtóir" -Scientist: "Eolaí" -Tracker: "Rianóir" -Noisemaker: "Athruitheoir" -GuardianAngel: "Anailleogóir" -Impostor: "Impostor" -Shapeshifter: "Athruitheoir" -Phantom: "Fionóga" -CrewmateGhost: "Fionóga Crewmate" -ImpostorGhost: "Fionóga Impostor" -CrewmateGhostBlurb: "Críochnaigh do chuid tascanna" -ImpostorGhostBlurb: "Lean ag dul ag an saboteachadh" -CrewmateGhostBlurbLong: "Críochnaigh tascanna, ná bí ina dhulach!" -ImpostorGhostBlurbLong: "Saboteach an tseirbhís, cabhraigh leis na hImpostor atá i bhfeidhm chun an bua a bhaint amach." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Bain sult as an deireadh!" -HnSEngineerBlurbLong: "Fág ar bheatha go dtí go dtiocfaidh an ama; críochnú tascanna le haghaidh an ama a leathnú. \nÚsáid na fhuiseoga agus na súil chun bhriseadh chun do sheanchaitheacht! \nNuair a thosaíonn an chronómaire, tosúfidh an Impostor leat ag dul!" -HnSImpostorBlurb: "Glac le gach duine!" -HnSImpostorBlurbLong: "Marbhadh ar gach ball chumann faoin límit ama! \nCaithfidh tú a bheith ag déanamh luath! Níl na fhuiseoga ar chumas. \nAr deireadh an ama, beidh tusaíocht agat agus súil chun bhriseadh!" -HnSCrewmateGhostBlurb: "Tug ceachtadh do do chomhghleacaithe" -HnSCrewmateGhostBlurbLong: "Go on! Cheacht! For! Do! Comhghleacaithe!" +IntroText.Imp: "Lig don olc DOMHAN A RIALÚ!" +IntroText.Imp_Only: "Cé uaigneach, tá CUMHACHT GAN DEIREADH agat!" +IntroText.Crewmate: "Críochnaigh do thascanna agus aimsigh na fealltóirí!" + +# 原版职业 / Vanilla +Role.Crewmate: "Foireann" +Role.Engineer: "Innealtóir" +Role.Scientist: "Eolaí" +Role.Tracker: "Rianaitheoir" +Role.Noisemaker: "Ceannteoir Fuaime" +Role.GuardianAngel: "Aingeal Caomhnóra" +Role.Impostor: "Fealltóir" +Role.Shapeshifter: "Athraitheoir Cruth" +Role.Phantom: "Fantóm" +Role.CrewmateGhost: "Taibhse-Fhoireann" +Role.ImpostorGhost: "Taibhse-Fhealltóir" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Críochnaigh tascanna" +ImpostorGhostBlurb: "Lean le sabóta" +CrewmateGhostBlurbLong: "Críochnaigh tascanna, ná tarraing an fhoireann síos!\nÚsáid 「Taibhseacht」 chun Fealltóirí a fheiceáil, agus cabhraigh le hAingil Chaomhnóra Foireann a chosaint!" +ImpostorGhostBlurbLong: "Sabótaigh áiseanna, cabhraigh le fealltóirí beo chun bua a fháil!" +HnSEngineerBlurb: "Críochnaigh tascanna agus mair go dtí an deireadh!" +HnSImpostorBlurb: "Sléacht iad GO LÉIR!" +HnSCrewmateGhostBlurb: "Spreag do chomhghleacaithe" +HnSCrewmateGhostBlurbLong: "Marbh? Cad é atá tú ag féachaint air? An féidir leat tascanna a dhéanamh? Nach bhfuil...\nTÉIGH! SPREAG! DO! CHOMHGHLEACAITHE!" # 死因 / Death Reason -DeathReason.Kill: "Marbhadh" -DeathReason.Exile: "Tír árda" -DeathReason.Disconnect: "Síntiú an tsuíomh" +DeathReason.Kill: "Marbh" +DeathReason.Exile: "Deoraíocht" +DeathReason.Disconnect: "Dícheangal" # 客户端选项 / Client Options FinalSuspectOptions: "Roghanna Final Suspect" -Back: "Ar ais" -Yes: "Tá" -No: "Níl" -UnlockFPS: "Díghlasaigh FPS" -ChangeOutfit: "Athraigh do chosúlacht" -BeanMode: "Mód Bean Chlasaiceach" -HorseMode: "Mód Capa Aibreánach" -LongMode: "Mód Long Aibreánach" -KickPlayerFriendCodeNotExist: "Cuir an ghlúiníocht ar na páistí nach bhfuil logáilte." -KickPlayerWithDenyName: "Cuir an ghlúiníocht ar na páistí a bhfuil ainmneacha neamhchóirshocraithe acu" -KickPlayerInBanList: "Cuir an ghlúiníocht ar na páistí atá ar an liosta gearán" -SpamDenyWord: "Glacadh ar fhocail neamhchóirshocraithe" -AutoStartGame: "Tosaigh go huathoibríoch nuair a bhí an tseomra lán" -AutoEndGame: "Téarfaigh ar ais go dtí an seomra agus an gcéim seo" -SwitchVanilla: "Glac le haghaidh an chéime sealbhaithe" -DisableVanillaSound: "Díchumasaigh ceol Among Us" -DisableFAC: "Díchumasaigh an t-antí-chéadú" -ShowPlayerInfo: "Taispeáin an t-ainm an phléidil agus an t-eolas an chliant" -UseModCursor: "Úsáid an chúrsóir mod" -FastBoot: "Módh Tosaigh Go Tapaidh" -PrunkMode: "Mód Prank" -VersionCheat: "Díchumasaigh an t-athrú leagan" -GodMode: "Mód Dia" -NoGameEnd: "Níl deireadh an gheim" -EnableFinalSuspect: "Cumasaigh「Final Suspect」" +ClientOption.UnlockFPS: "Oscail FPS" +ClientOption.SwitchOutfitType: "Athraigh Cineál Éadaí" +ClientOption.KickPlayerWithAbnormalFriendCode: "Cic imreoirí le Cód Cairde neamhghnách" +ClientOption.KickPlayerWithDenyName: "Cic imreoirí ag úsáid ainmneacha toirmiscthe" +ClientOption.KickPlayerInBanList: "Cic imreoirí toirmiscthe" +ClientOption.SpamDenyWord: "Blocáil focail toirmiscthe" +ClientOption.AutoStartGame: "Tosaigh an cluiche go huathoibríoch nuair atá sé lán" +ClientOption.AutoEndGame: "Fill go huathoibríoch ar an lobaidh nuair a chríochnaíonn an cluiche" +ClientOption.SwitchVanilla: "Athraigh go Vanilla" +ClientOption.DisableVanillaSound: "Díchumasaigh ceol cluiche Vanilla" +ClientOption.EnableFAC: "Cumasaigh Frith-Chalaois" +ClientOption.EnableGuardian: "Cumasaigh Caomhnóir Cliant (Turgnamhach)" +ClientOption.ShowPlayerInfo: "Taispeáin ardán an imreora & faisnéis cliant" +ClientOption.UseModCursor: "Úsáid curdar mod" +ClientOption.FastLaunchMode: "Mód Tosaigh Tapa" +ClientOption.OfflineMode: "Modh as líne (Turgnamhach)" +ClientOption.VersionCheat: "Seachad seic sioncronaithe leagan" +ClientOption.GodMode: "Mód Dia" +ClientOption.NoGameEnd: "Gan Críoch Cluiche" +ClientOption.EnableFinalSuspect: "Cumasaigh 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Mód Clasaiceach" +Value.HorseMode: "Mód Capall Aibreáin na nAmadán" +Value.LongMode: "Mód Fada Aibreáin na nAmadán" # 客户端功能 / Client Features FinalSuspectFeatures: "Gnéithe Final Suspect" -UnloadMod: "Athraigh go dtí an leagan bunaidh" -UnloadWarning: "Rabhadh\nChun an mod a athghníomhachtú, caithfidh tú an cluiche a atosú.\nAr mhaith leat leanúint ar aghaidh?" -CannotUnloadDuringGame: "Ní féidir athrú go dtí an leagan bunaidh le linn an chluiche" -Cancel: "Cealaigh" -Unload: "Dílódáil" -DumpLog: "Aschur Log" -ClearAutoLogs: "Glan úsáid shonraí comhroinnte" -SoundOptions: "Mo Cheol" -AudioManagementOptions: "Bainistíocht Fuaime" -OnlyAvailableInMainMenu: "Ar fáil ach amháin sa Phríomh-Roghchlár" +ClientFeature.UnloadMod: "Athraigh go Vanilla" +ClientFeature.DumpLog: "Dumpe Log" +ClientFeature.ClearAutoLogs: "Glan Loganna Uathoibríocha" +ClientFeature.MyMusic: "Mo Cheol" +ClientFeature.ResourceManager: "Bainisteoir Acmhainní" +ClientFeature.NameTagManager: "Bainisteoir Clibeanna Ainm" +ClientFeature.MainMenuStyleManager: "Athraigh Stíl an Phríomh-Roghchláir" # 提示 / Tips -updatePleaseWait: "Fan le do thoil..." -updateInProgress: "Nuashonrú ar siúl..." -DownloadingAudios: "Íoslódáil ar siúl..." -Playing: "Ag imirt..." -Parsing: "Ag Anáil..." -DownLoadSucceedNotice: "Íoslódáil rathúil!" -DownLoadFailureNotice: "Theip ar an íoslódáil =(" -PleaseWait: "Fan le do thoil..." +Tip.Downloading: "Á íoslódáil..." +Tip.PleaseWait: "Fan le do thoil..." +Tip.Playing: "Ag seinm..." +Tip.Parsing: "Ag parsáil..." +Tip.Updating: "Ag nuashonrú..." +Tip.DownLoadFinished: "Íoslódáil críochnaithe" +Tip.DownLoadSucceeded: "D’éirigh leis an íoslódáil!" +Tip.DownLoadFailed: "Theip ar an íoslódáil =(" +Tip.PackageExists: "Suiteáilte" +Tip.OnlyAvailableInMainMenu: "Ar fáil ar an bPríomh-Roghchlár amháin" +Tip.HideSummaryTextToShowWinText: "Cealaigh an Achoimre dheireanach chun téacs bua a fheiceáil" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Comhaid teanga luchtaithe" +Tip.CheckingForFiles: "Ag seiceáil sláine comhad acmhainní..." +Tip.DownloadingResources: "Ag íoslódáil comhaid acmhainní..." +Tip.Loading: "Ag lódáil" +Tip.LoadingWithDot: "Ag lódáil..." +Tip.LoadingComplete: "Lódáil críochnaithe!" +## 切换原版 +Tip.UnloadWarning: "Rabhadh!\nNí mór duit an cluiche a atosú más mian leat leagan modded a imirt.\nAn bhfuil tú cinnte gur mian leat athrú go Vanilla?" +Tip.CannotUnloadDuringGame: "Ní féidir athrú go Vanilla agus cluiche ar siúl." +## 资源管理 +Tip.ResourceManager: "Is féidir leat acmhainní comhoiriúnacha mod agus breise a íoslódáil i „Bainisteoir Acmhainní“\nIs pacáistí acmhainní réamh-íoslódála iad na comhaid le “Ré-” mar réimír" +## 我的音乐 +Tip.MyMusic: "Is féidir leat ceol tacaithe mod a íoslódáil i „Bainisteoir Acmhainní“ nó do chuid comhaid fuaime féin a chur sa fhillteán (Among Us/Final Suspect_Data/Musics). Mura bhfuil cosán acmhainní áitiúil an cheoil ann, taispeánfar „Comhad ar Iarraidh“" +Tip.Incomplete_Music: "Comhad ceoil neamhiomlán braite" +Tip.Incomplete_SoundEffect: "Comhad éifeacht fuaime neamhiomlán braite" +Tip.Incomplete_Image: "Comhad íomhá neamhiomlán braite" +Tip.Incomplete: "Chun do thaithí imeartha a fheabhsú, íoslódáil an pacáiste acmhainní comhoiriúnach mod i „Príomh-Roghchlár - Socruithe - Gnéithe Cliant - Bainisteoir Acmhainní“" +## 名称标识管理 +Tip.TextContent: "Inneachar Téacs" +Tip.TextSizeDescription: "Méid Téacs (Réamhshocrú 100%)" +Tip.TextColorDescription: "Dath Téacs (Cód Dath Héacsadéach)\nFág bán más gá, líon roinnt le haghaidh déanamh dathanna uathoibríoch\n" +Tip.PleaseEnterFriendCode: "Cuir isteach an Cód Cairde leis an gclib ainm nua a cheangal" +Tip.FriendCodeAlreadyExist: "Tá clib ainm ag an gCód Cairde seo cheana" +Tip.FriendCodeIncorrect: "Cuir isteach Cód Cairde bailí le do thoil" +Tip.CustomNameTagHelp: "Is féidir leat clibeanna ainm a chur le haon imreoir. Sannfar clibeanna go huathoibríoch nuair a bheidh an imreoir a bhfuil an Cód Cairde ceangailte aige ag teacht isteach. Ach ní féidir clibeanna a chur in eagar tar éis dul isteach i lobaidh.\nNí féidir le neamh-VIP ach nótaí a chur leis (níl fóntais VIP forbartha fós)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Is féidir leat „Pacáistí Stíl an Phríomh-Roghchláir“ a íoslódáil i „Bainisteoir Acmhainní“ chun acmhainní roghchláir a fháil, agus do stíl is fearr leat a roghnú anseo" + +# 更新结果 +UpdateResult.Succeed_Title: "D’éirigh leis an nuashonrú" +UpdateResult.Succeed_Text: "Gníomhófar é tar éis atosú an chluiche :)" +UpdateResult.Failed_Title: "Theip ar an nuashonrú" +UpdateResult.Failed_Reason_NotFound: "Fáth: {0}\nD’fhéadfadh an fhoinse seo a bheith sealadach neamh-ard-fhaighneachta, athraigh foinse íoslódála agus déan iarracht arís" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Fáth: Earráid fíoraithe comhaid\nNí an leagan is déanaí atá sa chomhad ón bhfoinse seo, athraigh foinse íoslódála agus déan iarracht arís" +UpdateResult.Failed_Reason_Ping: "Fáth: Am amach nuashonraithe nó cur isteach\nSeiceáil do líonra agus déan iarracht arís nó nuashonraigh de láimh" # 更新检查 / Update Checker -Retry: "Athsheiceáil" -updateCheckPopupTitle: "Seiceáil Nuashonraithe" -updateCheckFailedRetry: "Theip ar an seiceáil nuashonraithe :(\nAthsheiceáil?" -updateCheckFailedExit: "Theip ar an seiceáil nuashonraithe :(\nSeiceáil do nasc idirlín agus déan iarracht arís." +UpdateCheck.Popup_Title: "Seiceáil Nuashonraithe" +UpdateCheck.Failed_Retry: "Theip ar sheiceáil nuashonraithe :(\nArís?" +UpdateCheck.Failed_Exit: "Theip ar sheiceáil nuashonraithe :(\nSeiceáil do líonra agus déan iarracht arís!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Meabhrúchán Nuashonraithe" -updateNotice: "Meabhrúchán Nuashonraithe" -UpdateBySelfText: "Ní thacaíonn an leagan seo le nuashonruithe uathoibríocha. Déan nuashonrú de láimh le do thoil." -updateButton: "Nuashonraigh Anois" -updatePopupTitle: "Nuashonraigh Anois" -updatePopupTitleFailed: "Theip ar an Nuashonrú" -updatePopupTitleDone: "Nuashonrú Críochnaithe" +UpdateRemind.updatePopup: "Nuashonraigh Anois" +UpdateRemind.updateNotice: "Meabhrúchán Nuashonraithe" +UpdateRemind.BySelf_Title: "Leid Nuashonraithe" +UpdateRemind.BySelf_Text: "Ní thacaíonn an leagan seo le nuashonrú cliceáil-aon-uair, nuashonraigh de láimh le do thoil" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Roghnaigh cainéal le nuashonrú\nmura bhfuil a fhios agat cad atá le roghnú, roghnaigh [Github]\nMá theipeann ar an nuashonrú, roghnaigh [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Atosaigh an cluiche chun na hathruithe a chur i bhfeidhm :)" -updatePingFialed: "Cúis: {0}\nD’fhéadfadh sé go bhfuil an cainéal roghnaithe sealadach as feidhm. Déan iarracht cainéal íoslódála a athrú." -updateFileMd5Incorrect: "Cúis: Earráid suim sheiceála comhaid\nNíl an leagan comhaid ón gcainéal seo an ceann is déanaí. Déan iarracht cainéal íoslódála a athrú." -downloadFailed: "Cúis: Am íoslódála nó cur isteach\nDéan iarracht arís tar éis do líonra a athrú nó nuashonraigh de láimh." +UpdateSource.Choose: "Roghnaigh foinse nuashonraithe le do thoil\nMura bhfuil tú cinnte, roghnaigh [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Tá nuashonrú tábhachtach againn. Déan nuashonrú ar an mod seo le do thoil.\nMura ndéanann tú, ní bheidh tú in ann dul isteach i seomraí poiblí." -CanNotJoinPublicRoomNoLatest: "Tá nuashonrú tábhachtach againn. Déan nuashonrú ar an mod seo le do thoil.\nMura ndéanann tú, ní bheidh tú in ann dul isteach i seomraí poiblí." -ModBrokenMessage: "Tá comhaid an mod briste. Atosaigh an cluiche nó athshuiteáil an mod seo le do thoil." -UnsupportedVersion: "Níl do leagan de Among Us comhoiriúnach le FinalSuspect.\nDéan nuashonrú ar do chluiche le do thoil." +CanNotJoinPublicRoomNoLatest: "Tá nuashonrú tábhachtach againn, nuashonraigh an mod seo le do thoil\nNó ní féidir leat seomraí poiblí a bhog isteach" +ModBrokenMessage: "Thit comhaid mod, atosaigh an cluiche nó athshuiteáil an mod seo le do thoil" +UnsupportedVersion: "Tá leagan Among Us neamh-chomhoiriúnach le FinalSuspect\nNuashonraigh an cluiche le do thoil" + +# 名称标识 +NameTag.DisplayName: "Nóta" +NameTag.Title: "Teideal" +NameTag.Prefix: "Réimír" +NameTag.Suffix: "Iarmhír" +NameTag.Name: "Ainm" +NameTag.LastTag: "Iarmhír Bhreise" +NameTag.PreviewNotAvailable: " (Réamhamharc nach bhfuil ar fáil) " +NameTag.CanNotEdit: " (Ní féidir a chur in eagar) " +NameTag.RefreshPreview: "Athnuachan Réamhamhairc" +NameTag.SaveAndClose: "Sábháil & Scoir" +NameTag.NewNameTag: "Nua" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Ag Tóir ar an Maidin(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "De réir mar a théann an t-am thart, críochnaíonn an geimhreadh thuaidh, agus athbheoann an t-earrach.\nAg féachaint ar radharc sneachta Mira, an bhfuil cuimhní ag éirí? Sruthanna te ag sileadh sa chroí.\nBeag beann ar cé hé an fhealltóir dheireanach, beag beann ar cé hé a bhrisfidh an fhírinne,\ni dtaithí úr úr, smaoinigh le chéile agus fág cuimhní nua sona.\nCluichí a imirt, spraoi a bheith agat is tábhachtaí!!!\n\nAinmnithe ag: 一念旧情丶" +MainMenuStyle.Title_Security: "Croí Dearg, Grá Díograiseach (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Ní thitfimid, ní thréigfimid, ní bhrisfimid gealltanais imreoirí, agus ní stopfaimid ag dul chun cinn.\nMá thagaimid ar amharc fuar, cruthóimid do gach duine é!\nLe paisean, téimid ar ais ar an mbád\n\nAinmnithe ag: Slok" +MainMenuStyle.Title_NewYear: "Caidreamh (Féile an Earraigh)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fórtún: Mianach bronnta Nasc: Sníthe i gcroí-luathadh\nComhchuibheas: Snáithíní cionta Sonais: Síocháin i ngach anam\nNaisc chinniúnacha a chruthaíonn áthas comhroinnte Turais i bhfad, aimsigh áthas san aontas\nRé órga a chuimil gach duine le háthas Ár nguí — áthas do gach duine\nGo bhfaighidh gach duine a chosán cinnte sa bhliain atá le teacht\nLeis na beannachtaí ag solasú do bhealaigh, tosaigh ar do thuras chun do scéal féin a chruthú!\n\nAinmnithe ag: Slok" +MainMenuStyle.Title_MiraStudio: "Stiúideo Final (Féile an Earraigh)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Bláthanna ag bláthú cosúil le tonnta, séimh cosúil leis an ngaoth, ag guí gach croí a bheith chomh leathan leis an ngaoth, gach rud ag dul go réidh\"\n\"—Gaoithe earraigh ag sileadh go láidir, tonnta ag sileadh ar mhíle, ag guí gach duine seasamh go cróga ag an mbarr, ag fás agus ag bláthú\"\n\"—Fáilte go XtremeWave — Clár Speisialta Féile an Earraigh!\"\n\nAinmnithe ag: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final ag streachailt i dtreo barr feabhais, aisling ag ordú tonnta ag sileadh!」\n\nAinmnithe ag: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Nuair a Fheiceann tú Siar ag an Deireadh (Comhoibriú)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Ag féachaint siar ag an deireadh, críochnaíonn gach rud ag an deireadh」\n\nAinmnithe ag: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Gan Íoslódáil" +MainMenuStyle.NotApply: "Cuir i bhfeidhm" +MainMenuStyle.Applied: "Curtha i bhfeidhm" + +# 资源包 +Package.MainMenuStyle: "Pacáiste Stíl an Phríomh-Roghchláir" # 音频播放 / Audio Playback -PlayMode0: "Imir Uair Amháin" -PlayMode1: "Athsheinm Aonair" -PlayMode2: "Randamach" -PlayMode3: "Seicheamhach" -Stop: "Stop" -CanPlay: "← Cliceáil chun imirt" -NoFound: "[Comhad in easnamh]" -NextPage: "An Chéad Leathanach Eile" -PreviousPage: "An Leathanach Roimhe Seo" - -# 音频添加 / Audio Addition -download: "Íoslódáil" -delete: "Scrios" -NewSound: "Cuir Ceol Nua leis" -PleaseEnterMusic: "Iontráil Ainm an Cheoil le do thoil" -AudioManagementAlreadyExists: "Tá an t-ainm ceoil seo ann cheana" -NotAllowedMusic: "Níl an fhormáid ainm ceoil ceadaithe" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Is féidir leat ceol a íoslódáil atá tacaíocht ag XtremeWave nó do cheol féin a chur leis sa 「Bainistíocht Fuaime」. Nuair a chuireann tú do cheol féin leis, déan cinnte go gcuireann tú ainm an cheoil sa 「Bainistíocht Fuaime」 agus cuir an comhad fuaime comhfhreagrach sa fhillteán 「Among Us/Final Suspect_Data/Resources/Sounds」 (formáidí tacaíochta: .wav). Is féidir ceol a sheinm i 「Mo Cheol」." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Is féidir leat ceol a íoslódáil atá tacaíocht ag XtremeWave nó do cheol féin a chur leis sa 「Bainistíocht Fuaime」. Má tá an cosán acmhainní ceoil áitiúil in easnamh, taispeánfar é mar '[Comhad in easnamh]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Braitear go bhfuil an comhad ceoil reatha neamhiomlán" -AudioNYPro: "Chun taithí cluichíochta níos fearr a fháil, íoslódáil ár gceol sa 「Baile-Socruithe-Tuilleadh Gnéithe-Bainistíocht Fuaime」." +MusPlay.Mode0: "Seinn Uair Amháin" +MusPlay.Mode1: "Lúb Aonair" +MusPlay.Mode2: "Liosta Randamach" +MusPlay.Mode3: "Seinm Leanúnach" +MusPlay.Stop: "Stop Seinm" +MusPlay.CanPlay: "Cliceáil le Seinm" +MusPlay.NoFound: "Comhad ar Iarraidh" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财 (Athbhliain Shona Sínis)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Tonn Sileadh" +Mus.TrailOfTruth: "Slí na Fírinne" +Mus.Interlude: "Idirluí" +Mus.Fractured: "Scoilte" +Mus.ElegyOfFracturedVow: "Elegy of Fractured Vow" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] díbeartha mar gheall ar a ainm a mheaitseáil le [{1}]" -Message.BanedByBanList: "[{0}] díbeartha mar gheall ar a bheith díbeartha roimhe seo." -Message.BanedByFACList: "[{0}] díbeartha mar gheall ar a bheith ar liosta FAC de dhaoine díbeartha." -Message.DumpfileSaved: "Sábháladh an comhad loga go rathúil ar an deasc, ainm an chomhaid: {0}" -Message.KickedByNoFriendCode: "[{0}] díbeartha mar gheall ar a gcód cara nach bhfuil ann." -Message.AddedPlayerToBanList: "Cuireadh [{0}] leis an liosta díbeartha" -Message.KickedByFAC: "[{0}] díbeartha ag FAC, cúis: {1}" -Message.BanedByFAC: "[{0}] díbeartha ag FAC, cúis: {1}" +Mus.ReturnToSimplicity: "fill ar an tsimplíocht" +Mus.ReturnToSimplicity2: "fill ar an tsimplíocht (Leagan Iomlán)" +Mus.ChasingDawn: "Ag Tóir ar an Maidin" +Mus.StruggleAgainstFadingFlame: "Streachailt in aghaidh lasair ag dul i léig" +Mus.Affinity: "Caidreamh" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Ról" +DisplayedRoleTag.PlayerIdentityTag: "Clibeanna Aitheantais" +DisplayedRoleTag.Room: "Seomra" + +PlayerIdentityTag.Hard_Cleared: "Daor dearg" +PlayerIdentityTag.Silver_Clear: "Uisce airgid" +PlayerIdentityTag.Wolf_Bucket: "Pota na mactíre" +PlayerIdentityTag.No_Kill: "Gan marú" +PlayerIdentityTag.Outside_Position: "Suíomh seachtrach" +PlayerIdentityTag.Inside_Position: "Suíomh inmheánach" # 通知 / Notifications -PlayerLeft: "[{0}] d'fhág an cluiche" -PlayerLeftCuzTimeout: "[{0}] d'fhág an cluiche mar gheall ar am scoir naisc" -PlayerKickByHost: "[{0}] díbeartha ag an óstach" -PlayerBanByHost: "[{0}] díbeartha ag an óstach" -PlayerLeftCuzError: "[{0}] d'fhág an cluiche mar gheall ar earráid" -PlayerLeftByAU-Anticheat: "[{0}] díbeartha ag frith-chalaois oifigiúil AmongU (ní bhaineann le FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] díbeartha mar gheall ar a bheith le leagan eile den mod" +## 原版 +Notification.PlayerLeft: "D'fhág [{0}] an cluiche" +Notification.PlayerLeftCuzTimeout: "D'fhág [{0}] an cluiche mar gheall ar am amach ceangail" +Notification.PlayerKickByHost: "Cic ar [{0}] ag an óstach" +Notification.PlayerBanByHost: "Cosc ar [{0}] ag an óstach" +Notification.PlayerLeftCuzError: "D'fhág [{0}] an cluiche mar gheall ar earráid" +Notification.PlayerLeftByAU-Anticheat: "Cic ar [{0}] ag frith-chalaois Among Us (neamh-cheangailte le FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "Cic ar [{0}] mar tá leagan/mod difriúil aige" +## 模组 +Notification.KickedByDenyName: "Cic ar [{0}] mar bhí ainm le focail toirmiscthe aige" +Notification.DumpfileSaved: "Comhad log sábháilte go rathúil ar an deasc, ainm comhaid: {0}" +Notification.KickedByAbnormalFriendCode: "Baineadh [{0}] as toirmisceann an seomra seo imreoirí le Cód Cairde neamhghnách" +Notification.AddedPlayerToBanList: "Cuir [{0}] leis an liosta toirmeasc" +Notification.FPSSetTo: "Srian FPS socraithe ar: {0}" # 警告 / Warnings -Warning: "Rabhadh!" -Warning.MismatchedVersion: "{0}\nle leagan eile de {1}" -Warning.AutoExitAtMismatchedVersion: "Níl an óstach agus nó le leagan eile de {0}\nTá tú ag dul a bheith díbeartha i {1}" -Warning.InvalidRpc: "{0} díbeartha mar gheall ar RPC neamhchiallmhar a fháil." -Warning.InvalidRpc_NotHost: "{0} tá sé mar gheall ar chalaois a bheith agat. Déan iarracht ar an óstach a chur amach (RPC Neamhchiallmhar: {1})" -Warning.SetName: "{0} díbeartha mar gheall ar a ainm a shocruithe go luath." -Warning.SetName_NotHost: "{0} tá sé mar gheall ar chalaois a bheith agat. Déan iarracht ar an óstach a chur amach (Ainm a Shocruithe Go Luath)" -Warning.SendQuickChat: "{0} díbeartha mar gheall ar na h-earraí chat luath a sheoladh go luath i 3 soicind" -Warning.SendQuickChat_NotHost: "{0} tá sé mar gheall ar chalaois a bheith agat. Déan iarracht ar an óstach a chur amach (Seoladh Luath na hEarráí Chat i 3 Soicind)" -Warning.InvalidSlothRPC: "Dhírigh {0} amach mar gheall ar RPC neamhbhríosach a fháil (Ag seoladh Rpc oifigiúil go neamhbhríosach: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} tá sé mar gheall ar chalaois a bheith agat. Déan iarracht ar an óstach a chur amach (RPC oifigiúil neamhchiallmhar a sheoladh: {1})" -Warning.Cheater: "Dhírigh {0} amach mar gheall ar úsáid chléithe a shuíomh" -Warning.Cheater_NotHost: "Tá úsáid chléithe súspéicte ag {0}, Déan cuireadh don phríomhpháirtithe é a dhíriú amach" -Warning.CantKickDev: "Tá brón orm, ní féidir leat an t-oidhre a chur amach" -Warning.RoomBroken: "Tá brón orm, tá an seomra briste. Tá sé de dhíth chun dul isteach sa chluiche." +Warning: "Rabhadh" +Warning.MismatchedVersion: "[{0}]\ntá leagan difriúil de [{1}] suiteáilte aige" +Warning.AutoExitAtMismatchedVersion: "Tá do leagan de [{0}] difriúil leis an óstach\nCicfear thú i {1} soicind" +Warning.CantKickDev: "Buarth, ní féidir leat forbróirí a chiceáil" +Warning.RoomBroken: "Buarth, rinneadh ionsaí hack ar an seomra seo, imir i seomra eile le do thoil" +Warning.InvalidColor: "Imreoir le dath neamhbhailí braite" ## 错误等级 / Error Levels -ErrorLevel1: "Is féidir bugs a bheith ann." -ErrorLevel2: "Is féidir go bhfuil sé bug." -ErrorLevel3: "Ní bheadh an leagan seo ar bhealach." +ErrorLevel1: "D’fhéadfadh go leor fabhtanna a bheith ann ag an am céanna" +ErrorLevel2: "D’fhéadfadh fabhtanna tarlú" +ErrorLevel3: "Leagan neamh-eisithe" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Rabhadh: D'fháiltigh FAC leibhéal ard de chalaois." -FAC.CheatDetected.LowLevel: "Rabhadh: D'fháiltigh FAC leibhéal íochtar de chalaois. Tá duine de na cluicheoirí ag hackadh." -FAC.CheatDetected.FAC: "Úsáid clár chalaois (mar shampla, AUM, YuMenu, SM, agus araon)" +CheatDetected.HighLevel: "Rabhadh: Tá FAC ag cosaint ar chalaois bhuamaíochta" +CheatDetected.LowLevel: "Rabhadh: Braith FAC imreoir amhrasach" +CheatDetected.UseCheat: "D’úsáid {0} clár calaoise [{1}]" +CheatDetected.MayUseCheat: "Amhras ar {0} ag úsáid mod [{1}] nó clár calaoise" +CheatDetected.InvalidRpc: "Cic ar [{0}] as sonraí neamhbhailí a sheoladh (Rpc neamhbhailí: {1})" +CheatDetected.InvalidRpc_NotHost: "Amhras ar [{0}] ag calaois, cuir in iúl don óstach é a chiceáil go luath (Rpc neamhbhailí: {1})" +CheatDetected.SetName: "Cic ar [{0}] as ainm a shocrú arís agus arís eile" +CheatDetected.SetName_NotHost: "Amhras ar [{0}] ag calaois, cuir in iúl don óstach é a chiceáil go luath (Ainm socraithe arís agus arís eile)" +CheatDetected.SendQuickChat: "Cic ar [{0}] as roinnt teachtaireachtaí tapa a sheoladh laistigh de 3 soicind" +CheatDetected.SendQuickChat_NotHost: "Amhras ar [{0}] ag calaois, cuir in iúl don óstach é a chiceáil go luath (Teachtaireachtaí tapa il laistigh de 3 soicind)" +CheatDetected.InvalidSlothRPC: "Cic ar [{0}] as sonraí mídhleathacha a sheoladh (Rpc oifigiúil mídhleathach: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "Amhras ar [{0}] ag calaois, cuir in iúl don óstach é a chiceáil go luath (Rpc oifigiúil mídhleathach: {1})" +CheatDetected.Overload: "Cic ar [{0}] as ionsaí ró-uasluchtaithe a thosú" +CheatDetected.Overload_NotHost: "Thosaigh [{0}] ionsaí ró-uasluchtaithe, tá an seomra seo damáistithe, imir i seomra eile le do thoil" +CheatDetected.Cheater: "Cic ar [{0}] as calaois amhrasach" +CheatDetected.Cheater_NotHost: "Amhras ar [{0}] ag calaois, cuir in iúl don óstach é a chiceáil go luath" +CheatDetected.BanedByBanList: "Cic ar [{0}] mar tá sé ar an liosta toirmeasc" +CheatDetected.BanedByFACList: "Cic ar [{0}] mar tá sé ar an liosta toirmeasc FAC" # 模组信息 / Mod Infos -Contributors: "Dolaitheoirí" -Acknowledgement: "Dearadh" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Tá tú díbeartha ag an frith-chalaois.\n (Is féidir go mbeidh úsáid comhaid mar chalaois)" -DCNotify.Banned: "Níl tú ceartúil dul isteach sa seomra seo" -DCNotify.Kicked: "Tá tú díbeartha as an seomra" -DCNotify.DCFromServer: "Tá tú ar an nasc go dtí an fhreastalaí.\nIs féidir go mbeidh sé mar gheall ar neamhshníomh i do naisc.\nIs féidir go mbeidh sé mar gheall ar neamhshníomh an fhreastalaí." -DCNotify.GameNotFound: "Níor aimsíodh an seomra atá tú ag eolas, is féidir go bhfuil an seomra briste\n nó seiceáil an ceiste má tá tú ag roghnú freastalaí difriúil ón seomra" -DCNotify.GameStarted: "Tá an cluiche ag dul ar aghaidh cheana, fan go dtiocfaidh sé chun críche" -DCNotify.GameFull: "Tá an seomra lán, déan iarracht arís tar éis seo" -DCNotify.IncorrectVersion: "Níl do leagan de Among Us comhoiriúnach le seo seomra" -DCNotify.Description: "Tá tú díbeartha as an cluiche.\nCúis: {0}" -DCNotify.DenyName: "Tá do ainm neamhchiallmhar" -DCNotify.BanList: "Tá tú díbeartha ag an óstach" -DCNotify.FACList: "Tá tú díbeartha ag FAC" -DCNotify.CheatDetected: "Tá tú mar gheall ar chalaois a bhaint amach ag FAC" -DCNotify.InvalidRPC: "Tá tú mar gheall ar chalaois a bhaint amach ag FAC" -DCNotify.ModVersionIncorrect: "Níl do leagan den mod comhoiriúnach le an óstach" -DCNotify.LowLevel: "Níl do leibhéal ag fáil na riachtanais seo seomra" -DCNotify.NotLogin: "Níl cluicheoirí neamh-logáilte ceartúla i seo seomra" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Brúigh F1 chun críochán do róla a thaispeáint" -FakeTask: "Tasc Faoiseamh:" -KillCount: "Marbhadh" +ModInfo.Contributors: "Rannpháirtithe" +ModInfo.Acknowledgement: "Go raibh míle maith agat" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Cic as an seomra agat ag córas frith-chalaois InnerSloth" +DCNotify.Banned: "Cosc as an seomra seo agat" +DCNotify.Kicked: "Cic as an seomra seo agat" +DCNotify.DCFromServer: "Níor éirigh le do cheangal leis an bhfreastalaí\nD’fhéadfadh sé a bheith mar gheall ar líonra neamhshlán\nnó neamhshláine/diúltaithe rochtana an fhreastalaí" +DCNotify.GameNotFound: "Níor aimsíodh an seomra sonraithe, seans go bhfuil sé dúnta\nnó seiceáil an bhfuil freastalaí difriúil roghnaithe agat" +DCNotify.GameStarted: "Tá an cluiche seo tosaithe cheana, fan go dtí go mbeidh sé críochnaithe" +DCNotify.GameFull: "Tá an seomra seo lán, déan iarracht níos déanaí" +DCNotify.IncorrectVersion: "Tá do leagan Among Us difriúil leis an seomra" +DCNotify.Description: "Cic as an seomra agat\nFáth: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Brúigh F1 chun cur síos ar do ról a fheiceáil" +PressF2ToHidePane: "Brúigh F2 chun painéal a thaispeáint/folaigh" +FakeTask: "Tasc Bréige:" +KillCount: "Maruithe" # 复盘信息 / Last Results -RoleSummaryText: "Na hAinmhithe Seo:" -ShowResults: "Taispeáin na hAinmhithe Seo" -HideResults: "Folaigh na hAinmhithe Seo" -NoInfoExists: "Níl na hAinmhithe Seo ar fáil" -CrewsWin: "Team-Crewmate Win" -CrewmatesWin: "Crewmates Win" -CrewmatesWinBlurb: "Tá solas an fírinne ag loiscadh i m'fhiosrú!" -ImpsWin: "Team-Impostor Win" -ImpostorsWin: "Impostors Win" -ImpostorsWinBlurb: "Tá an diabhal ag cur an fírinne i gharraí" -HideSummaryTextToShowWinText: "Folaigh na hAinmhithe Seo chun an téacs buachailleacht a thaispeáint" - -# 禁用公开 -DisabledByProgram: "Tá oibrí poiblí díchumasaithe ag an chlár" -PublicNotAvailableOnThis Version: "Níl seomraí poiblí ar fáil ar an leagan seo de FinalSuspect" +Summary.Text: "Achoimre Deireanach:" +Summary.ShowResults: "Taispeáin an Achoimre Deireanach" +Summary.HideResults: "Folaigh an Achoimre Deireanach" +Summary.NoInfoExists: "Níl aon Achoimre Deireanach bhailí ann" +Summary.CrewsWin: "Bua Foireann-Fhoireann" +Summary.ImpsWin: "Bua Foireann-Fhealltóir" +Outro.Crews_Win: "Bua na bhFoireann" +Outro.Crews_WinBlurb: "Tá solas na fírinne ag lonrú san dóchas!" +Outro.Imps_Win: "Bua na bhFealltóir" +Outro.Imps_WinBlurb: "Scrios an t-olc an fhírinne go luaithreach" # 主页 / Main UI -FinalSuspectWelcomeText: "Ag iarraidh go mbeidh tú ag imirt go hiontach cluiche!" -ConnectToFinalSuspectServerFailed: "Theip ar an nasc chuig an bhfreastalaí FinalSuspect" +FinalSuspectWelcomeText: "Guímid taithí imeartha shona ort!" +RetrieveVersionInfoFailed: "Níorbh fhéidir faisnéis FinalSuspect a fháil" Website: "Suíomh Oifigiúil" MainMenuCredential: "{0} © 2025" -LShift: "Cliceáil an Shift chlé (LShift) chun ar ais chuig an seomra roimhe sin." -RShift: "Cliceáil an Shift deas (RShift) chun isteach go dtí an seomra leabhairte." -LobbyTimeDisplayText: "Tempus existentiae" +LShift: "Lobaidh Roimhe Seo" +RShift: "Lobaidh Gearrthaisce" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" -FrameRate: "Ráta Chuadach" +FrameRate: "Ráta Fráma" Server: "Freastalaí" -Local: "Lacal" +Local: "Áitiúil" # 其他 / Other -HongKong: "Hong Kong" -FPSSetTo: "Ráta chuaideadh chosúil le: {0}" -BrowsingMode: "Mód Seiceála" -Broken: "Briseadh" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Taispeáint na hAinmhithe Críochnaithe!" -CheckingForFiles: "Seiceáil na Neartughaidh na n-Ábhar..." -DownloadingResources: "Ag íoslódáil na n-ábhar..." -Loading: "Ag luchtú" -LoadingWithDot: "Ag luchtú..." -LoadingComplete: "Tá an luchtú críochnaithe!" +HongKong: "Hong Cong" +BrowsingMode: "Mód Brabhsála" +Broken: "Briste" +Unknown: "Anaithnid" +Back: "Siar" +Yes: "Tá" +No: "Níl" +Cancel: "Cealaigh" +Unload: "Athraigh" +Retry: "Arís" +PreviousPage: "Leathanach Roimhe Seo" +NextPage: "Leathanach Eile" +Download: "Íoslódáil" +Disable: "Díchumasaigh" +Delete: "Scrios" +Author: "Údar" +Close: "Dún" # 身份 / Identity -Host: "Óstach" -Cheater: "Calaoiseach" +Id.Host: "Óstach" +Id.Cheater: "Calaoiseoir" +Id.Developer: "Forbróir" +Id.Contributor: "Rannpháirtí" \ No newline at end of file diff --git a/Assets/Languages/Italian.yaml b/Assets/Languages/Italian.yaml index 2c00a23f..49cb5a4d 100644 --- a/Assets/Languages/Italian.yaml +++ b/Assets/Languages/Italian.yaml @@ -1,293 +1,337 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "10" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostori" -TypeCrewmate: "Crewmate" +RoleType.Imp: "Impostore" +RoleType.Crew: "Membro dell'Equipaggio" # 阵营 / Teams -TeamImpostor: "Team-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Team-Crewmate" +Team.Imp: "Team-Impostore" +Team.Imp_Only: "Impostore" +Team.Crew: "Team-Equipaggio" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Ci sono {0} Impostori nel nostro team" -ImpostorNumImpOnly: "C'è solo 1 Impostor nella folla" -ImpostorNumCrew: "Ci sono {0} Impostori tra noi" +ImpostorNum.Imp: "Ci sono {0} impostori nel nostro team" +ImpostorNum.Imp_Only: "C'è solo 1 impostore tra la folla" +ImpostorNum.Crew: "Ci sono {0} impostori tra noi" # 阵营开场 -ImpostorIntroText: "Lascia che il male avvolga il mondo!" -ImpostorIntroTextOnly: "Forse sono solo, ma possiedo una forza infinita!" -CrewmateIntroText: "Completa le tue missioni, unisciti per risolvere queste situazioni complicate!" - -## 原版职业 / Vanilla -Crewmate: "Crewmate" -Engineer: "Ingegnere" -Scientist: "Scienziato" -Tracker: "Tracker" -Noisemaker: "Generatore di rumore" -GuardianAngel: "Angelo Custode" -Impostor: "Impostor" -Shapeshifter: "Cambiante" -Phantom: "Fantasma" -CrewmateGhost: "Fantasma di Crewmate" -ImpostorGhost: "Fantasma di Impostor" -CrewmateGhostBlurb: "Completa le tue missioni" -ImpostorGhostBlurb: "Continua a sabotare le strutture" -CrewmateGhostBlurbLong: "Completa le missioni, non rimanere indietro!" -ImpostorGhostBlurbLong: "Sabotare le strutture, aiutare gli impostori rimanenti a ottenere la vittoria." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Sopravvivi fino alla fine!" -HnSEngineerBlurbLong: "Rimani in vita fino a quando il tempo non scade; completare le missioni estenderà il tempo. \nUsa i condotti di ventilazione e gli indicatori di minaccia per nasconderti! \nUna volta avviato il timer, l'Impostor inizierà a cacciarti!" -HnSImpostorBlurb: "Elimina tutti!" -HnSImpostorBlurbLong: "Uccidi tutti i membri dell'equipaggio entro il limite di tempo! \nDevi agire rapidamente! I condotti di ventilazione non sono accessibili. \nNel momento finale, riceverai un aumento di velocità e suggerimenti di tracciamento!" -HnSCrewmateGhostBlurb: "Sostieni i tuoi compagni" -HnSCrewmateGhostBlurbLong: "Vai! Sostieni! Per! I! Tuoi! Compagni!" +IntroText.Imp: "Lascia che il male DOMINI IL MONDO!" +IntroText.Imp_Only: "Anche se solo, hai un POTERE INFINITO!" +IntroText.Crewmate: "Completa i tuoi compiti e scopri gli impostori!" + +# 原版职业 / Vanilla +Role.Crewmate: "Membro dell'Equipaggio" +Role.Engineer: "Ingegnere" +Role.Scientist: "Scienziato" +Role.Tracker: "Inseguitore" +Role.Noisemaker: "Rumorista" +Role.GuardianAngel: "Angelo Custode" +Role.Impostor: "Impostore" +Role.Shapeshifter: "Mutaforma" +Role.Phantom: "Fantasma" +Role.CrewmateGhost: "Fantasma dell'Equipaggio" +Role.ImpostorGhost: "Fantasma dell'Impostore" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Completa i compiti" +ImpostorGhostBlurb: "Continua a sabotare" +CrewmateGhostBlurbLong: "Completa i compiti, non trascinare la squadra!\nUsa 「Persecuzione」 per vedere gli Impostori e aiuta gli Angeli Custodi a proteggere i Membri dell'Equipaggio!" +ImpostorGhostBlurbLong: "Sabota le strutture, aiuta gli impostori sopravvissuti a vincere!" +HnSEngineerBlurb: "Completa i compiti e sopravvivi fino alla fine!" +HnSImpostorBlurb: "UCCIDILI TUTTI!" +HnSCrewmateGhostBlurb: "Incoraggia i tuoi compagni" +HnSCrewmateGhostBlurbLong: "Morto? Cosa stai guardando? Puoi fare compiti? Se no...\nVA'! INCORAGGIA! I TUOI! COMPAGNI!" # 死因 / Death Reason DeathReason.Kill: "Ucciso" DeathReason.Exile: "Esiliato" -DeathReason.Disconnect: "Disconnesso" +DeathReason.Disconnect: "D/C" # 客户端选项 / Client Options -FinalSuspectOptions: "Opzioni di Final Suspect" -Back: "Indietro" -Yes: "Sì" -No: "No" -UnlockFPS: "Sblocca FPS" -ChangeOutfit: "Cambia Outfit" -BeanMode: "Modalità Fagiolo Classica" -HorseMode: "Modalità Cavallo di April Fool" -LongMode: "Modalità Lunga di April Fool" -KickPlayerFriendCodeNotExist: "Espelli giocatori non loggati." -KickPlayerWithDenyName: "Espelli giocatori con nomi inappropriati" -KickPlayerInBanList: "Espelli giocatori bannati" -SpamDenyWord: "Blocca parole inappropriati" -AutoStartGame: "Avvia automaticamente quando la lobby è piena" -AutoEndGame: "Ritorna automaticamente alla lobby alla fine" -SwitchVanilla: "Torna alla versione originale" -DisableVanillaSound: "Disabilita musica di Among Us" -DisableFAC: "Disabilita anti-cheat" -ShowPlayerInfo: "Mostra informazioni piattaforma e client del giocatore" -UseModCursor: "Usa cursore del mod" -FastBoot: "Modalità Avvio Rapido" -PrunkMode: "Modalità Scherzo" -VersionCheat: "Ignora verifica sincronizzazione versione mod" -GodMode: "Modalità Dio" -NoGameEnd: "Nessuna fine del gioco" -EnableFinalSuspect: "Abilita「Final Suspect」" +FinalSuspectOptions: "Opzioni Final Suspect" +ClientOption.UnlockFPS: "Sblocca FPS" +ClientOption.SwitchOutfitType: "Cambia Tipo Outfit" +ClientOption.KickPlayerWithAbnormalFriendCode: "Espelli giocatori con Friend Code anormale" +ClientOption.KickPlayerWithDenyName: "Espelli giocatori con nickname vietati" +ClientOption.KickPlayerInBanList: "Espelli giocatori bannati" +ClientOption.SpamDenyWord: "Blocca parole vietate" +ClientOption.AutoStartGame: "Avvia automaticamente il gioco quando pieno" +ClientOption.AutoEndGame: "Ritorna automaticamente in lobby alla fine" +ClientOption.SwitchVanilla: "Passa a Vanilla" +ClientOption.DisableVanillaSound: "Disabilita musica di gioco Vanilla" +ClientOption.EnableFAC: "Abilita Anti-Cheat" +ClientOption.EnableGuardian: "Abilita Client Guardian (Sperimentale)" +ClientOption.ShowPlayerInfo: "Mostra piattaforma e info client" +ClientOption.UseModCursor: "Usa cursore del mod" +ClientOption.FastLaunchMode: "Modalità Avvio Veloce" +ClientOption.OfflineMode: "Modalità offline (Sperimentale)" +ClientOption.VersionCheat: "Ignora controllo sincronizzazione versione" +ClientOption.GodMode: "Modalità Dio" +ClientOption.NoGameEnd: "Nessuna Fine Gioco" +ClientOption.EnableFinalSuspect: "Abilita 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Modalità Classica" +Value.HorseMode: "Modalità Cavallo 1 Aprile" +Value.LongMode: "Modalità Lunga 1 Aprile" # 客户端功能 / Client Features -FinalSuspectFeatures: "Funzioni di Final Suspect" -UnloadMod: "Torna alla versione originale" -UnloadWarning: "Avvertimento\nPer riattivare il mod, dovrai riavviare il gioco.\nVuoi comunque continuare?" -CannotUnloadDuringGame: "Non è possibile passare alla versione originale durante il gioco" -Cancel: "Annulla" -Unload: "Disattiva" -DumpLog: "Esporta Log" -ClearAutoLogs: "Cancella log automatico" -SoundOptions: "La mia musica" -AudioManagementOptions: "Gestione audio" -OnlyAvailableInMainMenu: "Disponibile solo nel menu principale" +FinalSuspectFeatures: "Funzionalità Final Suspect" +ClientFeature.UnloadMod: "Passa a Vanilla" +ClientFeature.DumpLog: "Scarica Log" +ClientFeature.ClearAutoLogs: "Pulisci Log Automatici" +ClientFeature.MyMusic: "La Mia Musica" +ClientFeature.ResourceManager: "Gestore Risorse" +ClientFeature.NameTagManager: "Gestore Etichette" +ClientFeature.MainMenuStyleManager: "Cambia Stile Menu Principale" # 提示 / Tips -updatePleaseWait: "Per favore, attendi..." -updateInProgress: "Aggiornamento in corso..." -DownloadingAudios: "Download in corso..." -Playing: "In gioco..." -Parsing: "Analisi in corso..." -DownLoadSucceedNotice: "Download riuscito!" -DownLoadFailureNotice: "Download fallito =(" -PleaseWait: "Per favore, attendi..." +Tip.Downloading: "Download in corso..." +Tip.PleaseWait: "Attendere prego..." +Tip.Playing: "Riproducendo..." +Tip.Parsing: "Analizzando..." +Tip.Updating: "Aggiornando..." +Tip.DownLoadFinished: "Download completato" +Tip.DownLoadSucceeded: "Download riuscito!" +Tip.DownLoadFailed: "Download fallito=(" +Tip.PackageExists: "Installato" +Tip.OnlyAvailableInMainMenu: "Disponibile solo nel Menu Principale" +Tip.HideSummaryTextToShowWinText: "Nascondi Riepilogo per vedere testo vittoria" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Caricamento file lingua completato" +Tip.CheckingForFiles: "Verifica integrità file risorse..." +Tip.DownloadingResources: "Download file risorse..." +Tip.Loading: "Caricamento" +Tip.LoadingWithDot: "Caricamento..." +Tip.LoadingComplete: "Caricamento completato!" +## 切换原版 +Tip.UnloadWarning: "Attenzione!\nDevi riavviare il gioco per giocare con mod.\nSicuro di voler passare a Vanilla?" +Tip.CannotUnloadDuringGame: "Impossibile passare a Vanilla durante una partita." +## 资源管理 +Tip.ResourceManager: "Puoi scaricare risorse compatibili con mod aggiuntive in 「Gestore Risorse」\nFile con prefisso \"Pre-\" sono pacchetti pre-scaricati" +## 我的音乐 +Tip.MyMusic: "Puoi scaricare musica compatibile con mod in 「Gestore Risorse」 o aggiungere file audio in (Among Us/Final Suspect_Data/Musics). Se il percorso locale non esiste, verrà mostrato 「File Mancante」" +Tip.Incomplete_Music: "Rilevato file musicale incompleto" +Tip.Incomplete_SoundEffect: "Rilevato file effetto sonoro incompleto" +Tip.Incomplete_Image: "Rilevato file immagine incompleto" +Tip.Incomplete: "Per migliorare l'esperienza, scarica il pacchetto risorse compatibile in 「Menu Principale - Impostazioni - Funzionalità Client - Gestore Risorse」" +## 名称标识管理 +Tip.TextContent: "Contenuto Testo" +Tip.TextSizeDescription: "Dimensione Testo (Predefinito 100%)" +Tip.TextColorDescription: "Colore Testo (Codice Colore Hex)\nLascia vuoto se non necessario, riempi più campi per gradiente automatico\n" +Tip.PleaseEnterFriendCode: "Inserisci Friend Code per associare nuova etichetta" +Tip.FriendCodeAlreadyExist: "Questo Friend Code ha già un'etichetta" +Tip.FriendCodeIncorrect: "Inserisci un Friend Code valido" +Tip.CustomNameTagHelp: "Puoi aggiungere etichette per qualsiasi giocatore. Le etichette verranno assegnate automaticamente quando il giocatore associato al Friend Code entra. Non puoi modificare le etichette dopo l'ingresso in lobby.\nI non-VIP possono solo aggiungere note (funzioni VIP non ancora sviluppate)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Puoi scaricare 「Pacchetti Stile Menu」 in 「Gestore Risorse」 e scegliere il tuo stile preferito qui" + +# 更新结果 +UpdateResult.Succeed_Title: "Aggiornamento Riuscito" +UpdateResult.Succeed_Text: "Attivo dopo il riavvio :)" +UpdateResult.Failed_Title: "Aggiornamento Fallito" +UpdateResult.Failed_Reason_NotFound: "Motivo: {0}\nQuesta fonte potrebbe essere temporaneamente non disponibile, cambia fonte e riprova" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Motivo: Errore verifica file\nLa versione da questa fonte non è la più recente, cambia fonte e riprova" +UpdateResult.Failed_Reason_Ping: "Motivo: Timeout download o interrotto\nControlla la tua rete e riprova o aggiorna manualmente" # 更新检查 / Update Checker -Retry: "Riprova" -updateCheckPopupTitle: "Controllo aggiornamenti" -updateCheckFailedRetry: "Controllo aggiornamenti fallito :(\nRiprova?" -updateCheckFailedExit: "Controllo aggiornamenti fallito :(\nPer favore, controlla la tua connessione internet e riprova." +UpdateCheck.Popup_Title: "Controllo Aggiornamenti" +UpdateCheck.Failed_Retry: "Controllo aggiornamenti fallito :(\nRiprova?" +UpdateCheck.Failed_Exit: "Controllo aggiornamenti fallito :(\nControlla la tua rete!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Promemoria aggiornamento" -updateNotice: "Promemoria aggiornamento" -UpdateBySelfText: "Questa versione non supporta gli aggiornamenti automatici. Aggiorna manualmente." -updateButton: "Aggiorna ora" -updatePopupTitle: "Aggiorna ora" -updatePopupTitleFailed: "Aggiornamento fallito" -updatePopupTitleDone: "Aggiornamento completato" +UpdateRemind.updatePopup: "Aggiorna Ora" +UpdateRemind.updateNotice: "Promemoria Aggiornamento" +UpdateRemind.BySelf_Title: "Suggerimento Aggiornamento" +UpdateRemind.BySelf_Text: "Questa versione non supporta aggiornamento con un clic, aggiorna manualmente" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Per favore, scegli una fonte per l'aggiornamento\nse non sai cosa scegliere, seleziona [Github]\nse l'aggiornamento fallisce, seleziona [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Riavvia il gioco per applicare le modifiche :)" -updatePingFialed: "Motivo: {0}\nIl canale selezionato potrebbe essere temporaneamente non disponibile. Prova a cambiare il canale di download." -updateFileMd5Incorrect: "Motivo: Errore checksum del file\nLa versione del file da questo canale non è la più recente. Prova a cambiare il canale di download." -downloadFailed: "Motivo: Timeout o interruzione del download\nProva di nuovo dopo aver cambiato la tua rete o aggiornare manualmente." +UpdateSource.Choose: "Scegli fonte aggiornamento\nSe incerto, scegli [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Abbiamo una importante aggiornamento. Aggiorna questo mod.\nAltrimenti, non potrai entrare nelle stanze pubbliche." -CanNotJoinPublicRoomNoLatest: "Abbiamo una importante aggiornamento. Aggiorna questo mod.\nAltrimenti, non potrai entrare nelle stanze pubbliche." -ModBrokenMessage: "I file del mod sono corrotti. Riavvia il gioco o reinstalla questo mod." -UnsupportedVersion: "La tua versione di Among Us non è compatibile con FinalSuspect.\nAggiorna il tuo gioco." +CanNotJoinPublicRoomNoLatest: "Abbiamo un aggiornamento importante, aggiorna questo mod\nAltrimenti non puoi entrare in stanze pubbliche" +ModBrokenMessage: "File mod corrotti, riavvia il gioco o reinstalla" +UnsupportedVersion: "La tua versione Among Us è incompatibile con FinalSuspect\nAggiorna il gioco" + +# 名称标识 +NameTag.DisplayName: "Nota" +NameTag.Title: "Titolo" +NameTag.Prefix: "Prefisso" +NameTag.Suffix: "Suffisso" +NameTag.Name: "Nome" +NameTag.LastTag: "Suffisso Aggiuntivo" +NameTag.PreviewNotAvailable: " (Anteprima non supportata) " +NameTag.CanNotEdit: " (Non modificabile) " +NameTag.RefreshPreview: "Aggiorna Anteprima" +NameTag.SaveAndClose: "Salva ed Esci" +NameTag.NewNameTag: "Nuovo" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Inseguendo l'Alba(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Col passare del tempo, l'inverno settentrionale finisce e la primavera rinasce.\nContemplando i paesaggi innevati di Mira, i ricordi si risvegliano? Flussi caldi scorrono nel cuore.\nNon importa chi sia il sospetto finale, non importa quali mani frantumino la verità,\nIn un'esperienza rinfrescante, pensa e lascia nuovi felici ricordi.\nGiocare, divertirsi è la cosa più importante!!!\n\nNominato da: 一念旧情丶" +MainMenuStyle.Title_Security: "Cuore Rosso, Amore Feroce (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Non cadremo, non ci arrenderemo, non deluderemo i giocatori, non smetteremo mai di progredire.\nSe incontrati da sguardi freddi, lo dimostreremo a tutti!\nCon passione, riprendiamo il mare\n\nNominato da: Slok" +MainMenuStyle.Title_NewYear: "Affinità (Capodanno)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortuna: Desideri concessi Legame: Tessuto nei battiti\nArmonia: Fili di affetto Beatitudine: Pace in ogni anima\nLegami del destino forgiano gioia condivisa Viaggia lontano, trova gioia nell'unità\nL'era principale culla tutti nella gioia La nostra preghiera—gioia per ogni essere\nPossano tutte le anime trovare il loro percorso nel nuovo anno\nCon le nostre benedizioni che illuminano il tuo cammino, inizia il viaggio per forgiare la tua leggenda!\n\nNominato da: Slok" +MainMenuStyle.Title_MiraStudio: "Final Studio (Capodanno)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Fiori sbocciano come onde, gentili come il vento, augurando a tutti il cuore vasto come il vento, tutto proceda liscio\"\n\"—Venti primaverili impetuosi, onde che avanzano per miglia, augurando a tutti di stare coraggiosi in prima linea, prosperando\"\n\"—Benvenuti a XtremeWave — Programma Speciale Capodanno!\"\n\nNominato da: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final Lottando per l'Eccellenza, Sogni Comandando Onde Montanti!」\n\nNominato da: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Guardando Indietro alla Fine (Collaborazione)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Guardando indietro alla fine, tutto finisce alla fine」\n\nNominato da: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Non Scaricato" +MainMenuStyle.NotApply: "Applica" +MainMenuStyle.Applied: "Applicato" + +# 资源包 +Package.MainMenuStyle: "Pacchetto Stile Menu" # 音频播放 / Audio Playback -PlayMode0: "Riproduci una volta" -PlayMode1: "Ripeti una sola volta" -PlayMode2: "Casuale" -PlayMode3: "Sequenziale" -Stop: "Ferma" -CanPlay: "← Clicca per riprodurre" -NoFound: "[File mancante]" -NextPage: "Pagina successiva" -PreviousPage: "Pagina precedente" - -# 音频添加 / Audio Addition -download: "Scarica" -delete: "Elimina" -NewSound: "Aggiungi nuova musica" -PleaseEnterMusic: "Inserisci il nome della musica" -AudioManagementAlreadyExists: "Questo nome di musica esiste già" -NotAllowedMusic: "Il formato del nome della musica non è consentito" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Puoi scaricare musica supportata da XtremeWave o aggiungere la tua musica in 'Gestione audio'. Quando aggiungi la tua musica, assicurati di inserire il nome della musica in 'Gestione audio' e di posizionare il file audio corrispondente nella cartella 'Among Us/Final Suspect_Data/Resources/Sounds' (formati supportati: .wav). La musica può essere riprodotta in 'La mia musica'." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Puoi scaricare musica supportata da XtremeWave o aggiungere la tua musica in 'Gestione audio'. Se il percorso della risorsa audio locale è mancante, mostrerà '[File mancante]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Il file audio attuale è stato rilevato come incompleto" -AudioNYPro: "Per un'esperienza di gioco migliore, scarica la nostra musica in 'Home-Settings-More Features-Audio Management'" +MusPlay.Mode0: "Riproduci Una Volta" +MusPlay.Mode1: "Ripetizione Singola" +MusPlay.Mode2: "Lista Casuale" +MusPlay.Mode3: "Riproduzione Sequenziale" +MusPlay.Stop: "Ferma Riproduzione" +MusPlay.CanPlay: "Clicca per Riprodurre" +MusPlay.NoFound: "File Mancante" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(Buon Capodanno Cinese)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Onda di Marea" +Mus.TrailOfTruth: "Traccia della Verità" +Mus.Interlude: "Interludio" +Mus.Fractured: "Fratturato" +Mus.ElegyOfFracturedVow: "Elegia del Voto Fratturato" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] è stato espulso perché il suo nome corrisponde a [{1}]" -Message.BanedByBanList: "[{0}] è stato bannato perché è stato bannato in precedenza." -Message.BanedByFACList: "[{0}] è stato bannato perché è nella lista dei bannati del FAC." -Message.DumpfileSaved: "Il file di log è stato salvato con successo sul desktop, nome del file: {0}" -Message.KickedByNoFriendCode: "[{0}] è stato espulso perché il suo codice amico non esiste." -Message.AddedPlayerToBanList: "Aggiunto [{0}] alla lista dei bannati" -Message.KickedByFAC: "[{0}] è stato espulso dal FAC, motivo: {1}" -Message.BanedByFAC: "[{0}] è stato espulso dal FAC, motivo:{1}" +Mus.ReturnToSimplicity: "Ritorno alla Semplicità" +Mus.ReturnToSimplicity2: "Ritorno alla Semplicità (Vers. Completa)" +Mus.ChasingDawn: "Inseguendo l'Alba" +Mus.StruggleAgainstFadingFlame: "Lotta Contro Fiamma Svanente" +Mus.Affinity: "Affinità" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Ruolo" +DisplayedRoleTag.PlayerIdentityTag: "Etichetta Identità" +DisplayedRoleTag.Room: "Stanza" + +PlayerIdentityTag.Hard_Cleared: "Confermato buono" +PlayerIdentityTag.Silver_Clear: "Probabilmente buono" +PlayerIdentityTag.Wolf_Bucket: "Fossa dei lupi" +PlayerIdentityTag.No_Kill: "Nessuna uccisione" +PlayerIdentityTag.Outside_Position: "Posizione esterna" +PlayerIdentityTag.Inside_Position: "Posizione interna" # 通知 / Notifications -PlayerLeft: "[{0}] ha lasciato il gioco" -PlayerLeftCuzTimeout: "[{0}] ha lasciato il gioco a causa di un timeout di connessione" -PlayerKickByHost: "[{0}] è stato espulso dall'host" -PlayerBanByHost: "[{0}] è stato bannato dall'host" -PlayerLeftCuzError: "[{0}] ha lasciato il gioco a causa di un errore" -PlayerLeftByAU-Anticheat: "[{0}] è stato espulso dai sistemi anti-trad di AmongU (non correlato a FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] è stato espulso perché aveva una versione diversa del mod" +## 原版 +Notification.PlayerLeft: "[{0}] ha lasciato il gioco" +Notification.PlayerLeftCuzTimeout: "[{0}] ha lasciato per timeout connessione" +Notification.PlayerKickByHost: "[{0}] espulso dall'host" +Notification.PlayerBanByHost: "[{0}] bannato dall'host" +Notification.PlayerLeftCuzError: "[{0}] ha lasciato per errore" +Notification.PlayerLeftByAU-Anticheat: "[{0}] espulso da Anti-Cheat Among Us (non correlato a FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] espulso per versione/mod diversa" +## 模组 +Notification.KickedByDenyName: "[{0}] espulso per nickname contenente parole vietate" +Notification.DumpfileSaved: "File log salvato sul desktop: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] rimosso per Friend Code anormale" +Notification.AddedPlayerToBanList: "Aggiungi [{0}] alla lista ban" +Notification.FPSSetTo: "FPS impostati a: {0}" # 警告 / Warnings -Warning: "Avvertimento!" -Warning.MismatchedVersion: "{0}\nha una versione diversa di {1}" -Warning.AutoExitAtMismatchedVersion: "L'host non ha o ha una versione diversa di {0}\nSarai espulso in {1}" -Warning.InvalidRpc: "{0} è stato espulso perché è stato ricevuto un RPC non valido." -Warning.InvalidRpc_NotHost: "{0} è sospettato di utilizzare trucchi. Chiedi all'host di espellerlo (RPC non valido: {1})" -Warning.SetName: "{0} è stato espulso perché ha impostato il nome più volte." -Warning.SetName_NotHost: "{0} è sospettato di utilizzare trucchi. Chiedi all'host di espellerlo (Nome impostato più volte)" -Warning.SendQuickChat: "{0} è stato espulso perché ha inviato più messaggi rapidi entro 3 secondi" -Warning.SendQuickChat_NotHost: "{0} è sospettato di utilizzare trucchi. Chiedi all'host di espellerlo (Invio di più messaggi rapidi entro 3 secondi)" -Warning.InvalidSlothRPC: "{0} è stato espulso perché è stato ricevuto un RPC illegale (Invio illegale del Rpc ufficiale: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} è sospettato di utilizzare trucchi. Chiedi all'host di espellerlo (Invio illegale di un RPC ufficiale: {1})" -Warning.Cheater: "{0} è stato espulso perché sospettato di utilizzare trucchi" -Warning.Cheater_NotHost: "{0} è sospettato di utilizzare trucchi, Si prega di chiedere all'ospite di espellerlo" -Warning.CantKickDev: "Spiacente, non puoi espellere lo sviluppatore" -Warning.RoomBroken: "Spiacente, questa stanza è stata compromessa. Procedi in un'altra stanza per continuare il gioco." +Warning: "Avvertimento" +Warning.MismatchedVersion: "[{0}]\nha versione diversa di [{1}] installata" +Warning.AutoExitAtMismatchedVersion: "La tua versione di [{0}] è diversa dall'host\nSarai espulso in {1} secondi" +Warning.CantKickDev: "Non puoi espellere sviluppatori" +Warning.RoomBroken: "Questa stanza è stata hackerata, gioca in un'altra" +Warning.InvalidColor: "Giocatore con colore non valido rilevato" ## 错误等级 / Error Levels -ErrorLevel1: "Potrebbero verificarsi errori." -ErrorLevel2: "Questo potrebbe essere un errore." -ErrorLevel3: "Questa versione non dovrebbe essere stata rilasciata." +ErrorLevel1: "Può causare bug multipli simultanei" +ErrorLevel2: "Potrebbero verificarsi bug" +ErrorLevel3: "Versione non rilasciata" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Avvertimento: FAC ha rilevato un alto livello di trucchi." -FAC.CheatDetected.LowLevel: "Avvertimento: FAC ha rilevato un basso livello di trucchi. Uno dei giocatori sta truccando." -FAC.CheatDetected.FAC: "Utilizzo di programmi truccati (ad esempio, AUM, YuMenu, SM, ecc.)" +CheatDetected.HighLevel: "Avviso: FAC sta difendendo da cheats bombing" +CheatDetected.LowLevel: "Avviso: FAC ha rilevato possibile cheater" +CheatDetected.UseCheat: "{0} ha usato cheat program [{1}]" +CheatDetected.MayUseCheat: "{0} sospettato di usare mod [{1}] o cheat" +CheatDetected.InvalidRpc: "[{0}] espulso per dati non validi (Rpc non valido: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] sospetto cheater, ricorda all'host di espellerlo (Rpc non valido: {1})" +CheatDetected.SetName: "[{0}] espulso per nome impostato più volte" +CheatDetected.SetName_NotHost: "[{0}] sospetto cheater, ricorda all'host di espellerlo (Nome impostato più volte)" +CheatDetected.SendQuickChat: "[{0}] espulso per messaggi rapidi multipli in 3 secondi" +CheatDetected.SendQuickChat_NotHost: "[{0}] sospetto cheater, ricorda all'host di espellerlo (Messaggi multipli in 3 secondi)" +CheatDetected.InvalidSlothRPC: "[{0}] espulso per dati illegali (Rpc ufficiale inviato illegalmente: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] sospetto cheater, ricorda all'host di espellerlo (Rpc ufficiale inviato illegalmente: {1})" +CheatDetected.Overload: "[{0}] espulso per attacco di sovraccarico" +CheatDetected.Overload_NotHost: "[{0}] ha iniziato attacco sovraccarico, stanza danneggiata - gioca altrove" +CheatDetected.Cheater: "[{0}] espulso per sospetto cheating" +CheatDetected.Cheater_NotHost: "[{0}] sospetto cheater, ricorda all'host di espellerlo" +CheatDetected.BanedByBanList: "[{0}] espulso per lista ban" +CheatDetected.BanedByFACList: "[{0}] espulso per lista ban FAC" # 模组信息 / Mod Infos -Contributors: "Contributori" -Acknowledgement: "Ringraziamenti" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Sei stato espulso dal sistema anti-trad.\n(L'uso di mod potrebbe essere mal interpretato come un tradimento)" -DCNotify.Banned: "Non sei autorizzato a entrare in questa stanza" -DCNotify.Kicked: "Sei stato espulso dalla stanza" -DCNotify.DCFromServer: "Sei stato disconnesso dal server.\nQuesto potrebbe essere dovuto a una connessione instabile.\nQuesto potrebbe anche essere dovuto a una connessione instabile del server." -DCNotify.GameNotFound: "La stanza assegnata non è stata trovata, la stanza potrebbe essere stata sciolta\no verifica se hai selezionato un server diverso dalla stanza" -DCNotify.GameStarted: "La partita è già iniziata, attendi che finisca" -DCNotify.GameFull: "La stanza è piena, riprova più tardi" -DCNotify.IncorrectVersion: "La tua versione di Among Us è diversa da questa stanza" -DCNotify.Description: "Sei stato espulso dal gioco.\nMotivo: {0}" -DCNotify.DenyName: "Il tuo nickname contiene caratteri irregolari" -DCNotify.BanList: "Sei stato bannato dall'host" -DCNotify.FACList: "Sei stato bannato dal FAC" -DCNotify.CheatDetected: "Sei stato rilevato come sospetto di trucco dal FAC" -DCNotify.InvalidRPC: "Potresti aver installato un mod diverso dall'host o il tuo mod è stato modificato in modo malizioso" -DCNotify.ModVersionIncorrect: "La tua versione del mod è diversa da quella dell'host" -DCNotify.LowLevel: "Il tuo livello non soddisfa i requisiti di questa stanza" -DCNotify.NotLogin: "I giocatori non loggati non sono ammessi in questa stanza" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Premi F1 per visualizzare la descrizione del tuo ruolo" -FakeTask: "Falso compito:" +ModInfo.Contributors: "Collaboratori" +ModInfo.Acknowledgement: "Ringraziamenti Speciali" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Sei stato espulso dall'anti-cheat di InnerSloth" +DCNotify.Banned: "Sei stato bannato da questa stanza" +DCNotify.Kicked: "Sei stato espulso da questa stanza" +DCNotify.DCFromServer: "Connessione al server interrotta\nRete instabile o problemi server" +DCNotify.GameNotFound: "Stanza specificata non trovata, forse chiusa\nO server diverso selezionato" +DCNotify.GameStarted: "Partita già iniziata, attendi la fine" +DCNotify.GameFull: "Stanza piena, riprova più tardi" +DCNotify.IncorrectVersion: "La tua versione Among Us differisce" +DCNotify.Description: "Sei stato espulso dalla stanza\nMotivo: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Premi F1 per descrizione ruolo" +PressF2ToHidePane: "Premi F2 per mostrare/nascondere pannello" +FakeTask: "Compito Falso:" KillCount: "Uccisioni" # 复盘信息 / Last Results -RoleSummaryText: "Ultimi risultati:" -ShowResults: "Mostra ultimi risultati" -HideResults: "Nascondi ultimi risultati" -NoInfoExists: "Nessun ultimo risultato disponibile" -CrewsWin: "Vittoria del Team-Crewmate" -CrewmatesWin: "Crewmates Win" -CrewmatesWinBlurb: "La luce della verità splende nella speranza!" -ImpsWin: "Vittoria del Team-Impostor" -ImpostorsWin: "Impostors Win" -ImpostorsWinBlurb: "Il male trasforma la verità in cenere" -HideSummaryTextToShowWinText: "Nascondi l'ultimo risultato per visualizzare il testo di vittoria" - -# 禁用公开 -DisabledByProgram: "Le operazioni della sala pubblica sono state disabilitate dal programma" -PublicNotAvailableOnThis Version: "Le stanze pubbliche non sono disponibili in questa versione di FinalSuspect" +Summary.Text: "Riepilogo Finale:" +Summary.ShowResults: "Mostra Riepilogo" +Summary.HideResults: "Nascondi Riepilogo" +Summary.NoInfoExists: "Nessun riepilogo valido" +Summary.CrewsWin: "Vittoria Equipaggio" +Summary.ImpsWin: "Vittoria Impostori" +Outro.Crews_Win: "Vittoria Equipaggio" +Outro.Crews_WinBlurb: "La luce della verità brilla nella speranza!" +Outro.Imps_Win: "Vittoria Impostori" +Outro.Imps_WinBlurb: "Il male ha ridotto la verità in cenere" # 主页 / Main UI -FinalSuspectWelcomeText: "Auguri per una esperienza di gioco piacevole!" -ConnectToFinalSuspectServerFailed: "Connessione al server di FinalSuspect non riuscita" -Website: "Sito ufficiale" +FinalSuspectWelcomeText: "Ti auguriamo una piacevole esperienza di gioco!" +RetrieveVersionInfoFailed: "Recupero informazioni FinalSuspect fallito" +Website: "Sito Ufficiale" MainMenuCredential: "{0} © 2025" -LShift: "Premi LShift per tornare alla stanza precedente" -RShift: "Premi RShift per entrare nella stanza della clipboard" -LobbyTimeDisplayText: "Tempo di esistenza trascorso" +LShift: "Lobby Precedente" +RShift: "Lobby Appunti" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" @@ -297,18 +341,25 @@ Local: "Locale" # 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Frame rate cap set to: {0}" -BrowsingMode: "Modalità di navigazione" +BrowsingMode: "Modalità Navigazione" Broken: "Rotto" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Caricamento delle traduzioni completo!" -CheckingForFiles: "Verifica dell'integrità dei file di risorse..." -DownloadingResources: "Download dei file di risorse..." -Loading: "Caricamento" -LoadingWithDot: "Caricamento..." -LoadingComplete: "Caricamento completo!" +Unknown: "Sconosciuto" +Back: "Indietro" +Yes: "Sì" +No: "No" +Cancel: "Annulla" +Unload: "Cambia" +Retry: "Riprova" +PreviousPage: "Pagina Precedente" +NextPage: "Pagina Successiva" +Download: "Scarica" +Disable: "Disabilita" +Delete: "Elimina" +Author: "Autore" +Close: "Chiudi" # 身份 / Identity -Host: "Host" -Cheater: "Truffatore" +Id.Host: "Host" +Id.Cheater: "Cheater" +Id.Developer: "Sviluppatore" +Id.Contributor: "Collaboratore" \ No newline at end of file diff --git a/Assets/Languages/Japanese.yaml b/Assets/Languages/Japanese.yaml index f0760259..deb39908 100644 --- a/Assets/Languages/Japanese.yaml +++ b/Assets/Languages/Japanese.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "11" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "インポスター" -TypeCrewmate: "クルーメイト" +RoleType.Imp: "インポスター" +RoleType.Crew: "クルー" # 阵营 / Teams -TeamImpostor: "インポスター陣営" -TeamImpostorOnly: "インポスター" -TeamCrewmate: "クルーメイト陣営" +Team.Imp: "インポスター陣営" +Team.Imp_Only: "インポスター" +Team.Crew: "クルー陣営" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "私たちのチームには {0} インポスターがいます" -ImpostorNumImpOnly: "群衆の中にインポスターは 1 人しかいません" -ImpostorNumCrew: "私たちの中に {0} インポスターがいます" +ImpostorNum.Imp: "味方の中にインポスターが{0}人います" +ImpostorNum.Imp_Only: "この中にインポスターはたった1人だけ" +ImpostorNum.Crew: "私たちの中にインポスターが{0}人います" # 阵营开场 -ImpostorIntroText: "悪が世界を包み込むように!" -ImpostorIntroTextOnly: "私は一人かもしれないけど、無限の力がある!" -CrewmateIntroText: "あなたのタスクを完了し、これらの厄介な状況を解決するために団結してください!" - -## 原版职业 / Vanilla -Crewmate: "クルーメイト" -Engineer: "エンジニア" -Scientist: "科学者" -Tracker: "トラッカー" -Noisemaker: "ノイズメーカー" -GuardianAngel: "ガーディアンエンジェル" -Impostor: "インポスター" -Shapeshifter: "シェイプシフター" -Phantom: "ファントム" -CrewmateGhost: "クルーメイトの幽霊" -ImpostorGhost: "インポスターの幽霊" +IntroText.Imp: "悪が世界を支配する!" +IntroText.Imp_Only: "孤独でも、無限の力を持っている!" +IntroText.Crewmate: "タスクを完了し、インポスターを見つけろ!" + +# 原版职业 / Vanilla +Role.Crewmate: "クルー" +Role.Engineer: "エンジニア" +Role.Scientist: "科学者" +Role.Tracker: "トラッカー" +Role.Noisemaker: "ノイズメーカー" +Role.GuardianAngel: "守護天使" +Role.Impostor: "インポスター" +Role.Shapeshifter: "シェイプシフター" +Role.Phantom: "ファントム" +Role.CrewmateGhost: "クルーゴースト" +Role.ImpostorGhost: "インポスターゴースト" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 CrewmateGhostBlurb: "タスクを完了する" -ImpostorGhostBlurb: "施設を引き続き妨害する" -CrewmateGhostBlurbLong: "タスクを完了し、遅れないで!" -ImpostorGhostBlurbLong: "施設を妨害し、生き残っているインポスターが勝利を手助けする。" - -## 捉迷藏 / HnS -HnSEngineerBlurb: "最後まで生き残れ!" -HnSEngineerBlurbLong: "時間切れまで生き残り、タスクを完了することで時間を延ばす。\nベントと脅威インジケーターを使用して隠れろ!\nタイマーが始まるやいなや、インポスターはあなたを狩り始める!" -HnSImpostorBlurb: "皆を排除せよ!" -HnSImpostorBlurbLong: "制限時間内にすべてのクルーメイトを殺害せよ!\n素早く行動しなければならない!ベントは使用不可。\n最後の瞬間に、スピードブーストと追跡ヒントが与えられる!" -HnSCrewmateGhostBlurb: "仲間を応援しよう" -HnSCrewmateGhostBlurbLong: "行け!応援!が!仲間たち!" +ImpostorGhostBlurb: "サボタージュを続ける" +CrewmateGhostBlurbLong: "タスクを完了して、チームを遅れさせない!\n「ホーンティング」を使ってインポスターを見て、守護天使がクルーを守るのを手伝おう!" +ImpostorGhostBlurbLong: "設備をサボタージュし、生き残ったインポスターを助けて勝利しよう!" +HnSEngineerBlurb: "タスクを完了して最後まで生き残れ!" +HnSImpostorBlurb: "全員を皆殺しにしろ!" +HnSCrewmateGhostBlurb: "仲間を励ませ!" +HnSCrewmateGhostBlurbLong: "死んだ?何見てるの?タスクできる?無理だろ…\nそれなら… 仲間!を!励ませ!" # 死因 / Death Reason -DeathReason.Kill: "殺害" -DeathReason.Exile: "追放" -DeathReason.Disconnect: "切断" - -# クライアントオプション / Client Options -FinalSuspectOptions: "Final Suspect オプション" -Back: "戻る" -Yes: "はい" -No: "いいえ" -UnlockFPS: "FPS 解除" -ChangeOutfit: "服装を変更" -BeanMode: "クラシックビーンモード" -HorseMode: "エイプリルフールの馬モード" -LongMode: "エイプリルフールのロングモード" -KickPlayerFriendCodeNotExist: "ログインしていないプレイヤーをキックする。" -KickPlayerWithDenyName: "不適切なニックネームを使用するプレイヤーをキックする" -KickPlayerInBanList: "禁止リストのプレイヤーをキックする" -SpamDenyWord: "不適切なワードをブロックする" -AutoStartGame: "ロビーが満員になると自動的に開始する" -AutoEndGame: "ゲーム終了後、自動的にロビーに戻る" -SwitchVanilla: "元のバージョンに切り替える" -DisableVanillaSound: "Among Us の音楽を無効にする" -DisableFAC: "チート検知機能を無効にする" -ShowPlayerInfo: "プレイヤーのプラットフォームとクライアント情報を表示する" -UseModCursor: "カスタムカーソルを使用する" -FastBoot: "高速起動モード" -PrunkMode: "いたずらモード" -VersionCheat: "MODバージョンの同期チェックをスキップする" -GodMode: "ゴッドモード" -NoGameEnd: "ゲーム終了を無効にする" -EnableFinalSuspect: "「Final Suspect」を有効にする" - -# クライアント機能 / Client Features -FinalSuspectFeatures: "Final Suspect 機能" -UnloadMod: "元のバージョンに切り替える" -UnloadWarning: "警告\nMODを再度有効化するには、ゲームを再起動する必要があります。\nそれでも続ける場合は、[OK]をクリックしてください。" -CannotUnloadDuringGame: "ゲーム中は元のバージョンに切り替えることはできません" -Cancel: "キャンセル" -Unload: "アンロード" -DumpLog: "ログを出力する" -ClearAutoLogs: "自動ログをクリア" -SoundOptions: "マイミュージック" -AudioManagementOptions: "オーディオ管理" -OnlyAvailableInMainMenu: "メインメニューでのみ使用可能です" - -# ヒント / Tips -updatePleaseWait: "しばらくお待ちください..." -updateInProgress: "更新中..." -DownloadingAudios: "ダウンロード中..." -Playing: "プレイ中..." -Parsing: "解析中..." -DownLoadSucceedNotice: "ダウンロードに成功しました!" -DownLoadFailureNotice: "ダウンロードに失敗しました =(" -PleaseWait: "しばらくお待ちください..." - -# 更新チェック / Update Checker -Retry: "再試行" -updateCheckPopupTitle: "更新の確認" -updateCheckFailedRetry: "更新の確認に失敗しました :(\n再度お試しください。" -updateCheckFailedExit: "更新の確認に失敗しました :(\nネットワーク接続を確認し、再度お試しください。" - -# 更新リマインダー / Update Reminder -UpdateBySelfTitle: "更新リマインダー" -updateNotice: "更新リマインダー" -UpdateBySelfText: "このバージョンではワンクリック更新はサポートされていません。手動で更新してください。" -updateButton: "今すぐ更新" -updatePopupTitle: "今すぐ更新" -updatePopupTitleFailed: "更新に失敗しました" -updatePopupTitleDone: "更新が完了しました" - -# 更新ソースの選択 / Update Chose Source -updateChoseSource: "更新するソースを選択してください\n選択に迷う場合は [Github] を選択してください\n更新に失敗した場合は [Api] を選択してください" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新完了の通知 / Update completion prompts -updateRestart: "変更を適用するにはゲームを再起動してください :)" -updatePingFialed: "理由: {0}\n選択したチャンネルが一時的に使用できない可能性があります。別のダウンロードチャンネルに切り替えてください。" -updateFileMd5Incorrect: "理由: ファイルのチェックサムエラー\nこのチャンネルのファイルバージョンが最新ではありません。別のダウンロードチャンネルに切り替えてください。" -downloadFailed: "理由: ダウンロードがタイムアウトまたは中断しました\nネットワークを変更するか、手動で更新してください。" - -# 公開ゲームに参加できない理由 / Unable to join public game reasons -onSetPublicNoLatest: "重要なアップデートがあります。この MOD を更新してください。\nさもないと、公開ルームに参加できません。" -CanNotJoinPublicRoomNoLatest: "重要なアップデートがあります。この MOD を更新してください。\nさもないと、公開ルームに参加できません。" -ModBrokenMessage: "MOD ファイルが破損しています。ゲームを再起動するか、この MOD を再インストールしてください。" -UnsupportedVersion: "あなたの Among Us のバージョンは FinalSuspect と互換性がありません。\nゲームを更新してください。" - -# 音声再生 / Audio Playback -PlayMode0: "一度だけ再生" -PlayMode1: "繰り返し再生" -PlayMode2: "シャッフル再生" -PlayMode3: "順番再生" -Stop: "停止" -CanPlay: "再生するにはクリック" -NoFound: "[ファイルが見つかりません]" -NextPage: "次ページ" -PreviousPage: "前のページ" - -# 音声の追加 / Audio Addition -download: "ダウンロード" -delete: "削除" -NewSound: "新しい音楽を追加" -PleaseEnterMusic: "音楽の名前を入力してください" -AudioManagementAlreadyExists: "この音楽名は既に存在します" -NotAllowedMusic: "音楽の名前が許可されていません" - -# インターフェースのヒント / Interface Tips -CustomAudioManagementHelp: "XtremeWave がサポートする音楽をダウンロードするか、自分の音楽を「オーディオ管理」に追加することができます。自分の音楽を追加する場合は、「オーディオ管理」に音楽の名前を追加し、対応する音声ファイルを「Among Us/Final Suspect_Data/Resources/Sounds」フォルダに配置してください(サポート形式:.wav)。音楽は「マイミュージック」で再生できます。" -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "XtremeWave がサポートする音楽をダウンロードするか、自分の音楽を「オーディオ管理」に追加することができます。ローカルの音楽リソースのパスが見つからない場合は、「[ファイルが見つかりません]」と表示されます。" - -# メインメニューの音楽の注意 / Main Menu Music Reminder -MusicNotYet: "現在の音楽ファイルが不完全と検出されました" -AudioNYPro: "最高のゲーム体験を得るには、「ホーム-設定-その他の機能-オーディオ管理」で音楽をダウンロードしてください。" +DeathReason.Kill: "キルされた" +DeathReason.Exile: "追放された" +DeathReason.Disconnect: "接続切断" + +# 客户端选项 / Client Options +FinalSuspectOptions: "Final Suspect設定" +ClientOption.UnlockFPS: "FPS制限を解除" +ClientOption.SwitchOutfitType: "アウトフィットタイプを切り替える" +ClientOption.KickPlayerWithAbnormalFriendCode: "異常なフレンドコードのプレイヤーをキック" +ClientOption.KickPlayerWithDenyName: "禁止名のプレイヤーをキック" +ClientOption.KickPlayerInBanList: "BANリストのプレイヤーをキック" +ClientOption.SpamDenyWord: "禁止ワードをブロック" +ClientOption.AutoStartGame: "満員時に自動でゲーム開始" +ClientOption.AutoEndGame: "ゲーム終了後に自動でロビーに戻る" +ClientOption.SwitchVanilla: "バニラに切り替える" +ClientOption.DisableVanillaSound: "バニラゲームのBGMを無効化" +ClientOption.EnableFAC: "アンチチートを有効化" +ClientOption.EnableGuardian: "クライアントガーディアンを有効化(実験的)" +ClientOption.ShowPlayerInfo: "プレイヤーのプラットフォームとクライアント情報を表示" +ClientOption.UseModCursor: "MODカーソルを使用" +ClientOption.FastLaunchMode: "高速起動モード" +ClientOption.OfflineMode: "オフライン モード(実験的)" +ClientOption.VersionCheat: "バージョン同期チェックを回避" +ClientOption.GodMode: "ゴッドモード" +ClientOption.NoGameEnd: "ゲーム終了なし" +ClientOption.EnableFinalSuspect: "「Final Suspect」を有効化" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "クラシックモード" +Value.HorseMode: "エイプリルフール・ホースモード" +Value.LongMode: "エイプリルフール・ロングモード" + +# 客户端功能 / Client Features +FinalSuspectFeatures: "Final Suspect機能" +ClientFeature.UnloadMod: "バニラに切り替える" +ClientFeature.DumpLog: "ログをダンプ" +ClientFeature.ClearAutoLogs: "自動ログをクリア" +ClientFeature.MyMusic: "マイミュージック" +ClientFeature.ResourceManager: "リソースマネージャー" +ClientFeature.NameTagManager: "ネームタグマネージャー" +ClientFeature.MainMenuStyleManager: "メインメニュースタイル切り替え" + +# 提示 / Tips +Tip.Downloading: "ダウンロード中…" +Tip.PleaseWait: "お待ちください…" +Tip.Playing: "再生中…" +Tip.Parsing: "解析中…" +Tip.Updating: "更新中…" +Tip.DownLoadFinished: "ダウンロード完了" +Tip.DownLoadSucceeded: "ダウンロード成功!" +Tip.DownLoadFailed: "ダウンロード失敗=(" +Tip.PackageExists: "インストール済み" +Tip.OnlyAvailableInMainMenu: "メインメニューのみ利用可能" +Tip.HideSummaryTextToShowWinText: "最終サマリーを非表示にして勝利テキストを表示" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "言語ファイルの読み込み完了" +Tip.CheckingForFiles: "リソースファイルの整合性を確認中…" +Tip.DownloadingResources: "リソースファイルをダウンロード中…" +Tip.Loading: "読み込み中" +Tip.LoadingWithDot: "読み込み中…" +Tip.LoadingComplete: "読み込み完了!" +## 切换原版 +Tip.UnloadWarning: "警告!\nMOD版を遊びたい場合は再起動が必要です。\n本当にバニラ版に切り替えますか?" +Tip.CannotUnloadDuringGame: "ゲーム中はバニラ版に切り替えられません。" +## 资源管理 +Tip.ResourceManager: "「リソースマネージャー」でMOD対応の追加リソースをダウンロードできます\n「Pre-」で始まるファイルは事前ダウンロードリソースパックです" +## 我的音乐 +Tip.MyMusic: "「リソースマネージャー」でMOD対応音楽をダウンロードするか、フォルダ(Among Us/Final Suspect_Data/Musics)に好きな音声ファイルを追加してください。音楽のローカルパスが存在しない場合は「ファイルなし」と表示されます" +Tip.Incomplete_Music: "不完全な音楽ファイルが検出されました" +Tip.Incomplete_SoundEffect: "不完全な効果音ファイルが検出されました" +Tip.Incomplete_Image: "不完全な画像ファイルが検出されました" +Tip.Incomplete: "より良いゲーム体験のため、「メインメニュー→設定→クライアント機能→リソースマネージャー」でMOD対応リソースパックをダウンロードしてください" +## 名称标识管理 +Tip.TextContent: "テキスト内容" +Tip.TextSizeDescription: "テキストサイズ(デフォルト100%)" +Tip.TextColorDescription: "テキストカラー(16進カラーコード)\n不要な場合は空欄、複数入力で自動グラデーション\n" +Tip.PleaseEnterFriendCode: "フレンドコードを入力して新しいネームタグをバインドしてください" +Tip.FriendCodeAlreadyExist: "このフレンドコードには既にネームタグがあります" +Tip.FriendCodeIncorrect: "有効なフレンドコードを入力してください" +Tip.CustomNameTagHelp: "任意のプレイヤーにネームタグを追加できます。バインドされたフレンドコードのプレイヤーが参加すると自動的に割り当てられますが、ロビー入室後は編集できません。\n非VIPは備考のみ追加可能(VIP機能は未実装)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "「リソースマネージャー」で「メインメニュースタイルパック」をダウンロードし、好きなメインメニュースタイルを選択できます" + +# 更新结果 +UpdateResult.Succeed_Title: "更新成功" +UpdateResult.Succeed_Text: "再起動後に適用されます :)" +UpdateResult.Failed_Title: "更新失敗" +UpdateResult.Failed_Reason_NotFound: "理由:{0}\nこのソースは一時的に利用できない可能性があります。ダウンロードソースを切り替えて再試行してください" +UpdateResult.Failed_Reason_FileMd5Incorrect: "理由:ファイル検証エラー\nこのソースのファイルは最新ではありません。ダウンロードソースを切り替えて再試行してください" +UpdateResult.Failed_Reason_Ping: "理由:タイムアウトまたは中断\nネットワークを確認して再試行するか、手動で更新してください" + +# 更新检查 / Update Checker +UpdateCheck.Popup_Title: "アップデート確認" +UpdateCheck.Failed_Retry: "アップデート確認失敗 :(\nリトライしますか?" +UpdateCheck.Failed_Exit: "アップデート確認失敗 :(\nネットワークを確認して再試行してください!" + +# 更新提醒 / Update Reminder +UpdateRemind.updatePopup: "今すぐ更新" +UpdateRemind.updateNotice: "アップデート通知" +UpdateRemind.BySelf_Title: "アップデートヒント" +UpdateRemind.BySelf_Text: "このバージョンはワンクリック更新に対応していません。手動で更新してください" + +# 选择更新渠道 / Update Chose Source +UpdateSource.Choose: "アップデートソースを選択してください\nわからない場合は [Github] を選択" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" + +# 无法加入公开游戏原因 / Unable to join public game reasons +CanNotJoinPublicRoomNoLatest: "重要なアップデートがあります。MODを更新してください\nそうでないと公開ルームに参加できません" +ModBrokenMessage: "MODファイルがクラッシュしました。ゲームを再起動するかMODを再インストールしてください" +UnsupportedVersion: "Among UsのバージョンがFinalSuspectと非互換です\nゲームを更新してください" + +# 名称标识 +NameTag.DisplayName: "備考" +NameTag.Title: "タイトル" +NameTag.Prefix: "プレフィックス" +NameTag.Suffix: "サフィックス" +NameTag.Name: "名前" +NameTag.LastTag: "追加サフィックス" +NameTag.PreviewNotAvailable: "(プレビュー非対応)" +NameTag.CanNotEdit: "(編集不可)" +NameTag.RefreshPreview: "プレビューを更新" +NameTag.SaveAndClose: "保存して閉じる" +NameTag.NewNameTag: "新規" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "黎明を追う(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "時が経ち、北の冬は終わり、春が息を吹き返す。\nミラの雪景色を眺めながら、記憶はよみがえる?心に温かな流れが流れる。\n最後の容疑者が誰であれ、真実を壊すのが誰の手であれ、\n新鮮な新体験でブレインストームし、新しい幸せな思い出を残そう。\nゲームをして、楽しむのが一番!!!\n\n命名:一念旧情丶" +MainMenuStyle.Title_Security: "赤い心、激しい愛 (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "私たちは倒れない、諦めない、プレイヤーを失望させない、前進を止めない。\n冷たい視線に遭っても、皆に証明してみせる!\n情熱を持って再び船出\n\n命名:Slok" +MainMenuStyle.Title_NewYear: "親和(春節)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "幸運:授かる願い 絆:鼓動に織られる\n調和:愛の糸 至福:すべての魂に平和\n宿命の絆が共有の喜びを作る 旅は遠く、団結に喜びを見いだす\n黄金時代がすべてを喜びに包む 私たちの祈り—すべての存在への喜び\n来る年にすべての魂が運命の道を見つけられますように\n私たちの祝福が道を照らし、あなた自身の伝説を作る旅に出かけてください!\n\n命名:Slok" +MainMenuStyle.Title_MiraStudio: "Finalスタジオ(春節)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "「—花が波のように咲き、風のように優しく、皆の心が風のように広く、すべてが順調にいきますように」\n「—春風が力強く吹き、波が何マイルも広がり、皆が最前線に勇敢に立ち、繁栄しますように」\n「—XtremeWave春節スペシャルへようこそ!」\n\n命名:Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Finalが卓越に向かって努力し、夢が押し寄せる波を指揮する!」\n\n命名:Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "最後に振り返るとき(コラボ)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「最後に振り返るとき、すべては終わりで終わる」\n\n命名:MAMTI.麦麦头" +MainMenuStyle.NotFound: "未ダウンロード" +MainMenuStyle.NotApply: "適用" +MainMenuStyle.Applied: "適用済み" + +# 资源包 +Package.MainMenuStyle: "メインメニュースタイルパック" + +# 音频播放 / Audio Playback +MusPlay.Mode0: "1回再生" +MusPlay.Mode1: "単曲ループ" +MusPlay.Mode2: "リストランダム" +MusPlay.Mode3: "順番再生" +MusPlay.Stop: "再生停止" +MusPlay.CanPlay: "クリックして再生" +MusPlay.NoFound: "ファイルなし" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(旧正月おめでとう)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" -Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# メッセージ / Messages -Message.KickedByDenyName: "[{0}] は名前が [{1}] と一致するためキックされました" -Message.BanedByBanList: "[{0}] は以前に禁止されていたため、禁止されました" -Message.BanedByFACList: "[{0}] は FAC の禁止リストにいたため、禁止されました" -Message.DumpfileSaved: "ログファイルはデスクトップに正常に保存されました。ファイル名: {0}" -Message.KickedByNoFriendCode: "[{0}] のフレンドコードが存在しないためキックされました" -Message.AddedPlayerToBanList: "[{0}] を禁止リストに追加しました" -Message.KickedByFAC: "[{0}] は FAC によってキックされました。理由: {1}" -Message.BanedByFAC: "[{0}] は FAC によってキックされました。理由: {1}" +Mus.TidalSurge: "潮のうねり" +Mus.TrailOfTruth: "真実の痕跡" +Mus.Interlude: "インタールード" +Mus.Fractured: "破砕" +Mus.ElegyOfFracturedVow: "破れた誓いの悲歌" +Mus.VestigiumSplendoris: "残光" +Mus.ReturnToSimplicity: "簡素への回帰" +Mus.ReturnToSimplicity2: "簡素への回帰(フルバージョン)" +Mus.ChasingDawn: "夜明けを追う" +Mus.StruggleAgainstFadingFlame: "消えゆく炎との闘い" +Mus.Affinity: "親和" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "役職" +DisplayedRoleTag.PlayerIdentityTag: "アイデンティティ・タグ" +DisplayedRoleTag.Room: "部屋" + +PlayerIdentityTag.Hard_Cleared: "確定市民" +PlayerIdentityTag.Silver_Clear: "銀水" +PlayerIdentityTag.Wolf_Bucket: "狼ポジション" +PlayerIdentityTag.No_Kill: "襲撃なし" +PlayerIdentityTag.Outside_Position: "外ポジション" +PlayerIdentityTag.Inside_Position: "内ポジション" # 通知 / Notifications -PlayerLeft: "[{0}] がゲームを退出しました" -PlayerLeftCuzTimeout: "[{0}] は 接続タイムアウト のためゲームを退出しました" -PlayerKickByHost: "[{0}] はホストによってキックされました" -PlayerBanByHost: "[{0}] はホストによって禁止されました" -PlayerLeftCuzError: "[{0}] は エラー のためゲームを退出しました" -PlayerLeftByAU-Anticheat: "[{0}] は AmongU の公式チート検知システムによってキックされました(FinalSuspect とは関係ありません)" -KickBecauseDiffrentVersionOrMod: "[{0}] は異なるバージョンの MOD を使用していたためキックされました" +## 原版 +Notification.PlayerLeft: "[{0}] がゲームから退出しました" +Notification.PlayerLeftCuzTimeout: "[{0}] が接続タイムアウトで退出しました" +Notification.PlayerKickByHost: "[{0}] がホストにキックされました" +Notification.PlayerBanByHost: "[{0}] がホストにBANされました" +Notification.PlayerLeftCuzError: "[{0}] がエラーで退出しました" +Notification.PlayerLeftByAU-Anticheat: "[{0}] がAmong Usアンチチートにキックされました(FinalSuspectとは無関係)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] はバージョン/MODが異なるためキックされました" +## 模组 +Notification.KickedByDenyName: "[{0}] は禁止ワードを含む名前のためキックされました" +Notification.DumpfileSaved: "ログファイルがデスクトップに保存されました、ファイル名:{0}" +Notification.KickedByAbnormalFriendCode: "[{0}] は異常なフレンドコードのため部屋から削除されました" +Notification.AddedPlayerToBanList: "[{0}] をBANリストに追加しました" +Notification.FPSSetTo: "FPS上限を {0} に設定しました" # 警告 / Warnings -Warning: "警告!" -Warning.MismatchedVersion: "{0}\n{1} の異なるバージョンを使用しています" -Warning.AutoExitAtMismatchedVersion: "ホストは {0} を使用しておらず、または異なるバージョンを使用しています\n{1} 秒後に自動的にキックされます" -Warning.InvalidRpc: "{0} は無効な RPC を受信したためキックされました" -Warning.InvalidRpc_NotHost: "{0} はチートを使用していると疑われます。ホストにキックするようお願いしてください (無効な RPC: {1})" -Warning.SetName: "{0} は名前を複数回設定したためキックされました" -Warning.SetName_NotHost: "{0} はチートを使用していると疑われます。ホストにキックするようお願いしてください (名前を複数回設定)" -Warning.SendQuickChat: "{0} は 3 秒以内に複数のクイックメッセージを送信したためキックされました" -Warning.SendQuickChat_NotHost: "{0} はチートを使用していると疑われます。ホストにキックするようお願いしてください (3 秒以内に複数のクイックメッセージを送信)" -Warning.InvalidSlothRPC: "{0}をキックしました。不正なRPCが受信されたためです (公式Rpcを不正に送信: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} はチートを使用していると疑われます。ホストにキックするようお願いしてください (公式 RPC を不正に送信: {1})" -Warning.Cheater: "{0}をキックしました。チートの使用が疑われたためです" -Warning.Cheater_NotHost: "{0}はチートを使用していると疑われています。ホストにキックするようお願いしてください" -Warning.CantKickDev: "開発者をキックすることはできません" -Warning.RoomBroken: "このルームは破棄されました。別のルームに移動してゲームを続行してください" - -## エラーのレベル / Error Levels -ErrorLevel1: "バグが発生する可能性があります" -ErrorLevel2: "これはバグかもしれません" -ErrorLevel3: "このバージョンはリリースされるべきではありませんでした" - -# チート検知 / FAC -FAC.CheatDetected.HighLevel: "警告: FAC が高レベルのチートを検知しました" -FAC.CheatDetected.LowLevel: "警告: FAC が低レベルのチートを検知しました。誰かがチートしています" -FAC.CheatDetected.FAC: "チートプログラムの使用(例: AUM, YuMenu, SM など)" - -# MOD 情報 / Mod Infos -Contributors: "貢献者" -Acknowledgement: "謝辞" - -# 接続切断の理由 / Disconect Reasons -DCNotify.Hacking: "チート検知システムによってキックされました。\n(MOD の使用はチートと誤認識される場合があります)" -DCNotify.Banned: "このルームに入室する権限がありません" -DCNotify.Kicked: "ルームからキックされました" -DCNotify.DCFromServer: "サーバーから切断されました。\nこれはあなたのネットワークの不安定さが原因かもしれません。\nサーバーの不安定さが原因の場合もあります。" -DCNotify.GameNotFound: "割り当てられたルームが見つかりません。ルームが解散された可能性があります。\nまたは、ルームとは異なるサーバーを選択している場合があります。" -DCNotify.GameStarted: "ゲーム已经开始,请等待结束" -DCNotify.GameFull: "ルームが満員です。しばらくしてから再度お試しください" -DCNotify.IncorrectVersion: "あなたの Among Us のバージョンがこのルームと異なる" -DCNotify.Description: "ゲームからキックされました。\n理由: {0}" -DCNotify.DenyName: "あなたのニックネームに不適切な文字が含まれています" -DCNotify.BanList: "ホストによって禁止されました" -DCNotify.FACList: "FAC によって禁止されました" -DCNotify.CheatDetected: "FAC によってチートが検知されました" -DCNotify.InvalidRPC: "ホストと異なる MOD をインストールしているか、MOD が悪意を持って変更されています" -DCNotify.ModVersionIncorrect: "あなたの MOD バージョンがホストと異なる" -DCNotify.LowLevel: "あなたのレベルがこのルームの要件を満たしていません" -DCNotify.NotLogin: "このルームではログインしていないプレイヤーは許可されていません" - -# タスクパネル / Task Panel -PressF1ShowRoleDescription: "F1 を押してあなたの役職の説明を表示する" -FakeTask: "偽のタスク:" -KillCount: "殺害数" - -# 最終結果 / Last Results -RoleSummaryText: "最終結果:" -ShowResults: "最終結果を表示する" -HideResults: "最終結果を隠す" -NoInfoExists: "最終結果がありません" -CrewsWin: "クルーメイト陣営の勝利" -CrewmatesWin: "クルーメイトの勝利" -CrewmatesWinBlurb: "希望の光が真実を照らし出します!" -ImpsWin: "インポスター陣営の勝利" -ImpostorsWin: "インポスターの勝利" -ImpostorsWinBlurb: "悪が真実を灰に変える" -HideSummaryTextToShowWinText: "最終結果を隠して勝利のテキストを表示する" - -# 公開ルームの無効化 -DisabledByProgram: "プログラムによって公開ルームの操作が無効化されました" -PublicNotAvailableOnThis Version: "このバージョンの FinalSuspect では公開ルームは利用できません" - -# ホームページ / Main UI -FinalSuspectWelcomeText: "pleasant ゲーム体験をお祈りします!" -ConnectToFinalSuspectServerFailed: "FinalSuspect サーバーへの接続に失敗しました" -Website: "公式ウェブサイト" +Warning: "警告" +Warning.MismatchedVersion: "[{0}]\nは [{1}] の異なるバージョンをインストールしています" +Warning.AutoExitAtMismatchedVersion: "あなたの [{0}] のバージョンはホストと異なります\n{1}秒後にキックされます" +Warning.CantKickDev: "申し訳ありません、開発者をキックできません" +Warning.RoomBroken: "申し訳ありません、この部屋はハッキング攻撃を受けました、別の部屋で遊んでください" +Warning.InvalidColor: "無効な色のプレイヤーが検出されました" + +## 错误等级 / Error Levels +ErrorLevel1: "複数のバグが同時に発生する可能性があります" +ErrorLevel2: "バグが発生する可能性があります" +ErrorLevel3: "未リリースバージョン" + +# 反作弊 / FAC +CheatDetected.HighLevel: "警告:FACが爆撃チートから防御しています" +CheatDetected.LowLevel: "警告:FACがチーターの可能性を検出しました" +CheatDetected.UseCheat: "{0} がチートプログラム [{1}] を使用しました" +CheatDetected.MayUseCheat: "{0} が MOD [{1}] またはチートプログラムを使用している可能性があります" +CheatDetected.InvalidRpc: "[{0}] は無効なデータを送信したためキックされました (無効なRpc: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] がチートの疑いがあります、ホストにキックするよう促してください (無効なRpc: {1})" +CheatDetected.SetName: "[{0}] は名前を複数回設定したためキックされました" +CheatDetected.SetName_NotHost: "[{0}] がチートの疑いがあります、ホストにキックするよう促してください (名前を複数回設定)" +CheatDetected.SendQuickChat: "[{0}] は3秒以内に複数のクイックメッセージを送信したためキックされました" +CheatDetected.SendQuickChat_NotHost: "[{0}] がチートの疑いがあります、ホストにキックするよう促してください (3秒以内に複数のクイックメッセージ)" +CheatDetected.InvalidSlothRPC: "[{0}] は不正なデータを送信したためキックされました (不正な公式Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] がチートの疑いがあります、ホストにキックするよう促してください (不正な公式Rpc: {1})" +CheatDetected.Overload: "[{0}] は過負荷攻撃を仕掛けたためキックされました" +CheatDetected.Overload_NotHost: "[{0}] が過負荷攻撃を仕掛けました、この部屋は破損しています、別の部屋で遊んでください" +CheatDetected.Cheater: "[{0}] はチートの疑いでキックされました" +CheatDetected.Cheater_NotHost: "[{0}] がチートの疑いがあります、ホストにキックするよう促してください" +CheatDetected.BanedByBanList: "[{0}] はBANリストに載っているためキックされました" +CheatDetected.BanedByFACList: "[{0}] はFAC BANリストに載っているためキックされました" + +# 模组信息 / Mod Infos +ModInfo.Contributors: "貢献者" +ModInfo.Acknowledgement: "スペシャルサンクス" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "InnerSlothのアンチチートシステムにより部屋からキックされました" +DCNotify.Banned: "この部屋からBANされました" +DCNotify.Kicked: "この部屋からキックされました" +DCNotify.DCFromServer: "サーバーとの接続が切断されました\nネットワークの不安定さまたは\nサーバーの不安定/アクセス拒否が原因かもしれません" +DCNotify.GameNotFound: "指定された部屋が見つかりません、閉鎖された可能性があります\nまたは部屋と異なるサーバーを選択していないか確認してください" +DCNotify.GameStarted: "このゲームは既に開始されています、終了までお待ちください" +DCNotify.GameFull: "この部屋は満員です、後でもう一度お試しください" +DCNotify.IncorrectVersion: "Among Usのバージョンが部屋と異なります" +DCNotify.Description: "部屋からキックされました\n理由:{0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "F1で役職説明を表示" +PressF2ToHidePane: "F2でパネル表示/非表示" +FakeTask: "偽タスク:" +KillCount: "キル数" + +# 复盘信息 / Last Results +Summary.Text: "最終サマリー:" +Summary.ShowResults: "最終サマリーを表示" +Summary.HideResults: "最終サマリーを非表示" +Summary.NoInfoExists: "有効な最終サマリーがありません" +Summary.CrewsWin: "クルー陣営の勝利" +Summary.ImpsWin: "インポスター陣営の勝利" +Outro.Crews_Win: "クルーの勝利" +Outro.Crews_WinBlurb: "真実の光が希望に輝く!" +Outro.Imps_Win: "インポスターの勝利" +Outro.Imps_WinBlurb: "悪は真実を灰に変えた" + +# 主页 / Main UI +FinalSuspectWelcomeText: "楽しいゲーム体験をお祈りしています!" +RetrieveVersionInfoFailed: "FinalSuspect情報の取得に失敗しました" +Website: "公式サイト" MainMenuCredential: "{0} © 2025" -LShift: "LShiftキーを押して前の部屋に戻る" -RShift: "RShiftキーを押してクリップボードルームに入る" -LobbyTimeDisplayText: "生存期間" +LShift: "前のロビー" +RShift: "クリップボードロビー" -# ロビーリスト表示 / Lobby List -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +# 客户端平台 / Platform +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" -# ピング表示 / Ping Tracker -Ping: "ピング" +# 延迟显示 / Ping Tracker +Ping: "Ping" FrameRate: "フレームレート" Server: "サーバー" Local: "ローカル" -# その他 / Other +# 其他 / Other HongKong: "香港" -FPSSetTo: "フレームレート上限が設定されました: {0}" -BrowsingMode: "ブラウズモード" -Broken: "故障" - -# 読み込み / Loading -LanguageFilesLoadingComplete: "翻訳の読み込みが完了しました!" -CheckingForFiles: "リソースファイルの整合性を確認中..." -DownloadingResources: "リソースファイルをダウンロード中..." -Loading: "読み込み中" -LoadingWithDot: "読み込み中..." -LoadingComplete: "読み込みが完了しました!" - -# 身元 / Identity -Host: "ホスト" -Cheater: "チーター" +BrowsingMode: "閲覧モード" +Broken: "破損" +Unknown: "不明" +Back: "戻る" +Yes: "はい" +No: "いいえ" +Cancel: "キャンセル" +Unload: "切り替え" +Retry: "リトライ" +PreviousPage: "前のページ" +NextPage: "次のページ" +Download: "ダウンロード" +Disable: "無効化" +Delete: "削除" +Author: "作者" +Close: "閉じる" + +# 身份 / Identity +Id.Host: "ホスト" +Id.Cheater: "チーター" +Id.Developer: "開発者" +Id.Contributor: "貢献者" \ No newline at end of file diff --git a/Assets/Languages/Korean.yaml b/Assets/Languages/Korean.yaml index 3f870d42..6acb484d 100644 --- a/Assets/Languages/Korean.yaml +++ b/Assets/Languages/Korean.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "4" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "임포스터" -TypeCrewmate: "크루원" +RoleType.Imp: "임포스터" +RoleType.Crew: "크루" # 阵营 / Teams -TeamImpostor: "임포스터 팀" -TeamImpostorOnly: "임포스터" -TeamCrewmate: "크루원 팀" +Team.Imp: "임포스터 팀" +Team.Imp_Only: "임포스터" +Team.Crew: "크루 팀" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "우리 팀에는 {0} 명의 임포스터가 있습니다" -ImpostorNumImpOnly: "우리 팀에는 임포스터가 1명뿐입니다" -ImpostorNumCrew: "우리 팀에는 {0} 명의 임포스터가 있습니다" +ImpostorNum.Imp: "우리 팀에 임포스터 {0}명이 있습니다" +ImpostorNum.Imp_Only: "군중 속에 임포스터는 단 한 명뿐입니다" +ImpostorNum.Crew: "우리 중에 임포스터 {0}명이 있습니다" # 阵营开场 -ImpostorIntroText: "악이 세상을 뒤덮이게 하라!" -ImpostorIntroTextOnly: "나 혼자일지라도 나는 무한한 힘을 가지고 있다!" -CrewmateIntroText: "당신의 임무를 완료하고, 이 어려운 상황들을 해결하기 위해 단결하십시오!" - -## 原版职业 / Vanilla -Crewmate: "크루원" -Engineer: "엔지니어" -Scientist: "과학자" -Tracker: "추적자" -Noisemaker: "소음기" -GuardianAngel: "수호천사" -Impostor: "임포스터" -Shapeshifter: "변신자" -Phantom: "유령" -CrewmateGhost: "크루원 유령" -ImpostorGhost: "임포스터 유령" -CrewmateGhostBlurb: "임무를 완료하라" -ImpostorGhostBlurb: "시설을 계속 방해하라" -CrewmateGhostBlurbLong: "임무를 완료하고, 뒤처지지 마라!" -ImpostorGhostBlurbLong: "시설을 방해하고, 생존한 임포스터가 승리에 도움을 주자." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "끝까지 생존하라!" -HnSEngineerBlurbLong: "시간이 끝날 때까지 생존하고, 임무를 완료하여 시간을 연장하라.\n환기구와 위험지표를 사용하여 숨어라!\n타이머가 시작되면 임포스터가 당신을 사냥하기 시작할 것이다!" -HnSImpostorBlurb: "모두를 제거하라!" -HnSImpostorBlurbLong: "시간 제한 내에 모든 크루원을 살해하라!\n빠르게 움직여야 한다! 환기구는 사용할 수 없다.\n마지막 순간에는 속도 부스트와 추적 힌트가 제공될 것이다!" -HnSCrewmateGhostBlurb: "동료들을 응원하라" -HnSCrewmateGhostBlurbLong: "가! 응원해! 동료들을 위해!" +IntroText.Imp: "악이 세계를 지배하도록 하라!" +IntroText.Imp_Only: "혼자지만 끝없는 힘을 가졌다!" +IntroText.Crewmate: "임무를 완수하고 임포스터를 찾아라!" + +# 原版职业 / Vanilla +Role.Crewmate: "크루" +Role.Engineer: "엔지니어" +Role.Scientist: "과학자" +Role.Tracker: "추적자" +Role.Noisemaker: "소음제조자" +Role.GuardianAngel: "수호천사" +Role.Impostor: "임포스터" +Role.Shapeshifter: "변신자" +Role.Phantom: "팬텀" +Role.CrewmateGhost: "크루 유령" +Role.ImpostorGhost: "임포스터 유령" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "임무 완수" +ImpostorGhostBlurb: "사보타주 계속" +CrewmateGhostBlurbLong: "임무를 완수하고 팀을 늦추지 마라!\n「유령행」으로 임포스터를 보고 수호천사가 크루를 보호하도록 도와라!" +ImpostorGhostBlurbLong: "설비를 사보타주하고 생존한 임포스터를 돕자!" +HnSEngineerBlurb: "임무를 완수하고 끝까지 살아남아라!" +HnSImpostorBlurb: "모두 도륙해 버려라!" +HnSCrewmateGhostBlurb: "팀원을 응원해라!" +HnSCrewmateGhostBlurbLong: "죽었어? 뭐 보고 있어? 임무 할 수 있어? 안 되잖아…\n그럼… 가! 팀원! 응원! 해!" # 死因 / Death Reason -DeathReason.Kill: "살해됨" -DeathReason.Exile: "추방됨" +DeathReason.Kill: "살해당함" +DeathReason.Exile: "추방당함" DeathReason.Disconnect: "연결 끊김" # 客户端选项 / Client Options FinalSuspectOptions: "Final Suspect 옵션" -Back: "뒤로" -Yes: "예" -No: "아니오" -UnlockFPS: "FPS 제한 해제" -ChangeOutfit: "의상 변경" -BeanMode: "클래식 빈 모드" -HorseMode: "에이프릴 풀 말 모드" -LongMode: "에이프릴 풀 롱 모드" -KickPlayerFriendCodeNotExist: "로그인하지 않은 플레이어 추방" -KickPlayerWithDenyName: "부적절한 닉네임 사용 플레이어 추방" -KickPlayerInBanList: "밴 목록에 있는 플레이어 추방" -SpamDenyWord: "부적절한 단어 차단" -AutoStartGame: "로비가 가득 차면 자동 시작" -AutoEndGame: "게임 종료 후 자동으로 로비로 돌아가기" -SwitchVanilla: "원래 버전으로 전환" -DisableVanillaSound: "Among Us 음악 비활성화" -DisableFAC: "방해 검사 기능 비활성화" -ShowPlayerInfo: "플레이어의 플랫폼과 클라이언트 정보 표시" -UseModCursor: "커스텀 커서 사용" -FastBoot: "빠른 시작 모드" -PrunkMode: "장난 모드" -VersionCheat: "MOD 버전의 동기화 검사를 건너뛰기" -GodMode: "신 모드" -NoGameEnd: "게임 종료 비활성화" -EnableFinalSuspect: "「Final Suspect」 활성화" +ClientOption.UnlockFPS: "FPS 제한 해제" +ClientOption.SwitchOutfitType: "옷 종류 변경" +ClientOption.KickPlayerWithAbnormalFriendCode: "비정상 친구 코드 플레이어 킥" +ClientOption.KickPlayerWithDenyName: "금지 닉네임 플레이어 킥" +ClientOption.KickPlayerInBanList: "차단 플레이어 킥" +ClientOption.SpamDenyWord: "금지어 차단" +ClientOption.AutoStartGame: "인원이 차면 자동 시작" +ClientOption.AutoEndGame: "게임 종료 시 자동 로비 복귀" +ClientOption.SwitchVanilla: "바닐라로 전환" +ClientOption.DisableVanillaSound: "바닐라 게임 음악 끄기" +ClientOption.EnableFAC: "앤티치트 활성화" +ClientOption.EnableGuardian: "클라이언트 가디언 활성화(실험적)" +ClientOption.ShowPlayerInfo: "플레이어 플랫폼 및 클라이언트 정보 표시" +ClientOption.UseModCursor: "모드 커서 사용" +ClientOption.FastLaunchMode: "빠른 시작 모드" +ClientOption.OfflineMode: "오프라인 모드(실험적)" +ClientOption.VersionCheat: "버전 동기화 확인 우회" +ClientOption.GodMode: "무적 모드" +ClientOption.NoGameEnd: "게임 종료 없음" +ClientOption.EnableFinalSuspect: "「Final Suspect」 활성화" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "클래식 모드" +Value.HorseMode: "만우절 말 모드" +Value.LongMode: "만우절 긴 모드" # 客户端功能 / Client Features FinalSuspectFeatures: "Final Suspect 기능" -UnloadMod: "원래 버전으로 전환" -UnloadWarning: "경고\nMOD를 다시 활성화하려면 게임을 다시 시작해야 합니다.\n그래도 계속하시겠습니까?" -CannotUnloadDuringGame: "게임 중에는 원래 버전으로 전환할 수 없습니다" -Cancel: "취소" -Unload: "アン로ード" -DumpLog: "로그 출력" -ClearAutoLogs: "자동 로그 삭제" -SoundOptions: "내 음악" -AudioManagementOptions: "오디오 관리" -OnlyAvailableInMainMenu: "메인 메뉴에서만 사용 가능" +ClientFeature.UnloadMod: "바닐라로 전환" +ClientFeature.DumpLog: "로그 덤프" +ClientFeature.ClearAutoLogs: "자동 로그 지우기" +ClientFeature.MyMusic: "나의 음악" +ClientFeature.ResourceManager: "리소스 관리자" +ClientFeature.NameTagManager: "이름 태그 관리자" +ClientFeature.MainMenuStyleManager: "메인 메뉴 스타일 전환" # 提示 / Tips -updatePleaseWait: "잠시만 기다려 주세요..." -updateInProgress: "업데이트 중..." -DownloadingAudios: "다운로드 중..." -Playing: "게임 중..." -Parsing: "분석 중..." -DownLoadSucceedNotice: "다운로드가 성공했습니다!" -DownLoadFailureNotice: "다운로드에 실패했습니다 =(" -PleaseWait: "잠시만 기다려 주세요..." - -# 업데이트 확인 / Update Checker -Retry: "재시도" -updateCheckPopupTitle: "업데이트 확인" -updateCheckFailedRetry: "업데이트 확인에 실패했습니다 :(\n다시 시도하시겠습니까?" -updateCheckFailedExit: "업데이트 확인에 실패했습니다 :(\n인터넷 연결을 확인하고 다시 시도해 주세요." - -# 업데이트 알림 / Update Reminder -UpdateBySelfTitle: "업데이트 알림" -updateNotice: "업데이트 알림" -UpdateBySelfText: "이 버전은 한 클릭 업데이트를 지원하지 않습니다. 수동으로 업데이트하십시오." -updateButton: "지금 업데이트" -updatePopupTitle: "지금 업데이트" -updatePopupTitleFailed: "업데이트 실패" -updatePopupTitleDone: "업데이트 완료" - -# 업데이트 소스 선택 / Update Chose Source -updateChoseSource: "업데이트할 소스를 선택해 주세요\n선택에 어려움이 있다면 [Github]를 선택하세요\n업데이트가 실패하면 [Api]를 선택하세요" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 업데이트 완료 안내 / Update completion prompts -updateRestart: "변경 사항을 적용하려면 게임을 다시 시작하십시오 :)" -updatePingFialed: "이유: {0}\n선택한 채널이 일시적으로 사용할 수 없습니다. 다른 다운로드 채널로 변경해 보세요." -updateFileMd5Incorrect: "이유: 파일 체크섬 오류\n이 채널의 파일 버전이 최신이 아닙니다. 다른 다운로드 채널로 변경해 보세요." -downloadFailed: "이유: 다운로드 시간 초과 또는 중단됨\n네트워크를 변경하거나 수동으로 업데이트해 보세요." - -# 공개 게임에 참가할 수 없는 이유 / Unable to join public game reasons -onSetPublicNoLatest: "중요한 업데이트가 있습니다. 이 모드를 업데이트하세요.\n그렇지 않으면 공개 룸에 입장할 수 없습니다." -CanNotJoinPublicRoomNoLatest: "중요한 업데이트가 있습니다. 이 모드를 업데이트하세요.\n그렇지 않으면 공개 룸에 입장할 수 없습니다." -ModBrokenMessage: "모드 파일이 손상되었습니다. 게임을 다시 시작하거나 이 모드를 다시 설치하세요." -UnsupportedVersion: "당신의 Among Us 버전이 FinalSuspect와 호환되지 않습니다.\n게임을 업데이트하세요." - -# 오디오 재생 / Audio Playback -PlayMode0: "한 번 재생" -PlayMode1: "한 곡 반복" -PlayMode2: "랜덤" -PlayMode3: "순차적" -Stop: "정지" -CanPlay: "재생하려면 클릭" -NoFound: "[파일이 없습니다]" -NextPage: "다음 페이지" -PreviousPage: "이전 페이지" - -# 오디오 추가 / Audio Addition -download: "다운로드" -delete: "삭제" -NewSound: "새로운 음악 추가" -PleaseEnterMusic: "음악 이름을 입력하세요" -AudioManagementAlreadyExists: "이 음악 이름은 이미 존재합니다" -NotAllowedMusic: "음악 이름 형식이 허용되지 않습니다" - -# 인터페이스 팁 / Interface Tips -CustomAudioManagementHelp: "XtremeWave에서 지원하는 음악을 다운로드하거나 '오디오 관리'에서 자신의 음악을 추가할 수 있습니다. 자신의 음악을 추가할 때는 '오디오 관리'에 음악 이름을 추가하고 해당 오디오 파일을 'Among Us/Final Suspect_Data/Resources/Sounds' 폴더에 배치해야 합니다 (지원 형식: .wav). 음악은 '내 음악'에서 재생할 수 있습니다." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "XtremeWave에서 지원하는 음악을 다운로드하거나 '오디오 관리'에서 자신의 음악을 추가할 수 있습니다. 로컬 음악 리소스 경로가 누락된 경우 '[파일이 없습니다]'로 표시됩니다." - -# 메인 메뉴 음악 안내 / Main Menu Music Reminder -MusicNotYet: "현재 음악 파일이 불완전하게 감지되었습니다" -AudioNYPro: "더 나은 게임 경험을 위해 '홈-설정-추가 기능-오디오 관리'에서 음악을 다운로드하세요." +Tip.Downloading: "다운로드 중..." +Tip.PleaseWait: "기다려 주세요..." +Tip.Playing: "재생 중..." +Tip.Parsing: "파싱 중..." +Tip.Updating: "업데이트 중..." +Tip.DownLoadFinished: "다운로드 완료" +Tip.DownLoadSucceeded: "다운로드 성공!" +Tip.DownLoadFailed: "다운로드 실패=(" +Tip.PackageExists: "설치됨" +Tip.OnlyAvailableInMainMenu: "메인 메뉴에서만 사용 가능" +Tip.HideSummaryTextToShowWinText: "최종 요약을 숨기고 승리 텍스트 보기" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "언어 파일 로딩 완료" +Tip.CheckingForFiles: "리소스 파일 무결성 확인 중..." +Tip.DownloadingResources: "리소스 파일 다운로드 중..." +Tip.Loading: "로딩 중" +Tip.LoadingWithDot: "로딩 중..." +Tip.LoadingComplete: "로딩 완료!" +## 切换原版 +Tip.UnloadWarning: "경고!\n모드 버전을 플레이하려면 게임을 재시작해야 합니다.\n정말 바닐라 버전으로 전환하시겠습니까?" +Tip.CannotUnloadDuringGame: "게임 중에는 바닐라 버전으로 전환할 수 없습니다." +## 资源管理 +Tip.ResourceManager: "「리소스 관리자」에서 모드 호환 및 추가 리소스를 다운로드할 수 있습니다\n\"Pre-\"로 시작하는 파일은 사전 다운로드 리소스 패키지입니다" +## 我的音乐 +Tip.MyMusic: "「리소스 관리자」에서 모드 지원 음악을 다운로드하거나 폴더(Among Us/Final Suspect_Data/Musics)에 좋아하는 오디오 파일을 추가할 수 있습니다. 로컬 음악 경로가 없으면 \"파일 없음\"이 표시됩니다" +Tip.Incomplete_Music: "불완전한 음악 파일 감지됨" +Tip.Incomplete_SoundEffect: "불완전한 효과음 파일 감지됨" +Tip.Incomplete_Image: "불완전한 이미지 파일 감지됨" +Tip.Incomplete: "더 나은 게임 경험을 위해 \"메인 메뉴 - 설정 - 클라이언트 기능 - 리소스 관리자\"에서 모드 호환 리소스 패키지를 다운로드하세요" +## 名称标识管理 +Tip.TextContent: "텍스트 내용" +Tip.TextSizeDescription: "텍스트 크기(기본값 100%)" +Tip.TextColorDescription: "텍스트 색상(16진 색상 코드)\n필요 없으면 비워두고, 여러 개 입력 시 자동 그라데이션\n" +Tip.PleaseEnterFriendCode: "새 이름 태그를 연결할 친구 코드를 입력하세요" +Tip.FriendCodeAlreadyExist: "이 친구 코드에는 이미 이름 태그가 있습니다" +Tip.FriendCodeIncorrect: "올바른 친구 코드를 입력하세요" +Tip.CustomNameTagHelp: "모든 플레이어에 대한 이름 태그를 추가할 수 있습니다. 연결된 친구 코드의 플레이어가 참여하면 자동으로 할당되지만, 로비 입장 후에는 태그를 편집할 수 없습니다.\n비 VIP는 메모만 추가할 수 있습니다(VIP 기능은 아직 개발되지 않음)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "\"리소스 관리자\"에서 \"메인 메뉴 스타일 팩\"을 다운로드하여 메인 메뉴 리소스를 얻고 여기서 원하는 스타일을 선택할 수 있습니다" + +# 更新结果 +UpdateResult.Succeed_Title: "업데이트 성공" +UpdateResult.Succeed_Text: "게임 재시작 후 적용됩니다 :)" +UpdateResult.Failed_Title: "업데이트 실패" +UpdateResult.Failed_Reason_NotFound: "이유: {0}\n이 소스는 일시적으로 사용 불가능할 수 있습니다. 다운로드 소스를 변경하고 다시 시도하세요" +UpdateResult.Failed_Reason_FileMd5Incorrect: "이유: 파일 검증 오류\n이 소스의 파일 버전이 최신이 아닙니다. 다운로드 소스를 변경하고 다시 시도하세요" +UpdateResult.Failed_Reason_Ping: "이유: 업데이트 시간 초과 또는 중단\n네트워크를 확인하고 다시 시도하거나 수동으로 업데이트하세요" + +# 更新检查 / Update Checker +UpdateCheck.Popup_Title: "업데이트 확인" +UpdateCheck.Failed_Retry: "업데이트 확인 실패 :(\n재시도?" +UpdateCheck.Failed_Exit: "업데이트 확인 실패 :(\n네트워크를 확인하고 다시 시도하세요!" + +# 更新提醒 / Update Reminder +UpdateRemind.updatePopup: "지금 업데이트" +UpdateRemind.updateNotice: "업데이트 알림" +UpdateRemind.BySelf_Title: "업데이트 힌트" +UpdateRemind.BySelf_Text: "이 버전은 원클릭 업데이트를 지원하지 않습니다. 수동으로 업데이트하세요" + +# 选择更新渠道 / Update Chose Source +UpdateSource.Choose: "업데이트 소스를 선택하세요\n확실하지 않으면 [Github]를 선택하세요" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" + +# 无法加入公开游戏原因 / Unable to join public game reasons +CanNotJoinPublicRoomNoLatest: "중요한 업데이트가 있습니다. 모드를 업데이트하세요\n그렇지 않으면 공개방에 참여할 수 없습니다" +ModBrokenMessage: "모드 파일이 충돌했습니다. 게임을 재시작하거나 모드를 재설치하세요" +UnsupportedVersion: "Among Us 버전이 FinalSuspect와 호환되지 않습니다\n게임을 업데이트하세요" + +# 名称标识 +NameTag.DisplayName: "메모" +NameTag.Title: "칭호" +NameTag.Prefix: "접두사" +NameTag.Suffix: "접미사" +NameTag.Name: "이름" +NameTag.LastTag: "추가 접미사" +NameTag.PreviewNotAvailable: " (미리보기 불가능)" +NameTag.CanNotEdit: " (편집 불가)" +NameTag.RefreshPreview: "미리보기 새로고침" +NameTag.SaveAndClose: "저장하고 닫기" +NameTag.NewNameTag: "새로 만들기" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "새벽을 좇아(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "시간이 흐르며 북쪽 겨울은 끝나고 봄이 되살아난다.\n미라의 설경을 바라보며 추억이 떠오르는가? 마음에 따뜻한 감물이 흐른다.\n최종 용의자가 누구든, 진실을 깨뜨린 이가 누구든,\n새로운 신선한 경험 속에서 두뇌싸움을 벌이며 새로운 행복한 추억을 남겨보자.\n게임하고 즐기는 것이 가장 중요하다!!!\n\n작명: 一念旧情丶" +MainMenuStyle.Title_Security: "붉은 마음, 뜨거운 사랑 (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "우리는 넘어지지 않고, 포기하지 않고, 플레이어를 실망시키지 않고, 전진을 멈추지 않는다.\n차가운 시선을 마주해도, 모두에게 증명해 보이겠다!\n열정으로 다시 항해를 시작하며\n\n작명: Slok" +MainMenuStyle.Title_NewYear: "친화 (설날)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "행운: 부여된 소원 인연: 심장 박동에 짜여진\n조화: 애정의 실 행복: 모든 영혼의 평화\n운명의 인연은 공유된 기쁨을 만들어 먼 여정에서 단결로 기쁨을 찾아라\n황금 시대는 모두를 즐거움에 품는다 우리의 기도—모든 존재에게 기쁨을\n다가올 해에 모든 영혼이 각자의 길을 찾길 바라며\n우리의 축복이 길을 비추길, 당신의 전설을 만들 여정을 떠나세요!\n\n작명: Slok" +MainMenuStyle.Title_MiraStudio: "Final 스튜디오 (설날)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—꽃이 파도처럼 피고 바람처럼 부드럽게, 모두의 마음이 바람처럼 넓고 모든 일이 순조롭길 바란다\"\n\"—봄바람이 강하게 불고 파도가 수십 리를 치며, 모두가 용감히 최전선에 서서 번창하길 바란다\"\n\"—XtremeWave 설날 특집에 오신 것을 환영합니다!\"\n\n작명: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final이 탁월함을 향해 노력하고, 꿈이 밀려드는 파도를 지휘한다!」\n\n작명: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "끝에서 돌아볼 때 (콜라보레이션)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「끝에서 돌아볼 때, 모든 것은 끝에서 끝난다」\n\n작명: MAMTI.麦麦头" +MainMenuStyle.NotFound: "다운로드되지 않음" +MainMenuStyle.NotApply: "적용" +MainMenuStyle.Applied: "적용됨" + +# 资源包 +Package.MainMenuStyle: "메인 메뉴 스타일 팩" + +# 音频播放 / Audio Playback +MusPlay.Mode0: "한 번 재생" +MusPlay.Mode1: "단곡 반복" +MusPlay.Mode2: "목록 무작위" +MusPlay.Mode3: "순차 재생" +MusPlay.Stop: "재생 중지" +MusPlay.CanPlay: "클릭하여 재생" +MusPlay.NoFound: "파일 없음" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财(행복한 중국 새해)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "조수의 밀물" +Mus.TrailOfTruth: "진실의 흔적" +Mus.Interlude: "인터루드" +Mus.Fractured: "파편" +Mus.ElegyOfFracturedVow: "부서진 맹세의 엘레지" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 메시지 / Messages -Message.KickedByDenyName: "[{0}]은(는) 이름이 [{1}]과 일치하여 추방되었습니다." -Message.BanedByBanList: "[{0}]은(는) 이전에 금지되었기 때문에 금지되었습니다." -Message.BanedByFACList: "[{0}]은(는) FAC 금지 목록에 있었기 때문에 금지되었습니다." -Message.DumpfileSaved: "로그 파일이 데스크톱에 성공적으로 저장되었습니다. 파일명: {0}" -Message.KickedByNoFriendCode: "[{0}]은(는) 친구 코드가 존재하지 않아 추방되었습니다." -Message.AddedPlayerToBanList: "[{0}]을(를) 금지 목록에 추가했습니다." -Message.KickedByFAC: "[{0}]은(는) FAC에 의해 추방되었습니다. 이유: {1}" -Message.BanedByFAC: "[{0}]은(는) FAC에 의해 추방되었습니다. 이유: {1}" - -# 알림 / Notifications -PlayerLeft: "[{0}]이(가) 게임을 떠났습니다." -PlayerLeftCuzTimeout: "[{0}]은(는) 연결 타임아웃으로 게임을 떠났습니다." -PlayerKickByHost: "[{0}]은(는) 호스트에 의해 추방되었습니다." -PlayerBanByHost: "[{0}]은(는) 호스트에 의해 금지되었습니다." -PlayerLeftCuzError: "[{0}]은(는) 에러로 게임을 떠났습니다." -PlayerLeftByAU-Anticheat: "[{0}]은(는) AmongU의 공식 반부정행위 시스템에 의해 추방되었습니다(이것은 FinalSuspect와는 관련이 없습니다)." -KickBecauseDiffrentVersionOrMod: "[{0}]은(는) 다른 버전의 모드를 사용하고 있었기 때문에 추방되었습니다." - -# 경고 / Warnings -Warning: "경고!" -Warning.MismatchedVersion: "{0}\n{1}의 다른 버전을 사용하고 있습니다." -Warning.AutoExitAtMismatchedVersion: "호스트는 {0}을 사용하지 않거나 다른 버전을 사용하고 있습니다.\n{1}초 후에 자동으로 추방됩니다." -Warning.InvalidRpc: "{0}은(는) 무효한 RPC를 수신하여 추방되었습니다." -Warning.InvalidRpc_NotHost: "{0}은(는) 부정행위를 사용하고 있는 것으로 의심됩니다. 호스트에게 추방을 요청하십시오 (무효한 RPC: {1})" -Warning.SetName: "{0}은(는) 이름을 여러 번 설정하여 추방되었습니다." -Warning.SetName_NotHost: "{0}은(는) 부정행위를 사용하고 있는 것으로 의심됩니다. 호스트에게 추방을 요청하십시오 (이름을 여러 번 설정)" -Warning.SendQuickChat: "{0}은(는) 3초 이내에 여러 빠른 메시지를 보냈기 때문에 추방되었습니다." -Warning.SendQuickChat_NotHost: "{0}은(는) 부정행위를 사용하고 있는 것으로 의심됩니다. 호스트에게 추방을 요청하십시오 (3초 이내에 여러 빠른 메시지를 보내기)" -Warning.InvalidSlothRPC: "{0}을 추방했습니다. 불법 RPC가 수신되었기 때문입니다 (공식Rpc를 불법적으로 전송: {1})" -Warning.InvalidSlothRPC_NotHost: "{0}은(는) 부정행위를 사용하고 있는 것으로 의심됩니다. 호스트에게 추방을 요청하십시오 (공식 RPC를 부정하게 보내기: {1})" -Warning.Cheater: "{0}을 추방했습니다. 부정행위가 의심되기 때문입니다" -Warning.Cheater_NotHost: "{0}이 부정행위를 의심됩니다. 호스트에게 추방을 요청하세요" -Warning.CantKickDev: "죄송합니다, 개발자를 추방할 수 없습니다." -Warning.RoomBroken: "죄송합니다, 이 방은 파괴되었습니다. 다른 방으로 이동하여 게임을 계속하십시오." - -## 오류 레벨 / Error Levels -ErrorLevel1: "버그가 발생할 수 있습니다." -ErrorLevel2: "이것은 버그일 수 있습니다." -ErrorLevel3: "이 버전은 출시되어서는 안되었습니다." - -# 부정행위 검사 / FAC -FAC.CheatDetected.HighLevel: "경고: FAC가 높은 수준의 부정행위를 감지했습니다." -FAC.CheatDetected.LowLevel: "경고: FAC가 낮은 수준의 부정행위를 감지했습니다. 어떤 플레이어가 부정행위를 하고 있습니다." -FAC.CheatDetected.FAC: "부정행위 프로그램 사용(예: AUM, YuMenu, SM 등)" - -# 모듈 정보 / Mod Infos -Contributors: "기여자" -Acknowledgement: "감사의 인사" - -# 연결 끊김 이유 / Disconect Reasons -DCNotify.Hacking: "당신은 부정행위 시스템에 의해 추방되었습니다.\n(모드 사용이 부정행위로 오해될 수 있습니다)" -DCNotify.Banned: "당신은 이 방에 들어갈 수 없습니다" -DCNotify.Kicked: "당신은 방에서 추방되었습니다" -DCNotify.DCFromServer: "당신은 서버로부터 연결이 끊겼습니다.\n이것은 당신의 네트워크가 불안정할 수 있습니다.\n또는 서버가 불안정할 수 있습니다." -DCNotify.GameNotFound: "할당된 방을 찾을 수 없습니다. 방이 해산되었을 수 있습니다.\n또는 방과 다른 서버를 선택했는지 확인하세요." -DCNotify.GameStarted: "게임이 이미 시작되었습니다. 끝날 때까지 기다려 주세요" -DCNotify.GameFull: "방이 가득 찼습니다. 나중에 다시 시도해 주세요" -DCNotify.IncorrectVersion: "당신의 Among Us 버전이 이 방과 다릅니다" -DCNotify.Description: "당신은 게임에서 추방되었습니다.\n이유: {0}" -DCNotify.DenyName: "당신의 닉네임에 불규칙한 문자가 있습니다" -DCNotify.BanList: "당신은 호스트에 의해 금지되었습니다" -DCNotify.FACList: "당신은 FAC에 의해 금지되었습니다" -DCNotify.CheatDetected: "당신은 FAC에 의해 부정행위로 의심되었습니다" -DCNotify.InvalidRPC: "당신은 호스트와 다른 모드를 설치했거나, 모드가 악의적으로 수정되었습니다" -DCNotify.ModVersionIncorrect: "당신의 모드 버전이 호스트와 다릅니다" -DCNotify.LowLevel: "당신의 레벨이 이 방의 요구사항을 충족하지 않습니다" -DCNotify.NotLogin: "로그인하지 않은 플레이어는 이 방에 허용되지 않습니다" - -# 태스크 패널 / Task Panel -PressF1ShowRoleDescription: "F1을 눌러 당신의 역할 설명을 확인하세요" -FakeTask: "가짜 태스크:" -KillCount: "킬 수" - -# 마지막 결과 / Last Results -RoleSummaryText: "마지막 결과:" -ShowResults: "마지막 결과 표시" -HideResults: "마지막 결과 숨기기" -NoInfoExists: "마지막 결과가 없습니다" -CrewsWin: "크루원 팀 승리" -CrewmatesWin: "크루원 승리" -CrewmatesWinBlurb: "진실의 빛이 희망을 비추다!" -ImpsWin: "임포스터 팀 승리" -ImpostorsWin: "임포스터 승리" -ImpostorsWinBlurb: "악이 진실을 재로 바꾼다" -HideSummaryTextToShowWinText: "마지막 결과를 숨겨서 승리 텍스트를 표시하세요" - -# 공개 룸 비활성화 -DisabledByProgram: "프로그램에 의해 공개 룸 운영이 비활성화되었습니다" -PublicNotAvailableOnThis Version: "이 버전의 FinalSuspect에서는 공개 룸이 사용할 수 없습니다" - -# 홈페이지 / Main UI -FinalSuspectWelcomeText: "쾌적한 게임 경험을 기원합니다!" -ConnectToFinalSuspectServerFailed: "FinalSuspect 서버에 연결할 수 없습니다" -Website: "공식 웹사이트" +Mus.ReturnToSimplicity: "단순함으로의 귀환" +Mus.ReturnToSimplicity2: "단순함으로의 귀환(풀 버전)" +Mus.ChasingDawn: "새벽을 좇아" +Mus.StruggleAgainstFadingFlame: "사라지는 불꽃과의 싸움" +Mus.Affinity: "친화력" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "직업" +DisplayedRoleTag.PlayerIdentityTag: "신분 태그" +DisplayedRoleTag.Room: "방" + +PlayerIdentityTag.Hard_Cleared: "확인된 시민" +PlayerIdentityTag.Silver_Clear: "유력한 시민" +PlayerIdentityTag.Wolf_Bucket: "늑대 구역" +PlayerIdentityTag.No_Kill: "살인 없음" +PlayerIdentityTag.Outside_Position: "외곽 위치" +PlayerIdentityTag.Inside_Position: "내부 위치" + +# 通知 / Notifications +## 原版 +Notification.PlayerLeft: "[{0}] 님이 게임을 나갔습니다" +Notification.PlayerLeftCuzTimeout: "[{0}] 님이 연결 시간 초과로 나갔습니다" +Notification.PlayerKickByHost: "[{0}] 님이 호스트에 의해 킥되었습니다" +Notification.PlayerBanByHost: "[{0}] 님이 호스트에 의해 차단되었습니다" +Notification.PlayerLeftCuzError: "[{0}] 님이 오류로 나갔습니다" +Notification.PlayerLeftByAU-Anticheat: "[{0}] 님이 Among Us 앤티치트에 의해 킥되었습니다 (FinalSuspect와 무관)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] 님이 다른 버전/모드를 설치하여 킥되었습니다" +## 模组 +Notification.KickedByDenyName: "[{0}] 님이 금지된 단어를 포함한 닉네임으로 킥되었습니다" +Notification.DumpfileSaved: "로그 파일이 데스크톱에 저장되었습니다, 파일명: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] 님이 비정상 친구 코드로 인해 제거되었습니다" +Notification.AddedPlayerToBanList: "[{0}] 님을 차단 목록에 추가했습니다" +Notification.FPSSetTo: "FPS 제한이 {0}(으)로 설정되었습니다" + +# 警告 / Warnings +Warning: "경고" +Warning.MismatchedVersion: "[{0}]\n님이 [{1}]의 다른 버전을 설치했습니다" +Warning.AutoExitAtMismatchedVersion: "[{0}] 버전이 호스트와 다릅니다\n{1}초 후에 킥됩니다" +Warning.CantKickDev: "죄송합니다, 개발자를 킥할 수 없습니다" +Warning.RoomBroken: "죄송합니다, 이 방은 해킹 공격을 받았습니다, 다른 방에서 플레이해 주세요" +Warning.InvalidColor: "잘못된 색상의 플레이어가 감지되었습니다" + +## 错误等级 / Error Levels +ErrorLevel1: "동시에 여러 버그가 발생할 수 있습니다" +ErrorLevel2: "버그가 발생할 수 있습니다" +ErrorLevel3: "미출시 버전" + +# 反作弊 / FAC +CheatDetected.HighLevel: "경고: FAC가 폭격 치트로부터 방어 중" +CheatDetected.LowLevel: "경고: FAC가 치터 가능성을 감지했습니다" +CheatDetected.UseCheat: "{0} 님이 치트 프로그램 [{1}]을(를) 사용했습니다" +CheatDetected.MayUseCheat: "{0} 님이 모드 [{1}] 또는 치트 프로그램을 사용 중일 수 있습니다" +CheatDetected.InvalidRpc: "[{0}] 님이 잘못된 데이터를 보내 킥되었습니다 (잘못된 Rpc: {1})" +CheatDetected.InvalidRpc_NotHost: "{0} 님이 치팅 의심, 호스트에게 빨리 킥하도록 알려주세요 (잘못된 Rpc: {1})" +CheatDetected.SetName: "[{0}] 님이 여러 번 이름을 설정하여 킥되었습니다" +CheatDetected.SetName_NotHost: "{0} 님이 치팅 의심, 호스트에게 빨리 킥하도록 알려주세요 (이름 여러 번 설정)" +CheatDetected.SendQuickChat: "[{0}] 님이 3초 내 여러 개 빠른 메시지를 보내 킥되었습니다" +CheatDetected.SendQuickChat_NotHost: "{0} 님이 치팅 의심, 호스트에게 빨리 킥하도록 알려주세요 (3초 내 여러 빠른 메시지)" +CheatDetected.InvalidSlothRPC: "[{0}] 님이 불법 데이터를 보내 킥되었습니다 (불법 공식 Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "{0} 님이 치팅 의심, 호스트에게 빨리 킥하도록 알려주세요 (불법 공식 Rpc: {1})" +CheatDetected.Overload: "[{0}] 님이 과부하 공격을 시도하여 킥되었습니다" +CheatDetected.Overload_NotHost: "{0} 님이 과부하 공격을 시도했습니다, 이 방은 손상되었으니 다른 방에서 플레이해 주세요" +CheatDetected.Cheater: "[{0}] 님이 치팅 의심으로 킥되었습니다" +CheatDetected.Cheater_NotHost: "{0} 님이 치팅 의심, 호스트에게 빨리 킥하도록 알려주세요" +CheatDetected.BanedByBanList: "[{0}] 님이 차단 목록에 있어 킥되었습니다" +CheatDetected.BanedByFACList: "[{0}] 님이 FAC 차단 목록에 있어 킥되었습니다" + +# 模组信息 / Mod Infos +ModInfo.Contributors: "기여자" +ModInfo.Acknowledgement: "특별 감사" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "InnerSloth의 앤티치트 시스템에 의해 방에서 킥되었습니다" +DCNotify.Banned: "이 방에서 차단되었습니다" +DCNotify.Kicked: "이 방에서 킥되었습니다" +DCNotify.DCFromServer: "서버와 연결이 끊어졌습니다\n네트워크 불안정 혹은\n서버 불안정/접근 거부일 수 있습니다" +DCNotify.GameNotFound: "지정한 방을 찾을 수 없습니다. 닫혔거나\n방과 다른 서버를 선택했는지 확인하세요" +DCNotify.GameStarted: "이 게임은 이미 시작되었습니다, 종료까지 기다려 주세요" +DCNotify.GameFull: "이 방은 가득 찼습니다, 나중에 다시 시도해 주세요" +DCNotify.IncorrectVersion: "Among Us 버전이 방과 다릅니다" +DCNotify.Description: "방에서 킥되었습니다\n이유: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "F1을 눌러 역할 설명 보기" +PressF2ToHidePane: "F2를 눌러 패널 표시/숨기기" +FakeTask: "가짜 임무:" +KillCount: "처치 수" + +# 复盘信息 / Last Results +Summary.Text: "최종 요약:" +Summary.ShowResults: "최종 요약 보기" +Summary.HideResults: "최종 요약 숨기기" +Summary.NoInfoExists: "유효한 최종 요약이 없습니다" +Summary.CrewsWin: "크루 팀 승리" +Summary.ImpsWin: "임포스터 팀 승리" +Outro.Crews_Win: "크루 승리" +Outro.Crews_WinBlurb: "진실의 빛이 희망 속에서 빛난다!" +Outro.Imps_Win: "임포스터 승리" +Outro.Imps_WinBlurb: "악이 진실을 재로 만들었다" + +# 主页 / Main UI +FinalSuspectWelcomeText: "즐거운 게임 되시길 기원합니다!" +RetrieveVersionInfoFailed: "FinalSuspect 정보를 가져오지 못했습니다" +Website: "공식 사이트" MainMenuCredential: "{0} © 2025" -LShift: "LShift를 눌러 이전 방으로 돌아가기" -RShift: "RShift를 눌러 클립보드 방으로 이동하기" -LobbyTimeDisplayText: "존재 경과 시간" +LShift: "이전 로비" +RShift: "클립보드 로비" -# 룸 리스트 표시 / Lobby List -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +# 客户端平台 / Platform +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" -# 지연 표시 / Ping Tracker +# 延迟显示 / Ping Tracker Ping: "핑" -FrameRate: "프레임율" +FrameRate: "프레임레이트" Server: "서버" Local: "로컬" -# 기타 / Other +# 其他 / Other HongKong: "홍콩" -FPSSetTo: "프레임율 제한이 설정되었습니다: {0}" -BrowsingMode: "브라우징 모드" -Broken: "고장" - -# 로딩 / Loading -LanguageFilesLoadingComplete: "번역 로딩 완료!" -CheckingForFiles: "리소스 파일의 무결성을 확인하고 있습니다..." -DownloadingResources: "리소스 파일을 다운로드하고 있습니다..." -Loading: "로딩 중" -LoadingWithDot: "로딩 중..." -LoadingComplete: "로딩 완료!" - -# 신원 / Identity -Host: "호스트" -Cheater: "부정행위자" +BrowsingMode: "탐색 모드" +Broken: "손상됨" +Unknown: "알 수 없음" +Back: "뒤로" +Yes: "예" +No: "아니오" +Cancel: "취소" +Unload: "전환" +Retry: "재시도" +PreviousPage: "이전 페이지" +NextPage: "다음 페이지" +Download: "다운로드" +Disable: "비활성화" +Delete: "삭제" +Author: "작가" +Close: "닫기" + +# 身份 / Identity +Id.Host: "호스트" +Id.Cheater: "치터" +Id.Developer: "개발자" +Id.Contributor: "기여자" \ No newline at end of file diff --git a/Assets/Languages/Latam.yaml b/Assets/Languages/Latam.yaml index ad3f7257..2662f48f 100644 --- a/Assets/Languages/Latam.yaml +++ b/Assets/Languages/Latam.yaml @@ -1,313 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "1" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostor" -TypeCrewmate: "Tripulante" +RoleType.Imp: "Impostor" +RoleType.Crew: "Tripulante" # 阵营 / Teams -TeamImpostor: "Equipo-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Equipo-Tripulante" +Team.Imp: "Equipo-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Equipo-Tripulante" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Hay {0} Impostores en nuestro equipo" -ImpostorNumImpOnly: "Solo hay 1 Impostor en la multitud" -ImpostorNumCrew: "Hay {0} Impostores entre nosotros" +ImpostorNum.Imp: "Hay {0} impostores en nuestro equipo" +ImpostorNum.Imp_Only: "Solo hay 1 impostor entre nosotros" +ImpostorNum.Crew: "Hay {0} impostores entre nosotros" # 阵营开场 -ImpostorIntroText: "¡Que el mal envuelva el mundo!" -ImpostorIntroTextOnly: "¡Puede que esté solo, pero poseo una fuerza infinita!" -CrewmateIntroText: "Completa tus tareas, únite para resolver estas situaciones complicadas" - -## 原版职业 / Vanilla -Crewmate: "Tripulante" -Engineer: "Ingeniero" -Scientist: "Científico" -Tracker: "Rastreador" -Noisemaker: "Generador de Ruido" -GuardianAngel: "Ángel de la Guarda" -Impostor: "Impostor" -Shapeshifter: "Cambiante de Forma" -Phantom: "Fantasma" -CrewmateGhost: "Fantasma de Tripulante" -ImpostorGhost: "Fantasma de Impostor" -CrewmateGhostBlurb: "Completa tus tareas" -ImpostorGhostBlurb: "Continúa saboteando las instalaciones" -CrewmateGhostBlurbLong: "Completa las tareas, no te quedes atrás" -ImpostorGhostBlurbLong: "Sabotea las instalaciones, ayuda a los impostores sobrevivientes a alcanzar la victoria" - -## 捉迷藏 / HnS -HnSEngineerBlurb: "¡Sobrevive hasta el final!" -HnSEngineerBlurbLong: "¡Permanece vivo hasta que se acabe el tiempo; completar tareas extenderá el tiempo! \nUsa las ventanas de ventilación y los indicadores de amenaza para esconderte! \nUna vez que el temporizador comience, el Impostor comenzará a cazarte." -HnSImpostorBlurb: "¡Elimina a todos!" -HnSImpostorBlurbLong: "¡Mata a todos los miembros de la tripulación dentro del límite de tiempo! \n¡Debes actuar rápidamente! Las ventanas de ventilación no están disponibles. \nEn el último momento, recibirás un aumento de velocidad y pistas de seguimiento." +IntroText.Imp: "¡Que el mal DOMINE EL MUNDO!" +IntroText.Imp_Only: "¡Aunque estés solo, tienes un PODER INFINITO!" +IntroText.Crewmate: "¡Completa tus tareas y encuentra a los impostores!" + +# 原版职业 / Vanilla +Role.Crewmate: "Tripulante" +Role.Engineer: "Ingeniero" +Role.Scientist: "Científico" +Role.Tracker: "Rastreador" +Role.Noisemaker: "Creador de Ruido" +Role.GuardianAngel: "Ángel Guardián" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Cambiapieles" +Role.Phantom: "Fantasma" +Role.CrewmateGhost: "Fantasma Tripulante" +Role.ImpostorGhost: "Fantasma Impostor" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Completa tareas" +ImpostorGhostBlurb: "Continúa saboteando" +CrewmateGhostBlurbLong: "¡Completa tus tareas y no retrases al equipo!\n¡Usa 「Acechar」 para ver a los impostores y ayuda a los Ángeles Guardianes a proteger a los Tripulantes!" +ImpostorGhostBlurbLong: "¡Sabotea las instalaciones y ayuda a los impostores supervivientes a ganar!" +HnSEngineerBlurb: "¡Completa tareas y sobrevive hasta el final!" +HnSImpostorBlurb: "¡MÁTALOS A TODOS!" HnSCrewmateGhostBlurb: "¡Anima a tus compañeros!" -HnSCrewmateGhostBlurbLong: "¡Vamos! ¡Anímales! ¡Por! ¡Tus! ¡Compañeros!" +HnSCrewmateGhostBlurbLong: "¿Muerto? ¿Qué miras? ¿Puedes hacer tareas? Pues no...\n¡VE! ¡ANIIMA! ¡A! ¡TUS! ¡COMPAÑEROS!" # 死因 / Death Reason -DeathReason.Kill: "Muerto" +DeathReason.Kill: "Asesinado" DeathReason.Exile: "Exiliado" DeathReason.Disconnect: "Desconectado" # 客户端选项 / Client Options FinalSuspectOptions: "Opciones de Final Suspect" -Back: "Atrás" -Yes: "Sí" -No: "No" -UnlockFPS: "Desbloquear FPS" -ChangeOutfit: "Cambiar Traje" -BeanMode: "Modo Clásico de Frijol" -HorseMode: "Modo Caballo de Abril" -LongMode: "Modo Largo de Abril" -KickPlayerFriendCodeNotExist: "Expulsar a los jugadores que no están conectados." -KickPlayerWithDenyName: "Expulsar a los jugadores con nombres inapropiados" -KickPlayerInBanList: "Expulsar a los jugadores baneados" -SpamDenyWord: "Bloquear palabras inapropiadas" -AutoStartGame: "Iniciar automáticamente cuando el lobby esté lleno" -AutoEndGame: "Volver automáticamente al lobby al final" -SwitchVanilla: "Cambiar a la versión original" -DisableVanillaSound: "Deshabilitar música de Among Us" -DisableFAC: "Deshabilitar anti-trampas" -ShowPlayerInfo: "Mostrar información de plataforma y cliente del jugador" -UseModCursor: "Usar cursor del mod" -FastBoot: "Modo de Inicio Rápido" -PrunkMode: "Modo Broma" -VersionCheat: "Ignorar verificación de sincronización de versión del mod" -GodMode: "Modo Dios" -NoGameEnd: "Sin final del juego" -EnableFinalSuspect: "Habilitar「Final Suspect」" +ClientOption.UnlockFPS: "Desbloquear FPS" +ClientOption.SwitchOutfitType: "Cambiar Tipo de Vestimenta" +ClientOption.KickPlayerWithAbnormalFriendCode: "Expulsar jugadores con códigos de amigo anormales" +ClientOption.KickPlayerWithDenyName: "Expulsar jugadores con apodos prohibidos" +ClientOption.KickPlayerInBanList: "Expulsar jugadores bloqueados" +ClientOption.SpamDenyWord: "Bloquear palabras prohibidas" +ClientOption.AutoStartGame: "Iniciar automáticamente cuando esté lleno" +ClientOption.AutoEndGame: "Regresar automáticamente al lobby al terminar" +ClientOption.SwitchVanilla: "Cambiar a Vanilla" +ClientOption.DisableVanillaSound: "Desactivar música del juego Vanilla" +ClientOption.EnableFAC: "Activar Anti-Cheat" +ClientOption.EnableGuardian: "Activar Guardián Cliente (Experimental)" +ClientOption.ShowPlayerInfo: "Mostrar plataforma y info del cliente" +ClientOption.UseModCursor: "Usar cursor mod" +ClientOption.FastLaunchMode: "Modo Lanzamiento Rápido" +ClientOption.OfflineMode: "Modo sin conexión (Experimental)" +ClientOption.VersionCheat: "Evitar verificación de sincronización" +ClientOption.GodMode: "Modo Dios" +ClientOption.NoGameEnd: "Sin fin de juego" +ClientOption.EnableFinalSuspect: "Activar 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Modo Clásico" +Value.HorseMode: "Modo Caballo de April Fools" +Value.LongMode: "Modo Largo de April Fools" # 客户端功能 / Client Features -FinalSuspectFeatures: "Características de Final Suspect" -UnloadMod: "Cambiar a la versión original" -UnloadWarning: "Advertencia\nPara reactivar el mod, tendrás que reiniciar el juego.\n¿Quieres continuar de todos modos?" -CannotUnloadDuringGame: "No se puede cambiar a la versión original durante el juego" -Cancel: "Cancelar" -Unload: "Desactivar" -DumpLog: "Exportar Log" -ClearAutoLogs: "Limpar Log Automático" -SoundOptions: "Mi Música" -AudioManagementOptions: "Gestión de Audio" -OnlyAvailableInMainMenu: "Sólo disponible en el menú principal" +FinalSuspectFeatures: "Funciones de Final Suspect" +ClientFeature.UnloadMod: "Cambiar a Vanilla" +ClientFeature.DumpLog: "Volcar Log" +ClientFeature.ClearAutoLogs: "Limpiar Logs Auto" +ClientFeature.MyMusic: "Mi Música" +ClientFeature.ResourceManager: "Administrador de Recursos" +ClientFeature.NameTagManager: "Administrador de Etiquetas" +ClientFeature.MainMenuStyleManager: "Cambiar Estilo del Menú Principal" # 提示 / Tips -updatePleaseWait: "Por favor, espera..." -updateInProgress: "Actualización en progreso..." -DownloadingAudios: "Descargando..." -Playing: "Jugando..." -Parsing: "Analizando..." -DownLoadSucceedNotice: "Descarga exitosa!" -DownLoadFailureNotice: "Fallo en la descarga =(" -PleaseWait: "Por favor, espera..." +Tip.Downloading: "Descargando..." +Tip.PleaseWait: "Por favor espera..." +Tip.Playing: "Reproduciendo..." +Tip.Parsing: "Analizando..." +Tip.Updating: "Actualizando..." +Tip.DownLoadFinished: "Descarga finalizada" +Tip.DownLoadSucceeded: "¡Descarga exitosa!" +Tip.DownLoadFailed: "Descarga fallida=(" +Tip.PackageExists: "Instalado" +Tip.OnlyAvailableInMainMenu: "Solo disponible en el Menú Principal" +Tip.HideSummaryTextToShowWinText: "Oculta el Resumen Final para ver el texto de victoria" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Archivos de idioma cargados" +Tip.CheckingForFiles: "Verificando integridad de archivos de recursos..." +Tip.DownloadingResources: "Descargando archivos de recursos..." +Tip.Loading: "Cargando" +Tip.LoadingWithDot: "Cargando..." +Tip.LoadingComplete: "¡Carga completada!" +## 切换原版 +Tip.UnloadWarning: "¡Advertencia!\nDebes reiniciar el juego si quieres jugar la versión con mods.\n¿Estás seguro de querer cambiar a Vanilla?" +Tip.CannotUnloadDuringGame: "No se puede cambiar a Vanilla durante una partida." +## 资源管理 +Tip.ResourceManager: "Puedes descargar recursos compatibles con mods en el \"Administrador de Recursos\"\nLos archivos con prefijo \"Pre-\" son paquetes pre-descargados" +## 我的音乐 +Tip.MyMusic: "Puedes descargar música compatible con mods en el \"Administrador de Recursos\" o agregar tus archivos de audio favoritos en la carpeta (Among Us/Final Suspect_Data/Musics). Si no existe la ruta local de música, se mostrará \"Archivo Faltante\"" +Tip.Incomplete_Music: "Archivo de música incompleto detectado" +Tip.Incomplete_SoundEffect: "Archivo de efecto de sonido incompleto detectado" +Tip.Incomplete_Image: "Archivo de imagen incompleto detectado" +Tip.Incomplete: "Para una mejor experiencia, descarga el paquete de recursos compatible con mods en \"Menú Principal - Ajustes - Funciones del Cliente - Administrador de Recursos\"" +## 名称标识管理 +Tip.TextContent: "Contenido del texto" +Tip.TextSizeDescription: "Tamaño del texto(Por defecto 100%)" +Tip.TextColorDescription: "Color del texto(Código hexadecimal)\nDéjalo en blanco si no es necesario; introduce varios para degradado automático\n" +Tip.PleaseEnterFriendCode: "Ingresa el código de amigo para vincular la nueva etiqueta" +Tip.FriendCodeAlreadyExist: "Este código de amigo ya tiene una etiqueta" +Tip.FriendCodeIncorrect: "Ingresa un código de amigo válido" +Tip.CustomNameTagHelp: "Puedes agregar etiquetas para cualquier jugador. Se asignarán automáticamente cuando se una el jugador con el código vinculado. No puedes editar etiquetas después de entrar al lobby.\nLos no VIP solo pueden agregar notas (funciones VIP aún no disponibles)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Puedes descargar \"Paquetes de Estilo del Menú Principal\" en el \"Administrador de Recursos\" para obtener recursos del menú y elegir tu estilo favorito aquí" + +# 更新结果 +UpdateResult.Succeed_Title: "Actualización exitosa" +UpdateResult.Succeed_Text: "Se activará después de reiniciar el juego :)" +UpdateResult.Failed_Title: "Actualización fallida" +UpdateResult.Failed_Reason_NotFound: "Razón: {0}\nEsta fuente puede estar temporalmente no disponible, cambia de fuente y reintenta" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Razón: Error de verificación de archivo\nLa versión de archivo de esta fuente no es la más reciente, cambia de fuente y reintenta" +UpdateResult.Failed_Reason_Ping: "Razón: Tiempo de espera de actualización excedido o interrumpido\nVerifica tu red y reintenta o actualiza manualmente" # 更新检查 / Update Checker -Retry: "Reintentar" -updateCheckPopupTitle: "Comprobación de Actualizaciones" -updateCheckFailedRetry: "La comprobación de actualizaciones falló :(\n¿Reintentar?" -updateCheckFailedExit: "La comprobación de actualizaciones falló :(\nPor favor, verifique su conexión a Internet y vuelva a intentarlo." +UpdateCheck.Popup_Title: "Verificar actualización" +UpdateCheck.Failed_Retry: "Falló la verificación de actualización :(\n¿Reintentar?" +UpdateCheck.Failed_Exit: "Falló la verificación de actualización :(\n¡Verifica tu red y reintenta!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Recordatorio de Actualización" -updateNotice: "Recordatorio de Actualización" -UpdateBySelfText: "Esta versión no admite actualizaciones automáticas. Por favor, actualice manualmente." -updateButton: "Actualizar Ahora" -updatePopupTitle: "Actualizar Ahora" -updatePopupTitleFailed: "Actualización Fallida" -updatePopupTitleDone: "Actualización Completada" +UpdateRemind.updatePopup: "Actualizar ahora" +UpdateRemind.updateNotice: "Recordatorio de actualización" +UpdateRemind.BySelf_Title: "Sugerencia de actualización" +UpdateRemind.BySelf_Text: "Esta versión no admite actualización con un clic, actualiza manualmente" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Por favor, seleccione una fuente para actualizar\nsi no sabe qué seleccionar, elija [Github]\nsi la actualización falla, elija [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Reinicie el juego para aplicar los cambios :)" -updatePingFialed: "Motivo: {0}\nEl canal seleccionado puede estar temporalmente no disponible. Intente cambiar el canal de descarga." -updateFileMd5Incorrect: "Motivo: Error de suma de comprobación del archivo\nLa versión del archivo de este canal no es la más reciente. Intente cambiar el canal de descarga." -downloadFailed: "Motivo: Tiempo de descarga agotado o interrumpido\nIntente nuevamente después de cambiar su red o actualizar manualmente." +UpdateSource.Choose: "Elige fuente de actualización\nSi no estás seguro, elige [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Tenemos una actualización importante. Por favor, actualice este mod.\nDe lo contrario, no podrá unirse a las salas públicas." -CanNotJoinPublicRoomNoLatest: "Tenemos una actualización importante. Por favor, actualice este mod.\nDe lo contrario, no podrá unirse a las salas públicas." -ModBrokenMessage: "Los archivos del mod están dañados. Por favor, reinicie el juego o reinstale este mod." -UnsupportedVersion: "Su versión de Among Us no es compatible con FinalSuspect.\nPor favor, actualice su juego." +CanNotJoinPublicRoomNoLatest: "Tenemos una actualización importante, actualiza este mod\nDe lo contrario no podrás unirte a salas públicas" +ModBrokenMessage: "Los archivos del mod fallaron, reinicia el juego o reinstala este mod" +UnsupportedVersion: "Tu versión de Among Us es incompatible con FinalSuspect\nActualiza el juego" + +# 名称标识 +NameTag.DisplayName: "Nota" +NameTag.Title: "Título" +NameTag.Prefix: "Prefijo" +NameTag.Suffix: "Sufijo" +NameTag.Name: "Nombre" +NameTag.LastTag: "Sufijo adicional" +NameTag.PreviewNotAvailable: " (Vista previa no disponible) " +NameTag.CanNotEdit: " (No editable) " +NameTag.RefreshPreview: "Refrescar vista previa" +NameTag.SaveAndClose: "Guardar y cerrar" +NameTag.NewNameTag: "Nuevo" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Siguiendo el Amanecer(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Con el paso del tiempo, el invierno del norte termina y la primavera renace.\nContemplando el paisaje nevado de Mira, ¿despiertan los recuerdos? Fluyen corrientes cálidas en el corazón.\nNo importa quién sea el sospechoso final, ni en manos de quién se rompa la verdad,\nen una nueva experiencia refrescante, haz lluvia de ideas y deja nuevos y felices recuerdos.\n¡Jugar y divertirse es lo más importante!!!\n\nNombrado por: 一念旧情丶" +MainMenuStyle.Title_Security: "Corazón Rojo, Amor Feroz (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "No caeremos, no nos rendiremos, no decepcionaremos a los jugadores y nunca dejaremos de avanzar.\nSi enfrentamos miradas frías, ¡lo demostraremos a todos!\nCon pasión, zarparemos de nuevo\n\nNombrado por: Slok" +MainMenuStyle.Title_NewYear: "Afinidad (Año Nuevo)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortuna: Deseos otorgados Vínculo: Tejido en latidos\nArmonía: Hilos de afecto Dicha: Paz en cada alma\nVínculos del destino forjan alegría compartida Viaja lejos, encuentra alegría en la unión\nEra dorada acuna a todos en regocijo Nuestra oración—alegría para cada ser\nQue cada alma encuentre su camino en el año venidero\n¡Con nuestras bendiciones iluminando tu camino, emprende tu viaje para forjar tu propia leyenda!\n\nNombrado por: Slok" +MainMenuStyle.Title_MiraStudio: "Final Estudio (Año Nuevo)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Las flores florecen como olas, suaves como el viento, deseando que cada corazón sea tan vasto como el viento y todo vaya bien\"\n\"—Los vientos primaverales soplan con fuerza, las olas se extienden por millas, deseando que todos se mantengan valientes al frente, floreciendo y prosperando\"\n\"—¡Bienvenidos al Especial de Año Nuevo de XtremeWave!\"\n\nNombrado por: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final esforzándose por la excelencia, ¡el sueño comanda olas empujantes!」\n\nNombrado por: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Cuando Mires Atrás al Final (Colaboración)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Mirando atrás al final, todo termina al final」\n\nNombrado por: MAMTI.麦麦头" +MainMenuStyle.NotFound: "No descargado" +MainMenuStyle.NotApply: "Aplicar" +MainMenuStyle.Applied: "Aplicado" + +# 资源包 +Package.MainMenuStyle: "Paquete de Estilo del Menú Principal" # 音频播放 / Audio Playback -PlayMode0: "Reproducir una vez" -PlayMode1: "Repetir una sola vez" -PlayMode2: "Aleatorio" -PlayMode3: "Secuencial" -Stop: "Detener" -CanPlay: "← Haz clic para reproducir" -NoFound: "[Archivo ausente]" -NextPage: "Página siguiente" -PreviousPage: "Página anterior" - -# 音频添加 / Audio Addition -download: "Descargar" -delete: "Eliminar" -NewSound: "Añadir nueva música" -PleaseEnterMusic: "Por favor, ingrese el nombre de la música" -AudioManagementAlreadyExists: "Este nombre de música ya existe" -NotAllowedMusic: "El formato del nombre de la música no está permitido" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Puedes descargar música compatible con XtremeWave o añadir tu propia música en 'Gestión de Audio'. Al añadir tu propia música, asegúrate de añadir el nombre de la música en 'Gestión de Audio' y colocar el archivo de audio correspondiente en la carpeta 'Among Us/Final Suspect_Data/Resources/Sounds' (formatos compatibles: .wav). La música se puede reproducir en 'Mi Música'." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Puedes descargar música compatible con XtremeWave o añadir tu propia música en 'Gestión de Audio'. Si falta la ruta del recurso de música local, se mostrará '[Archivo ausente]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "El archivo de música actual se ha detectado como incompleto" -AudioNYPro: "Para una mejor experiencia de juego, descarga nuestra música en 'Inicio-Configuración-Más funciones-Gestión de audio'" +MusPlay.Mode0: "Reproducir una vez" +MusPlay.Mode1: "Repetir una sola" +MusPlay.Mode2: "Lista aleatoria" +MusPlay.Mode3: "Reproducción secuencial" +MusPlay.Stop: "Detener reproducción" +MusPlay.CanPlay: "Clic para reproducir" +MusPlay.NoFound: "Archivo faltante" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财 (Feliz Año Nuevo Chino)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Oleaje de Marea" +Mus.TrailOfTruth: "Camino de la Verdad" +Mus.Interlude: "Interludio" +Mus.Fractured: "Fracturado" +Mus.ElegyOfFracturedVow: "Elegía del Voto Roto" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] fue expulsado porque su nombre coincide con [{1}]" -Message.BanedByBanList: "[{0}] fue baneado porque fue baneado anteriormente." -Message.BanedByFACList: "【{0}】 fue baneado porque está en la lista de baneados del FAC." -Message.DumpfileSaved: "El archivo de registro se guardó correctamente en el escritorio, nombre del archivo: {0}" -Message.KickedByNoFriendCode: "[{0}] fue expulsado porque su código de amigo no existe." -Message.AddedPlayerToBanList: "Añadido [{0}] a la lista de baneados" -Message.KickedByFAC: "[{0}] fue expulsado por el FAC, motivo: {1}" -Message.BanedByFAC: "【{0}】 fue baneado por el FAC, motivo: {1}" +Mus.ReturnToSimplicity: "volver a la simplicidad" +Mus.ReturnToSimplicity2: "volver a la simplicidad (Versión Completa)" +Mus.ChasingDawn: "Persiguiendo el Amanecer" +Mus.StruggleAgainstFadingFlame: "Lucha contra la Llama que se Apaga" +Mus.Affinity: "Afinidad" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Rol" +DisplayedRoleTag.PlayerIdentityTag: "Etiqueta de Identidad" +DisplayedRoleTag.Room: "Sala" + +PlayerIdentityTag.Hard_Cleared: "Confirmado Bueno" +PlayerIdentityTag.Silver_Clear: "Semi-Limpiado" +PlayerIdentityTag.Wolf_Bucket: "Cupo Lobo" +PlayerIdentityTag.No_Kill: "Sin Asesinato" +PlayerIdentityTag.Outside_Position: "Posición Externa" +PlayerIdentityTag.Inside_Position: "Posición Interna" # 通知 / Notifications -PlayerLeft: "[{0}] dejó el juego" -PlayerLeftCuzTimeout: "[{0}] dejó el juego debido a un tiempo de espera de conexión" -PlayerKickByHost: "[{0}] fue expulsado por el anfitrión" -PlayerBanBy Host: "[{0}] fue baneado por el anfitrión" -PlayerLeftCuzError: "[{0}] dejó el juego debido a un error" -PlayerLeftByAU-Anticheat: "[{0}] fue expulsado por el sistema antitrampas oficial de AmongU (no relacionado con FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] fue expulsado porque tenía una versión diferente del mod" +## 原版 +Notification.PlayerLeft: "[{0}] abandonó el juego" +Notification.PlayerLeftCuzTimeout: "[{0}] abandonó el juego debido a timeout de conexión" +Notification.PlayerKickByHost: "[{0}] fue expulsado por el host" +Notification.PlayerBanByHost: "[{0}] fue bloqueado por el host" +Notification.PlayerLeftCuzError: "[{0}] abandonó el juego debido a un error" +Notification.PlayerLeftByAU-Anticheat: "[{0}] fue expulsado por el Anti-Cheat de Among Us (sin relación con FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] fue expulsado por tener una versión/mod diferente" +## 模组 +Notification.KickedByDenyName: "[{0}] fue expulsado por tener un apodo con palabras prohibidas" +Notification.DumpfileSaved: "Archivo de log guardado en el escritorio, nombre: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] fue removido porque esta sala prohíbe códigos de amigo anormales" +Notification.AddedPlayerToBanList: "Agregado [{0}] a la lista de bloqueo" +Notification.FPSSetTo: "Límite de FPS establecido a: {0}" # 警告 / Warnings -Warning: "¡Advertencia!" -Warning.MismatchedVersion: "{0}\ntiene una versión diferente de {1}" -Warning.AutoExitAtMismatchedVersion: "El anfitrión no tiene o tiene una versión diferente de {0}\nSerás expulsado en {1}" -Warning.InvalidRpc: "{0} fue expulsado porque se recibió un RPC no válido." -Warning.InvalidRpc_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (RPC no válido: {1})" -Warning.SetName: "{0} fue expulsado porque configuró el nombre varias veces." -Warning.SetName_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (Configurar nombre varias veces)" -Warning.SendQuickChat: "{0} fue expulsado porque envió varios mensajes rápidos en 3 segundos" -Warning.SendQuickChat_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (Enviar varios mensajes rápidos en 3 segundos)" -Warning.InvalidSlothRPC: "Expulsado a {0} porque se recibió un RPC ilegal (Enviando ilegalmente el Rpc oficial: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (Envío ilegal de RPC oficial: {1})" -Warning.Cheater: "Expulsado a {0} porque se sospecha que está usando trucos" -Warning.Cheater_NotHost: "{0} se sospecha que está usando trucos, Por favor, recuerda al anfitrión que lo expulse" -Warning.CantKickDev: "Lo siento, no puedes expulsar al desarrollador" -Warning.RoomBroken: "Lo siento, esta sala ha sido comprometida. Por favor, dirígete a otra sala para continuar tu juego." +Warning: "Advertencia" +Warning.MismatchedVersion: "[{0}]\ntiene una versión diferente de [{1}] instalada" +Warning.AutoExitAtMismatchedVersion: "Tu versión de [{0}] es diferente a la del host\nSerás expulsado en {1} segundos" +Warning.CantKickDev: "Lo sentimos, no puedes expulsar a los desarrolladores" +Warning.RoomBroken: "Lo sentimos, esta sala sufrió un ataque, juega en otra sala" +Warning.InvalidColor: "Jugador con color inválido detectado" ## 错误等级 / Error Levels -ErrorLevel1: "Pueden ocurrir errores." -ErrorLevel2: "Esto puede ser un error." -ErrorLevel3: "Esta versión no debería haber sido lanzada." +ErrorLevel1: "Puede causar varios errores al mismo tiempo" +ErrorLevel2: "Puede haber errores" +ErrorLevel3: "Versión no lanzada" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Advertencia: FAC detectó un alto nivel de trampas." -FAC.CheatDetected.LowLevel: "Advertencia: FAC detectó un bajo nivel de trampas. Uno de los jugadores está haciendo trampas." -FAC.CheatDetected.FAC: "Uso de programas de trampas (por ejemplo, AUM, YuMenu, SM, etc.)" +CheatDetected.HighLevel: "Advertencia: FAC está defendiendo contra trampas de bombardeo" +CheatDetected.LowLevel: "Advertencia: FAC detectó posible tramposo" +CheatDetected.UseCheat: "{0} usó el programa de trampa [{1}]" +CheatDetected.MayUseCheat: "{0} sospechoso de usar mod [{1}] o programa de trampa" +CheatDetected.InvalidRpc: "[{0}] fue expulsado por enviar datos inválidos (Rpc inválido: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Rpc inválido: {1})" +CheatDetected.SetName: "[{0}] fue expulsado por configurar el nombre varias veces" +CheatDetected.SetName_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Configuró nombre varias veces)" +CheatDetected.SendQuickChat: "[{0}] fue expulsado por enviar varios mensajes rápidos en 3 segundos" +CheatDetected.SendQuickChat_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Envió varios mensajes rápidos en 3 segundos)" +CheatDetected.InvalidSlothRPC: "[{0}] fue expulsado por enviar datos ilegales (Rpc oficial ilegal: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Rpc oficial ilegal: {1})" +CheatDetected.Overload: "[{0}] fue expulsado por iniciar un ataque de sobrecarga" +CheatDetected.Overload_NotHost: "[{0}] inició un ataque de sobrecarga, esta sala está dañada, juega en otra sala" +CheatDetected.Cheater: "[{0}] fue expulsado por sospecha de trampa" +CheatDetected.Cheater_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto" +CheatDetected.BanedByBanList: "[{0}] fue expulsado por estar en la lista de bloqueo" +CheatDetected.BanedByFACList: "[{0}] fue expulsado por estar en la lista de bloqueo FAC" # 模组信息 / Mod Infos -Contributors: "Contribuyentes" -Acknowledgement: "Agradecimientos" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Has sido expulsado por el sistema antitrampas.\n (el uso de módulos puede ser malinterpretado como trampas)" -DCNotify.Banned: "No tienes permitido entrar en esta sala" -DCNotify.Kicked: "Has sido expulsado de la sala" -DCNotify.DCFromServer: "Te has desconectado del servidor.\nEsto puede deberse a la inestabilidad de tu red.\nEsto también puede deberse a la inestabilidad del servidor." -DCNotify.GameNotFound: "No se encontró la sala asignada, la sala puede haber sido disuelta\n o verifica si has seleccionado un servidor diferente al de la sala" -DCNotify.GameStarted: "El juego ya ha comenzado, por favor espera a que termine" -DCNotify.GameFull: "La sala está llena, por favor intenta nuevamente más tarde" -DCNotify.IncorrectVersion: "Tu versión de Among Us es diferente a la de esta sala" -DCNotify.Description: "Has sido expulsado del juego.\nMotivo: {0}" -DCNotify.DenyName: "Tu apodo contiene caracteres irregulares" -DCNotify.BanList: "Has sido baneado por el anfitrión" -DCNotify.FACList: "Has sido baneado por el FAC" -DCNotify.CheatDetected: "Has sido detectado como sospechoso de trampas por el FAC" -DCNotify.InvalidRPC: "Puede que hayas instalado un mod diferente al del anfitrión o tu mod ha sido modificado maliciosamente" -DCNotify.ModVersionIncorrect: "Tu versión del mod es diferente a la del anfitrión" -DCNotify.LowLevel: "Tu nivel no cumple con los requisitos de esta sala" -DCNotify.NotLogin: "No se permiten jugadores no logueados en esta sala" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Presiona F1 para ver la descripción de tu rol" +ModInfo.Contributors: "Colaboradores" +ModInfo.Acknowledgement: "Agradecimientos Especiales" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Fuiste expulsado del sala por el sistema anti-trampas de InnerSloth" +DCNotify.Banned: "Fuiste bloqueado de esta sala" +DCNotify.Kicked: "Fuiste expulsado de esta sala" +DCNotify.DCFromServer: "Tu conexión con el servidor se interrumpió\nPuede ser debido a red inestable\no inestabilidad/rechazo del servidor" +DCNotify.GameNotFound: "Sala especificada no encontrada, puede haberse cerrado\no verifica que no hayas elegido un servidor distinto al de la sala" +DCNotify.GameStarted: "Esta partida ya comenzó, espera a que termine" +DCNotify.GameFull: "Esta sala está llena, inténtalo más tarde" +DCNotify.IncorrectVersion: "Tu versión de Among Us es diferente a la sala" +DCNotify.Description: "Fuiste expulsado del sala\nRazón: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Presiona F1 para ver descripción de tu rol" +PressF2ToHidePane: "Presiona F2 para mostrar/ocultar panel" FakeTask: "Tarea Falsa:" -KillCount: "Muertes" +KillCount: "Asesinatos" # 复盘信息 / Last Results -RoleSummaryText: "Últimos Resultados:" -ShowResults: "Mostrar Últimos Resultados" -HideResults: "Ocultar Últimos Resultados" -NoInfoExists: "No hay últimos resultados disponibles" -CrewsWin: "Victoria del Equipo-Tripulante" -CrewmatesWin: "Victoria de los Tripulantes" -CrewmatesWinBlurb: "¡La luz de la verdad brilla en la esperanza!" -ImpsWin: "Victoria del Equipo-Impostor" -ImpostorsWin: "Victoria de los Impostores" -ImpostorsWinBlurb: "El mal convierte la verdad en cenizas" -HideSummaryTextToShowWinText: "Ocultar último resultado para ver el texto de victoria" - -# 禁用公开 -DisabledByProgram: "Las operaciones de sala pública han sido deshabilitadas por el programa" -PublicNotAvailableOnThis Version: "Las salas públicas no están disponibles en esta versión de FinalSuspect" +Summary.Text: "Resumen Final:" +Summary.ShowResults: "Mostrar Resumen Final" +Summary.HideResults: "Ocultar Resumen Final" +Summary.NoInfoExists: "No existe un Resumen Final válido" +Summary.CrewsWin: "Victoria del Equipo-Tripulante" +Summary.ImpsWin: "Victoria del Equipo-Impostor" +Outro.Crews_Win: "Victoria de los Tripulantes" +Outro.Crews_WinBlurb: "¡La luz de la verdad brilla con esperanza!" +Outro.Imps_Win: "Victoria de los Impostores" +Outro.Imps_WinBlurb: "El mal redujo la verdad a cenizas" # 主页 / Main UI -FinalSuspectWelcomeText: "¡Deseándote una experiencia de juego agradable!" -ConnectToFinalSuspectServerFailed: "Fallo al conectar con el servidor de FinalSuspect" +FinalSuspectWelcomeText: "¡Te deseamos una experiencia de juego agradable!" +RetrieveVersionInfoFailed: "No se pudo obtener información de FinalSuspect" Website: "Sitio Oficial" MainMenuCredential: "{0} © 2025" -LShift: "Presiona LShift para regresar a la habitación anterior" -RShift: "Presiona RShift para entrar en la habitación del portapapeles" +LShift: "Lobby Anterior" +RShift: "Lobby Portapapeles" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" -FrameRate: "Tasa de Cuadros" +FrameRate: "Tasa de FPS" Server: "Servidor" Local: "Local" # 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Límite de tasa de cuadros establecido en: {0}" -BrowsingMode: "Modo de Navegación" +BrowsingMode: "Modo Navegación" Broken: "Roto" - -# 加载 / Loading -LanguageFilesLoadingComplete: "¡Carga de traducciones completa!" -CheckingForFiles: "Verificando la integridad de los archivos de recursos..." -DownloadingResources: "Descargando archivos de recursos..." -Loading: "Cargando" -LoadingWithDot: "Cargando..." -LoadingComplete: "¡Carga completa!" +Unknown: "Desconocido" +Back: "Atrás" +Yes: "Sí" +No: "No" +Cancel: "Cancelar" +Unload: "Cambiar" +Retry: "Reintentar" +PreviousPage: "Página Anterior" +NextPage: "Página Siguiente" +Download: "Descargar" +Disable: "Desactivar" +Delete: "Eliminar" +Author: "Autor" +Close: "Cerrar" # 身份 / Identity -Host: "Anfitrión" -Cheater: "Tramposo" +Id.Host: "Host" +Id.Cheater: "Tramposo" +Id.Developer: "Desarrollador" +Id.Contributor: "Colaborador" \ No newline at end of file diff --git a/Assets/Languages/Portuguese.yaml b/Assets/Languages/Portuguese.yaml index ad11bdd8..0f4201df 100644 --- a/Assets/Languages/Portuguese.yaml +++ b/Assets/Languages/Portuguese.yaml @@ -1,56 +1,51 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "3" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostor" -TypeCrewmate: "Tripulante" +RoleType.Imp: "Impostor" +RoleType.Crew: "Tripulante" # 阵营 / Teams -TeamImpostor: "Equipe-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Equipe-Tripulante" +Team.Imp: "Equipe-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Equipe-Tripulante" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Há {0} impostores na nossa equipe" -ImpostorNumImpOnly: "Há apenas 1 impostor entre a multidão" -ImpostorNumCrew: "{0} impostores entre nós" +ImpostorNum.Imp: "Há {0} impostores na nossa equipe" +ImpostorNum.Imp_Only: "Há apenas 1 impostor entre nós" +ImpostorNum.Crew: "{0} impostores entre nós" # 阵营开场 -ImpostorIntroText: "Deixe o mal envolver o mundo!" -ImpostorIntroTextOnly: "Eu posso estar sozinho, mas possuo força infinita!" -CrewmateIntroText: "Complete suas tarefas, unam-se para resolver essas situações complicadas!" - -## 原版职业 / Vanilla -Crewmate: "Tripulante" -Engineer: "Engenheiro" -Scientist: "Cientista" -Tracker: "Rastreador" -Noisemaker: "Barulhento" -GuardianAngel: "Anjo da Guarda" -Impostor: "Impostor" -Shapeshifter: "Metamorfo" -Phantom: "Fantasma" -CrewmateGhost: "Fantasma de Tripulante" -ImpostorGhost: "Fantasma de Impostor" -CrewmateGhostBlurb: "Complete suas tarefas" -ImpostorGhostBlurb: "Continue sabotando as instalações" -CrewmateGhostBlurbLong: "Complete as tarefas, não fique para trás!" -ImpostorGhostBlurbLong: "Sabote as instalações, ajude os impostores sobreviventes a alcançar a vitória." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Sobreviva até o fim!" -HnSEngineerBlurbLong: "Permaneça vivo até que o tempo acabe; completar tarefas estenderá o tempo.\nUse os dutos de ventilação e indicadores de ameaça para se esconder!\nQuando o cronômetro começar, o impostor começará a caçar você!" -HnSImpostorBlurb: "Elimine todos!" -HnSImpostorBlurbLong: "Mate todos os membros da tripulação dentro do limite de tempo!\nVocê deve agir rapidamente! Os dutos de ventilação não estão acessíveis.\nNo último momento, você receberá um aumento de velocidade e dicas de rastreamento!" -HnSCrewmateGhostBlurb: "Torça por seus companheiros" -HnSCrewmateGhostBlurbLong: "Vai! Torça! Por! Seus! Companheiros!" +IntroText.Imp: "Que o mal DOMINE O MUNDO!" +IntroText.Imp_Only: "Apesar de estar só, tens PODER INFINITO!" +IntroText.Crewmate: "Completa tuas tarefas e encontra os impostores!" + +# 原版职业 / Vanilla +Role.Crewmate: "Tripulante" +Role.Engineer: "Engenheiro" +Role.Scientist: "Cientista" +Role.Tracker: "Rastreador" +Role.Noisemaker: "Barulhento" +Role.GuardianAngel: "Anjo da Guarda" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Metamorfo" +Role.Phantom: "Fantasma" +Role.CrewmateGhost: "Fantasma Tripulante" +Role.ImpostorGhost: "Fantasma Impostor" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Completar tarefas" +ImpostorGhostBlurb: "Continuar sabotando" +CrewmateGhostBlurbLong: "Completa tarefas, não atrase a equipe!\nUse 「Assombração」 para ver os Impostores e ajude os Anjos da Guarda a protegerem os Tripulantes!" +ImpostorGhostBlurbLong: "Sabota instalações, ajude os impostores sobreviventes a vencerem!" +HnSEngineerBlurb: "Completa tarefas e sobreviva até o fim!" +HnSImpostorBlurb: "Massacra TODOS!" +HnSCrewmateGhostBlurb: "Encoraja teus companheiros!" +HnSCrewmateGhostBlurbLong: "Morto? O que estás a ver? Consegues fazer tarefas? Não...\nENTÃO! ENCORAJA! OS! TEUS! COMPANHEIROS!" # 死因 / Death Reason DeathReason.Kill: "Morto" @@ -58,236 +53,285 @@ DeathReason.Exile: "Exilado" DeathReason.Disconnect: "Desconectado" # 客户端选项 / Client Options -FinalSuspectOptions: "Opções do Final Suspect" -Back: "Voltar" -Yes: "Sim" -No: "Não" -UnlockFPS: "Desbloquear FPS" -ChangeOutfit: "Mudar Roupa" -BeanMode: "Modo Clássico de Feijão" -HorseMode: "Modo Cavalo de Primeiro de Abril" -LongMode: "Modo Longo de Primeiro de Abril" -KickPlayerFriendCodeNotExist: "Expulsar jogadores que não estão logados." -KickPlayerWithDenyName: "Expulsar jogadores com apelidos inapropriados" -KickPlayerInBanList: "Expulsar jogadores banidos" -SpamDenyWord: "Bloquear palavras inapropriadas" -AutoStartGame: "Iniciar automaticamente com lobby cheio" -AutoEndGame: "Retornar automaticamente ao lobby no fim" -SwitchVanilla: "Alternar para a versão original" -DisableVanillaSound: "Desativar músicas do Among Us" -DisableFAC: "Desativar anti-cheat" -ShowPlayerInfo: "Mostrar informações da plataforma e do cliente do jogador" -UseModCursor: "Usar cursor do mod" -FastBoot: "Modo de Inicialização Rápida" -PrunkMode: "Modo Brincadeira" -VersionCheat: "Ignorar verificação de sincronização da versão do mod" -GodMode: "Modo Deus" -NoGameEnd: "Sem fim de jogo" -EnableFinalSuspect: "Ativar「Final Suspect」" +FinalSuspectOptions: "Opções Final Suspect" +ClientOption.UnlockFPS: "Desbloquear FPS" +ClientOption.SwitchOutfitType: "Mudar Tipo de Roupa" +ClientOption.KickPlayerWithAbnormalFriendCode: "Expulsar jogadores com códigos de amigo anormais" +ClientOption.KickPlayerWithDenyName: "Expulsar jogadores com apelidos proibidos" +ClientOption.KickPlayerInBanList: "Expulsar jogadores banidos" +ClientOption.SpamDenyWord: "Bloquear palavras proibidas" +ClientOption.AutoStartGame: "Começar automaticamente quando cheio" +ClientOption.AutoEndGame: "Voltar automaticamente ao lobby ao fim do jogo" +ClientOption.SwitchVanilla: "Mudar para Vanilla" +ClientOption.DisableVanillaSound: "Desativar música do jogo Vanilla" +ClientOption.EnableFAC: "Ativar Anti-Cheat" +ClientOption.EnableGuardian: "Ativar Guardião Cliente (Experimental)" +ClientOption.ShowPlayerInfo: "Mostrar plataforma e info do cliente" +ClientOption.UseModCursor: "Usar cursor mod" +ClientOption.FastLaunchMode: "Modo Lançamento Rápido" +ClientOption.OfflineMode: "Modo Offline (Experimental)" +ClientOption.VersionCheat: "Ignorar verificação de versão" +ClientOption.GodMode: "Modo Deus" +ClientOption.NoGameEnd: "Sem Fim de Jogo" +ClientOption.EnableFinalSuspect: "Ativar 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Modo Clássico" +Value.HorseMode: "Modo Cavalo do Dia das Mentiras" +Value.LongMode: "Modo Longo do Dia das Mentiras" # 客户端功能 / Client Features -FinalSuspectFeatures: "Recursos do Final Suspect" -UnloadMod: "Alternar para a versão original" -UnloadWarning: "Aviso\nPara reativar o mod, você deve reiniciar o jogo.\nDeseja continuar mesmo assim?" -CannotUnloadDuringGame: "Não é possível alternar para a versão original durante o jogo" -Cancel: "Cancelar" -Unload: "Descarregar" -DumpLog: "Exportar Log" -ClearAutoLogs: "Limpar Log Automático" -SoundOptions: "Minhas Músicas" -AudioManagementOptions: "Gerenciamento de Áudio" -OnlyAvailableInMainMenu: "Disponível apenas no menu principal" +FinalSuspectFeatures: "Funcionalidades Final Suspect" +ClientFeature.UnloadMod: "Mudar para Vanilla" +ClientFeature.DumpLog: "Descarregar Log" +ClientFeature.ClearAutoLogs: "Limpar Logs Automáticos" +ClientFeature.MyMusic: "Minha Música" +ClientFeature.ResourceManager: "Gestor de Recursos" +ClientFeature.NameTagManager: "Gestor de Etiquetas" +ClientFeature.MainMenuStyleManager: "Mudar Estilo do Menu Principal" # 提示 / Tips -updatePleaseWait: "Por favor, aguarde..." -updateInProgress: "Atualização em andamento..." -DownloadingAudios: "Baixando..." -Playing: "Jogando..." -Parsing: "Analisando..." -DownLoadSucceedNotice: "Download bem-sucedido!" -DownLoadFailureNotice: "Falha no download =(" -PleaseWait: "Por favor, aguarde..." +Tip.Downloading: "A descarregar..." +Tip.PleaseWait: "Por favor espera..." +Tip.Playing: "A tocar..." +Tip.Parsing: "A analisar..." +Tip.Updating: "A atualizar..." +Tip.DownLoadFinished: "Descarga concluída" +Tip.DownLoadSucceeded: "Descarga bem-sucedida!" +Tip.DownLoadFailed: "Falha na descarga=(" +Tip.PackageExists: "Instalado" +Tip.OnlyAvailableInMainMenu: "Disponível apenas no Menu Principal" +Tip.HideSummaryTextToShowWinText: "Esconder Resumo Final para ver texto de vitória" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Carregamento de ficheiros de idioma concluído" +Tip.CheckingForFiles: "A verificar integridade dos ficheiros de recursos..." +Tip.DownloadingResources: "A descarregar ficheiros de recursos..." +Tip.Loading: "A carregar" +Tip.LoadingWithDot: "A carregar..." +Tip.LoadingComplete: "Carregamento concluído!" +## 切换原版 +Tip.UnloadWarning: "Aviso!\nDeves reiniciar o jogo se quiseres jogar a versão mod.\nTens a certeza de que queres mudar para Vanilla?" +Tip.CannotUnloadDuringGame: "Não é possível mudar para Vanilla durante um jogo." +## 资源管理 +Tip.ResourceManager: "Podes descarregar recursos compatíveis com mods no \"Gestor de Recursos\"\nOs ficheiros com prefixo \"Pre-\" são pacotes de recursos pré-descarregados" +## 我的音乐 +Tip.MyMusic: "Podes descarregar música suportada por mods no \"Gestor de Recursos\" ou adicionar os teus ficheiros de áudio na pasta (Among Us/Final Suspect_Data/Musics). Se o caminho local da música não existir, será mostrado \"Ficheiro em Falta\"" +Tip.Incomplete_Music: "Ficheiro de música incompleto detetado" +Tip.Incomplete_SoundEffect: "Ficheiro de efeito sonoro incompleto detetado" +Tip.Incomplete_Image: "Ficheiro de imagem incompleto detetado" +Tip.Incomplete: "Para uma melhor experiência de jogo, descarrega o pacote de recursos compatível com mods em \"Menu Principal - Definições - Funcionalidades do Cliente - Gestor de Recursos\"" +## 名称标识管理 +Tip.TextContent: "Conteúdo de Texto" +Tip.TextSizeDescription: "Tamanho do Texto(Padrão 100%)" +Tip.TextColorDescription: "Cor do Texto(Código Hexadecimal)\nDeixa vazio se não for necessário, preenche vários para gradiente automático\n" +Tip.PleaseEnterFriendCode: "Por favor insere o Código de Amigo para vincular a nova etiqueta de nome" +Tip.FriendCodeAlreadyExist: "Este Código de Amigo já tem uma etiqueta de nome" +Tip.FriendCodeIncorrect: "Por favor insere um Código de Amigo válido" +Tip.CustomNameTagHelp: "Podes adicionar etiquetas de nome para qualquer jogador. As etiquetas serão atribuídas automaticamente quando o jogador vinculado ao Código de Amigo entrar. Mas não podes editar etiquetas depois de entrares num lobby.\nNão-VIPs só podem adicionar notas (funcionalidades VIP ainda não desenvolvidas)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Podes descarregar \"Pacotes de Estilo do Menu Principal\" no \"Gestor de Recursos\" para obter recursos do menu principal e escolher o teu estilo favorito aqui" + +# 更新结果 +UpdateResult.Succeed_Title: "Atualização bem-sucedida" +UpdateResult.Succeed_Text: "Será ativado após reiniciar o jogo :)" +UpdateResult.Failed_Title: "Atualização falhou" +UpdateResult.Failed_Reason_NotFound: "Razão: {0}\nEsta fonte pode estar temporariamente indisponível, muda a fonte de descarga e tenta novamente" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Razão: Erro de verificação de ficheiro\nA versão do ficheiro desta fonte não é a mais recente, muda a fonte de descarga e tenta novamente" +UpdateResult.Failed_Reason_Ping: "Razão: Tempo limite da atualização excedido ou interrompido\nVerifica a tua rede e tenta novamente ou atualiza manualmente" # 更新检查 / Update Checker -Retry: "Tentar Novamente" -updateCheckPopupTitle: "Verificação de Atualização" -updateCheckFailedRetry: "Falha na verificação de atualização :(\nTentar novamente?" -updateCheckFailedExit: "Falha na verificação de atualização :(\nPor favor, verifique sua conexão de rede e tente novamente." +UpdateCheck.Popup_Title: "Verificar Atualização" +UpdateCheck.Failed_Retry: "Falha ao verificar atualização :(\nTentar novamente?" +UpdateCheck.Failed_Exit: "Falha ao verificar atualização :(\nVerifica a tua rede e tenta novamente!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Lembrete de Atualização" -updateNotice: "Lembrete de Atualização" -UpdateBySelfText: "Esta versão não suporta atualizações automáticas. Por favor, atualize manualmente." -updateButton: "Atualizar Agora" -updatePopupTitle: "Atualizar Agora" -updatePopupTitleFailed: "Falha na Atualização" -updatePopupTitleDone: "Atualização Concluída" +UpdateRemind.updatePopup: "Atualizar Agora" +UpdateRemind.updateNotice: "Lembrete de Atualização" +UpdateRemind.BySelf_Title: "Dica de Atualização" +UpdateRemind.BySelf_Text: "Esta versão não suporta atualização com um clique, atualiza manualmente" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Por favor, selecione uma fonte para atualizar\nse você não souber o que selecionar, escolha [Github]\nSe a atualização falhar, escolha [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Reinicie o jogo para aplicar as alterações :)" -updatePingFialed: "Motivo: {0}\nO canal selecionado pode estar temporariamente indisponível. Tente mudar o canal de download." -updateFileMd5Incorrect: "Motivo: Erro de checksum do arquivo\nA versão do arquivo deste canal não é a mais recente. Tente mudar o canal de download." -downloadFailed: "Motivo: Tempo de download esgotado ou interrompido\nTente novamente após mudar sua rede ou atualizar manualmente." +UpdateSource.Choose: "Por favor escolhe a fonte de atualização\nSe não tiveres certeza, escolhe [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Temos uma atualização importante. Por favor, atualize este mod.\nCaso contrário, você não poderá entrar em salas públicas." -CanNotJoinPublicRoomNoLatest: "Temos uma atualização importante. Por favor, atualize este mod.\nCaso contrário, você não poderá entrar em salas públicas." -ModBrokenMessage: "Os arquivos do mod estão corrompidos. Por favor, reinicie o jogo ou reinstale este mod." -UnsupportedVersion: "Sua versão de Among Us é incompatível com FinalSuspect.\nPor favor, atualize seu jogo." +CanNotJoinPublicRoomNoLatest: "Temos uma atualização importante, atualiza este mod\nSenão não poderás juntar-te a salas públicas" +ModBrokenMessage: "Ficheiros do mod falharam, reinicia o jogo ou reinstala este mod" +UnsupportedVersion: "A tua versão de Among Us é incompatível com FinalSuspect\nAtualiza o jogo" + +# 名称标识 +NameTag.DisplayName: "Nota" +NameTag.Title: "Título" +NameTag.Prefix: "Prefixo" +NameTag.Suffix: "Sufixo" +NameTag.Name: "Nome" +NameTag.LastTag: "Sufixo Adicional" +NameTag.PreviewNotAvailable: " (Pré-visualização indisponível) " +NameTag.CanNotEdit: " (Não editável) " +NameTag.RefreshPreview: "Atualizar Pré-visualização" +NameTag.SaveAndClose: "Guardar e Fechar" +NameTag.NewNameTag: "Novo" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Perseguindo o Amanhecer(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Com o passar do tempo, o inverno do norte termina e a primavera renasce.\nAo contemplar a paisagem nevada de Mira, será que as memórias despertam? Correntes quentes fluem no coração.\nNão importa quem seja o suspeito final, não importa de quem sejam as mãos que fraturam a verdade,\nnuma nova experiência refrescante, faça brainstorming e deixe novas memórias felizes.\nJogar e divertir-se é o mais importante!!!\n\nNomeado por: 一念旧情丶" +MainMenuStyle.Title_Security: "Coração Vermelho, Amor Feroz (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Não vamos cair, não vamos desistir, não vamos desapontar os jogadores e nunca vamos parar de progredir.\nSe encontrarmos olhares frios, vamos provar a todos!\nCom paixão, partiremos de novo\n\nNomeado por: Slok" +MainMenuStyle.Title_NewYear: "Afinidade (Festival da Primavera)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortuna: Desejos concedidos Vínculo: Tecido nos batimentos\nHarmonia: Fios de afeto Felicidade: Paz em cada alma\nVínculos do destino forjam alegria partilhada Viaje longe, encontre alegria na união\nEra dourada acolhe todos na alegria A nossa oração—alegria para cada ser\nQue cada alma encontre o seu caminho no ano vindouro\nCom as nossas bênçãos a iluminar o teu caminho, embarca na tua jornada para forjar a tua própria lenda!\n\nNomeado por: Slok" +MainMenuStyle.Title_MiraStudio: "Final Estúdio (Festival da Primavera)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Flores florescem como ondas, suaves como o vento, desejando que cada coração seja tão vasto quanto o vento e todas as coisas corram bem\"\n\"—Ventos primaveris sopram vigorosamente, ondas avançam por milhas, desejando que todos se mantenham corajosamente na vanguarda, florescendo e prosperando\"\n\"—Bem-vindo ao Especial do Festival da Primavera da XtremeWave!\"\n\nNomeado por: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final esforçando-se pela excelência, sonho comandando ondas impetuosas!」\n\nNomeado por: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Quando Olhar para Trás no Fim (Colaboração)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Olhando para trás no fim, tudo termina no fim」\n\nNomeado por: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Não Descarregado" +MainMenuStyle.NotApply: "Aplicar" +MainMenuStyle.Applied: "Aplicado" + +# 资源包 +Package.MainMenuStyle: "Pacote de Estilo do Menu Principal" # 音频播放 / Audio Playback -PlayMode0: "Tocar uma vez" -PlayMode1: "Repetir uma única vez" -PlayMode2: "Aleatório" -PlayMode3: "Sequencial" -Stop: "Parar" -CanPlay: "← Clique para tocar" -NoFound: "[Arquivo ausente]" -NextPage: "Próxima página" -PreviousPage: "Página anterior" - -# 音频添加 / Audio Addition -download: "Baixar" -delete: "Excluir" -NewSound: "Adicionar nova música" -PleaseEnterMusic: "Por favor, insira o nome da música" -AudioManagementAlreadyExists: "Este nome de música já existe" -NotAllowedMusic: "O formato do nome da música não é permitido" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Você pode baixar músicas suportadas pelo XtremeWave ou adicionar suas próprias músicas em 'Gerenciamento de Áudio'. Ao adicionar suas próprias músicas, certifique-se de adicionar o nome da música em 'Gerenciamento de Áudio' e colocar o arquivo de áudio correspondente na pasta 'Among Us/Final Suspect_Data/Resources/Sounds' (formatos suportados: .wav). As músicas podem ser tocadas em 'Minhas Músicas'." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Você pode baixar músicas suportadas pelo XtremeWave ou adicionar suas próprias músicas em 'Gerenciamento de Áudio'. Se o caminho do recurso de música local estiver ausente, ele exibirá '[Arquivo ausente]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "O arquivo de música atual foi detectado como incompleto" -AudioNYPro: "Para uma experiência de jogo aprimorada, baixe nossas músicas em 'Home-Settings-More Features-Audio Management'" +MusPlay.Mode0: "Tocar Uma Vez" +MusPlay.Mode1: "Repetir Única" +MusPlay.Mode2: "Lista Aleatória" +MusPlay.Mode3: "Reprodução Sequencial" +MusPlay.Stop: "Parar Reprodução" +MusPlay.CanPlay: "Clique para Tocar" +MusPlay.NoFound: "Ficheiro em Falta" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财 (Feliz Ano Novo Chinês)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Maré de Vazante" +Mus.TrailOfTruth: "Trilha da Verdade" +Mus.Interlude: "Interlúdio" +Mus.Fractured: "Fraturado" +Mus.ElegyOfFracturedVow: "Elegia do Voto Partido" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] foi expulso porque seu nome corresponde a [{1}]" -Message.BanedByBanList: "[{0}] foi banido porque foi banido anteriormente." -Message.BanedByFACList: "[{0}] foi banido porque está na lista de banidos do FAC." -Message.DumpfileSaved: "O arquivo de log foi salvo com sucesso na área de trabalho, nome do arquivo: {0}" -Message.KickedByNoFriendCode: "[{0}] foi expulso porque seu código de amigo não existe." -Message.AddedPlayerToBanList: "Adicionado [{0}] à lista de banidos" -Message.KickedByFAC: "[{0}] foi expulso pelo FAC, motivo: {1}" -Message.BanedByFAC: "[{0}] foi banido pelo FAC, motivo:{1}" +Mus.ReturnToSimplicity: "regresso à simplicidade" +Mus.ReturnToSimplicity2: "regresso à simplicidade (Versão Completa)" +Mus.ChasingDawn: "Perseguindo a Aurora" +Mus.StruggleAgainstFadingFlame: "Luta contra a Chama que se Apaga" +Mus.Affinity: "Afinidade" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Função" +DisplayedRoleTag.PlayerIdentityTag: "Etiqueta de Identidade" +DisplayedRoleTag.Room: "Sala" + +PlayerIdentityTag.Hard_Cleared: "Confirmado Bom" +PlayerIdentityTag.Silver_Clear: "Parcialmente Seguro" +PlayerIdentityTag.Wolf_Bucket: "Reduto de Lobos" +PlayerIdentityTag.No_Kill: "Sem Abate" +PlayerIdentityTag.Outside_Position: "Posição Exterior" +PlayerIdentityTag.Inside_Position: "Posição Interior" # 通知 / Notifications -PlayerLeft: "[{0}] saiu do jogo" -PlayerLeftCuzTimeout: "[{0}] saiu do jogo devido a timeout de conexão" -PlayerKickByHost: "[{0}] foi expulso pelo anfitrião" -PlayerBanBy Host: "[{0}] foi banido pelo anfitrião" -PlayerLeftCuzError: "[{0}] saiu do jogo devido a um erro" -PlayerLeftByAU-Anticheat: "[{0}] foi expulso pelos antitrapaças oficiais do AmongU (não relacionado ao FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] foi expulso porque tinha uma versão diferente do mod" +## 原版 +Notification.PlayerLeft: "[{0}] saiu do jogo" +Notification.PlayerLeftCuzTimeout: "[{0}] saiu do jogo devido a timeout de conexão" +Notification.PlayerKickByHost: "[{0}] foi expulso pelo host" +Notification.PlayerBanByHost: "[{0}] foi banido pelo host" +Notification.PlayerLeftCuzError: "[{0}] saiu do jogo devido a um erro" +Notification.PlayerLeftByAU-Anticheat: "[{0}] foi expulso pelo Anti-Cheat do Among Us (não relacionado ao FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] foi expulso por ter uma versão/mod diferente" +## 模组 +Notification.KickedByDenyName: "[{0}] foi expulso por ter um apelido com palavras proibidas" +Notification.DumpfileSaved: "Ficheiro de log guardado no ambiente de trabalho, nome: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] foi removido pois esta sala proíbe códigos de amigo anormais" +Notification.AddedPlayerToBanList: "Adicionado [{0}] à lista de banidos" +Notification.FPSSetTo: "Limite de FPS definido para: {0}" # 警告 / Warnings -Warning: "Aviso!" -Warning.MismatchedVersion: "{0}\npossui uma versão diferente de {1}" -Warning.AutoExitAtMismatchedVersion: "O anfitrião não tem ou tem uma versão diferente de {0}\nVocê será expulso em {1}" -Warning.InvalidRpc: "{0} foi expulso porque um RPC inválido foi recebido." -Warning.InvalidRpc_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (RPC Inválido: {1})" -Warning.SetName: "{0} foi expulso porque definiu o nome várias vezes." -Warning.SetName_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (Definir Nome Várias Vezes)" -Warning.SendQuickChat: "{0} foi expulso porque enviou várias mensagens rápidas em 3s" -Warning.SendQuickChat_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (Enviar Várias Mensagens Rápidas em 3s)" -Warning.InvalidSlothRPC: "Expulsado {0} porque um RPC ilegal foi recebido (Enviando ilegalmente o Rpc oficial: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} é suspeito de usar trapaças. Por favor, lembre o anfitrião de expulsá-lo (Enviando oficialmente RPC ilegal: {1})" -Warning.Cheater: "Expulsado {0} porque é suspeito de uso de trapaças" -Warning.Cheater_NotHost: "{0} é suspeito de uso de trapaças, Por favor, lembre o anfitrião de expulsá-lo" -Warning.CantKickDev: "Desculpe, você não pode expulsar o desenvolvedor" -Warning.RoomBroken: "Desculpe, esta sala foi comprometida. Por favor, vá para outra sala para continuar o jogo." +Warning: "Aviso" +Warning.MismatchedVersion: "[{0}]\ntem uma versão diferente de [{1}] instalada" +Warning.AutoExitAtMismatchedVersion: "A tua versão de [{0}] é diferente da do host\nSerás expulso em {1} segundos" +Warning.CantKickDev: "Desculpa, não podes expulsar desenvolvedores" +Warning.RoomBroken: "Desculpa, esta sala sofreu um ataque de hack, joga noutra sala" +Warning.InvalidColor: "Jogador com cor inválida detetado" ## 错误等级 / Error Levels -ErrorLevel1: "Pode ocorrer bugs." -ErrorLevel2: "Isso pode ser um bug." -ErrorLevel3: "Esta versão não deveria ter sido lançada." +ErrorLevel1: "Pode causar vários bugs ao mesmo tempo" +ErrorLevel2: "Podem ocorrer bugs" +ErrorLevel3: "Versão não lançada" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Aviso: FAC detectou um alto nível de trapaças." -FAC.CheatDetected.LowLevel: "Aviso: FAC detectou um baixo nível de trapaças. Um dos jogadores está hackeando." -FAC.CheatDetected.FAC: "Usando programas de trapaça (por exemplo, AUM, YuMenu, SM, etc.)" +CheatDetected.HighLevel: "Aviso: FAC está a defender contra cheats de bombardeamento" +CheatDetected.LowLevel: "Aviso: FAC detetou possível cheater" +CheatDetected.UseCheat: "{0} usou o programa de cheat [{1}]" +CheatDetected.MayUseCheat: "{0} suspeito de usar mod [{1}] ou programa de cheat" +CheatDetected.InvalidRpc: "[{0}] foi expulso por enviar dados inválidos (Rpc inválido: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] suspeito de cheat, lembra o host para o expulsar rapidamente (Rpc inválido: {1})" +CheatDetected.SetName: "[{0}] foi expulso por definir o nome várias vezes" +CheatDetected.SetName_NotHost: "[{0}] suspeito de cheat, lembra o host para o expulsar rapidamente (Definiu o nome várias vezes)" +CheatDetected.SendQuickChat: "[{0}] foi expulso por enviar várias mensagens rápidas em 3 segundos" +CheatDetected.SendQuickChat_NotHost: "[{0}] suspeito de cheat, lembra o host para o expulsar rapidamente (Enviou várias mensagens rápidas em 3 segundos)" +CheatDetected.InvalidSlothRPC: "[{0}] foi expulso por enviar dados ilegais (Rpc oficial ilegal: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] suspeito de cheat, lembra o host para o expulsar rapidamente (Rpc oficial ilegal: {1})" +CheatDetected.Overload: "[{0}] foi expulso por iniciar um ataque de sobrecarga" +CheatDetected.Overload_NotHost: "[{0}] iniciou um ataque de sobrecarga, esta sala está danificada, joga noutra sala" +CheatDetected.Cheater: "[{0}] foi expulso por suspeita de cheat" +CheatDetected.Cheater_NotHost: "[{0}] suspeito de cheat, lembra o host para o expulsar rapidamente" +CheatDetected.BanedByBanList: "[{0}] foi expulso por estar na lista de banidos" +CheatDetected.BanedByFACList: "[{0}] foi expulso por estar na lista de banidos FAC" # 模组信息 / Mod Infos -Contributors: "Colaboradores" -Acknowledgement: "Agradecimentos" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Você foi expulso pelo antitrapaças.\n (o uso de módulos pode ser mal interpretado como trapaça)" -DCNotify.Banned: "Você não está autorizado a entrar nesta sala" -DCNotify.Kicked: "Você foi expulso da sala" -DCNotify.DCFromServer: "Você se desconectou do servidor.\nIsso pode ser devido à instabilidade em sua rede.\nTambém pode ser devido à instabilidade do servidor." -DCNotify.GameNotFound: "A sala atribuída não foi encontrada, a sala pode ter sido dissolvida\n ou verifique se você selecionou um servidor diferente da sala" -DCNotify.GameStarted: "O jogo já começou, por favor, espere até que ele termine" -DCNotify.GameFull: "A sala está cheia, por favor, tente novamente mais tarde" -DCNotify.IncorrectVersion: "Sua versão de Among Us é diferente desta sala" -DCNotify.Description: "Você foi expulso do jogo.\nMotivo: {0}" -DCNotify.DenyName: "Seu apelido contém caracteres irregulares" -DCNotify.BanList: "Você foi banido pelo anfitrião" -DCNotify.FACList: "Você foi banido pelo FAC" -DCNotify.CheatDetected: "Você foi detectado como suspeito de trapaça pelo FAC" -DCNotify.InvalidRPC: "Você pode ter instalado um mod diferente do anfitrião ou seu mod foi modificado maliciosamente" -DCNotify.ModVersionIncorrect: "Sua versão do mod é diferente do anfitrião" -DCNotify.LowLevel: "Seu nível não atinge o requisito desta sala" -DCNotify.NotLogin: "Jogadores não logados não são permitidos nesta sala" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Pressione F1 para visualizar a descrição de seu papel" +ModInfo.Contributors: "Contribuidores" +ModInfo.Acknowledgement: "Agradecimentos Especiais" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Foste expulso da sala pelo sistema anti-cheat da InnerSloth" +DCNotify.Banned: "Foste banido desta sala" +DCNotify.Kicked: "Foste expulso desta sala" +DCNotify.DCFromServer: "A tua conexão com o servidor foi interrompida\nPode ser devido a rede instável\nou instabilidade/recusa do servidor" +DCNotify.GameNotFound: "Sala especificada não encontrada, pode ter fechado\nou verifica se escolheste um servidor diferente da sala" +DCNotify.GameStarted: "Este jogo já começou, espera que termine" +DCNotify.GameFull: "Esta sala está cheia, tenta mais tarde" +DCNotify.IncorrectVersion: "A tua versão de Among Us é diferente da sala" +DCNotify.Description: "Foste expulso da sala\nRazão: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Pressiona F1 para ver descrição do teu papel" +PressF2ToHidePane: "Pressiona F2 para mostrar/ocultar painel" FakeTask: "Tarefa Falsa:" -KillCount: "Assassinatos" +KillCount: "Abates" # 复盘信息 / Last Results -RoleSummaryText: "Últimos Resultados:" -ShowResults: "Mostrar Últimos Resultados" -HideResults: "Ocultar Últimos Resultados" -NoInfoExists: "Nenhum último resultado disponível" -CrewsWin: "Time-Tripulante Vence" -CrewmatesWin: "Tripulantes Vencem" -CrewmatesWinBlurb: "A luz da verdade brilha na esperança!" -ImpsWin: "Time-Impostor Vence" -ImpostorsWin: "Impostores Vencem" -ImpostorsWinBlurb: "O mal transforma a verdade em cinzas" -HideSummaryTextToShowWinText: "Ocultar último resultado para visualizar texto de vitória" - -# 禁用公开 -DisabledByProgram: "Operações de sala pública foram desabilitadas pelo programa" -PublicNotAvailableOnThis Version: "Salas públicas não estão disponíveis nesta versão do FinalSuspect" +Summary.Text: "Resumo Final:" +Summary.ShowResults: "Mostrar Resumo Final" +Summary.HideResults: "Esconder Resumo Final" +Summary.NoInfoExists: "Não existe um Resumo Final válido" +Summary.CrewsWin: "Vitória da Equipe-Tripulante" +Summary.ImpsWin: "Vitória da Equipe-Impostor" +Outro.Crews_Win: "Vitória dos Tripulantes" +Outro.Crews_WinBlurb: "A luz da verdade brilha na esperança!" +Outro.Imps_Win: "Vitória dos Impostores" +Outro.Imps_WinBlurb: "O mal reduziu a verdade a cinzas" # 主页 / Main UI -FinalSuspectWelcomeText: "Desejando uma experiência de jogo agradável!" -ConnectToFinalSuspectServerFailed: "Falha ao conectar ao servidor do FinalSuspect" +FinalSuspectWelcomeText: "Desejamos-te uma experiência de jogo agradável!" +RetrieveVersionInfoFailed: "Falha ao obter informações do FinalSuspect" Website: "Site Oficial" MainMenuCredential: "{0} © 2025" -LShift: "Pressione LShift para retornar ao quarto anterior" -RShift: "Pressione RShift para entrar no quarto da área de transferência" -LobbyTimeDisplayText: "Tempo de existência decorrido" +LShift: "Lobby Anterior" +RShift: "Lobby da Área de Transferência" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" @@ -297,18 +341,25 @@ Local: "Local" # 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Taxa de quadros limitada definida para: {0}" BrowsingMode: "Modo de Navegação" Broken: "Quebrado" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Carregamento de Traduções Completo!" -CheckingForFiles: "Verificando Integridade dos Arquivos de Recursos..." -DownloadingResources: "Baixando Arquivos de Recursos..." -Loading: "Carregando" -LoadingWithDot: "Carregando..." -LoadingComplete: "Carregamento Completo!" +Unknown: "Desconhecido" +Back: "Voltar" +Yes: "Sim" +No: "Não" +Cancel: "Cancelar" +Unload: "Mudar" +Retry: "Tentar Novamente" +PreviousPage: "Página Anterior" +NextPage: "Página Seguinte" +Download: "Descarregar" +Disable: "Desativar" +Delete: "Apagar" +Author: "Autor" +Close: "Fechar" # 身份 / Identity -Host: "Anfitrião" -Cheater: "Trapaceiro" +Id.Host: "Host" +Id.Cheater: "Cheater" +Id.Developer: "Desenvolvedor" +Id.Contributor: "Contribuidor" \ No newline at end of file diff --git a/Assets/Languages/Russian.yaml b/Assets/Languages/Russian.yaml index db1b65cd..85bde2a2 100644 --- a/Assets/Languages/Russian.yaml +++ b/Assets/Languages/Russian.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "5" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Импостор" -TypeCrewmate: "Член экипажа" +RoleType.Imp: "Предатель" +RoleType.Crew: "Член Экипажа" # 阵营 / Teams -TeamImpostor: "Команда Импосторов" -TeamImpostorOnly: "Импостор" -TeamCrewmate: "Команда Членов Экипажа" +Team.Imp: "Команда-Предатель" +Team.Imp_Only: "Предатель" +Team.Crew: "Команда-Член Экипажа" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "В нашей команде {0} импосторов" -ImpostorNumImpOnly: "В толпе только один импостор" -ImpostorNumCrew: "Среди нас {0} импосторов" +ImpostorNum.Imp: "В нашей команде {0} предателя(ей)" +ImpostorNum.Imp_Only: "Среди нас только 1 предатель" +ImpostorNum.Crew: "Среди нас {0} предателя(ей)" # 阵营开场 -ImpostorIntroText: "Пусть зло окутает мир!" -ImpostorIntroTextOnly: "Я могу быть один, но у меня бесконечная сила!" -CrewmateIntroText: "Завершите свои задачи, объединитесь, чтобы решить эти сложные ситуации!" - -## 原版职业 / Vanilla -Crewmate: "Член экипажа" -Engineer: "Инженер" -Scientist: "Ученый" -Tracker: "Отслеживатель" -Noisemaker: "Шумогенератор" -GuardianAngel: "Хранитель ангел" -Impostor: "Импостор" -Shapeshifter: "Меняющий форму" -Phantom: "Фантом" -CrewmateGhost: "Призрак члена экипажа" -ImpostorGhost: "Призрак импостора" -CrewmateGhostBlurb: "Завершите свои задачи" -ImpostorGhostBlurb: "Продолжайте саботировать установки" -CrewmateGhostBlurbLong: "Завершите задачи, не отставайте!" -ImpostorGhostBlurbLong: "Саботируйте установки, помогите выжившим импосторам добиться победы." - -## 捉迷藏 / HnS -HnSEngineerBlurb: "Выживай до конца!" -HnSEngineerBlurbLong: "Оставайся в живых до тех пор, пока не закончится время; выполнение задач продлит время. \nИспользуйте вентиляцию и индикаторы угроз, чтобы спрятаться! \nКак только таймер начнет отсчет, импостор начнет охотиться на вас!" -HnSImpostorBlurb: "Уничтожь всех!" -HnSImpostorBlurbLong: "Убей всех членов экипажа в пределах временного лимита! \nТебе нужно действовать быстро! Вентиляция недоступна. \nВ последний момент ты получишь прирост скорости и подсказки для отслеживания!" -HnSCrewmateGhostBlurb: "Поддерживай своих товарищей" -HnSCrewmateGhostBlurbLong: "Вперед! Поддерживай! За! Твоих! Товарищей!" +IntroText.Imp: "Пусть зло ПОДЧИНИТ СЕБЕ МИР!" +IntroText.Imp_Only: "Хоть ты и один, у тебя БЕЗГРАНИЧНАЯ СИЛА!" +IntroText.Crewmate: "Выполняй задания и выяви предателей!" + +# 原版职业 / Vanilla +Role.Crewmate: "Член Экипажа" +Role.Engineer: "Инженер" +Role.Scientist: "Учёный" +Role.Tracker: "Следопыт" +Role.Noisemaker: "Шумахер" +Role.GuardianAngel: "Ангел-Хранитель" +Role.Impostor: "Предатель" +Role.Shapeshifter: "Оборотень" +Role.Phantom: "Фантом" +Role.CrewmateGhost: "Призрак Члена Экипажа" +Role.ImpostorGhost: "Призрак Предателя" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Выполняй задания" +ImpostorGhostBlurb: "Продолжай саботировать" +CrewmateGhostBlurbLong: "Выполняй задания, не тормози команду!\nИспользуй «Преследование», чтобы видеть Предателей, и помоги Ангелам-Хранителям защитить Членов Экипажа!" +ImpostorGhostBlurbLong: "Саботируй оборудование, помоги выжившим Предателям победить!" +HnSEngineerBlurb: "Выполни задания и выживи до конца!" +HnSImpostorBlurb: "ИСТРЕБИ ВСЕХ!" +HnSCrewmateGhostBlurb: "Поддержи товарищей!" +HnSCrewmateGhostBlurbLong: "Мёртв? На кого смотришь? Можешь делать задания? Нет…\nТОГДА! ПОДДЕРЖИ! СВОИХ! ТОВАРИЩЕЙ!" # 死因 / Death Reason DeathReason.Kill: "Убит" -DeathReason.Exile: "Исключен" -DeathReason.Disconnect: "Отключен" +DeathReason.Exile: "Изгнан" +DeathReason.Disconnect: "Отключился" # 客户端选项 / Client Options -FinalSuspectOptions: "Опции Final Suspect" -Back: "Назад" -Yes: "Да" -No: "Нет" -UnlockFPS: "Разблокировать FPS" -ChangeOutfit: "Сменить одежду" -BeanMode: "Классический режим боба" -HorseMode: "Режим лошади на 1 апреля" -LongMode: "Режим длинного на 1 апреля" -KickPlayerFriendCodeNotExist: "Исключить игроков, которые не вошли в игру." -KickPlayerWithDenyName: "Исключить игроков с неподходящими никнеймами" -KickPlayerInBanList: "Исключить заблокированных игроков" -SpamDenyWord: "Блокировать неподходящие слова" -AutoStartGame: "Автоматически начать, когда лобби будет заполнено" -AutoEndGame: "Автоматически вернуться в лобби в конце" -SwitchVanilla: "Переключиться на оригинальную версию" -DisableVanillaSound: "Отключить музыку Among Us" -DisableFAC: "Отключить античит" -ShowPlayerInfo: "Показать информацию о платформе и клиенте игрока" -UseModCursor: "Использовать курсор мода" -FastBoot: "Быстрый запуск" -PrunkMode: "Режим розыгрыша" -VersionCheat: "Обойти проверку версии мода" -GodMode: "Режим Бога" -NoGameEnd: "Без окончания игры" -EnableFinalSuspect: "Включить「Final Suspect」" +FinalSuspectOptions: "Настройки Final Suspect" +ClientOption.UnlockFPS: "Снять ограничение FPS" +ClientOption.SwitchOutfitType: "Сменить тип костюма" +ClientOption.KickPlayerWithAbnormalFriendCode: "Выгонять игроков с подозрительными кодами друга" +ClientOption.KickPlayerWithDenyName: "Выгонять игроков с запрещёнными никами" +ClientOption.KickPlayerInBanList: "Выгонять игроков из чёрного списка" +ClientOption.SpamDenyWord: "Блокировать запрещённые слова" +ClientOption.AutoStartGame: "Автоматически начинать игру при полном составе" +ClientOption.AutoEndGame: "Возвращаться в лобби после завершения игры" +ClientOption.SwitchVanilla: "Переключиться на оригинал" +ClientOption.DisableVanillaSound: "Отключить оригинальную музыку" +ClientOption.EnableFAC: "Включить античит" +ClientOption.EnableGuardian: "Включить клиент-защиту (экспериментально)" +ClientOption.ShowPlayerInfo: "Показывать платформу и клиент игрока" +ClientOption.UseModCursor: "Использовать мод-указатель" +ClientOption.FastLaunchMode: "Быстрый запуск" +ClientOption.OfflineMode: "Автономный режим (экспериментально)" +ClientOption.VersionCheat: "Обход проверки версии" +ClientOption.GodMode: "Режим бога" +ClientOption.NoGameEnd: "Бесконечная игра" +ClientOption.EnableFinalSuspect: "Включить «Final Suspect»" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Классический режим" +Value.HorseMode: "Апрельский режим «Лошадь»" +Value.LongMode: "Апрельский режим «Длинный»" # 客户端功能 / Client Features FinalSuspectFeatures: "Функции Final Suspect" -UnloadMod: "Переключиться на оригинальную версию" -UnloadWarning: "Предупреждение\nДля повторного включения мода, вам нужно перезапустить игру.\nХотите все же продолжить?" -CannotUnloadDuringGame: "Нельзя переключиться на оригинальную версию во время игры" -Cancel: "Отмена" -Unload: "Выгрузить" -DumpLog: "Выгрузить лог" -ClearAutoLogs: "Очистить автоматический лог" -SoundOptions: "Моя музыка" -AudioManagementOptions: "Управление аудио" -OnlyAvailableInMainMenu: "Доступно только в главном меню" +ClientFeature.UnloadMod: "Переключиться на оригинал" +ClientFeature.DumpLog: "Сохранить журнал" +ClientFeature.ClearAutoLogs: "Очистить автоматические логи" +ClientFeature.MyMusic: "Моя музыка" +ClientFeature.ResourceManager: "Управление ресурсами" +ClientFeature.NameTagManager: "Управление тегами" +ClientFeature.MainMenuStyleManager: "Смена стиля главного меню" # 提示 / Tips -updatePleaseWait: "Пожалуйста, подождите..." -updateInProgress: "Обновление в процессе..." -DownloadingAudios: "Загрузка в процессе..." -Playing: "Играем..." -Parsing: "Анализ..." -DownLoadSucceedNotice: "Загрузка успешна!" -DownLoadFailureNotice: "Загрузка не удалась =(" -PleaseWait: "Пожалуйста, подождите..." +Tip.Downloading: "Загрузка…" +Tip.PleaseWait: "Пожалуйста, подождите…" +Tip.Playing: "Воспроизведение…" +Tip.Parsing: "Анализ…" +Tip.Updating: "Обновление…" +Tip.DownLoadFinished: "Загрузка завершена" +Tip.DownLoadSucceeded: "Загрузка успешна!" +Tip.DownLoadFailed: "Ошибка загрузки=(" +Tip.PackageExists: "Установлено" +Tip.OnlyAvailableInMainMenu: "Доступно только в главном меню" +Tip.HideSummaryTextToShowWinText: "Скройте итоговый отчёт, чтобы увидеть текст победы" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Языковые файлы загружены" +Tip.CheckingForFiles: "Проверка целостности ресурсов…" +Tip.DownloadingResources: "Загрузка ресурсов…" +Tip.Loading: "Загрузка" +Tip.LoadingWithDot: "Загрузка…" +Tip.LoadingComplete: "Загрузка завершена!" +## 切换原版 +Tip.UnloadWarning: "Внимание!\nДля возврата к модифицированной версии требуется перезапуск.\nТочно переключиться на оригинал?" +Tip.CannotUnloadDuringGame: "Нельзя переключиться на оригинал во время игры." +## 资源管理 +Tip.ResourceManager: "В «Управлении ресурсами» можно загрузить совместимые и дополнительные ресурсы\nФайлы с префиксом «Pre-» — это предварительно загруженные пакеты" +## 我的音乐 +Tip.MyMusic: "Загрузите музыку, совместимую с модом, в «Управлении ресурсами» или добавьте любимые аудиофайлы в папку Among Us/Final Suspect_Data/Musics. Если локального пути нет, будет отображено «Файл отсутствует»" +Tip.Incomplete_Music: "Обнаружен неполный музыкальный файл" +Tip.Incomplete_SoundEffect: "Обнаружен неполный звуковой файл" +Tip.Incomplete_Image: "Обнаружен неполный файл изображения" +Tip.Incomplete: "Для улучшения игры загрузите ресурсы, совместимые с модом, в «Главное меню → Настройки → Функции клиента → Управление ресурсами»" +## 名称标识管理 +Tip.TextContent: "Текст" +Tip.TextSizeDescription: "Размер текста(по умолчанию 100%)" +Tip.TextColorDescription: "Цвет текста(шестнадцатеричный код)\nПусто — без цвета, несколько значений — автоматический градиент\n" +Tip.PleaseEnterFriendCode: "Введите код друга для привязки новой метки имени" +Tip.FriendCodeAlreadyExist: "У этого кода друга уже есть метка" +Tip.FriendCodeIncorrect: "Введите корректный код друга" +Tip.CustomNameTagHelp: "Можно добавить метки имени для любого игрока. Они назначаются автоматически, когда игрок с привязанным кодом друга заходит в игру. После входа в лобби редактирование невозможно.\nНе-VIP могут добавить только заметки (VIP-функции в разработке)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Загрузите «Пакеты стилей главного меню» в «Управлении ресурсами» и выберите любимый стиль здесь" + +# 更新结果 +UpdateResult.Succeed_Title: "Обновление успешно" +UpdateResult.Succeed_Text: "Вступит в силу после перезапуска игры :)" +UpdateResult.Failed_Title: "Ошибка обновления" +UpdateResult.Failed_Reason_NotFound: "Причина: {0}\nВозможно, источник временно недоступен; смените зеркало и повторите" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Причина: ошибка проверки файла\nВерсия файла устарела; смените зеркало и повторите" +UpdateResult.Failed_Reason_Ping: "Причина: тайм-аут или прерывание загрузки\nПроверьте сеть и повторите или обновите вручную" # 更新检查 / Update Checker -Retry: "Повторить" -updateCheckPopupTitle: "Проверка обновлений" -updateCheckFailedRetry: "Проверка обновлений не удалась :(\nПовторить?" -updateCheckFailedExit: "Проверка обновлений не удалась :(\nПожалуйста, проверьте ваше интернет-соединение и попробуйте снова." +UpdateCheck.Popup_Title: "Проверка обновлений" +UpdateCheck.Failed_Retry: "Не удалось проверить обновления :(\nПовторить?" +UpdateCheck.Failed_Exit: "Не удалось проверить обновления :(\nПроверьте сеть и повторите!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Напоминание об обновлении" -updateNotice: "Напоминание об обновлении" -UpdateBySelfText: "Эта версия не поддерживает автоматические обновления. Обновите вручную." -updateButton: "Обновить сейчас" -updatePopupTitle: "Обновить сейчас" -updatePopupTitleFailed: "Обновление неудачно" -updatePopupTitleDone: "Обновление завершено" +UpdateRemind.updatePopup: "Обновить сейчас" +UpdateRemind.updateNotice: "Напоминание об обновлении" +UpdateRemind.BySelf_Title: "Совет по обновлению" +UpdateRemind.BySelf_Text: "В этой версии нет авто-обновления; обновите вручную" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Пожалуйста, выберите канал для обновления\nесли вы не знаете, что выбрать, выберите [Github]\nесли обновление неудачно, выберите [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Перезапустите игру, чтобы применить изменения :)" -updatePingFialed: "Причина: {0}\nВыбранный канал может быть временно недоступен. Попробуйте изменить канал загрузки." -updateFileMd5Incorrect: "Причина: Ошибка контрольной суммы файла\nВерсия файла с этого канала не является последней. Попробуйте изменить канал загрузки." -downloadFailed: "Причина: Время загрузки истекло или прервано\nПопробуйте снова после изменения вашей сети или обновите вручную." +UpdateSource.Choose: "Выберите источник обновлений\nЕсли не уверены — выберите [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "У нас есть важное обновление. Пожалуйста, обновите это мод.\nВ противном случае, вы не сможете присоединиться к публичным комнатам." -CanNotJoinPublicRoomNoLatest: "У нас есть важное обновление. Пожалуйста, обновите это мод.\nВ противном случае, вы не сможете присоединиться к публичным комнатам." -ModBrokenMessage: "Мод-файлы повреждены. Пожалуйста, перезапустите игру или переустановите этот мод." -UnsupportedVersion: "Ваша версия Among Us не совместима с FinalSuspect.\nПожалуйста, обновите вашу игру." +CanNotJoinPublicRoomNoLatest: "Доступно важное обновление; обновите мод\nИначе не получится войти в публичные комнаты" +ModBrokenMessage: "Файлы мода повреждены; перезапустите игру или переустановите мод" +UnsupportedVersion: "Ваша версия Among Us несовместима с FinalSuspect\nОбновите игру" + +# 名称标识 +NameTag.DisplayName: "Заметка" +NameTag.Title: "Титул" +NameTag.Prefix: "Приставка" +NameTag.Suffix: "Суффикс" +NameTag.Name: "Имя" +NameTag.LastTag: "Дополнительный суффикс" +NameTag.PreviewNotAvailable: " (предпросмотр недоступен) " +NameTag.CanNotEdit: " (нельзя изменить) " +NameTag.RefreshPreview: "Обновить предпросмотр" +NameTag.SaveAndClose: "Сохранить и выйти" +NameTag.NewNameTag: "Новая" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "В погоне за рассветом(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "По мере течения времени северная зима заканчивается, и весна оживает.\nГлядя на заснеженный пейзаж Mira, пробуждаются ли воспоминания? Тёплые потоки текут в сердце.\nНеважно, кто станет последним подозреваемым, чьи руки разрушат истину,\nв новом свежем опыте мозговой штурм и оставь новые счастливые воспоминания.\nИграть и веселиться — самое главное!!!\n\nНазвано: 一念旧情丶" +MainMenuStyle.Title_Security: "Красное сердце, горячая любовь (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "Мы не упадём, не сдадимся, не подведём игроков и никогда не остановимся в развитии.\nЕсли встретимся с холодными взглядами — докажем всем!\nСнова отправимся в путь с пылом\n\nНазвано: Slok" +MainMenuStyle.Title_NewYear: "Симпатия (Весенний фестиваль)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Фортуна: дарованные желания Связь: сплетённые в сердцебиениях\nГармония: нити симпатии Блаженство: покой каждой души\nСудьбоносные связи куют общую радость Далёкое путешествие, найди радость в единстве\nЗолотой век обнимает всех в радости Наша молитва — радость каждому существу\nПусть каждая душа найдёт свой путь в грядущем году\nС нашими благословениями, освещающими твой путь, начни путешествие, чтобы создать свою собственную легенду!\n\nНазвано: Slok" +MainMenuStyle.Title_MiraStudio: "Final Студия (Весенний фестиваль)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Цветы цветут как волны, мягкие как ветер, желаю каждому сердцу быть таким же широким, как ветер, всё идёт гладко\"\n\"—Весенние ветры поднимаются могущественно, волны разливаются на мили, желаю каждому стойко стоять на передовой, процветать и богатеть\"\n\"—Добро пожаловать в специальную программу весеннего фестиваля XtremeWave!\"\n\nНазвано: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "«Final стремится к совершенству, мечта командует поднимающимися волнами!»\n\nНазвано: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Оглядываясь в конце (Сотрудничество)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "«Оглядываясь в конце, всё заканчивается в конце»\n\nНазвано: MAMTI.麦麦头" +MainMenuStyle.NotFound: "Не загружено" +MainMenuStyle.NotApply: "Применить" +MainMenuStyle.Applied: "Применено" + +# 资源包 +Package.MainMenuStyle: "Пакет стилей главного меню" # 音频播放 / Audio Playback -PlayMode0: "Воспроизвести один раз" -PlayMode1: "Повторить один раз" -PlayMode2: "Случайный порядок" -PlayMode3: "Последовательный порядок" -Stop: "Остановить" -CanPlay: "Нажмите, чтобы воспроизвести" -NoFound: "[Файл не найден]" -NextPage: "Следующая страница" -PreviousPage: "Предыдущая страница" - -# 音频添加 / Audio Addition -download: "Загрузить" -delete: "Удалить" -NewSound: "Добавить новую музыку" -PleaseEnterMusic: "Пожалуйста, введите название музыки" -AudioManagementAlreadyExists: "Это название музыки уже существует" -NotAllowedMusic: "Формат названия музыки не разрешен" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Вы можете загрузить музыку, поддерживаемую XtremeWave, или добавить свою собственную музыку в 'Управление аудио'. При добавлении своей собственной музыки, убедитесь, что вы добавили название музыки в 'Управление аудио' и поместили соответствующий аудиофайл в папку 'Among Us/Final Suspect_Data/Resources/Sounds' (поддерживаемые форматы: .wav). Музыка может быть воспроизведена в 'Моя музыка'." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Вы можете загрузить музыку, поддерживаемую XtremeWave, или добавить свою собственную музыку в 'Управление аудио'. Если локальный ресурс музыки отсутствует, будет отображаться '[Файл не найден]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "Текущий музыкальный файл был обнаружен как неполный" -AudioNYPro: "Для лучшего игрового опыта загрузите нашу музыку в разделе 'Главная-Настройки-Дополнительные функции-Управление аудио'" +MusPlay.Mode0: "Один раз" +MusPlay.Mode1: "Один цикл" +MusPlay.Mode2: "Случайный список" +MusPlay.Mode3: "Последовательно" +MusPlay.Stop: "Стоп" +MusPlay.CanPlay: "Нажмите для воспроизведения" +MusPlay.NoFound: "Файл отсутствует" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财 (Счастливый китайский новый год)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Приливная волна" +Mus.TrailOfTruth: "След истины" +Mus.Interlude: "Интерлюдия" +Mus.Fractured: "Разлом" +Mus.ElegyOfFracturedVow: "Элегия разбитого обета" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] был исключен, потому что его имя соответствует [{1}]" -Message.BanedByBanList: "[{0}] был заблокирован, потому что он был заблокирован ранее." -Message.BanedByFACList: "[{0}] был заблокирован, потому что он находится в списке FAC." -Message.DumpfileSaved: "Файл журнала был успешно сохранен на рабочем столе, имя файла: {0}" -Message.KickedByNoFriendCode: "[{0}] был исключен, потому что его код друга не существует." -Message.AddedPlayerToBanList: "Добавлен [{0}] в список блокировок" -Message.KickedByFAC: "[{0}] был исключен FAC, причина: {1}" -Message.BanedByFAC: "[{0}] был исключен FAC, причина: {1}" +Mus.ReturnToSimplicity: "возвращение к простоте" +Mus.ReturnToSimplicity2: "возвращение к простоте (Полная версия)" +Mus.ChasingDawn: "В погоне за рассветом" +Mus.StruggleAgainstFadingFlame: "Борьба с угасающим пламенем" +Mus.Affinity: "Симпатия" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Роль" +DisplayedRoleTag.PlayerIdentityTag: "Метка личности" +DisplayedRoleTag.Room: "Комната" + +PlayerIdentityTag.Hard_Cleared: "Проверенный мирный" +PlayerIdentityTag.Silver_Clear: "Вероятный мирный" +PlayerIdentityTag.Wolf_Bucket: "Волчья яма" +PlayerIdentityTag.No_Kill: "Без убийства" +PlayerIdentityTag.Outside_Position: "Внешняя позиция" +PlayerIdentityTag.Inside_Position: "Внутренняя позиция" # 通知 / Notifications -PlayerLeft: "[{0}] вышел из игры" -PlayerLeftCuzTimeout: "[{0}] вышел из игры из-за истечения времени подключения" -PlayerKickByHost: "[{0}] был исключен хостом" -PlayerBanByHost: "[{0}] был заблокирован хостом" -PlayerLeftCuzError: "[{0}] вышел из игры из-за ошибки" -PlayerLeftByAU-Anticheat: "[{0}] был исключен системой античита AmongU (не связано с FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] был исключен, потому что у него была другая версия мода" +## 原版 +Notification.PlayerLeft: "[{0}] покинул(а) игру" +Notification.PlayerLeftCuzTimeout: "[{0}] покинул(а) игру из-за тайм-аута соединения" +Notification.PlayerKickByHost: "[{0}] был(а) выгнан(а) хостом" +Notification.PlayerBanByHost: "[{0}] был(а) забанен(а) хостом" +Notification.PlayerLeftCuzError: "[{0}] покинул(а) игру из-за ошибки" +Notification.PlayerLeftByAU-Anticheat: "[{0}] был(а) выгнан(а) античитом Among Us (не связано с FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] был(а) выгнан(а) из-за другой версии/мода" +## 模组 +Notification.KickedByDenyName: "[{0}] был(а) выгнан(а) за запрещённое имя" +Notification.DumpfileSaved: "Журнал сохранён на рабочий стол: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] был(а) удалён(а): запрещены подозрительные коды друга" +Notification.AddedPlayerToBanList: "Добавлен(а) [{0}] в чёрный список" +Notification.FPSSetTo: "Ограничение FPS установлено: {0}" # 警告 / Warnings -Warning: "Предупреждение!" -Warning.MismatchedVersion: "{0}\nимеет другую версию {1}" -Warning.AutoExitAtMismatchedVersion: "У хоста нет или есть другая версия {0}\nВы будете исключены через {1}" -Warning.InvalidRpc: "{0} был исключен, потому что был получен недействительный RPC." -Warning.InvalidRpc_NotHost: "{0} подозревается в использовании читов. Пожалуйста, попросите хоста исключить его (Недействительный RPC: {1})" -Warning.SetName: "{0} был исключен, потому что он несколько раз устанавливал имя." -Warning.SetName_NotHost: "{0} подозревается в использовании читов. Пожалуйста, попросите хоста исключить его (Установка имени несколько раз)" -Warning.SendQuickChat: "{0} был исключен, потому что он отправил несколько быстрых сообщений в течение 3 секунд" -Warning.SendQuickChat_NotHost: "{0} подозревается в использовании читов. Пожалуйста, попросите хоста исключить его (Отправка нескольких быстрых сообщений в течение 3 секунд)" -Warning.InvalidSlothRPC: "Игрок {0} был исключен, так как был получен незаконный RPC (Незаконная отправка официального Rpc: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} подозревается в использовании читов. Пожалуйста, попросите хоста исключить его (Незаконная отправка официального RPC: {1})" -Warning.Cheater: "Игрок {0} был исключен, так как подозревается в использовании читов" -Warning.Cheater_NotHost: "Игрок {0} подозревается в использовании читов. Пожалуйста, попросите хоста исключить его" -Warning.CantKickDev: "Извините, вы не можете исключить разработчика" -Warning.RoomBroken: "Извините, эта комната была взломана. Пожалуйста, перейдите в другую комнату, чтобы продолжить игру." +Warning: "Предупреждение" +Warning.MismatchedVersion: "[{0}]\nустановил(а) другую версию [{1}]" +Warning.AutoExitAtMismatchedVersion: "Ваша версия [{0}] отличается от хоста\nВы будете выгнаны через {1} сек." +Warning.CantKickDev: "Извините, разработчиков выгнать нельзя" +Warning.RoomBroken: "Извините, комната была взломана, играйте в другой" +Warning.InvalidColor: "Обнаружен игрок с недопустимым цветом" ## 错误等级 / Error Levels -ErrorLevel1: "Могут возникнуть ошибки." -ErrorLevel2: "Это может быть ошибка." -ErrorLevel3: "Эта версия не должна была быть выпущена." +ErrorLevel1: "Может вызвать множество багов одновременно" +ErrorLevel2: "Возможны баги" +ErrorLevel3: "Невыпущенная версия" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Предупреждение: FAC обнаружил высокий уровень читов." -FAC.CheatDetected.LowLevel: "Предупреждение: FAC обнаружил низкий уровень читов. Один из игроков использует читы." -FAC.CheatDetected.FAC: "Использование чит-программ (например, AUM, YuMenu, SM и т. д.)" +CheatDetected.HighLevel: "Предупреждение: FAC защищает от спам-читов" +CheatDetected.LowLevel: "Предупреждение: FAC подозревает читера" +CheatDetected.UseCheat: "{0} использовал чит [{1}]" +CheatDetected.MayUseCheat: "{0} подозревается в использовании мода [{1}] или чита" +CheatDetected.InvalidRpc: "[{0}] выгнан за отправку неверных данных (Неверный Rpc: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] подозревается в читах, попросите хоста выгнать (Неверный Rpc: {1})" +CheatDetected.SetName: "[{0}] выгнан за многократную смену имени" +CheatDetected.SetName_NotHost: "[{0}] подозревается в читах, попросите хоста выгнать (Многократная смена имени)" +CheatDetected.SendQuickChat: "[{0}] выгнан за спам быстрыми сообщениями за 3 сек" +CheatDetected.SendQuickChat_NotHost: "[{0}] подозревается в читах, попросите хоста выгнать (Спам быстрых сообщений за 3 сек)" +CheatDetected.InvalidSlothRPC: "[{0}] выгнан за отправку нелегальных данных (Нелегальный оф. Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] подозревается в читах, попросите хоста выгнать (Нелегальный оф. Rpc: {1})" +CheatDetected.Overload: "[{0}] выгнан за попытку перегрузки" +CheatDetected.Overload_NotHost: "[{0}] запустил атаку перегрузки, комната повреждена; играйте в другой" +CheatDetected.Cheater: "[{0}] выгнан за подозрение в читах" +CheatDetected.Cheater_NotHost: "[{0}] подозревается в читах, попросите хоста выгнать" +CheatDetected.BanedByBanList: "[{0}] выгнан: находится в чёрном списке" +CheatDetected.BanedByFACList: "[{0}] выгнан: находится в чёрном списке FAC" # 模组信息 / Mod Infos -Contributors: "Участники" -Acknowledgement: "Благодарности" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Вы были исключены системой античита.\n(использование модов может быть ошибочно расценено как читы)" -DCNotify.Banned: "Вам запрещено входить в эту комнату" -DCNotify.Kicked: "Вы были исключены из комнаты" -DCNotify.DCFromServer: "Вы отключились от сервера.\nЭто может быть из-за нестабильности вашей сети.\nЭто также может быть из-за нестабильности сервера." -DCNotify.GameNotFound: "Не удалось найти назначенную комнату, комната может быть распущена\nили проверьте, не выбрали ли вы другой сервер, отличный от комнаты" -DCNotify.GameStarted: "Игра уже началась, пожалуйста, подождите, пока она закончится" -DCNotify.GameFull: "Комната заполнена, пожалуйста, попробуйте позже" -DCNotify.IncorrectVersion: "Ваша версия Among Us отличается от этой комнаты" -DCNotify.Description: "Вы были исключены из игры.\nПричина: {0}" -DCNotify.DenyName: "Ваш никнейм содержит неправильные символы" -DCNotify.BanList: "Вы были заблокированы хостом" -DCNotify.FACList: "Вы были заблокированы FAC" -DCNotify.CheatDetected: "Вы были обнаружены как подозреваемый в читах FAC" -DCNotify.InvalidRPC: "Вы, возможно, установили другой мод, чем у хоста, или ваш мод был изменен с умыслом" -DCNotify.ModVersionIncorrect: "Ваша версия мода отличается от хоста" -DCNotify.LowLevel: "Ваш уровень не соответствует требованиям этой комнаты" -DCNotify.NotLogin: "Неавторизованным игрокам не разрешен вход в эту комнату" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Нажмите F1, чтобы показать описание вашей роли" -FakeTask: "Ложная задача:" +ModInfo.Contributors: "Участники" +ModInfo.Acknowledgement: "Особая благодарность" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Вы были выгнаны античитом InnerSloth" +DCNotify.Banned: "Вы были забанены в этой комнате" +DCNotify.Kicked: "Вы были выгнаны из комнаты" +DCNotify.DCFromServer: "Соединение с сервером прервано\nПричина может быть в нестабильной сети\nили проблемах/отказе сервера" +DCNotify.GameNotFound: "Комната не найдена, возможно, закрыта\nили выбран другой сервер" +DCNotify.GameStarted: "Игра уже началась, дождитесь окончания" +DCNotify.GameFull: "Комната заполнена, попробуйте позже" +DCNotify.IncorrectVersion: "Ваша версия Among Us отличается от комнаты" +DCNotify.Description: "Вы были выгнаны из комнаты\nПричина: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Нажмите F1 для описания роли" +PressF2ToHidePane: "F2 — показать/скрыть панель" +FakeTask: "Ложное задание:" KillCount: "Убийства" # 复盘信息 / Last Results -RoleSummaryText: "Последние результаты:" -ShowResults: "Показать последние результаты" -HideResults: "Скрыть последние результаты" -NoInfoExists: "Нет доступной информации о последних результатах" -CrewsWin: "Победа команды Членов Экипажа" -CrewmatesWin: "Победа Членов Экипажа" -CrewmatesWinBlurb: "Свет истины сияет в надежде!" -ImpsWin: "Победа команды Импосторов" -ImpostorsWin: "Победа Импосторов" -ImpostorsWinBlurb: "Зло превращает истину в пепел" -HideSummaryTextToShowWinText: "Скрыть последние результаты, чтобы показать текст победы" - -# 禁用公开 -DisabledByProgram: "Публичные операции были отключены программой" -PublicNotAvailableOnThis Version: "Публичные комнаты не доступны в этой версии FinalSuspect" +Summary.Text: "Итоговый отчёт:" +Summary.ShowResults: "Показать итог" +Summary.HideResults: "Скрыть итог" +Summary.NoInfoExists: "Итоговый отчёт отсутствует" +Summary.CrewsWin: "Победа Команды-Членов Экипажа" +Summary.ImpsWin: "Победа Команды-Предателей" +Outro.Crews_Win: "Победа Членов Экипажа" +Outro.Crews_WinBlurb: "Свет истины сияет надеждой!" +Outro.Imps_Win: "Победа Предателей" +Outro.Imps_WinBlurb: "Зло превратило истину в пепел" # 主页 / Main UI -FinalSuspectWelcomeText: "Желаем вам приятной игры!" -ConnectToFinalSuspectServerFailed: "Не удалось подключиться к серверу FinalSuspect" +FinalSuspectWelcomeText: "Желаем вам приятной игры!" +RetrieveVersionInfoFailed: "Не удалось получить информацию FinalSuspect" Website: "Официальный сайт" MainMenuCredential: "{0} © 2025" -LShift: "Нажмите LShift чтобы вернуться в предыдущую комнату" -RShift: "Нажмите RShift чтобы перейти в комнату буфера обмена" -LobbyTimeDisplayText: "Прошедшее время существования" +LShift: "Предыдущее лобби" +RShift: "Лобби из буфера" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Пинг" -FrameRate: "Кадров в секунду" +FrameRate: "FPS" Server: "Сервер" -Local: "Локально" +Local: "Локальный" # 其他 / Other HongKong: "Гонконг" -FPSSetTo: "Ограничение кадров установлено на: {0}" BrowsingMode: "Режим просмотра" -Broken: "Повреждено" - -# 加载 / Loading -LanguageFilesLoadingComplete: "Загрузка переводов завершена!" -CheckingForFiles: "Проверка целостности файлов ресурсов..." -DownloadingResources: "Загрузка файлов ресурсов..." -Loading: "Загрузка" -LoadingWithDot: "Загрузка..." -LoadingComplete: "Загрузка завершена!" +Broken: "Сломано" +Unknown: "Неизвестно" +Back: "Назад" +Yes: "Да" +No: "Нет" +Cancel: "Отмена" +Unload: "Переключить" +Retry: "Повторить" +PreviousPage: "Предыдущая страница" +NextPage: "Следующая страница" +Download: "Скачать" +Disable: "Отключить" +Delete: "Удалить" +Author: "Автор" +Close: "Закрыть" # 身份 / Identity -Host: "Хост" -Cheater: "Читер" +Id.Host: "Хост" +Id.Cheater: "Читер" +Id.Developer: "Разработчик" +Id.Contributor: "Участник" \ No newline at end of file diff --git a/Assets/Languages/SChinese.yaml b/Assets/Languages/SChinese.yaml index f9836973..12561900 100644 --- a/Assets/Languages/SChinese.yaml +++ b/Assets/Languages/SChinese.yaml @@ -1,54 +1,49 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "13" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "伪装者职业" -TypeCrewmate: "船员职业" +RoleType.Imp: "伪装者职业" +RoleType.Crew: "船员职业" # 阵营 / Teams -TeamImpostor: "伪装者阵营" -TeamImpostorOnly: "伪装者" -TeamCrewmate: "船员阵营" +Team.Imp: "伪装者阵营" +Team.Imp_Only: "伪装者" +Team.Crew: "船员阵营" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "团队之中有{0}名伪装者" -ImpostorNumImpOnly: "人群之中仅有1名伪装者" -ImpostorNumCrew: "我们之中有{0}名伪装者" +ImpostorNum.Imp: "团队之中有{0}名伪装者" +ImpostorNum.Imp_Only: "人群之中仅有1名伪装者" +ImpostorNum.Crew: "我们之中有{0}伪装者" # 阵营开场 -ImpostorIntroText: "让邪恶的力量笼罩整个世界!" -ImpostorIntroTextOnly: "尽管独自一人,仍有无穷力量!" -CrewmateIntroText: "完成你的任务,揪出那群坏蛋!" - -## 原版职业 / Vanilla -Crewmate: "船员" -Engineer: "工程师" -Scientist: "科学家" -Tracker: "侦查员" -Noisemaker: "大嗓门" -GuardianAngel: "守护天使" -Impostor: "伪装者" -Shapeshifter: "变形者" -Phantom: "幻象师" -CrewmateGhost: "船员灵魂" -ImpostorGhost: "伪装者灵魂" +IntroText.Imp: "让邪恶的力量笼罩整个世界!" +IntroText.Imp_Only: "尽管独自一人,仍有无穷力量!" +IntroText.Crewmate: "完成你的任务,揪出那群坏蛋!" + +# 原版职业 / Vanilla +Role.Crewmate: "船员" +Role.Engineer: "工程师" +Role.Scientist: "科学家" +Role.Tracker: "侦查员" +Role.Noisemaker: "大嗓门" +Role.GuardianAngel: "守护天使" +Role.Impostor: "伪装者" +Role.Shapeshifter: "变形者" +Role.Phantom: "幻象师" +Role.CrewmateGhost: "船员灵魂" +Role.ImpostorGhost: "伪装者灵魂" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 CrewmateGhostBlurb: "完成任务" ImpostorGhostBlurb: "继续破坏设施" CrewmateGhostBlurbLong: "完成任务,别拖后腿!\n使用「幽幽鬼影」查看伪装者,帮助守护天使守护船员!" ImpostorGhostBlurbLong: "破坏设施,协助存活的队友取得胜利!" - -## 捉迷藏 / HnS -HnSEngineerBlurb: "活到最后!" -HnSEngineerBlurbLong: "坚持活下来,直到时间结束,完成任务可以推进时间。\n利用通风口和威胁指示器躲藏!\n开始计时后,伪装者会开始追杀你!" -HnSImpostorBlurb: "杀光所有人!" -HnSImpostorBlurbLong: "在规定时间内,杀死所有船员!\n必须尽快行动!并且无法进入通风口。\n在最后关头,你将获得速度提升和行踪提示!" +HnSEngineerBlurb: "完成任务!活到最后!" +HnSImpostorBlurb: "将这一切屠戮殆尽!" HnSCrewmateGhostBlurb: "给你的队友加油打气" HnSCrewmateGhostBlurbLong: "死了还看啥呀,你有任务能做吗?既然没有...\n去!给!你!队!友!加!油!" @@ -59,121 +54,161 @@ DeathReason.Disconnect: "断连" # 客户端选项 / Client Options FinalSuspectOptions: "Final Suspect 选项" -Back: "返回" -Yes: "是" -No: "否" -UnlockFPS: "解锁帧数限制" -ChangeOutfit: "切换形象" -BeanMode: "经典豆子模式" -HorseMode: "愚人节牧马模式" -LongMode: "愚人节长颈豆模式" -KickPlayerFriendCodeNotExist: "踢出没有登录的玩家" -KickPlayerWithDenyName: "踢出使用违规昵称的玩家" -KickPlayerInBanList: "踢出被封禁的玩家" -SpamDenyWord: "屏蔽违禁词" -AutoStartGame: "人满自动开始游戏" -AutoEndGame: "结束时自动返回大厅" -SwitchVanilla: "切换为原版" -DisableVanillaSound: "禁用原版游戏音乐" -DisableFAC: "禁用反作弊" -ShowPlayerInfo: "展示玩家平台与客户端信息" -UseModCursor: "使用模组鼠标光标" -FastBoot: "快速启动模式" -PrunkMode: "恶搞模式" -VersionCheat: "绕过版本同步检查" -GodMode: "上帝模式" -NoGameEnd: "测试模式" -EnableFinalSuspect: "启用「终极嫌疑」" +ClientOption.UnlockFPS: "解锁帧数限制" +ClientOption.SwitchOutfitType: "切换外观形象" +ClientOption.KickPlayerWithAbnormalFriendCode: "踢出好友代码异常的玩家" +ClientOption.KickPlayerWithDenyName: "踢出使用违规昵称的玩家" +ClientOption.KickPlayerInBanList: "踢出被封禁的玩家" +ClientOption.SpamDenyWord: "屏蔽违禁词" +ClientOption.AutoStartGame: "人满自动开始游戏" +ClientOption.AutoEndGame: "结束时自动返回大厅" +ClientOption.SwitchVanilla: "切换为原版" +ClientOption.DisableVanillaSound: "禁用原版游戏音乐" +ClientOption.EnableFAC: "启用反作弊" +ClientOption.EnableGuardian: "启用客户端守护(试验性)" +ClientOption.ShowPlayerInfo: "展示玩家平台与客户端信息" +ClientOption.UseModCursor: "使用模组鼠标光标" +ClientOption.FastLaunchMode: "快速启动模式" +ClientOption.OfflineMode: "离线模式(试验性)" +ClientOption.VersionCheat: "绕过版本同步检查" +ClientOption.GodMode: "上帝模式" +ClientOption.NoGameEnd: "测试模式" +ClientOption.EnableFinalSuspect: "启用「终极嫌疑」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "经典豆子模式" +Value.HorseMode: "愚人节牧马模式" +Value.LongMode: "愚人节长颈豆模式" # 客户端功能 / Client Features FinalSuspectFeatures: "Final Suspect 功能" -UnloadMod: "切换原版" -UnloadWarning: "警告\n如果您想要回到模组,您必须重启游戏\n您确定要切换为原版吗?" -CannotUnloadDuringGame: "游戏中不能切换为原版" -Cancel: "取消" -Unload: "切换" -DumpLog: "输出日志" -ClearAutoLogs: "清空自动日志" -SoundOptions: "我的音乐" -AudioManagementOptions: "音频管理" -OnlyAvailableInMainMenu: "仅在主页使用" +ClientFeature.UnloadMod: "切换原版" +ClientFeature.DumpLog: "输出日志" +ClientFeature.ClearAutoLogs: "清空自动日志" +ClientFeature.MyMusic: "我的音乐" +ClientFeature.ResourceManager: "资源管理" +ClientFeature.NameTagManager: "名称标识管理" +ClientFeature.MainMenuStyleManager: "切换主页风格" # 提示 / Tips -updatePleaseWait: "请稍候……" -updateInProgress: "更新中……" -DownloadingAudios: "下载中……" -Playing: "播放中……" -Parsing: "解析中……" -DownLoadFinishNotice: "下载完成!" -DownLoadSucceedNotice: "下载成功!" -DownLoadFailureNotice: "下载失败=(" -PleaseWait: "请稍候……" -LanguageFilesLoadingComplete: "语言文件加载完成" -CheckingForFiles: "校验资源文件完整性……" -DownloadingResources: "下载资源文件……" -Loading: "加载中" -LoadingWithDot: "加载中……" -LoadingComplete: "加载完成!" +Tip.Downloading: "下载中……" +Tip.PleaseWait: "请稍候……" +Tip.Playing: "播放中……" +Tip.Parsing: "解析中……" +Tip.Updating: "更新中……" +Tip.DownLoadFinished: "下载完成" +Tip.DownLoadSucceeded: "下载成功!" +Tip.DownLoadFailed: "下载失败=(" +Tip.PackageExists: "资源包已安装" +Tip.OnlyAvailableInMainMenu: "仅在主页使用" +Tip.HideSummaryTextToShowWinText: "隐藏复盘信息查看胜利文本" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "语言文件加载完成" +Tip.CheckingForFiles: "校验资源文件完整性……" +Tip.DownloadingResources: "下载资源文件……" +Tip.Loading: "加载中" +Tip.LoadingWithDot: "加载中……" +Tip.LoadingComplete: "加载完成!" +## 切换原版 +Tip.UnloadWarning: "警告\n如果您想要回到模组,您必须重启游戏\n您确定要切换为原版吗?" +Tip.CannotUnloadDuringGame: "游戏中不能切换为原版" +## 资源管理 +Tip.ResourceManager: "您可以在「资源管理」中下载本模组的配套以及扩展资源\n以\"Pre-\"开头的文件为预下载资源包" +## 我的音乐 +Tip.MyMusic: "您可以在「资源管理」中下载本模组支持的音乐或在文件夹(Among Us/Final Suspect_Data/Musics)中添加自己喜爱的音频文件,如果音乐本地资源路径不存在则会显示「文件丢失」" +Tip.Incomplete_Music: "检测到当前音乐文件不完整" +Tip.Incomplete_SoundEffect: "检测到当前音效文件不完整" +Tip.Incomplete_Image: "检测到当前音乐文件不完整" +Tip.Incomplete: "如果您希望提高您的游戏体验,请在「主页-设置-客户端功能-资源管理」下载模组配套资源包" +## 名称标识管理 +Tip.TextContent: "文本内容" +Tip.TextSizeDescription: "文本大小(默认100%)" +Tip.TextColorDescription: "文本颜色(Hex 颜色代码)\n不需要请留空,填写多个则自动渐变\n" +Tip.PleaseEnterFriendCode: "请输入新名称标识绑定的好友代码" +Tip.FriendCodeAlreadyExist: "该好友代码已拥有名称标识" +Tip.FriendCodeIncorrect: "请输入正确的好友代码" +Tip.CustomNameTagHelp: "您可以为任意玩家添加名称标识,标识会在绑定好友代码的玩家加入后自动发放,进入房间后不能编辑标识\n非会员用户仅可添加备注(会员功能暂未开发)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "您可以在「资源管理」中下载「主页风格包」以获取主页资源,并在此处选择您喜爱的主页风格" + +# 更新结果 +UpdateResult.Succeed_Title: "更新成功" +UpdateResult.Succeed_Text: "重启游戏后生效 :)" +UpdateResult.Failed_Title: "更新失败" +UpdateResult.Failed_Reason_NotFound: "原因:{0}\n该渠道可能暂时停用,请更换下载渠道重试" +UpdateResult.Failed_Reason_FileMd5Incorrect: "原因:文件校验错误\n该渠道的文件版本并不是最新的,请切换下载渠道重试" +UpdateResult.Failed_Reason_Ping: "原因:下载更新文件超时或中断\n请更换网络后重试或手动更新" # 更新检查 / Update Checker -Retry: "重试" -updateCheckPopupTitle: "更新检查" -updateCheckFailedRetry: "检查更新失败 :(\n要重试吗?\n或者试试关掉你的加速器?" -updateCheckFailedExit: "检查更新失败 :(\n请检查您的网络强度后重试\n或者试试关掉你的加速器!" +UpdateCheck.Popup_Title: "更新检查" +UpdateCheck.Failed_Retry: "检查更新失败 :(\n要重试吗?\n试试关掉你的加速器?" +UpdateCheck.Failed_Exit: "检查更新失败 :(\n请检查您的网络强度后重试\n试试关掉你的加速器!" # 更新提醒 / Update Reminder -updateBySelfTitle: "更新提示" -updateButton: "一键更新" -updatePopupTitle: "一键更新" -updatePopupTitleFailed: "更新失败" -updatePopupTitleDone: "更新完成" -updateNotice: "更新提醒" -UpdateBySelfText: "该版本不支持一键更新,请您手动更新" +UpdateRemind.updatePopup: "一键更新" +UpdateRemind.updateNotice: "更新提醒" +UpdateRemind.BySelf_Title: "更新提示" +UpdateRemind.BySelf_Text: "该版本不支持一键更新,请您手动更新" # 选择更新渠道 / Update Chose Source -updateChoseSource: "请选择更新渠道\n如果您不知道选什么,请选择【Gitee】或者【Api(境内首选)】" -updateSource.Github: "Github(境外首选)" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api(境内首选)" - -# 更新结束提示 / Update completion prompts -updateRestart: "重启游戏后生效 :)" -updatePingFialed: "原因:{0}\n该渠道可能暂时停用,请更换下载渠道重试" -updateFileMd5Incorrect: "原因:文件校验错误\n该渠道的文件版本并不是最新的,请切换下载渠道重试" -downloadFailed: "原因:下载更新文件超时或中断\n请更换网络后重试或手动更新" +UpdateSource.Choose: "请选择更新渠道\n如果您不知道选什么,请选择【Gitee】或者【Api(境内首选)】" +UpdateSource.Github: "Github(境外首选)" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api(境内首选)" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "我们有一个重要的更新,请更新本模组\n否则您无法公开房间" CanNotJoinPublicRoomNoLatest: "我们有一个重要的更新,请更新本模组\n否则您无法加入公开房间" ModBrokenMessage: "模组文件损坏,请重启游戏或重装本模组" UnsupportedVersion: "您的 AmongUs 版本与 FinalSuspect 不兼容\n请更新游戏" -# 音频播放 / Audio Playback -PlayMode0: "单次播放" -PlayMode1: "单曲循环" -PlayMode2: "列表随机" -PlayMode3: "顺序播放" -Stop: "停止播放" -CanPlay: "←点击播放" -NoFound: "【文件丢失】" -NextPage: "下一页" -PreviousPage: "上一页" - -# 音频添加 / Audio Addition -download: "下载" -delete: "删除" -NewSound: "新增音乐" -PleaseEnterMusic: "请输入音乐名" -AudioManagementAlreadyExists: "该音乐名已存在" -NotAllowedMusic: "音乐名称格式不合规" +# 名称标识 +NameTag.DisplayName: "备注" +NameTag.Title: "头衔" +NameTag.Prefix: "前缀" +NameTag.Suffix: "后缀" +NameTag.Name: "名称" +NameTag.LastTag: "附加缀" +NameTag.PreviewNotAvailable: " (不支持预览) " +NameTag.CanNotEdit: " (不可修改) " +NameTag.RefreshPreview: "刷新预览" +NameTag.SaveAndClose: "保存并退出" +NameTag.NewNameTag: "新建" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "逐光寻晓(FS经典背景)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "随着时间的推移,北半球的冬天迎来尾声,春意正在各处复苏。\n眺望米拉的雪景,内心中的回忆是否唤醒?暖流依旧心中流淌。\n无论嫌疑最终落在谁身,无论真理最后破碎于谁手,\n在焕然一新的体验中,头脑风暴,留下全新的快乐回忆。\n玩游戏,开心最重要!!!\n\n命名:一念旧情丶" +MainMenuStyle.Title_Security: "赤心炽爱(TONEX经典背景)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "我们不会就此跌下、不会就此放弃、不会让玩家失望、更不会停止进步\n若有人冷眼相待,我们会证明给所有人看!\n伴着热爱,我们再次起航\n\n命名:Slok" +MainMenuStyle.Title_NewYear: "祉缘谐乐(新春特别节目)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "祉,祝予福 缘,织于心\n谐,连与情 乐,安喻人\n挚缘,创谐乐 致远,谐得乐\n至元,皆怀乐 只愿,人皆乐\n愿每个人在新的一年都能寻得自己的道路\n带着我们的祝福,勇往直前,追寻属于自己的人生吧!\n\n命名:Slok" +MainMenuStyle.Title_MiraStudio: "极致演播厅(新春特别节目)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"——花开似浪,温柔如风,愿大家心海如风,事事如意\"\n\"——春风浩荡,浪涌千里,愿大家勇立潮头,蒸蒸日上\"\n\"——欢迎来到XtremeWave 极致狂澜——新春特别节目!\"\n\n命名:Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave 极致狂澜" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「极行致远,梦领狂澜!」\n\n命名:Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "回望末端时(联动背景)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「回望末端时,终尽于末端」\n\n命名:MAMTI.麦麦头" +MainMenuStyle.NotFound: "未下载" +MainMenuStyle.NotApply: "应用" +MainMenuStyle.Applied: "应用中" + +# 资源包 +Package.MainMenuStyle: "主页风格包" -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "您可以在「音频管理」中下载XtremeWave支持的音乐或添加自己的音乐。当您添加自己的音乐时,请您在「音频管理」中添加音乐名并在路径“Among Us/Final Suspect_Data/Resources/Sounds”文件夹内添加同名音频文件(支持格式:.wav)。音乐可在「我的音乐」中播放。" -CustomSoundHelp: "您可以在「音频管理」中下载XtremeWave支持的音乐或添加自己的音乐,如果音乐本地资源路径不存在则会显示「【文件丢失】」" - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "检测到当前音乐文件不完整" -AudioNYPro: "如果您希望提高您的游戏体验,请在「主页-设置-更多功能-音频管理」下载我们的音频" +# 音频播放 / Audio Playback +MusPlay.Mode0: "单次播放" +MusPlay.Mode1: "单曲循环" +MusPlay.Mode2: "列表随机" +MusPlay.Mode3: "顺序播放" +MusPlay.Stop: "停止播放" +MusPlay.CanPlay: "点击播放" +MusPlay.NoFound: "文件丢失" # 官方音乐 / Musics Mus.GongXiFaCai: "恭喜发财" @@ -186,44 +221,46 @@ Mus.Fractured: "破碎" Mus.ElegyOfFracturedVow: "誓碎终殇" Mus.VestigiumSplendoris: "余璨" Mus.ReturnToSimplicity: "返璞归真" +Mus.ReturnToSimplicity2: "返璞归真(完整版)" +Mus.ChasingDawn: "逐光寻晓" +Mus.StruggleAgainstFadingFlame: "峙燃绝焰" Mus.Affinity: "祉缘谐乐" -Mus.Inceps_Plus_InProgress: "始 + 进行时" - -# 信息 / Messages -Message.KickedByDenyName: "【{0}】被踢出,因其昵称包含违禁词【{1}】" -Message.BanedByBanList: "【{0}】因在封禁名单内被踢出" -Message.BanedByFACList: "【{0}】因在FAC封禁名单内被踢出" -Message.DumpfileSaved: "日志文件已成功保存到桌面上了,文件名为:{0}" -Message.KickedByNoFriendCode: "【{0}】因该房禁止未登录玩家被请离" -Message.AddedPlayerToBanList: "【{0}】被添加至黑名单" -Message.KickedByFAC: "【{0}】被FAC踢出,理由:{1}" -Message.BanedByFAC: "【{0}】被FAC封禁,理由:{1}" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "职业" +DisplayedRoleTag.PlayerIdentityTag: "身份标记" +DisplayedRoleTag.Room: "房间" + +PlayerIdentityTag.Hard_Cleared: "金水" +PlayerIdentityTag.Silver_Clear: "银水" +PlayerIdentityTag.Wolf_Bucket: "狼坑" +PlayerIdentityTag.No_Kill: "不出刀" +PlayerIdentityTag.Outside_Position: "外置位" +PlayerIdentityTag.Inside_Position: "内置位" # 通知 / Notifications -PlayerLeft: "【{0}】离开了游戏" -PlayerLeftCuzTimeout: "【{0}】因连接超时离开了游戏" -PlayerKickByHost: "【{0}】被房主踢出游戏" -PlayerBanByHost: "【{0}】被房主封禁" -PlayerLeftCuzError: "【{0}】因为发生错误离开了游戏" -PlayerLeftByAU-Anticheat: "【{0}】被AmongUs官方的反作弊踢出(和FinalSuspect无关)" -KickBecauseDiffrentVersionOrMod: "【{0}】因安装了不同版本的模组被请离房间" +## 原版 +Notification.PlayerLeft: "【{0}】离开了游戏" +Notification.PlayerLeftCuzTimeout: "【{0}】因连接超时离开了游戏" +Notification.PlayerKickByHost: "【{0}】被房主踢出游戏" +Notification.PlayerBanByHost: "【{0}】被房主封禁" +Notification.PlayerLeftCuzError: "【{0}】因为发生错误离开了游戏" +Notification.PlayerLeftByAU-Anticheat: "【{0}】被AmongUs官方的反作弊踢出(和FinalSuspect无关)" +Notification.KickBecauseDifferentVersionOrMod: "【{0}】因安装了不同版本的模组被请离房间" +## 模组 +Notification.KickedByDenyName: "【{0}】因其昵称包含违禁词被踢出" +Notification.DumpfileSaved: "日志文件已成功保存到桌面上了,文件名为:{0}" +Notification.KickedByAbnormalFriendCode: "【{0}】因该房禁止好友代码异常的玩家被请离" +Notification.AddedPlayerToBanList: "【{0}】被添加至黑名单" +Notification.FPSSetTo: "帧数上限设置为: {0}" # 警告 / Warnings Warning: "警告" -Warning.MismatchedVersion: "{0}\n安装了其它版本的 {1}" -Warning.AutoExitAtMismatchedVersion: "您的 {0} 版本与房主不同\n您将在 {1} 秒内被踢出" -Warning.InvalidRpc: "{0} 因为发送了无效的数据被踢出(无效的Rpc:{1})" -Warning.InvalidRpc_NotHost: "{0} 疑似使用了作弊程序,请提醒房主及时将其踢出(无效的Rpc:{1})" -Warning.SetName: "{0} 因为多次设置名称被踢出" -Warning.SetName_NotHost: "{0} 疑似使用了作弊程序,请提醒房主及时将其踢出(多次设置名称)" -Warning.SendQuickChat: "{0} 因为三秒内发送多条快捷消息被踢出" -Warning.SendQuickChat_NotHost: "{0} 疑似使用了作弊程序,请提醒房主及时将其踢出(三秒内发送多条快捷消息)" -Warning.InvalidSlothRPC: "{0} 因为发送了非法数据被踢出(非法发送官方Rpc: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} 疑似使用了作弊程序,请提醒房主及时将其踢出(非法发送官方Rpc: {1})" -Warning.Cheater: "{0} 因为疑似使用作弊程序被踢出" -Warning.Cheater_NotHost: "{0} 疑似使用了作弊程序,请提醒房主及时将其踢出" +Warning.MismatchedVersion: "【{0}】\n安装了其它版本的 【{1}】" +Warning.AutoExitAtMismatchedVersion: "您的 【{0}】 版本与房主不同\n您将在 {1} 秒内被踢出" Warning.CantKickDev: "很抱歉,您无法踢出开发者" Warning.RoomBroken: "很抱歉,该房间遭受炸房攻击,请到其他房间进行游戏" +Warning.InvalidColor: "出现非法颜色玩家" ## 错误等级 / Error Levels ErrorLevel1: "可能同时产生多个bug" @@ -231,16 +268,30 @@ ErrorLevel2: "可能出现bug" ErrorLevel3: "未发布版本" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "警告:FAC正在抵御炸房外挂" -FAC.CheatDetected.LowLevel: "警告:FAC检测到可能存在作弊玩家" -FAC.CheatDetected.UsingCheat: "{0}使用作弊程序【{1}】" -FAC.CheatDetected.MayUseCheat: "{0}疑似使用模组【{1}】或作弊程序" +CheatDetected.HighLevel: "警告:FAC正在抵御炸房外挂" +CheatDetected.LowLevel: "警告:FAC检测到可能存在作弊玩家" +CheatDetected.UseCheat: "{0}使用作弊程序【{1}】" +CheatDetected.MayUseCheat: "{0}疑似使用模组【{1}】或作弊程序" +CheatDetected.InvalidRpc: "【{0}】 因为发送了无效的数据被踢出(无效的Rpc:{1})" +CheatDetected.InvalidRpc_NotHost: "【{0}】 疑似使用了作弊程序,请提醒房主及时将其踢出(无效的Rpc:{1})" +CheatDetected.SetName: "【{0}】 因为多次设置名称被踢出" +CheatDetected.SetName_NotHost: "【{0}】 疑似使用了作弊程序,请提醒房主及时将其踢出(多次设置名称)" +CheatDetected.SendQuickChat: "【{0}】 因为三秒内发送多条快捷消息被踢出" +CheatDetected.SendQuickChat_NotHost: "【{0}】 疑似使用了作弊程序,请提醒房主及时将其踢出(三秒内发送多条快捷消息)" +CheatDetected.InvalidSlothRPC: "【{0}】 因为发送了非法数据被踢出(非法发送官方Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "【{0}】 疑似使用了作弊程序,请提醒房主及时将其踢出(非法发送官方Rpc: {1})" +CheatDetected.Overload: "【{0}】因为发起了过载攻击被踢出" +CheatDetected.Overload_NotHost: "【{0}】发起了过载攻击,本房间损坏,请到其他房间进行游戏" +CheatDetected.Cheater: "【{0}】 因为疑似使用作弊程序被踢出" +CheatDetected.Cheater_NotHost: "【{0}】 疑似使用了作弊程序,请提醒房主及时将其踢出" +CheatDetected.BanedByBanList: "【{0}】因在封禁名单内被踢出" +CheatDetected.BanedByFACList: "【{0}】因在FAC封禁名单内被踢出" # 模组信息 / Mod Infos -Contributors: "贡献者" -Acknowledgement: "特别致谢" +ModInfo.Contributors: "贡献者" +ModInfo.Acknowledgement: "特别致谢" -# 断连提示 / Disconect Reasons +# 断连提示 / Disconnect Reasons DCNotify.Hacking: "您被树懒的反作弊系统踢出房间" DCNotify.Banned: "您被该房间封禁" DCNotify.Kicked: "您被该房间踢出" @@ -250,50 +301,37 @@ DCNotify.GameStarted: "该房间正在游戏中,请等待游戏结束后加入 DCNotify.GameFull: "该房间已满人,请稍后重试" DCNotify.IncorrectVersion: "您的Among Us版本与该房间不同" DCNotify.Description: "您被踢出房间\n理由:{0}" -DCNotify.DenyName: "您的昵称含有违规字符" -DCNotify.BanList: "您存在于该房间的黑名单" -DCNotify.FACList: "您已被 FAC 封禁" -DCNotify.CheatDetected: "您被 FAC 检测到疑似作弊行为" -DCNotify.InvalidRPC: "您可能安装了与房主不同的模组或您的模组被恶意修改" -DCNotify.ModVersionIncorrect: "您模组的版本与房主不同" -DCNotify.LowLevel: "您的等级未达到该房间的等级要求" -DCNotify.NotLogin: "该房间不允许未登录的玩家加入" - -# 任务与栏相关 / Task Panel + +# 任务栏相关 / Task Panel PressF1ShowRoleDescription: "按F1查看您的职业介绍" +PressF2ToHidePane: "按下F2显示/隐藏信息版" FakeTask: "假任务:" KillCount: "击杀" # 复盘信息 / Last Results -RoleSummaryText: "复盘信息:" -ShowResults: "显示复盘信息" -HideResults: "隐藏复盘信息" -NoInfoExists: "没有存在有效的复盘信息" -CrewsWin: "船员阵营胜利" -CrewmatesWin: "船员胜利" -CrewmatesWinBlurb: "真理的光芒在希望中闪耀!" -ImpsWin: "伪装者阵营胜利" -ImpostorsWin: "伪装者胜利" -ImpostorsWinBlurb: "邪恶将真理化为灰烬" -HideSummaryTextToShowWinText: "隐藏复盘信息查看胜利文本" - -# 禁用公开 / Disable Public -DisabledByProgram: "公开房间的操作已被程序禁用" -PublicNotAvailableOnThisVersion: "该版本的 FinalSuspect 暂不支持公开" +Summary.Text: "复盘信息:" +Summary.ShowResults: "显示复盘信息" +Summary.HideResults: "隐藏复盘信息" +Summary.NoInfoExists: "没有存在有效的复盘信息" +Summary.CrewsWin: "船员阵营胜利" +Summary.ImpsWin: "伪装者阵营胜利" +Outro.Crews_Win: "船员胜利" +Outro.Crews_WinBlurb: "真理的光芒在希望中闪耀!" +Outro.Imps_Win: "伪装者胜利" +Outro.Imps_WinBlurb: "邪恶将真理化为灰烬" # 主页 / Main UI FinalSuspectWelcomeText: "祝愿您拥有一场愉快的游戏体验!" -ConnectToFinalSuspectServerFailed: "无法连接至FinalSuspect服务器" +RetrieveVersionInfoFailed: "无法获取FinalSuspect信息" Website: "模组官网" MainMenuCredential: "{0} © 2025" -LShift: "按LShift返回上一个房间" -RShift: "按RShift进入剪贴板房间" -LobbyTimeDisplayText: "存在时间" +LShift: "上一个大厅" +RShift: "剪切板大厅" # 客户端平台 / Platform -IPhone: "苹果" -Android: "安卓" -MicrosoftStore: "微软" +Platform.IPhone: "苹果" +Platform.Android: "安卓" +Platform.MicrosoftStore: "微软" # 延迟显示 / Ping Tracker Ping: "延迟" @@ -303,11 +341,27 @@ Local: "本地" # 其他 / Other HongKong: "香港" -FPSSetTo: "帧数上限设置为: {0}" BrowsingMode: "浏览模式" Broken: "损坏" Unknown: "未知" +Back: "返回" +Yes: "是" +No: "否" +Cancel: "取消" +Unload: "切换" +Retry: "重试" +PreviousPage: "上一页" +NextPage: "下一页" +Download: "下载" +Disable: "禁用" +Delete: "删除" +Author: "作者" +Close: "关闭" +Clear: "清除" +Other: "其他" # 身份 / Identity -Host: "房主" -Cheater: "作弊者" \ No newline at end of file +Id.Host: "房主" +Id.Cheater: "作弊者" +Id.Developer: "开发者" +Id.Contributor: "贡献者" \ No newline at end of file diff --git a/Assets/Languages/Spanish.yaml b/Assets/Languages/Spanish.yaml index a0db648a..3f7050e0 100644 --- a/Assets/Languages/Spanish.yaml +++ b/Assets/Languages/Spanish.yaml @@ -1,314 +1,365 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "12" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "Impostor" -TypeCrewmate: "Tripulante" +RoleType.Imp: "Impostor" +RoleType.Crew: "Tripulante" # 阵营 / Teams -TeamImpostor: "Equipo-Impostor" -TeamImpostorOnly: "Impostor" -TeamCrewmate: "Equipo-Tripulante" +Team.Imp: "Equipo-Impostor" +Team.Imp_Only: "Impostor" +Team.Crew: "Equipo-Tripulante" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "Hay {0} Impostores en nuestro equipo" -ImpostorNumImpOnly: "Solo hay 1 Impostor en la multitud" -ImpostorNumCrew: "Hay {0} Impostores entre nosotros" +ImpostorNum.Imp: "Hay {0} impostores en nuestro equipo" +ImpostorNum.Imp_Only: "Solo hay 1 impostor entre nosotros" +ImpostorNum.Crew: "Hay {0} impostores entre nosotros" # 阵营开场 -ImpostorIntroText: "¡Que el mal envuelva el mundo!" -ImpostorIntroTextOnly: "¡Puede que esté solo, pero poseo una fuerza infinita!" -CrewmateIntroText: "Completa tus tareas, únite para resolver estas situaciones complicadas" - -## 原版职业 / Vanilla -Crewmate: "Tripulante" -Engineer: "Ingeniero" -Scientist: "Científico" -Tracker: "Rastreador" -Noisemaker: "Generador de Ruido" -GuardianAngel: "Ángel de la Guarda" -Impostor: "Impostor" -Shapeshifter: "Cambiante de Forma" -Phantom: "Fantasma" -CrewmateGhost: "Fantasma de Tripulante" -ImpostorGhost: "Fantasma de Impostor" -CrewmateGhostBlurb: "Completa tus tareas" -ImpostorGhostBlurb: "Continúa saboteando las instalaciones" -CrewmateGhostBlurbLong: "Completa las tareas, no te quedes atrás" -ImpostorGhostBlurbLong: "Sabotea las instalaciones, ayuda a los impostores sobrevivientes a alcanzar la victoria" - -## 捉迷藏 / HnS -HnSEngineerBlurb: "¡Sobrevive hasta el final!" -HnSEngineerBlurbLong: "¡Permanece vivo hasta que se acabe el tiempo; completar tareas extenderá el tiempo! \nUsa las ventanas de ventilación y los indicadores de amenaza para esconderte! \nUna vez que el temporizador comience, el Impostor comenzará a cazarte." -HnSImpostorBlurb: "¡Elimina a todos!" -HnSImpostorBlurbLong: "¡Mata a todos los miembros de la tripulación dentro del límite de tiempo! \n¡Debes actuar rápidamente! Las ventanas de ventilación no están disponibles. \nEn el último momento, recibirás un aumento de velocidad y pistas de seguimiento." +IntroText.Imp: "¡Que el mal DOMINE EL MUNDO!" +IntroText.Imp_Only: "¡Aunque estés solo, tienes un PODER INFINITO!" +IntroText.Crewmate: "¡Completa tus tareas y encuentra a los impostores!" + +# 原版职业 / Vanilla +Role.Crewmate: "Tripulante" +Role.Engineer: "Ingeniero" +Role.Scientist: "Científico" +Role.Tracker: "Rastreador" +Role.Noisemaker: "Creador de Ruido" +Role.GuardianAngel: "Ángel Guardián" +Role.Impostor: "Impostor" +Role.Shapeshifter: "Cambiapieles" +Role.Phantom: "Fantasma" +Role.CrewmateGhost: "Fantasma Tripulante" +Role.ImpostorGhost: "Fantasma Impostor" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 +CrewmateGhostBlurb: "Completar tareas" +ImpostorGhostBlurb: "Continuar saboteando" +CrewmateGhostBlurbLong: "¡Completa tus tareas y no retrases al equipo!\n¡Usa 「Acechar」 para ver a los impostores y ayuda a los Ángeles Guardianes a proteger a los tripulantes!" +ImpostorGhostBlurbLong: "¡Sabotea las instalaciones y ayuda a los impostores supervivientes a ganar!" +HnSEngineerBlurb: "¡Completa tareas y sobrevive hasta el final!" +HnSImpostorBlurb: "¡MASACRA A TODOS!" HnSCrewmateGhostBlurb: "¡Anima a tus compañeros!" -HnSCrewmateGhostBlurbLong: "¡Vamos! ¡Anímales! ¡Por! ¡Tus! ¡Compañeros!" +HnSCrewmateGhostBlurbLong: "¿Muerto? ¿Qué miras? ¿Puedes hacer tareas? Pues no...\n¡VE! ¡ANIIMA! ¡A! ¡TUS! ¡COMPAÑEROS!" # 死因 / Death Reason -DeathReason.Kill: "Muerto" +DeathReason.Kill: "Asesinado" DeathReason.Exile: "Exiliado" DeathReason.Disconnect: "Desconectado" # 客户端选项 / Client Options FinalSuspectOptions: "Opciones de Final Suspect" -Back: "Atrás" -Yes: "Sí" -No: "No" -UnlockFPS: "Desbloquear FPS" -ChangeOutfit: "Cambiar Traje" -BeanMode: "Modo Clásico de Frijol" -HorseMode: "Modo Caballo de Abril" -LongMode: "Modo Largo de Abril" -KickPlayerFriendCodeNotExist: "Expulsar a los jugadores que no están conectados." -KickPlayerWithDenyName: "Expulsar a los jugadores con nombres inapropiados" -KickPlayerInBanList: "Expulsar a los jugadores baneados" -SpamDenyWord: "Bloquear palabras inapropiadas" -AutoStartGame: "Iniciar automáticamente cuando el lobby esté lleno" -AutoEndGame: "Volver automáticamente al lobby al final" -SwitchVanilla: "Cambiar a la versión original" -DisableVanillaSound: "Deshabilitar música de Among Us" -DisableFAC: "Deshabilitar anti-trampas" -ShowPlayerInfo: "Mostrar información de plataforma y cliente del jugador" -UseModCursor: "Usar cursor del mod" -FastBoot: "Modo de inicio rápido" -PrunkMode: "Modo Broma" -VersionCheat: "Ignorar verificación de sincronización de versión del mod" -GodMode: "Modo Dios" -NoGameEnd: "Sin final del juego" -EnableFinalSuspect: "Habilitar「Final Suspect」" +ClientOption.UnlockFPS: "Desbloquear FPS" +ClientOption.SwitchOutfitType: "Cambiar Tipo de Vestimenta" +ClientOption.KickPlayerWithAbnormalFriendCode: "Expulsar jugadores con códigos de amigo anormales" +ClientOption.KickPlayerWithDenyName: "Expulsar jugadores con apodos prohibidos" +ClientOption.KickPlayerInBanList: "Expulsar jugadores bloqueados" +ClientOption.SpamDenyWord: "Bloquear palabras prohibidas" +ClientOption.AutoStartGame: "Iniciar automáticamente cuando esté lleno" +ClientOption.AutoEndGame: "Regresar automáticamente al lobby al terminar" +ClientOption.SwitchVanilla: "Cambiar a Vanilla" +ClientOption.DisableVanillaSound: "Desactivar música del juego Vanilla" +ClientOption.EnableFAC: "Activar Anti-Cheat" +ClientOption.EnableGuardian: "Activar Guardián Cliente (Experimental)" +ClientOption.ShowPlayerInfo: "Mostrar plataforma e info del cliente" +ClientOption.UseModCursor: "Usar cursor mod" +ClientOption.FastLaunchMode: "Modo Lanzamiento Rápido" +ClientOption.OfflineMode: "Modo sin conexión (Experimental)" +ClientOption.VersionCheat: "Evitar verificación de versión" +ClientOption.GodMode: "Modo Dios" +ClientOption.NoGameEnd: "Sin fin de juego" +ClientOption.EnableFinalSuspect: "Activar 「Final Suspect」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "Modo Clásico" +Value.HorseMode: "Modo Caballo de April Fools" +Value.LongMode: "Modo Largo de April Fools" # 客户端功能 / Client Features -FinalSuspectFeatures: "Características de Final Suspect" -UnloadMod: "Cambiar a la versión original" -UnloadWarning: "Advertencia\nPara reactivar el mod, tendrás que reiniciar el juego.\n¿Quieres continuar de todos modos?" -CannotUnloadDuringGame: "No se puede cambiar a la versión original durante el juego" -Cancel: "Cancelar" -Unload: "Desactivar" -DumpLog: "Exportar Log" -ClearAutoLogs: "Borrar registro automático" -SoundOptions: "Mi Música" -AudioManagementOptions: "Gestión de Audio" -OnlyAvailableInMainMenu: "Sólo disponible en el menú principal" +FinalSuspectFeatures: "Funciones de Final Suspect" +ClientFeature.UnloadMod: "Cambiar a Vanilla" +ClientFeature.DumpLog: "Volcar Log" +ClientFeature.ClearAutoLogs: "Limpiar Logs Auto" +ClientFeature.MyMusic: "Mi Música" +ClientFeature.ResourceManager: "Administrador de Recursos" +ClientFeature.NameTagManager: "Administrador de Etiquetas" +ClientFeature.MainMenuStyleManager: "Cambiar Estilo del Menú Principal" # 提示 / Tips -updatePleaseWait: "Por favor, espera..." -updateInProgress: "Actualización en progreso..." -DownloadingAudios: "Descargando..." -Playing: "Jugando..." -Parsing: "Analizando..." -DownLoadSucceedNotice: "Descarga exitosa!" -DownLoadFailureNotice: "Fallo en la descarga =(" -PleaseWait: "Por favor, espera..." +Tip.Downloading: "Descargando..." +Tip.PleaseWait: "Por favor espera..." +Tip.Playing: "Reproduciendo..." +Tip.Parsing: "Analizando..." +Tip.Updating: "Actualizando..." +Tip.DownLoadFinished: "Descarga finalizada" +Tip.DownLoadSucceeded: "¡Descarga exitosa!" +Tip.DownLoadFailed: "Descarga fallida=(" +Tip.PackageExists: "Instalado" +Tip.OnlyAvailableInMainMenu: "Solo disponible en el Menú Principal" +Tip.HideSummaryTextToShowWinText: "Ocultar Resumen Final para ver texto de victoria" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "Archivos de idioma cargados" +Tip.CheckingForFiles: "Verificando integridad de archivos de recursos..." +Tip.DownloadingResources: "Descargando archivos de recursos..." +Tip.Loading: "Cargando" +Tip.LoadingWithDot: "Cargando..." +Tip.LoadingComplete: "¡Carga completada!" +## 切换原版 +Tip.UnloadWarning: "¡Advertencia!\nDebes reiniciar el juego si quieres jugar la versión con mods.\n¿Estás seguro de querer cambiar a Vanilla?" +Tip.CannotUnloadDuringGame: "No se puede cambiar a Vanilla durante una partida." +## 资源管理 +Tip.ResourceManager: "Puedes descargar recursos compatibles con mods en el \"Administrador de Recursos\"\nLos archivos con prefijo \"Pre-\" son paquetes pre-descargados" +## 我的音乐 +Tip.MyMusic: "Puedes descargar música compatible con mods en el \"Administrador de Recursos\" o agregar tus archivos de audio favoritos en la carpeta (Among Us/Final Suspect_Data/Musics). Si no existe la ruta local de música, se mostrará \"Archivo Faltante\"" +Tip.Incomplete_Music: "Archivo de música incompleto detectado" +Tip.Incomplete_SoundEffect: "Archivo de efecto de sonido incompleto detectado" +Tip.Incomplete_Image: "Archivo de imagen incompleto detectado" +Tip.Incomplete: "Para una mejor experiencia, descarga el paquete de recursos compatible con mods en \"Menú Principal - Ajustes - Funciones del Cliente - Administrador de Recursos\"" +## 名称标识管理 +Tip.TextContent: "Contenido del texto" +Tip.TextSizeDescription: "Tamaño del texto(Por defecto 100%)" +Tip.TextColorDescription: "Color del texto(Código hexadecimal)\nDéjalo en blanco si no es necesario; introduce varios para degradado automático\n" +Tip.PleaseEnterFriendCode: "Ingresa el código de amigo para vincular la nueva etiqueta" +Tip.FriendCodeAlreadyExist: "Este código de amigo ya tiene una etiqueta" +Tip.FriendCodeIncorrect: "Ingresa un código de amigo válido" +Tip.CustomNameTagHelp: "Puedes agregar etiquetas para cualquier jugador. Se asignarán automáticamente cuando se una el jugador con el código vinculado. No puedes editar etiquetas después de entrar al lobby.\nLos no VIP solo pueden agregar notas (funciones VIP aún no disponibles)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "Puedes descargar \"Paquetes de Estilo del Menú Principal\" en el \"Administrador de Recursos\" para obtener recursos del menú y elegir tu estilo favorito aquí" + +# 更新结果 +UpdateResult.Succeed_Title: "Actualización exitosa" +UpdateResult.Succeed_Text: "Se activará después de reiniciar el juego :)" +UpdateResult.Failed_Title: "Actualización fallida" +UpdateResult.Failed_Reason_NotFound: "Razón: {0}\nEsta fuente puede estar temporalmente no disponible, cambia de fuente y reintenta" +UpdateResult.Failed_Reason_FileMd5Incorrect: "Razón: Error de verificación de archivo\nLa versión de archivo de esta fuente no es la más reciente, cambia de fuente y reintenta" +UpdateResult.Failed_Reason_Ping: "Razón: Tiempo de espera de actualización excedido o interrumpido\nVerifica tu red y reintenta o actualiza manualmente" # 更新检查 / Update Checker -Retry: "Reintentar" -updateCheckPopupTitle: "Comprobación de Actualizaciones" -updateCheckFailedRetry: "La comprobación de actualizaciones falló :(\n¿Reintentar?" -updateCheckFailedExit: "La comprobación de actualizaciones falló :(\nPor favor, verifique su conexión a Internet y vuelva a intentarlo." +UpdateCheck.Popup_Title: "Verificar actualización" +UpdateCheck.Failed_Retry: "Falló la verificación de actualización :(\n¿Reintentar?" +UpdateCheck.Failed_Exit: "Falló la verificación de actualización :(\n¡Verifica tu red y reintenta!" # 更新提醒 / Update Reminder -UpdateBySelfTitle: "Recordatorio de Actualización" -updateNotice: "Recordatorio de Actualización" -UpdateBySelfText: "Esta versión no admite actualizaciones automáticas. Por favor, actualice manualmente." -updateButton: "Actualizar Ahora" -updatePopupTitle: "Actualizar Ahora" -updatePopupTitleFailed: "Actualización Fallida" -updatePopupTitleDone: "Actualización Completada" +UpdateRemind.updatePopup: "Actualizar ahora" +UpdateRemind.updateNotice: "Recordatorio de actualización" +UpdateRemind.BySelf_Title: "Sugerencia de actualización" +UpdateRemind.BySelf_Text: "Esta versión no admite actualización con un clic, actualiza manualmente" # 选择更新渠道 / Update Chose Source -updateChoseSource: "Por favor, seleccione una fuente para actualizar\nsi no sabe qué seleccionar, elija [Github]\nsi la actualización falla, elija [Api]" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -updateSource.XtremeApi: "Api" - -# 更新结束提示 / Update completion prompts -updateRestart: "Reinicie el juego para aplicar los cambios :)" -updatePingFialed: "Motivo: {0}\nEl canal seleccionado puede estar temporalmente no disponible. Intente cambiar el canal de descarga." -updateFileMd5Incorrect: "Motivo: Error de suma de comprobación del archivo\nLa versión del archivo de este canal no es la más reciente. Intente cambiar el canal de descarga." -downloadFailed: "Motivo: Tiempo de descarga agotado o interrumpido\nIntente nuevamente después de cambiar su red o actualizar manualmente." +UpdateSource.Choose: "Elige fuente de actualización\nSi no estás seguro, elige [Github]" +UpdateSource.Github: "Github" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api" # 无法加入公开游戏原因 / Unable to join public game reasons -onSetPublicNoLatest: "Tenemos una actualización importante. Por favor, actualice este mod.\nDe lo contrario, no podrá unirse a las salas públicas." -CanNotJoinPublicRoomNoLatest: "Tenemos una actualización importante. Por favor, actualice este mod.\nDe lo contrario, no podrá unirse a las salas públicas." -ModBrokenMessage: "Los archivos del mod están dañados. Por favor, reinicie el juego o reinstale este mod." -UnsupportedVersion: "Su versión de Among Us no es compatible con FinalSuspect.\nPor favor, actualice su juego." +CanNotJoinPublicRoomNoLatest: "Tenemos una actualización importante, actualiza este mod\nDe lo contrario no podrás unirte a salas públicas" +ModBrokenMessage: "Los archivos del mod fallaron, reinicia el juego o reinstala este mod" +UnsupportedVersion: "Tu versión de Among Us es incompatible con FinalSuspect\nActualiza el juego" + +# 名称标识 +NameTag.DisplayName: "Nota" +NameTag.Title: "Título" +NameTag.Prefix: "Prefijo" +NameTag.Suffix: "Sufijo" +NameTag.Name: "Nombre" +NameTag.LastTag: "Sufijo adicional" +NameTag.PreviewNotAvailable: " (Vista previa no disponible) " +NameTag.CanNotEdit: " (No editable) " +NameTag.RefreshPreview: "Refrescar vista previa" +NameTag.SaveAndClose: "Guardar y cerrar" +NameTag.NewNameTag: "Nuevo" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "Persiguiendo el Amanecer(FS)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "Con el paso del tiempo, el invierno del norte termina y la primavera renace.\nContemplando el paisaje nevado de Mira, ¿despiertan los recuerdos? Fluyen corrientes cálidas en el corazón.\nNo importa quién sea el sospechoso final, ni en manos de quién se rompa la verdad,\nen una nueva experiencia refrescante, haz lluvia de ideas y deja nuevos y felices recuerdos.\n¡Jugar y divertirse es lo más importante!!!\n\nNombrado por: 一念旧情丶" +MainMenuStyle.Title_Security: "Corazón Rojo, Amor Feroz (TONEX)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "No caeremos, no nos rendiremos, no decepcionaremos a los jugadores y nunca dejaremos de avanzar.\nSi enfrentamos miradas frías, ¡lo demostraremos a todos!\nCon pasión, zarparemos de nuevo\n\nNombrado por: Slok" +MainMenuStyle.Title_NewYear: "Afinidad (Año Nuevo)" +MainMenuStyle.Author_NewYear: "小黄117" +MainMenuStyle.Description_NewYear: "Fortuna: Deseos otorgados Vínculo: Tejido en latidos\nArmonía: Hilos de afecto Dicha: Paz en cada alma\nVínculos del destino forjan alegría compartida Viaja lejos, encuentra alegría en la unión\nEra dorada acuna a todos en regocijo Nuestra oración—alegría para cada ser\nQue cada alma encuentre su camino en el año venidero\n¡Con nuestras bendiciones iluminando tu camino, emprende tu viaje para forjar tu propia leyenda!\n\nNombrado por: Slok" +MainMenuStyle.Title_MiraStudio: "Final Estudio (Año Nuevo)" +MainMenuStyle.Author_MiraStudio: "小黄117" +MainMenuStyle.Description_MiraStudio: "\"—Las flores florecen como olas, suaves como el viento, deseando que cada corazón sea tan vasto como el viento y todo vaya bien\"\n\"—Los vientos primaverales soplan con fuerza, las olas se extienden por millas, deseando que todos se mantengan valientes al frente, floreciendo y prosperando\"\n\"—¡Bienvenidos al Especial de Año Nuevo de XtremeWave!\"\n\nNombrado por: Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「Final esforzándose por la excelencia, ¡el sueño comanda olas empujantes!」\n\nNombrado por: Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "Cuando Mires Atrás al Final (Colaboración)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麦麦头" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「Mirando atrás al final, todo termina al final」\n\nNombrado por: MAMTI.麦麦头" +MainMenuStyle.NotFound: "No descargado" +MainMenuStyle.NotApply: "Aplicar" +MainMenuStyle.Applied: "Aplicado" + +# 资源包 +Package.MainMenuStyle: "Paquete de Estilo del Menú Principal" # 音频播放 / Audio Playback -PlayMode0: "Reproducir una vez" -PlayMode1: "Repetir una sola vez" -PlayMode2: "Aleatorio" -PlayMode3: "Secuencial" -Stop: "Detener" -CanPlay: "← Haz clic para reproducir" -NoFound: "[Archivo ausente]" -NextPage: "Página siguiente" -PreviousPage: "Página anterior" - -# 音频添加 / Audio Addition -download: "Descargar" -delete: "Eliminar" -NewSound: "Añadir nueva música" -PleaseEnterMusic: "Por favor, ingrese el nombre de la música" -AudioManagementAlreadyExists: "Este nombre de música ya existe" -NotAllowedMusic: "El formato del nombre de la música no está permitido" - -# 界面提示 / Interface Tips -CustomAudioManagementHelp: "Puedes descargar música compatible con XtremeWave o añadir tu propia música en 'Gestión de Audio'. Al añadir tu propia música, asegúrate de añadir el nombre de la música en 'Gestión de Audio' y colocar el archivo de audio correspondiente en la carpeta 'Among Us/Final Suspect_Data/Resources/Sounds' (formatos compatibles: .wav). La música se puede reproducir en 'Mi Música'." -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "Puedes descargar música compatible con XtremeWave o añadir tu propia música en 'Gestión de Audio'. Si falta la ruta del recurso de música local, se mostrará '[Archivo ausente]'." - -# 主界面音乐提醒 / Main Menu Music Reminder -MusicNotYet: "El archivo de música actual se ha detectado como incompleto" -AudioNYPro: "Para una mejor experiencia de juego, descarga nuestra música en 'Inicio-Configuración-Más funciones-Gestión de audio'" +MusPlay.Mode0: "Reproducir una vez" +MusPlay.Mode1: "Repetir una sola" +MusPlay.Mode2: "Lista aleatoria" +MusPlay.Mode3: "Reproducción secuencial" +MusPlay.Stop: "Detener reproducción" +MusPlay.CanPlay: "Clic para reproducir" +MusPlay.NoFound: "Archivo faltante" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜发财 (Feliz Año Nuevo Chino)" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - -Mus.TidalSurge: "Tidal Surge" -Mus.TrailOfTruth: "Trail Of Truth" -Mus.Interlude: "Interlude" -Mus.Fractured: "Fractured" -Mus.ElegyOfFracturedVow: "Elegy Of Fractured Vow" +Mus.TidalSurge: "Oleaje de Marea" +Mus.TrailOfTruth: "Camino de la Verdad" +Mus.Interlude: "Interludio" +Mus.Fractured: "Fracturado" +Mus.ElegyOfFracturedVow: "Elegía del Voto Roto" Mus.VestigiumSplendoris: "Vestigium Splendoris" -Mus.ReturnToSimplicity: "Return To Simplicity" -Mus.Affinity: "Affinity" -Mus.Inceps_Plus_InProgress: "Inceps + InProgress" - -# 信息 / Messages -Message.KickedByDenyName: "[{0}] fue expulsado porque su nombre coincide con [{1}]" -Message.BanedByBanList: "[{0}] fue baneado porque fue baneado anteriormente." -Message.BanedByFACList: "[{0}] fue baneado porque está en la lista de baneados del FAC." -Message.DumpfileSaved: "El archivo de registro se guardó correctamente en el escritorio, nombre del archivo: {0}" -Message.KickedByNoFriendCode: "[{0}] fue expulsado porque su código de amigo no existe." -Message.AddedPlayerToBanList: "Añadido [{0}] a la lista de baneados" -Message.KickedByFAC: "[{0}] fue expulsado por el FAC, motivo: {1}" -Message.BanedByFAC: "[{0}] fue baneado por el FAC, motivo: {1}" +Mus.ReturnToSimplicity: "volver a la simplicidad" +Mus.ReturnToSimplicity2: "volver a la simplicidad (Versión Completa)" +Mus.ChasingDawn: "Persiguiendo el Amanecer" +Mus.StruggleAgainstFadingFlame: "Lucha contra la Llama que se Apaga" +Mus.Affinity: "Afinidad" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "Rol" +DisplayedRoleTag.PlayerIdentityTag: "Etiqueta de identidad" +DisplayedRoleTag.Room: "Sala" + +PlayerIdentityTag.Hard_Cleared: "Confirmado bueno" +PlayerIdentityTag.Silver_Clear: "Semi-limpiado" +PlayerIdentityTag.Wolf_Bucket: "Pozo de lobos" +PlayerIdentityTag.No_Kill: "Sin asesinato" +PlayerIdentityTag.Outside_Position: "Posición exterior" +PlayerIdentityTag.Inside_Position: "Posición interior" # 通知 / Notifications -PlayerLeft: "[{0}] dejó el juego" -PlayerLeftCuzTimeout: "[{0}] dejó el juego debido a un tiempo de espera de conexión" -PlayerKickByHost: "[{0}] fue expulsado por el anfitrión" -PlayerBanByHost: "[{0}] fue baneado por el anfitrión" -PlayerLeftCuzError: "[{0}] dejó el juego debido a un error" -PlayerLeftByAU-Anticheat: "[{0}] fue expulsado por el sistema antitrampas oficial de AmongU (no relacionado con FinalSuspect)" -KickBecauseDiffrentVersionOrMod: "[{0}] fue expulsado porque tenía una versión diferente del mod" +## 原版 +Notification.PlayerLeft: "[{0}] abandonó el juego" +Notification.PlayerLeftCuzTimeout: "[{0}] abandonó el juego debido a timeout de conexión" +Notification.PlayerKickByHost: "[{0}] fue expulsado por el host" +Notification.PlayerBanByHost: "[{0}] fue bloqueado por el host" +Notification.PlayerLeftCuzError: "[{0}] abandonó el juego debido a un error" +Notification.PlayerLeftByAU-Anticheat: "[{0}] fue expulsado por el Anti-Cheat de Among Us (sin relación con FinalSuspect)" +Notification.KickBecauseDifferentVersionOrMod: "[{0}] fue expulsado por tener una versión/mod diferente" +## 模组 +Notification.KickedByDenyName: "[{0}] fue expulsado por tener un apodo con palabras prohibidas" +Notification.DumpfileSaved: "Archivo de log guardado en el escritorio, nombre: {0}" +Notification.KickedByAbnormalFriendCode: "[{0}] fue removido porque esta sala prohíbe códigos de amigo anormales" +Notification.AddedPlayerToBanList: "Agregado [{0}] a la lista de bloqueo" +Notification.FPSSetTo: "Límite de FPS establecido a: {0}" # 警告 / Warnings -Warning: "¡Advertencia!" -Warning.MismatchedVersion: "{0}\ntiene una versión diferente de {1}" -Warning.AutoExitAtMismatchedVersion: "El anfitrión no tiene o tiene una versión diferente de {0}\nSerás expulsado en {1}" -Warning.InvalidRpc: "{0} fue expulsado porque se recibió un RPC no válido." -Warning.InvalidRpc_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (RPC no válido: {1})" -Warning.SetName: "{0} fue expulsado porque configuró el nombre varias veces." -Warning.SetName_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (Configurar nombre varias veces)" -Warning.SendQuickChat: "{0} fue expulsado porque envió varios mensajes rápidos en 3 segundos" -Warning.SendQuickChat_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (Enviar varios mensajes rápidos en 3 segundos)" -Warning.InvalidSlothRPC: "Expulsado a {0} porque se recibió un RPC ilegal (Enviando ilegalmente el Rpc oficial: {1})" -Warning.InvalidSlothRPC_NotHost: "{0} es sospechoso de usar trampas. Por favor, recuerda al anfitrión que lo expulse (Envío ilegal de RPC oficial: {1})" -Warning.Cheater: "Expulsado a {0} porque se sospecha que está usando trucos" -Warning.Cheater_NotHost: "{0} se sospecha que está usando trucos, Por favor, recuerda al anfitrión que lo expulse" -Warning.CantKickDev: "Lo siento, no puedes expulsar al desarrollador" -Warning.RoomBroken: "Lo siento, esta sala ha sido comprometida. Por favor, dirígete a otra sala para continuar tu juego." +Warning: "Advertencia" +Warning.MismatchedVersion: "[{0}]\ntiene una versión diferente de [{1}] instalada" +Warning.AutoExitAtMismatchedVersion: "Tu versión de [{0}] es diferente a la del host\nSerás expulsado en {1} segundos" +Warning.CantKickDev: "Lo sentimos, no puedes expulsar a los desarrolladores" +Warning.RoomBroken: "Lo sentimos, esta sala sufrió un ataque, juega en otra sala" +Warning.InvalidColor: "Jugador con color inválido detectado" ## 错误等级 / Error Levels -ErrorLevel1: "Pueden ocurrir errores." -ErrorLevel2: "Esto puede ser un error." -ErrorLevel3: "Esta versión no debería haber sido lanzada." +ErrorLevel1: "Puede causar varios errores al mismo tiempo" +ErrorLevel2: "Puede haber errores" +ErrorLevel3: "Versión no lanzada" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "Advertencia: FAC detectó un alto nivel de trampas." -FAC.CheatDetected.LowLevel: "Advertencia: FAC detectó un bajo nivel de trampas. Uno de los jugadores está haciendo trampas." -FAC.CheatDetected.FAC: "Uso de programas de trampas (por ejemplo, AUM, YuMenu, SM, etc.)" +CheatDetected.HighLevel: "Advertencia: FAC está defendiendo contra trampas de bombardeo" +CheatDetected.LowLevel: "Advertencia: FAC detectó posible tramposo" +CheatDetected.UseCheat: "{0} usó el programa de trampa [{1}]" +CheatDetected.MayUseCheat: "{0} sospechoso de usar mod [{1}] o programa de trampa" +CheatDetected.InvalidRpc: "[{0}] fue expulsado por enviar datos inválidos (Rpc inválido: {1})" +CheatDetected.InvalidRpc_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Rpc inválido: {1})" +CheatDetected.SetName: "[{0}] fue expulsado por configurar el nombre varias veces" +CheatDetected.SetName_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Configuró nombre varias veces)" +CheatDetected.SendQuickChat: "[{0}] fue expulsado por enviar varios mensajes rápidos en 3 segundos" +CheatDetected.SendQuickChat_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Envió varios mensajes rápidos en 3 segundos)" +CheatDetected.InvalidSlothRPC: "[{0}] fue expulsado por enviar datos ilegales (Rpc oficial ilegal: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto (Rpc oficial ilegal: {1})" +CheatDetected.Overload: "[{0}] fue expulsado por iniciar un ataque de sobrecarga" +CheatDetected.Overload_NotHost: "[{0}] inició un ataque de sobrecarga, esta sala está dañada, juega en otra sala" +CheatDetected.Cheater: "[{0}] fue expulsado por sospecha de trampa" +CheatDetected.Cheater_NotHost: "[{0}] sospechoso de trampa, recuerda al host que lo expulse pronto" +CheatDetected.BanedByBanList: "[{0}] fue expulsado por estar en la lista de bloqueo" +CheatDetected.BanedByFACList: "[{0}] fue expulsado por estar en la lista de bloqueo FAC" # 模组信息 / Mod Infos -Contributors: "Contribuyentes" -Acknowledgement: "Agradecimientos" - -# 断连提示 / Disconect Reasons -DCNotify.Hacking: "Has sido expulsado por el sistema antitrampas.\n (el uso de módulos puede ser malinterpretado como trampas)" -DCNotify.Banned: "No tienes permitido entrar en esta sala" -DCNotify.Kicked: "Has sido expulsado de la sala" -DCNotify.DCFromServer: "Te has desconectado del servidor.\nEsto puede deberse a la inestabilidad de tu red.\nEsto también puede deberse a la inestabilidad del servidor." -DCNotify.GameNotFound: "No se encontró la sala asignada, la sala puede haber sido disuelta\n o verifica si has seleccionado un servidor diferente al de la sala" -DCNotify.GameStarted: "El juego ya ha comenzado, por favor espera a que termine" -DCNotify.GameFull: "La sala está llena, por favor intenta nuevamente más tarde" -DCNotify.IncorrectVersion: "Tu versión de Among Us es diferente a la de esta sala" -DCNotify.Description: "Has sido expulsado del juego.\nMotivo: {0}" -DCNotify.DenyName: "Tu apodo contiene caracteres irregulares" -DCNotify.BanList: "Has sido baneado por el anfitrión" -DCNotify.FACList: "Has sido baneado por el FAC" -DCNotify.CheatDetected: "Has sido detectado como sospechoso de trampas por el FAC" -DCNotify.InvalidRPC: "Puede que hayas instalado un mod diferente al del anfitrión o tu mod ha sido modificado maliciosamente" -DCNotify.ModVersionIncorrect: "Tu versión del mod es diferente a la del anfitrión" -DCNotify.LowLevel: "Tu nivel no cumple con los requisitos de esta sala" -DCNotify.NotLogin: "No se permiten jugadores no logueados en esta sala" - -# 任务栏 / Task Panel -PressF1ShowRoleDescription: "Presiona F1 para ver la descripción de tu rol" +ModInfo.Contributors: "Colaboradores" +ModInfo.Acknowledgement: "Agradecimientos Especiales" + +# 断连提示 / Disconnect Reasons +DCNotify.Hacking: "Fuiste expulsado del sala por el sistema anti-trampas de InnerSloth" +DCNotify.Banned: "Fuiste bloqueado de esta sala" +DCNotify.Kicked: "Fuiste expulsado de esta sala" +DCNotify.DCFromServer: "Tu conexión con el servidor se interrumpió\nPuede ser debido a red inestable\no inestabilidad/rechazo del servidor" +DCNotify.GameNotFound: "Sala especificada no encontrada, puede haberse cerrado\no verifica que no hayas elegido un servidor distinto al de la sala" +DCNotify.GameStarted: "Esta partida ya comenzó, espera a que termine" +DCNotify.GameFull: "Esta sala está llena, inténtalo más tarde" +DCNotify.IncorrectVersion: "Tu versión de Among Us es diferente a la sala" +DCNotify.Description: "Fuiste expulsado del sala\nRazón: {0}" + +# 任务栏相关 / Task Panel +PressF1ShowRoleDescription: "Presiona F1 para ver descripción de tu rol" +PressF2ToHidePane: "Presiona F2 para mostrar/ocultar panel" FakeTask: "Tarea Falsa:" -KillCount: "Muertes" +KillCount: "Asesinatos" # 复盘信息 / Last Results -RoleSummaryText: "Últimos Resultados:" -ShowResults: "Mostrar Últimos Resultados" -HideResults: "Ocultar Últimos Resultados" -NoInfoExists: "No hay últimos resultados disponibles" -CrewsWin: "Victoria del Equipo-Tripulante" -CrewmatesWin: "Victoria de los Tripulantes" -CrewmatesWinBlurb: "¡La luz de la verdad brilla en la esperanza!" -ImpsWin: "Victoria del Equipo-Impostor" -ImpostorsWin: "Victoria de los Impostores" -ImpostorsWinBlurb: "El mal convierte la verdad en cenizas" -HideSummaryTextToShowWinText: "Ocultar último resultado para ver el texto de victoria" - -# 禁用公开 -DisabledByProgram: "Las operaciones de sala pública han sido deshabilitadas por el programa" -PublicNotAvailableOnThis Version: "Las salas públicas no están disponibles en esta versión de FinalSuspect" +Summary.Text: "Resumen Final:" +Summary.ShowResults: "Mostrar Resumen Final" +Summary.HideResults: "Ocultar Resumen Final" +Summary.NoInfoExists: "No existe un Resumen Final válido" +Summary.CrewsWin: "Victoria del Equipo-Tripulante" +Summary.ImpsWin: "Victoria del Equipo-Impostor" +Outro.Crews_Win: "Victoria de los Tripulantes" +Outro.Crews_WinBlurb: "¡La luz de la verdad brilla con esperanza!" +Outro.Imps_Win: "Victoria de los Impostores" +Outro.Imps_WinBlurb: "El mal redujo la verdad a cenizas" # 主页 / Main UI -FinalSuspectWelcomeText: "¡Deseándote una experiencia de juego agradable!" -ConnectToFinalSuspectServerFailed: "Fallo al conectar con el servidor de FinalSuspect" +FinalSuspectWelcomeText: "¡Te deseamos una experiencia de juego agradable!" +RetrieveVersionInfoFailed: "No se pudo obtener información de FinalSuspect" Website: "Sitio Oficial" MainMenuCredential: "{0} © 2025" -LShift: "Presiona LShift para regresar a la habitación anterior" -RShift: "Presiona RShift para entrar en la habitación del portapapeles" -LobbyTimeDisplayText: "Tiempo de existencia transcurrido" +LShift: "Lobby Anterior" +RShift: "Lobby Portapapeles" # 客户端平台 / Platform -IPhone: "IPhone" -Android: "Android" -MicrosoftStore: "Microsoft" +Platform.IPhone: "iOS" +Platform.Android: "Android" +Platform.MicrosoftStore: "Microsoft" # 延迟显示 / Ping Tracker Ping: "Ping" -FrameRate: "Tasa de Cuadros" +FrameRate: "Tasa de FPS" Server: "Servidor" Local: "Local" # 其他 / Other HongKong: "Hong Kong" -FPSSetTo: "Límite de tasa de cuadros establecido en: {0}" -BrowsingMode: "Modo de Navegación" +BrowsingMode: "Modo Navegación" Broken: "Roto" - -# 加载 / Loading -LanguageFilesLoadingComplete: "¡Carga de traducciones completa!" -CheckingForFiles: "Verificando la integridad de los archivos de recursos..." -DownloadingResources: "Descargando archivos de recursos..." -Loading: "Cargando" -LoadingWithDot: "Cargando..." -LoadingComplete: "¡Carga completa!" +Unknown: "Desconocido" +Back: "Atrás" +Yes: "Sí" +No: "No" +Cancel: "Cancelar" +Unload: "Cambiar" +Retry: "Reintentar" +PreviousPage: "Página Anterior" +NextPage: "Página Siguiente" +Download: "Descargar" +Disable: "Desactivar" +Delete: "Eliminar" +Author: "Autor" +Close: "Cerrar" # 身份 / Identity -Host: "Anfitrión" -Cheater: "Tramposo" +Id.Host: "Host" +Id.Cheater: "Tramposo" +Id.Developer: "Desarrollador" +Id.Contributor: "Colaborador" \ No newline at end of file diff --git a/Assets/Languages/TChinese.yaml b/Assets/Languages/TChinese.yaml index 2b5f1ce8..9675985d 100644 --- a/Assets/Languages/TChinese.yaml +++ b/Assets/Languages/TChinese.yaml @@ -1,54 +1,49 @@ # FinalSuspect 的语言文件 / Translation file of FinalSuspect -# 当前翻译文件语言的ID / Language ID of current file -LangID: "14" - # 作者署名(不需要请留空)/ A sign of an author (Please leave blank when not needed) -# 注: 为了防止您的翻译在版本更新中重制,请在本地目录Among Us/Final Suspect_Data/Bypass/中添加文件: BypassCheck_Languages_Longterm.xwc(仅需空文件即可) -# Note: To prevent your translation from being reset during version updates, please add the file: BypassCheck_Languages_Longterm.xwc (an empty file is sufficient) in the local directory Among Us/Final Suspect_Data/Bypass/. +# 注: 为了防止您的翻译在版本更新中重制,请修改本地目录Among Us/BepInEx/cn.XtremeWave.finalsuspect.cfg的值"Language Update Bypass"修改为"LongTerm" TextBelowVersionText: "" # 职业类型 / Role Type -TypeImpostor: "偽裝者職業" -TypeCrewmate: "船員職業" +RoleType.Imp: "偽裝者職業" +RoleType.Crew: "船員職業" # 阵营 / Teams -TeamImpostor: "偽裝者陣營" -TeamImpostorOnly: "偽裝者" -TeamCrewmate: "船員陣營" +Team.Imp: "偽裝者陣營" +Team.Imp_Only: "偽裝者" +Team.Crew: "船員陣營" # 伪装者数量文字 / Impostor Text -ImpostorNumImp: "團隊之中有{0}名偽裝者" -ImpostorNumImpOnly: "人群之中僅有1名偽裝者" -ImpostorNumCrew: "我們之中有{0}名偽裝者" +ImpostorNum.Imp: "團隊之中有{0}名偽裝者" +ImpostorNum.Imp_Only: "人群之中僅有1名偽裝者" +ImpostorNum.Crew: "我們之中有{0}偽裝者" # 阵营开场 -ImpostorIntroText: "讓邪惡的力量籠罩整個世界!" -ImpostorIntroTextOnly: "儘管獨自一人,仍有無窮力量!" -CrewmateIntroText: "完成你的任務,揪出那群壞蛋!" - -## 原版职业 / Vanilla -Crewmate: "船員" -Engineer: "工程師" -Scientist: "科學家" -Tracker: "追蹤者" -Noisemaker: "警示者" -GuardianAngel: "守護天使" -Impostor: "偽裝者" -Shapeshifter: "變形者" -Phantom: "魅影" -CrewmateGhost: "船員靈魂" -ImpostorGhost: "偽裝者靈魂" +IntroText.Imp: "讓邪惡的力量籠罩整個世界!" +IntroText.Imp_Only: "儘管獨自一人,仍有無窮力量!" +IntroText.Crewmate: "完成你的任務,揪出那群壞蛋!" + +# 原版职业 / Vanilla +Role.Crewmate: "船員" +Role.Engineer: "工程師" +Role.Scientist: "科學家" +Role.Tracker: "偵查員" +Role.Noisemaker: "大嗓門" +Role.GuardianAngel: "守護天使" +Role.Impostor: "偽裝者" +Role.Shapeshifter: "變形者" +Role.Phantom: "幻象師" +Role.CrewmateGhost: "船員靈魂" +Role.ImpostorGhost: "偽裝者靈魂" + +# 职业信息 / RoleInfo +## 因为原版职业在enum StringNames中已经有格式,所以按此格式呈现 CrewmateGhostBlurb: "完成任務" ImpostorGhostBlurb: "繼續破壞設施" CrewmateGhostBlurbLong: "完成任務,別拖後腿!\n使用「幽幽鬼影」查看偽裝者,幫助守護天使守護船員!" ImpostorGhostBlurbLong: "破壞設施,協助存活的隊友取得勝利!" - -## 捉迷藏 / HnS -HnSEngineerBlurb: "活到最後!" -HnSEngineerBlurbLong: "堅持活下來,直到時間結束,完成任務可以推進時間。\n利用通風口和威脅指示器躲藏!\n開始計時後,偽裝者會開始追殺你!" -HnSImpostorBlurb: "殺光所有人!" -HnSImpostorBlurbLong: "在規定時間內,殺死所有船員!\n必須盡快行動!並且無法進入通風口。\n在最後關頭,你將獲得速度提升和行蹤提示!" +HnSEngineerBlurb: "完成任務!活到最後!" +HnSImpostorBlurb: "將這一切屠戮殆盡!" HnSCrewmateGhostBlurb: "給你的隊友加油打氣" HnSCrewmateGhostBlurbLong: "死了還看啥呀,你有任務能做嗎?既然沒有...\n去!給!你!隊!友!加!油!" @@ -59,241 +54,284 @@ DeathReason.Disconnect: "斷連" # 客户端选项 / Client Options FinalSuspectOptions: "Final Suspect 選項" -Back: "返回" -Yes: "是" -No: "否" -UnlockFPS: "解鎖幀數限制" -ChangeOutfit: "切換形象" -BeanMode: "經典豆子模式" -HorseMode: "愚人節牧馬模式" -LongMode: "愚人節長頸豆模式" -KickPlayerFriendCodeNotExist: "踢出沒有登錄的玩家" -KickPlayerWithDenyName: "踢出使用違規暱稱的玩家" -KickPlayerInBanList: "踢出被封禁的玩家" -SpamDenyWord: "屏蔽違禁詞" -AutoStartGame: "人滿自動開始遊戲" -AutoEndGame: "結束時自動返回大廳" -SwitchVanilla: "切換為原版" -DisableVanillaSound: "禁用原版遊戲音樂" -DisableFAC: "禁用反作弊" -ShowPlayerInfo: "展示玩家平台與客戶端信息" -UseModCursor: "使用模組鼠標光標" -FastBoot: "快速啟動模式" -PrunkMode: "惡搞模式" -VersionCheat: "繞過版本同步檢查" -GodMode: "上帝模式" -NoGameEnd: "測試模式" -EnableFinalSuspect: "啟用「終極嫌疑」" +ClientOption.UnlockFPS: "解鎖幀數限制" +ClientOption.SwitchOutfitType: "切換外觀形象" +ClientOption.KickPlayerWithAbnormalFriendCode: "踢出好友代碼異常的玩家" +ClientOption.KickPlayerWithDenyName: "踢出使用違規暱稱的玩家" +ClientOption.KickPlayerInBanList: "踢出被封禁的玩家" +ClientOption.SpamDenyWord: "屏蔽違禁詞" +ClientOption.AutoStartGame: "人滿自動開始遊戲" +ClientOption.AutoEndGame: "結束時自動返回大廳" +ClientOption.SwitchVanilla: "切換為原版" +ClientOption.DisableVanillaSound: "禁用原版遊戲音樂" +ClientOption.EnableFAC: "啟用反作弊" +ClientOption.EnableGuardian: "啟用客戶端守護(試驗性)" +ClientOption.ShowPlayerInfo: "展示玩家平台與客戶端信息" +ClientOption.UseModCursor: "使用模組鼠標光標" +ClientOption.FastLaunchMode: "快速啟動模式" +ClientOption.OfflineMode: "離線模式(試驗性)" +ClientOption.VersionCheat: "繞過版本同步檢查" +ClientOption.GodMode: "上帝模式" +ClientOption.NoGameEnd: "測試模式" +ClientOption.EnableFinalSuspect: "啟用「終極嫌疑」" + +## 客户端选项值 / Client Options Values +### 愚人节相关 / AprilFoolsMode +Value.BeanMode: "經典豆子模式" +Value.HorseMode: "愚人節牧馬模式" +Value.LongMode: "愚人節長頸豆模式" # 客户端功能 / Client Features FinalSuspectFeatures: "Final Suspect 功能" -UnloadMod: "切換原版" -UnloadWarning: "警告\n如果您想要回到模組,您必須重啟遊戲\n您確定要切換為原版嗎?" -CannotUnloadDuringGame: "遊戲中不能切換為原版" -Cancel: "取消" -Unload: "切換" -DumpLog: "輸出日誌" -ClearAutoLogs: "清除自動日誌" -SoundOptions: "我的音樂" -AudioManagementOptions: "音頻管理" -OnlyAvailableInMainMenu: "僅在主頁使用" +ClientFeature.UnloadMod: "切換原版" +ClientFeature.DumpLog: "輸出日誌" +ClientFeature.ClearAutoLogs: "清空自動日誌" +ClientFeature.MyMusic: "我的音樂" +ClientFeature.ResourceManager: "資源管理" +ClientFeature.NameTagManager: "名稱標識管理" +ClientFeature.MainMenuStyleManager: "切換主頁風格" # 提示 / Tips -updatePleaseWait: "請稍候……" -updateInProgress: "更新中……" -DownloadingAudios: "下載中……" -Playing: "播放中……" -Parsing: "解析中……" -DownLoadSucceedNotice: "下載成功!" -DownLoadFailureNotice: "下載失敗=(" -PleaseWait: "請稍候……" -LanguageFilesLoadingComplete: "翻譯文本加載完成!" -CheckingForFiles: "驗證資源文件完整性中..." -DownloadingResources: "下載資源文件中..." -Loading: "加載中" -LoadingWithDot: "加載中..." -LoadingComplete: "加載完成!" - -# 更新检查 -Retry: "重試" -updateCheckPopupTitle: "更新檢查" -updateCheckFailedRetry: "檢查更新失敗 :(\n要重試嗎?\n或者試試關掉你的加速器?" -updateCheckFailedExit: "檢查更新失敗 :(\n請檢查您的網絡強度後重試\n或者試試關掉你的加速器!" - -# 更新提醒 -UpdateBySelfTitle: "更新提示" -updateButton: "一鍵更新" -updatePopupTitle: "一鍵更新" -updatePopupTitleFailed: "更新失敗" -updatePopupTitleDone: "更新完成" -updateNotice: "更新提醒" -UpdateBySelfText: "該版本不支持一鍵更新,請您手動更新" - -# 选择更新渠道 / update Chose Source -updateChoseSource: "請選擇更新渠道\n如果您不知道選擇什麼,請選擇【Github】或者【Api】" -updateSource.Github: "Github" -updateSource.Gitee: "Gitee" -Updatesource.XtremeApi: "Api" - -# 更新结束提示 / up -updateRestart: "重啟遊戲後生效 :)" -updatePingFialed: "原因:{0}\n該渠道可能暫時停用,請更換下載渠道重試" -updateFileMd5Incorrect: "原因:文件校驗錯誤\n該渠道的文件版本並不是最新的,請切換下載渠道重試" -downloadFailed: "原因:下載更新文件超時或中斷\n請更換網路後重試或手動更新" - -# 无法加入公开游戏原因 / Unable to join public game reason -onSetPublicNoLatest: "我們有一個重要的更新,請更新本模組\n否則您無法公開房間" +Tip.Downloading: "下載中……" +Tip.PleaseWait: "請稍候……" +Tip.Playing: "播放中……" +Tip.Parsing: "解析中……" +Tip.Updating: "更新中……" +Tip.DownLoadFinished: "下載完成" +Tip.DownLoadSucceeded: "下載成功!" +Tip.DownLoadFailed: "下載失敗=(" +Tip.PackageExists: "資源包已安裝" +Tip.OnlyAvailableInMainMenu: "僅在主頁使用" +Tip.HideSummaryTextToShowWinText: "隱藏覆盤信息查看勝利文本" +## 启动加载 +Tip.LanguageFilesLoadingComplete: "語言文件加載完成" +Tip.CheckingForFiles: "校驗資源文件完整性……" +Tip.DownloadingResources: "下載資源文件……" +Tip.Loading: "加載中" +Tip.LoadingWithDot: "加載中……" +Tip.LoadingComplete: "加載完成!" +## 切换原版 +Tip.UnloadWarning: "警告\n如果您想要回到模組,您必須重啟遊戲\n您確定要切換為原版嗎?" +Tip.CannotUnloadDuringGame: "遊戲中不能切換為原版" +## 资源管理 +Tip.ResourceManager: "您可以在「資源管理」中下載本模組的配套以及擴展資源\n以\"Pre-\"開頭的文件為預下載資源包" +## 我的音乐 +Tip.MyMusic: "您可以在「資源管理」中下載本模組支援的音樂或在文件夾(Among Us/Final Suspect_Data/Musics)中添加自己喜愛的音頻文件,如果音樂本地資源路徑不存在則會顯示「文件缺失」" +Tip.Incomplete_Music: "檢測到當前音樂文件不完整" +Tip.Incomplete_SoundEffect: "檢測到當前音效文件不完整" +Tip.Incomplete_Image: "檢測到當前音樂文件不完整" +Tip.Incomplete: "如果您希望提高您的遊戲體驗,請在「主頁-設置-客戶端功能-資源管理」下載模組配套資源包" +## 名称标识管理 +Tip.TextContent: "文本內容" +Tip.TextSizeDescription: "文本大小(默認100%)" +Tip.TextColorDescription: "文本顏色(Hex 顏色代碼)\n不需要請留空,填寫多個則自動漸變\n" +Tip.PleaseEnterFriendCode: "請輸入新名稱標識綁定的好友代碼" +Tip.FriendCodeAlreadyExist: "該好友代碼已擁有名稱標識" +Tip.FriendCodeIncorrect: "請輸入正確的好友代碼" +Tip.CustomNameTagHelp: "您可以為任意玩家添加名稱標識,標識會在綁定好友代碼的玩家加入後自動發放,進入房間後不能編輯標識\n非會員用戶僅可添加備註(會員功能暫未開發)" +## 切换主页风格 +Tip.MainMenuStyleHelp: "您可以在「資源管理」中下載「主頁風格包」以獲取主頁資源,並在此處選擇您喜愛的主頁風格" + +# 更新结果 +UpdateResult.Succeed_Title: "更新成功" +UpdateResult.Succeed_Text: "重啟遊戲後生效 :)" +UpdateResult.Failed_Title: "更新失敗" +UpdateResult.Failed_Reason_NotFound: "原因:{0}\n該渠道可能暫時停用,請更換下載渠道重試" +UpdateResult.Failed_Reason_FileMd5Incorrect: "原因:文件校驗錯誤\n該渠道的文件版本並不是最新的,請切換下載渠道重試" +UpdateResult.Failed_Reason_Ping: "原因:下載更新文件超時或中斷\n請更換網絡後重試或手動更新" + +# 更新检查 / Update Checker +UpdateCheck.Popup_Title: "更新檢查" +UpdateCheck.Failed_Retry: "檢查更新失敗 :(\n要重試嗎?\n試試關掉你的加速器?" +UpdateCheck.Failed_Exit: "檢查更新失敗 :(\n請檢查您的網絡強度後重試\n試試關掉你的加速器!" + +# 更新提醒 / Update Reminder +UpdateRemind.updatePopup: "一鍵更新" +UpdateRemind.updateNotice: "更新提醒" +UpdateRemind.BySelf_Title: "更新提示" +UpdateRemind.BySelf_Text: "該版本不支持一鍵更新,請您手動更新" + +# 选择更新渠道 / Update Chose Source +UpdateSource.Choose: "請選擇更新渠道\n如果您不知道選什麼,請選擇【Gitee】或者【Api(境內首選)】" +UpdateSource.Github: "Github(境外首選)" +UpdateSource.Gitee: "Gitee" +UpdateSource.FinalApi: "Api(境內首選)" + +# 无法加入公开游戏原因 / Unable to join public game reasons CanNotJoinPublicRoomNoLatest: "我們有一個重要的更新,請更新本模組\n否則您無法加入公開房間" ModBrokenMessage: "模組文件損壞,請重啟遊戲或重裝本模組" UnsupportedVersion: "您的 AmongUs 版本與 FinalSuspect 不兼容\n請更新遊戲" -# 音频播放 -PlayMode0: "單次播放" -PlayMode1: "單曲循環" -PlayMode2: "列表隨機" -PlayMode3: "順序播放" -Stop: "停止播放" -CanPlay: "←點擊播放" -NoFound: "【文件丟失】" -NextPage: "下一頁" -PreviousPage: "上一頁" - -# 音频添加 -download: "下載" -delete: "刪除" -NewSound: "新增音樂" -PleaseEnterMusic: "請輸入音樂名" -AudioManagementAlreadyExists: "該音樂名已存在" -NotAllowedMusic: "音樂名稱格式不合規" - -# 界面提示 -CustomAudioManagementHelp: "您可以在「音訊管理」中下載 XtremeWave 支援的音樂或添加自己的音樂。當您添加自己的音樂時,請在「音訊管理」中添加音樂名並在路徑“Among Us/Final Suspect_Data/Resources/Audios”文件夾內添加同名音頻文件(支援格式:.wav)。音樂可在「我的音樂」中播放。" -# , .flac, .aiff, .mp3, .aac, .ogg, .m4a -CustomSoundHelp: "您可以在「音訊管理」中下載 XtremeWave 支援的音樂或添加自己的音樂,如果音樂本地資源路徑不存在則會顯示「【文件丟失】」" - -# 主界面音乐提醒 -MusicNotYet: "檢測到當前音樂文件不完整" -AudioNYPro: "如果您希望提高您的遊戲體驗,請在「首頁-設定-更多功能-音訊管理」下載我們的音訊" +# 名称标识 +NameTag.DisplayName: "備註" +NameTag.Title: "頭銜" +NameTag.Prefix: "前綴" +NameTag.Suffix: "後綴" +NameTag.Name: "名稱" +NameTag.LastTag: "附加綴" +NameTag.PreviewNotAvailable: " (不支持預覽) " +NameTag.CanNotEdit: " (不可修改) " +NameTag.RefreshPreview: "刷新預覽" +NameTag.SaveAndClose: "保存並退出" +NameTag.NewNameTag: "新建" + +# 主页风格 +MainMenuStyle.Title_MiraHQ: "逐光尋曉(FS經典背景)" +MainMenuStyle.Author_MiraHQ: "KpCam" +MainMenuStyle.Description_MiraHQ: "隨著時間的推移,北半球的冬天迎來尾聲,春意正在各處復甦。\n眺望米拉的雪景,內心中的回憶是否喚醒?暖流依舊心中流淌。\n無論嫌疑最終落在誰身,無論真理最後破碎於誰手,\n在煥然一新的體驗中,頭腦風暴,留下全新的快樂回憶。\n玩遊戲,開心最重要!!!\n\n命名:一念舊情丶" +MainMenuStyle.Title_Security: "赤心熾愛(TONEX經典背景)" +MainMenuStyle.Author_Security: "KpCam" +MainMenuStyle.Description_Security: "我們不會就此跌下、不會就此放棄、不會讓玩家失望、更不會停止進步\n若有人冷眼相待,我們會證明給所有人看!\n伴著熱愛,我們再次起航\n\n命名:Slok" +MainMenuStyle.Title_NewYear: "祉緣諧樂(新春特別節目)" +MainMenuStyle.Author_NewYear: "小黃117" +MainMenuStyle.Description_NewYear: "祉,祝予福 緣,織於心\n諧,連與情 樂,安喻人\n摯緣,創諧樂 致遠,諧得樂\n至元,皆懷樂 只願,人皆樂\n願每個人在新的一年都能尋得自己的道路\n帶著我們的祝福,勇往直前,追尋屬於自己的人生吧!\n\n命名:Slok" +MainMenuStyle.Title_MiraStudio: "極致演播廳(新春特別節目)" +MainMenuStyle.Author_MiraStudio: "小黃117" +MainMenuStyle.Description_MiraStudio: "\"——花開似浪,溫柔如風,願大家心海如風,事事如意\"\n\"——春風浩蕩,浪涌千里,願大家勇立潮頭,蒸蒸日上\"\n\"——歡迎來到XtremeWave 極致狂瀾——新春特別節目!\"\n\n命名:Slok" +MainMenuStyle.Title_XtremeWave: "XtremeWave 極致狂瀾" +MainMenuStyle.Author_XtremeWave: "Slok" +MainMenuStyle.Description_XtremeWave: "「極行致遠,夢領狂瀾!」\n\n命名:Slok" +MainMenuStyle.Title_WhenLookingBackAtTheEnd: "回望末端時(聯動背景)" +MainMenuStyle.Author_WhenLookingBackAtTheEnd: "MAMTI.麥麥頭" +MainMenuStyle.Description_WhenLookingBackAtTheEnd: "「回望末端時,終盡於末端」\n\n命名:MAMTI.麥麥頭" +MainMenuStyle.NotFound: "未下載" +MainMenuStyle.NotApply: "應用" +MainMenuStyle.Applied: "應用中" + +# 资源包 +Package.MainMenuStyle: "主頁風格包" + +# 音频播放 / Audio Playback +MusPlay.Mode0: "單次播放" +MusPlay.Mode1: "單曲循環" +MusPlay.Mode2: "列表隨機" +MusPlay.Mode3: "順序播放" +MusPlay.Stop: "停止播放" +MusPlay.CanPlay: "點擊播放" +MusPlay.NoFound: "文件缺失" # 官方音乐 / Musics -Mus.GongXiFaCai: "恭喜发财" +Mus.GongXiFaCai: "恭喜發財" Mus.NeverGonnaGiveYouUp: "Never Gonna Give You Up" Mus.CountingStars: "Counting Stars" - Mus.TidalSurge: "塘潮暗涌" Mus.TrailOfTruth: "真理之途" -Mus.Interlude: "插叙" +Mus.Interlude: "插敘" Mus.Fractured: "破碎" -Mus.ElegyOfFracturedVow: "誓碎终殇" -Mus.VestigiumSplendoris: "余璨" -Mus.ReturnToSimplicity: "返璞归真" -Mus.Affinity: "祉缘谐乐" -Mus.Inceps_Plus_InProgress: "始 + 进行时" - -# 信息 / Messages -Message.KickedByDenyName: "【{0}】被踢出,因其暱稱包含違禁詞【{1}】" -Message.BanedByBanList: "【{0}】因在封禁名單內被踢出" -Message.BanedByFACList: "【{0}】因在FAC封禁名單內被踢出" -Message.DumpfileSaved: "日誌文件已成功保存到桌面上了,文件名為:{0}" -Message.KickedByNoFriendCode: "【{0}】因該房禁止未登錄玩家被請離" -Message.AddedPlayerToBanList: "【{0}】被添加至黑名單" -Message.KickedByFAC: "【{0}】被FAC踢出,理由:{1}" -Message.BanedByFAC: "【{0}】被FAC封禁,理由:{1}" +Mus.ElegyOfFracturedVow: "誓碎終殤" +Mus.VestigiumSplendoris: "餘璨" +Mus.ReturnToSimplicity: "返璞歸真" +Mus.ReturnToSimplicity2: "返璞歸真(完整版)" +Mus.ChasingDawn: "逐光尋曉" +Mus.StruggleAgainstFadingFlame: "峙燃絕焰" +Mus.Affinity: "祉緣諧樂" + +# 会议界面职业标签 / DisplayedRoleTag +DisplayedRoleTag.Role: "職業" +DisplayedRoleTag.PlayerIdentityTag: "身份標記" +DisplayedRoleTag.Room: "房間" + +PlayerIdentityTag.Hard_Cleared: "金水" +PlayerIdentityTag.Silver_Clear: "銀水" +PlayerIdentityTag.Wolf_Bucket: "狼坑" +PlayerIdentityTag.No_Kill: "不出刀" +PlayerIdentityTag.Outside_Position: "外置位" +PlayerIdentityTag.Inside_Position: "內置位" # 通知 / Notifications -PlayerLeft: "【{0}】離開了遊戲" -PlayerLeftCuzTimeout: "【{0}】因連接超時離開了遊戲" -PlayerKickByHost: "【{0}】被房主踢出遊戲" -PlayerBanByHost: "【{0}】被房主封禁" -PlayerLeftCuzError: "【{0}】因為發生錯誤離開了遊戲" -PlayerLeftByAU-Anticheat: "【{0}】被AmongUs官方的反作弊踢出(和FinalSuspect無關)" -KickBecauseDiffrentVersionOrMod: "【{0}】因安裝了不同版本的模組被請離房間" +## 原版 +Notification.PlayerLeft: "【{0}】離開了遊戲" +Notification.PlayerLeftCuzTimeout: "【{0}】因連接超時離開了遊戲" +Notification.PlayerKickByHost: "【{0}】被房主踢出遊戲" +Notification.PlayerBanByHost: "【{0}】被房主封禁" +Notification.PlayerLeftCuzError: "【{0}】因為發生錯誤離開了遊戲" +Notification.PlayerLeftByAU-Anticheat: "【{0}】被AmongUs官方的防作弊踢出(與FinalSuspect無關)" +Notification.KickBecauseDifferentVersionOrMod: "【{0}】因安裝了不同版本的模組被請離房間" +## 模组 +Notification.KickedByDenyName: "【{0}】因其暱稱包含違禁詞被踢出" +Notification.DumpfileSaved: "日誌文件已成功保存到桌面上了,文件名為:{0}" +Notification.KickedByAbnormalFriendCode: "【{0}】因該房禁止好友代碼異常的玩家被請離" +Notification.AddedPlayerToBanList: "【{0}】被添加至黑名單" +Notification.FPSSetTo: "幀數上限設置為: {0}" # 警告 / Warnings Warning: "警告" -Warning.MismatchedVersion: "{0}\n安裝了其它版本的 {1}" -Warning.AutoExitAtMismatchedVersion: "您的 {0} 版本與房主不同\n您將在 {1} 秒內被踢出" -Warning.InvalidRpc: "{0} 因為發送了無效的數據被踢出" -Warning.InvalidRpc_NotHost: "{0} 疑似使用了作弊程序,請提醒房主及時將其踢出(無效的Rpc:{1})" -Warning.SetName: "{0} 因為多次設置名稱被踢出" -Warning.SetName_NotHost: "{0} 疑似使用了作弊程序,請提醒房主及時將其踢出(多次設置名稱)" -Warning.SendQuickChat: "{0} 因為三秒內發送多條快捷消息被踢出" -Warning.SendQuickChat_NotHost: "{0} 疑似使用了作弊程序,請提醒房主及時將其踢出(三秒內發送多條快捷消息)" -Warning.InvalidSlothRPC: "{0} 因為發送了非法數據被踢出(非法發送官方Rpc:{1})" -Warning.InvalidSlothRPC_NotHost: "{0} 疑似使用了作弊程序,請提醒房主及時將其踢出(非法發送官方Rpc: {1})" -Warning.Cheater: "{0} 因為疑似使用作弊程式被踢出" -Warning.Cheater_NotHost: "{0} 疑似使用了作弊程式,請提醒房主及時將其踢出" +Warning.MismatchedVersion: "【{0}】\n安裝了其它版本的 【{1}】" +Warning.AutoExitAtMismatchedVersion: "您的 【{0}】 版本與房主不同\n您將在 {1} 秒內被踢出" Warning.CantKickDev: "很抱歉,您無法踢出開發者" Warning.RoomBroken: "很抱歉,該房間遭受炸房攻擊,請到其他房間進行遊戲" +Warning.InvalidColor: "出現非法顏色玩家" -## 錯誤等級 / Error Levels +## 错误等级 / Error Levels ErrorLevel1: "可能同時產生多個bug" -ErrorLevel2: "可能會出現bug" +ErrorLevel2: "可能出現bug" ErrorLevel3: "未發布版本" # 反作弊 / FAC -FAC.CheatDetected.HighLevel: "警告:FAC正在抵禦炸房外掛" -FAC.CheatDetected.LowLevel: "警告:FAC檢測到可能存在作弊玩家" -FAC.CheatDetected.FAC: "使用作弊程序" - -# 模組信息 / Mod Infos -Contributors: "貢獻者" -Acknowledgement: "特別致謝" - -# 斷連提示 / Disconect Reasons +CheatDetected.HighLevel: "警告:FAC正在抵禦炸房外掛" +CheatDetected.LowLevel: "警告:FAC檢測到可能存在作弊玩家" +CheatDetected.UseCheat: "{0}使用作弊程序【{1}】" +CheatDetected.MayUseCheat: "{0}疑似使用模組【{1}】或作弊程序" +CheatDetected.InvalidRpc: "【{0}】 因為發送了無效的數據被踢出(無效的Rpc:{1})" +CheatDetected.InvalidRpc_NotHost: "【{0}】 疑似使用了作弊程序,請提醒房主及時將其踢出(無效的Rpc:{1})" +CheatDetected.SetName: "【{0}】 因為多次設置名稱被踢出" +CheatDetected.SetName_NotHost: "【{0}】 疑似使用了作弊程序,請提醒房主及時將其踢出(多次設置名稱)" +CheatDetected.SendQuickChat: "【{0}】 因為三秒內發送多條快捷消息被踢出" +CheatDetected.SendQuickChat_NotHost: "【{0}】 疑似使用了作弊程序,請提醒房主及時將其踢出(三秒內發送多條快捷消息)" +CheatDetected.InvalidSlothRPC: "【{0}】 因為發送了非法數據被踢出(非法發送官方Rpc: {1})" +CheatDetected.InvalidSlothRPC_NotHost: "【{0}】 疑似使用了作弊程序,請提醒房主及時將其踢出(非法發送官方Rpc: {1})" +CheatDetected.Overload: "【{0}】因為發起了過載攻擊被踢出" +CheatDetected.Overload_NotHost: "【{0}】發起了過載攻擊,本房間損壞,請到其他房間進行遊戲" +CheatDetected.Cheater: "【{0}】 因為疑似使用作弊程序被踢出" +CheatDetected.Cheater_NotHost: "【{0}】 疑似使用了作弊程序,請提醒房主及時將其踢出" +CheatDetected.BanedByBanList: "【{0}】因在封禁名單內被踢出" +CheatDetected.BanedByFACList: "【{0}】因在FAC封禁名單內被踢出" + +# 模组信息 / Mod Infos +ModInfo.Contributors: "貢獻者" +ModInfo.Acknowledgement: "特別致謝" + +# 断连提示 / Disconnect Reasons DCNotify.Hacking: "您被樹懶的反作弊系統踢出房間" DCNotify.Banned: "您被該房間封禁" DCNotify.Kicked: "您被該房間踢出" -DCNotify.DCFromServer: "您與服務器的連接已中斷\n這可能因為您的網絡不穩定\n也可能因為服務器不穩定或拒絕了您的訪問" +DCNotify.DCFromServer: "您與服務器的連接已中斷\n這可能是因為您的網絡不穩定\n也可能是因為服務器不穩定或拒絕了您的訪問" DCNotify.GameNotFound: "未找到指定房間,可能是房間已解散\n或檢查您是否選擇了與房間不同的服務器" DCNotify.GameStarted: "該房間正在遊戲中,請等待遊戲結束後加入" DCNotify.GameFull: "該房間已滿人,請稍後重試" DCNotify.IncorrectVersion: "您的Among Us版本與該房間不同" DCNotify.Description: "您被踢出房間\n理由:{0}" -DCNotify.DenyName: "您的暱稱含有違規字符" -DCNotify.BanList: "您存在於該房間的黑名單" -DCNotify.FACList: "您已被 FAC 封禁" -DCNotify.CheatDetected: "您被 FAC 檢測到疑似作弊行為" -DCNotify.InvalidRPC: "您可能安裝了與房主不同的模組或您的模組被惡意修改" -DCNotify.ModVersionIncorrect: "您模組的版本與房主不同" -DCNotify.LowLevel: "您的等級未達到該房間的等級要求" -DCNotify.NotLogin: "該房間不允許未登入的玩家加入" - -# 任务与栏相关 / Task Panel + +# 任务栏相关 / Task Panel PressF1ShowRoleDescription: "按F1查看您的職業介紹" +PressF2ToHidePane: "按下F2顯示/隱藏信息版" FakeTask: "假任務:" KillCount: "擊殺" # 复盘信息 / Last Results -RoleSummaryText: "複盤信息:" -ShowResults: "顯示複盤信息" -HideResults: "隱藏複盤信息" -NoInfoExists: "沒有存在的上局信息" -CrewsWin: "船員陣營勝利" -CrewmatesWin: "船員勝利" -CrewmatesWinBlurb: "真理的光芒在希望中閃耀!" -ImpsWin: "偽裝者陣營勝利" -ImpostorsWin: "偽裝者勝利" -ImpostorsWinBlurb: "邪惡將真理化為灰燼" -HideSummaryTextToShowWinText: "隱藏復盤信息查看勝利文本" - -# 禁用公開 / Disable Public -DisabledByProgram: "公開房間的操作已被程序禁用" -PublicNotAvailableOnThisVersion: "該版本的 FinalSuspect 暫不支援公開" +Summary.Text: "覆盤信息:" +Summary.ShowResults: "顯示覆盤信息" +Summary.HideResults: "隱藏覆盤信息" +Summary.NoInfoExists: "沒有存在有效的覆盤信息" +Summary.CrewsWin: "船員陣營勝利" +Summary.ImpsWin: "偽裝者陣營勝利" +Outro.Crews_Win: "船員勝利" +Outro.Crews_WinBlurb: "真理的光芒在希望中閃耀!" +Outro.Imps_Win: "偽裝者勝利" +Outro.Imps_WinBlurb: "邪惡將真理化為灰燼" # 主页 / Main UI FinalSuspectWelcomeText: "祝願您擁有一場愉快的遊戲體驗!" -ConnectToFinalSuspectServerFailed: "無法連接至FinalSuspect服務器" +RetrieveVersionInfoFailed: "無法獲取FinalSuspect信息" Website: "模組官網" MainMenuCredential: "{0} © 2025" -LShift: "按LShift返回上一個房間" -RShift: "按RShift進入剪貼板房間" -LobbyTimeDisplayText: "生存期間" +LShift: "上一個大廳" +RShift: "剪切板大廳" # 客户端平台 / Platform -IPhone: "蘋果" -Android: "安卓" -MicrosoftStore: "Microsoft" +Platform.IPhone: "蘋果" +Platform.Android: "安卓" +Platform.MicrosoftStore: "微軟" # 延迟显示 / Ping Tracker Ping: "延遲" @@ -303,10 +341,25 @@ Local: "本地" # 其他 / Other HongKong: "香港" -FPSSetTo: "幀數上限設置為: {0}" BrowsingMode: "瀏覽模式" Broken: "損壞" +Unknown: "未知" +Back: "返回" +Yes: "是" +No: "否" +Cancel: "取消" +Unload: "切換" +Retry: "重試" +PreviousPage: "上一頁" +NextPage: "下一頁" +Download: "下載" +Disable: "禁用" +Delete: "刪除" +Author: "作者" +Close: "關閉" # 身份 / Identity -Host: "房主" -Cheater: "作弊者" \ No newline at end of file +Id.Host: "房主" +Id.Cheater: "作弊者" +Id.Developer: "開發者" +Id.Contributor: "貢獻者" \ No newline at end of file diff --git a/Assets/Mod/BaseBepInEx/.doorstop_version b/Assets/Mod/BaseBepInEx/.doorstop_version new file mode 100644 index 00000000..81911389 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/.doorstop_version @@ -0,0 +1 @@ +4.3.0 \ No newline at end of file diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/0Harmony.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/0Harmony.dll new file mode 100644 index 00000000..c2a2298e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/0Harmony.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.DotNet.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.DotNet.dll new file mode 100644 index 00000000..10d20525 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.DotNet.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.PE.File.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.PE.File.dll new file mode 100644 index 00000000..0f676a9f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.PE.File.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.PE.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.PE.dll new file mode 100644 index 00000000..ca6786f2 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.PE.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.dll new file mode 100644 index 00000000..1274c52c Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/AsmResolver.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/AssetRipper.CIL.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/AssetRipper.CIL.dll new file mode 100644 index 00000000..21bbc840 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/AssetRipper.CIL.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/AssetRipper.Primitives.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/AssetRipper.Primitives.dll new file mode 100644 index 00000000..02b060ee Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/AssetRipper.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Core.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Core.dll new file mode 100644 index 00000000..4b542c76 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Core.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Core.xml b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Core.xml new file mode 100644 index 00000000..ec55e6e2 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Core.xml @@ -0,0 +1,1742 @@ + + + + BepInEx.Core + + + + + Analyzes the given type definition and attempts to convert it to a valid + + Type definition to analyze. + The filepath of the assembly, to keep as metadata. + If the type represent a valid plugin, returns a instance. Otherwise, return null. + + + + List of all instances loaded via the chainloader. + + + + + Collection of error chainloader messages that occured during plugin loading. + Contains information about what certain plugins were not loaded. + + + + + Occurs after a plugin is loaded. + + + + + Occurs after all plugins are loaded. + + + + + Discovers all plugins in the plugin directory without loading them. + + + This is useful for discovering BepInEx plugin metadata. + + Path from which to search the plugins. + Cache name to use. If null, results are not cached. + List of discovered plugins and their metadata. + + + + Discovers plugins to load. + + List of plugins to be loaded. + + + + Preprocess the plugins and modify the load order. + + Some plugins may be skipped if they cannot be loaded (wrong metadata, etc). + Plugins to process. + List of plugins to load in the correct load order. + + + + Run the chainloader and load all plugins from the plugins folder. + + + + + Detects and loads all plugins in the specified directories. + + + It is better to collect all paths at once and use a single call to LoadPlugins than multiple calls. + This allows to run proper dependency resolving and to load all plugins in one go. + + Directories to search the plugins from. + List of loaded plugin infos. + + + + A cacheable metadata item. Can be used with and + to cache plugin metadata. + + + + + Serialize the object into a binary format. + + + + + + Loads the object from binary format. + + + + + + A cached assembly. + + + + + + List of cached items inside the assembly. + + + + + Hash of the assembly. Used to verify that the assembly hasn't been changed. + + + + + Provides methods for loading specified types from an assembly. + + + + + Default assembly resolved used by the + + + + + Default reader parameters used by + + + + + Event fired when fails to resolve a type during type loading. + + + + + Looks up assemblies in the given directory and locates all types that can be loaded and collects their metadata. + + The specific base type to search for. + The directory to search for assemblies. + A function to check if a type should be selected and to build the type metadata. + A filter function to quickly determine if the assembly can be loaded. + The name of the cache to get cached types from. + + A dictionary of all assemblies in the directory and the list of type metadatas of types that match the + selector. + + + + + Loads an index of type metadatas from a cache. + + Name of the cache + Cacheable item + + Cached type metadatas indexed by the path of the assembly that defines the type. If no cache is defined, + return null. + + + + + Saves indexed type metadata into a cache. + + Name of the cache + List of plugin metadatas indexed by the path to the assembly that contains the types + Hash values that can be used for checking similarity between cached and live assembly + Cacheable item + + + + Converts TypeLoadException to a readable string. + + TypeLoadException + Readable representation of the exception + + + + Base type of all classes representing and enforcing acceptable values of config settings. + + + + Type of values that this class can Clamp. + + + + Type of the supported values. + + + + + Change the value to be acceptable, if it's not already. + + + + + Check if the value is an acceptable value. + + + + + Get the string for use in config files. + + + + + Specify the list of acceptable values for a setting. + + + + + Specify the list of acceptable values for a setting. + If the setting does not equal any of the values, it will be set to the first one. + + + + + List of values that a setting can take. + + + + + + + + + + + + + + Specify the range of acceptable values for a setting. + + + + Lowest acceptable value + Highest acceptable value + + + + Lowest acceptable value + + + + + Highest acceptable value + + + + + + + + + + + + + + Section and key of a setting. Used as a unique key for identification within a + . + The same definition can be used in multiple config files, it will point to different settings then. + + + + + + Create a new definition. Definitions with same section and key are equal. + + Group of the setting, case sensitive. + Name of the setting, case sensitive. + + + + + + + Group of the setting. All settings within a config file are grouped by this. + + + + + Name of the setting. + + + + + Check if the definitions are the same. + + + + + + Check if the definitions are the same. + + + + + + + + Check if the definitions are the same. + + + + + Check if the definitions are the same. + + + + + + + + Metadata of a . + + + + + Create a new description. + + Text describing the function of the setting and any notes or warnings. + + Range of values that this setting can take. The setting's value will be automatically + clamped. + + Objects that can be used by user-made classes to add functionality. + + + + Text describing the function of the setting and any notes or warnings. + + + + + Range of acceptable values for a setting. + + + + + Objects that can be used by user-made classes to add functionality. + + + + + An empty description. + + + + + Provides access to a single setting inside of a . + + Type of the setting. + + + + Value of this setting. + + + + + + + + Fired when the setting is changed. Does not detect changes made outside from this object. + + + + + Container for a single setting of a . + Each config entry is linked to one config file. + + + + + Types of defaultValue and definition.AcceptableValues have to be the same as settingType. + + + + + Config file this entry is a part of. + + + + + Category and name of this setting. Used as a unique key for identification within a + . + + + + + Description / metadata of this setting. + + + + + Type of the that this setting holds. + + + + + Default value of this setting (set only if the setting was not changed before). + + + + + Get or set the value of the setting. + + + + + Get the serialized representation of the value. + + + + + Set the value by using its serialized form. + + + + + If necessary, clamp the value to acceptable value range. T has to be equal to settingType. + + + + + Trigger setting changed event. + + + + + Write a description of this setting using all available metadata. + + + + + A helper class to handle persistent data. All public methods are thread-safe. + + + + + + + + Create a new config file at the specified config path. + + Full path to a file that contains settings. The file will be created as needed. + If the config file/directory doesn't exist, create it immediately. + Information about the plugin that owns this setting file. + + + + All config entries inside + + + + + Create a list with all config entries inside of this config file. + + + + + Full path to the config file. The file might not exist until a setting is added and changed, or + is called. + + + + + If enabled, writes the config to disk every time a value is set. + If disabled, you have to manually use or the changes will be lost! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Returns the ConfigDefinitions that the ConfigFile contains. + Creates a new array when the property is accessed. Thread-safe. + + + + + Returns the ConfigEntryBase values that the ConfigFile contains. + Creates a new array when the property is accessed. Thread-safe. + + + + + Create an array with all config entries inside of this config file. Should be only used for metadata purposes. + If you want to access and modify an existing setting then use + + instead with no description. + + + + + Generate user-readable comments for each of the settings in the saved .cfg file. + + + + + Reloads the config from disk. Unsaved changes are lost. + + + + + Writes the config to disk. + + + + + Access one of the existing settings. If the setting has not been added yet, null is returned. + If the setting exists but has a different type than T, an exception is thrown. + New settings should be added with . + + Type of the value contained in this setting. + Section and Key of the setting. + + + + Access one of the existing settings. If the setting has not been added yet, null is returned. + If the setting exists but has a different type than T, an exception is thrown. + New settings should be added with . + + Type of the value contained in this setting. + Section/category/group of the setting. Settings are grouped by this. + Name of the setting. + + + + Access one of the existing settings. If the setting has not been added yet, false is returned. Otherwise, true. + If the setting exists but has a different type than T, an exception is thrown. + New settings should be added with + . + + Type of the value contained in this setting. + Section and Key of the setting. + The ConfigEntry value to return. + + + + Access one of the existing settings. If the setting has not been added yet, null is returned. + If the setting exists but has a different type than T, an exception is thrown. + New settings should be added with + . + + Type of the value contained in this setting. + Section/category/group of the setting. Settings are grouped by this. + Name of the setting. + The ConfigEntry value to return. + + + + Create a new setting. The setting is saved to drive and loaded automatically. + Each definition can be used to add only one setting, trying to add a second setting will throw an exception. + + Type of the value contained in this setting. + Section and Key of the setting. + Value of the setting if the setting was not created yet. + Description of the setting shown to the user and other metadata. + + + + Create a new setting. The setting is saved to drive and loaded automatically. + Each section and key pair can be used to add only one setting, trying to add a second setting will throw an + exception. + + Type of the value contained in this setting. + Section/category/group of the setting. Settings are grouped by this. + Name of the setting. + Value of the setting if the setting was not created yet. + Description of the setting shown to the user and other metadata. + + + + Create a new setting. The setting is saved to drive and loaded automatically. + Each section and key pair can be used to add only one setting, trying to add a second setting will throw an + exception. + + Type of the value contained in this setting. + Section/category/group of the setting. Settings are grouped by this. + Name of the setting. + Value of the setting if the setting was not created yet. + Simple description of the setting shown to the user. + + + + Create a new setting. The setting is saved to drive and loaded automatically. + Each definition can be used to add only one setting, trying to add a second setting will throw an exception. + + Type of the value contained in this setting. + Section and Key of the setting. + Value of the setting if the setting was not created yet. + Description of the setting shown to the user and other metadata. + + + + Create a new setting. The setting is saved to drive and loaded automatically. + Each section and key pair can be used to add only one setting, trying to add a second setting will throw an + exception. + + Type of the value contained in this setting. + Section/category/group of the setting. Settings are grouped by this. + Name of the setting. + Value of the setting if the setting was not created yet. + Description of the setting shown to the user and other metadata. + + + + Create a new setting. The setting is saved to drive and loaded automatically. + Each section and key pair can be used to add only one setting, trying to add a second setting will throw an + exception. + + Type of the value contained in this setting. + Section/category/group of the setting. Settings are grouped by this. + Name of the setting. + Value of the setting if the setting was not created yet. + Simple description of the setting shown to the user. + + + + Access a setting. Use Bind instead. + + + + + Access a setting. Use Bind instead. + + + + + An event that is fired every time the config is reloaded. + + + + + Fired when one of the settings is changed. + + + + + Provides access to a single setting inside of a . + + Type of the setting. + + + + Entry of this setting in the . + + + + + Unique definition of this setting. + + + + + Config file this setting is inside of. + + + + + Value of this setting. + + + + + Fired when the setting is changed. Does not detect changes made outside from this object. + + + + + Arguments for events concerning a change of a setting. + + + + + + + + + Setting that was changed + + + + + Serializer/deserializer used by the config system. + + + + + Convert object of a given type to a string using available converters. + + + + + Convert string to an object of a given type using available converters. + + + + + Convert string to an object of a given type using available converters. + + + + + Get a converter for a given type if there is any. + + + + + Add a new type converter for a given type. + If a different converter is already added, this call is ignored and false is returned. + + + + + Check if a given type can be converted to and from string. + + + + + Give a list of types with registered converters. + + + + + A serializer/deserializer combo for some type(s). Used by the config system. + + + + + Used to serialize the type into a (hopefully) human-readable string. + Object is the instance to serialize, Type is the object's type. + + + + + Used to deserialize the type from a string. + String is the data to deserialize, Type is the object's type, should return instance to an object of Type. + + + + + True if an external console has been started, false otherwise. + + + + + The stream that writes to the standard out stream of the process. Should never be null. + + + + + The stream that writes to an external console. Null if no such console exists + + + + + This attribute denotes that a class is a plugin, and specifies the required metadata. + + + + The unique identifier of the plugin. Should not change between plugin versions. + The user friendly name of the plugin. Is able to be changed between versions. + The specific version of the plugin. + + + + The unique identifier of the plugin. Should not change between plugin versions. + + + + + The user friendly name of the plugin. Is able to be changed between versions. + + + + + The specific version of the plugin. + + + + + This attribute specifies any dependencies that this plugin has on other plugins. + + + + + Flags that are applied to a dependency + + + + + The plugin has a hard dependency on the referenced plugin, and will not run without it. + + + + + This plugin has a soft dependency on the referenced plugin, and is able to run without it. + + + + + Marks this as dependent on another plugin. The other plugin will be loaded before + this one. + If the other plugin doesn't exist, what happens depends on the parameter. + + The GUID of the referenced plugin. + The flags associated with this dependency definition. + + + + Marks this as dependent on another plugin. The other plugin will be loaded before + this one. + If the other plugin doesn't exist or is of a version not satisfying , this plugin will + not load and an error will be logged instead. + + The GUID of the referenced plugin. + The version range of the referenced plugin. + When version is supplied the dependency is always treated as HardDependency + + + + The GUID of the referenced plugin. + + + + + The flags associated with this dependency definition. + + + + + The version range of the referenced plugin. + + + + + This attribute specifies other plugins that are incompatible with this plugin. + + + + + Marks this as incompatible with another plugin. + If the other plugin exists, this plugin will not be loaded and a warning will be shown. + + The GUID of the referenced plugin. + + + + The GUID of the referenced plugin. + + + + + This attribute specifies which processes this plugin should be run for. Not specifying this attribute will load the + plugin under every process. + + + + The name of the process that this plugin will run under. + + + + The name of the process that this plugin will run under. + + + + + Helper class to use for retrieving metadata about a plugin, defined as attributes. + + + + + Retrieves the BepInPlugin metadata from a plugin type. + + The plugin type. + The BepInPlugin metadata of the plugin type. + + + + Retrieves the BepInPlugin metadata from a plugin instance. + + The plugin instance. + The BepInPlugin metadata of the plugin instance. + + + + Gets the specified attributes of a type, if they exist. + + The attribute type to retrieve. + The plugin type. + The attributes of the type, if existing. + + + + Gets the specified attributes of an assembly, if they exist. + + The assembly. + The attribute type to retrieve. + The attributes of the type, if existing. + + + + Gets the specified attributes of an instance, if they exist. + + The attribute type to retrieve. + The plugin instance. + The attributes of the instance, if existing. + + + + Gets the specified attributes of a reflection metadata type, if they exist. + + The attribute type to retrieve. + The reflection metadata instance. + The attributes of the instance, if existing. + + + + Retrieves the dependencies of the specified plugin type. + + The plugin type. + A list of all plugin types that the specified plugin type depends upon. + + + + Data class that represents information about a loadable BepInEx plugin. + Contains all metadata and additional info required for plugin loading by . + + + + + General metadata about a plugin. + + + + + Collection of attributes that describe what processes the plugin can run on. + + + + + Collection of attributes that describe what plugins this plugin depends on. + + + + + Collection of attributes that describe what plugins this plugin + is incompatible with. + + + + + File path to the plugin DLL + + + + + Instance of the plugin that represents this info. NULL if no plugin is instantiated from info (yet) + + + + + + + + Interpolated string handler for BepInEx . This allows to conditionally skip logging certain + messages and speed up logging in certain places. + + + The class isn't meant to be constructed manually. + Instead, use with + string interpolation. + + + + + Constructs a log handler. + + Length of the literal string. + Number for formatted items. + Log level the message belongs to. + Whether this string should be logged. + + + + Whether the interpolation is enabled and string will be logged. + + + + + Appends a literal string to the interpolation. + + String to append. + + + + Appends a value to the interpolation. + + Value to append. + Type of the value to append. + + + + Append a formattable item. + + Item to append. + Format to append with. + Item type. + + + + Append an IntPtr. + + Item to append. + Format to append with. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Logs entries using a console spawned by BepInEx. + + + + + + + + + + + + + + Logs entries using Unity specific outputs. + + + + + Creates a new disk log listener. + + Path to the log. + Log levels to display. + Whether to append logs to an already existing log file. + + Whether to delay flushing to disk to improve performance. Useful to set this to false + when debugging crashes. + + Maximum amount of concurrently opened log files. Can help with infinite game boot loops. + + + + Log levels to display. + + + + + Writer for the disk log. + + + + + Timer for flushing the logs to a file. + + + + + + + + + + + + + + A generic log listener that receives log events and can route them to some output (e.g. file, console, socket). + + + + + What log levels the listener preliminarily wants. + + + The filter is used to more efficiently discard log messages that aren't being listened to. + As such, the filter should represent the log levels that the listener will always want to process. + It is up to the the implementation of whether the messages are going to be processed or + discarded. + + TODO: Right now the filter cannot be updated after the log listener has been attached to the logger. + + + + Handle an incoming log event. + + Log source that sent the event. Don't use; instead use + Information about the log message. + + + + Log source that can output log messages. + + + + + Name of the log source. + + + + + Event that sends the log message. Call to send a log message. + + + + + Log event arguments. Contains info about the log message. + + + + + Creates the log event args- + + Logged data. + Log level of the data. + Log source that emits these args. + + + + Logged data. + + + + + Log levels for the data. + + + + + Log source that emitted the log event. + + + + + + + + Like but appends newline at the end. + + Same output as but with new line. + + + + Handles pub-sub event marshalling across all log listeners and sources. + + + + + Log levels that are currently listened to by at least one listener. + + + + + Collection of all log listeners that receive log events. + + + + + Collection of all log source that output log events. + + + + + Logs an entry to the internal logger instance. + + The level of the entry. + The data of the entry. + + + + Logs an entry to the internal logger instance if any log listener wants the message. + + The level of the entry. + Log handler to resolve log from. + + + + Creates a new log source with a name and attaches it to . + + Name of the log source to create. + An instance of that allows to write logs. + + + + Simple thread safe list that prioritizes read speed over write speed. + Read is the same as a normal list, while write locks and allocates a copy of the list. + Logger lists are rarely updated so this tradeoff should be fine. + + + + + + The level, or severity of a log entry. + + + + + No level selected. + + + + + A fatal error has occurred, which cannot be recovered from. + + + + + An error has occured, but can be recovered from. + + + + + A warning has been produced, but does not necessarily mean that something wrong has happened. + + + + + An important message that should be displayed to the user. + + + + + A message of low importance. + + + + + A message that would likely only interest a developer. + + + + + All log levels. + + + + + Helper methods for log level handling. + + + + + Gets the highest log level when there could potentially be multiple levels provided. + + The log level(s). + The highest log level supplied. + + + + Returns a translation of a log level to it's associated console colour. + + The log level(s). + A console color associated with the highest log level supplied. + + + + A generic, multi-purpose log source. Exposes simple API to manually emit logs. + + + + + Creates a manual log source. + + Name of the log source. + + + + + + + + + + + + + Logs a message with the specified log level. + + Log levels to attach to the message. Multiple can be used with bitwise ORing. + Data to log. + + + + Logs an interpolated string with the specified log level. + + Log levels to attach to the message. Multiple can be used with bitwise ORing. + Handler for the interpolated string. + + + + Logs a message with level. + + Data to log. + + + + Logs an interpolated string with level. + + Handler for the interpolated string. + + + + Logs a message with level. + + Data to log. + + + + Logs an interpolated string with level. + + Handler for the interpolated string. + + + + Logs a message with level. + + Data to log. + + + + Logs an interpolated string with level. + + Handler for the interpolated string. + + + + Logs a message with level. + + Data to log. + + + + Logs an interpolated string with level. + + Handler for the interpolated string. + + + + Logs a message with level. + + Data to log. + + + + Logs an interpolated string with level. + + Handler for the interpolated string. + + + + Logs a message with level. + + Data to log. + + + + Logs an interpolated string with level. + + Handler for the interpolated string. + + + + A source that routes all logs from the inbuilt .NET API to the BepInEx logging system. + + + + + + Creates a new trace log source. + + + + + Whether Trace logs are currently being rerouted. + + + + + Internal log source. + + + + + Creates a new trace log source. + + New log source (or already existing one). + + + + Writes a message to the underlying instance. + + The message to write. + + + + Writes a message and a newline to the underlying instance. + + The message to write. + + + + + + + + + + Paths used by BepInEx + + + + + BepInEx version. + + + + + The path to the Managed folder that contains the main managed assemblies. + + + + + The path to the game data folder of the currently running Unity game. + + + + + The directory that the core BepInEx DLLs reside in. + + + + + The path to the core BepInEx DLL. + + + + + The path to the main BepInEx folder. + + + + + The path of the currently executing program BepInEx is encapsulated in. + + + + + The directory that the currently executing process resides in. + On OSX however, this is the parent directory of the game.app folder. + + + + + The path to the config directory. + + + + + The path to the global BepInEx configuration file. + + + + + The path to temporary cache files. + + + + + The path to the patcher plugin folder which resides in the BepInEx folder. + + + + + The path to the plugin folder which resides in the BepInEx folder. + + This is ONLY guaranteed to be set correctly when Chainloader has been initialized. + + + + + + The name of the currently executing process. + + + + + List of directories from where Mono will search assemblies before assembly resolving is invoked. + + + + + Generic helper properties and methods. + + + + + Whether current Common Language Runtime supports dynamic method generation using + namespace. + + + + + An encoding for UTF-8 which does not emit a byte order mark (BOM). + + + + + Try to perform an action. + + Action to perform. + Possible exception that gets returned. + True, if action succeeded, false if an exception occured. + + + + Combines multiple paths together, as the specific method is not available in .NET 3.5. + + The multiple paths to combine together. + A combined path. + + + + Returns the parent directory of a path, optionally specifying the amount of levels. + + The path to get the parent directory of. + The amount of levels to traverse. Defaults to 1 + The parent directory. + + + + Tries to parse a bool, with a default value if unable to parse. + + The string to parse + The value to return if parsing is unsuccessful. + Boolean value of input if able to be parsed, otherwise default value. + + + + Converts a file path into a UnityEngine.WWW format. + + The file path to convert. + A converted file path. + + + + Indicates whether a specified string is null, empty, or consists only of white-space characters. + + The string to test. + True if the value parameter is null or empty, or if value consists exclusively of white-space characters. + + + + Sorts a given dependency graph using a direct toposort, reporting possible cyclic dependencies. + + Nodes to sort + Function that maps a node to a collection of its dependencies. + Type of the node in a dependency graph. + Collection of nodes sorted in the order of least dependencies to the most. + Thrown when a cyclic dependency occurs. + + + + Try to resolve and load the given assembly DLL. + + Name of the assembly, of the type . + Directory to search the assembly from. + The loaded assembly. + True, if the assembly was found and loaded. Otherwise, false. + + + + Checks whether a given cecil type definition is a subtype of a provided type. + + Cecil type definition + Type to check against + Whether the given cecil type is a subtype of the type. + + + + Try to resolve and load the given assembly DLL. + + Name of the assembly, of the type . + Directory to search the assembly from. + The loaded assembly. + True, if the assembly was found and loaded. Otherwise, false. + + + + Try to resolve and load the given assembly DLL. + + Name of the assembly, of the type . + Directory to search the assembly from. + Reader parameters that contain possible custom assembly resolver. + The loaded assembly. + True, if the assembly was found and loaded. Otherwise, false. + + + + Tries to create a file with the given name + + Path of the file to create + File open mode + Resulting filestream + File access options + File share options + + + + + Compute a MD5 hash of the given stream. + + Stream to hash + MD5 hash as a hex string + + + + Hash a list of strings using MD5 + + Strings to hash + MD5 of the strings + + + + Convert the given array to a hex string. + + Bytes to convert. + Bytes reinterpreted as a hex number. + + + + Get a value of a command line argument + + Argument name + Next argument after the given argument name. If not found, returns null. + + + + Try to parse given string as an assembly name + + Fully qualified assembly name + Resulting instance + true, if parsing was successful, otherwise false + + On some versions of mono, using fails because it runs on unmanaged side + which has problems with encoding. + Using solves this by doing parsing on managed side instead. + + + + + Gets unique files in all given directories. If the file with the same name exists in multiple directories, + only the first occurrence is returned. + + Directories to search from. + File pattern to search. + Collection of all files in the directories. + + + + Console class with safe handlers for Unity 4.x, which does not have a proper Console implementation + + + + diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Preloader.Core.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Preloader.Core.dll new file mode 100644 index 00000000..e697fbf5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Preloader.Core.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Preloader.Core.xml b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Preloader.Core.xml new file mode 100644 index 00000000..c591252c --- /dev/null +++ b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Preloader.Core.xml @@ -0,0 +1,313 @@ + + + + BepInEx.Preloader.Core + + + + + + + + Doorstop environment variables, passed into the BepInEx preloader. + https://github.com/NeighTools/UnityDoorstop/wiki#environment-variables + + + + + Path to the assembly that was invoked via Doorstop. Contains the same value as in "targetAssembly" configuration + option in the config file. + + + + + Full path to the game's "Managed" folder that contains all the game's managed assemblies + + + + + Full path to the game executable currently running. + + + + + Array of paths where Mono searches DLLs from before assembly resolvers are invoked. + + + + + Path of the DLL that contains mono imports. + + + + + Log listener that listens to logs during preloading time and buffers messages for output in Unity logs later. + + + + + A list of all objects that this listener has received. + + + + + + + + + + + + + + Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and + loading assemblies one at a time. + + + + + The context of this assembly patcher instance that is passed to all patcher plugins. + + + + + A cloned version of to ensure that any foreach loops do not break when the collection + gets modified. + + + + + Performs work to dispose collection objects. + + + + + Adds all patchers from all managed assemblies specified in a directory. + + Directory to search patcher DLLs from. + + + + Adds all .dll assemblies in given directories to be patched and loaded by this patcher instance. Non-managed + assemblies + are skipped. + + The directories to search. + + + + Adds all assemblies in given directories to be patched and loaded by this patcher instance. Non-managed assemblies + are + skipped. + + The directory to search. + The file extensions to attempt to load. + + + + Attempts to load a managed assembly as an . Returns true if successful. + + The path of the assembly. + The loaded assembly. Null if not successful in loading. + + + + Applies patchers to all assemblies loaded into this assembly patcher and then loads patched assemblies into memory. + + + + + This attribute denotes that a class is a patcher plugin, and specifies the required metadata. + + + + The unique identifier of the plugin. Should not change between plugin versions. + The user friendly name of the plugin. Is able to be changed between versions. + The specific version of the plugin. + + + + The unique identifier of the plugin. Should not change between plugin versions. + + + + + The user friendly name of the plugin. Is able to be changed between versions. + + + + + The specific version of the plugin. + + + + + Defines an assembly that a patch method will target. + + + + + Marker used to indicate all possible assemblies to be targeted by a patch method. + + + + + The short filename of the assembly. Use to mark all possible + assemblies as targets. + + + + + The short filename of the assembly to target. + + + + + Defines a type that a patch method will target. + + + + The short filename of the assembly of which belongs to. + The full name of the type to target for patching. + + + + The short filename of the assembly to target. + + + + + The full name of the type to target for patching. + + + + + A patcher that can contain multiple methods for patching assemblies. + + + + + A instance created for use by this patcher plugin. + + + + + A configuration file binding created with the of this plugin as the + filename. + + + + + Metadata associated with this patcher plugin. + + + + + The context of the this BasePatcher is associated with. + + + + + Executed before any patches from any plugin are applied. + + + + + Executed after all patches from all plugins have been applied. + + + + + A definition of an individual patch for use by . + + + + + The assembly / assemblies this patch will target, if there any. + + + + + The type / types this patch will target, if there are any. + + + + + The instance of the this originates from. + + + + + The method that will perform the patching logic defined by this instance. + + + + + A friendly name for this patch definition, for use in logging and error tracking. + + + + + Context provided to patcher plugins from the associated patcher engine. + + + + + Contains a list of assemblies that will be patched and loaded into the runtime. + + The dictionary has the name of the file, without any directories. These are used by the dumping + functionality, and as such, these are also required to be unique. They do not have to be exactly the same as + the real filename, however they have to be mapped deterministically. + + Order is not respected, as it will be sorted by dependencies. + + + + + Contains a mapping of available assembly name to their original filenames. + + + + + Contains a dictionary of assemblies that have been loaded as part of executing this assembly patcher. + + The key is the same key as used in , while the value is the actual assembly + itself. + + + + + + A list of plugins that will be initialized and executed, in the order of the list. + + + + + A list of individual patches that will execute, generated by parsing + . + + + + + + A single cached assembly patcher. + + + + + Type name of the patcher. + + + + + + + + + + + Recreation of MonoMod's PlatformHelper.DeterminePlatform method, but with libc calls instead of creating processes. + + + + diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.Common.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.Common.dll new file mode 100644 index 00000000..5ac53615 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.Common.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.Common.xml b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.Common.xml new file mode 100644 index 00000000..82e25728 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.Common.xml @@ -0,0 +1,32 @@ + + + + BepInEx.Unity.Common + + + + + Various information about the currently executing Unity player. + + + + + Path to the player executable. + + + + + Path to the game data directory (directory that contains the game assets). + + + + + Version of the Unity player + + + Because BepInEx can execute very early, the exact Unity version might not be available in early + bootstrapping phases. The version should be treated as an estimation of the actual version of the Unity player. + + + + diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.dll new file mode 100644 index 00000000..96678a98 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.dll.config b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.dll.config new file mode 100644 index 00000000..4cb6f5e5 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.dll.config @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.xml b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.xml new file mode 100644 index 00000000..b22fdc77 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/BepInEx/core/BepInEx.Unity.IL2CPP.xml @@ -0,0 +1,39 @@ + + + + BepInEx.Unity.IL2CPP + + + + + Add a Component (e.g. MonoBehaviour) into Unity scene. + Automatically registers the type with Il2Cpp Type system if it isn't already. + + Type of the component to add. + + + + Register and add a Unity Component (for example MonoBehaviour) into BepInEx global manager. + Automatically registers the type with Il2Cpp type system if it isn't initialised already. + + Type of the component to add. + + + + Register and add a Unity Component (for example MonoBehaviour) into BepInEx global manager. + Automatically registers the type with Il2Cpp type system if it isn't initialised already. + + Type of the component to add + + + + Occurs after a plugin is instantiated and just before is called. + + + + + The main entrypoint of BepInEx, called from Doorstop. + + + + diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Cpp2IL.Core.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Cpp2IL.Core.dll new file mode 100644 index 00000000..20dbe2e4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Cpp2IL.Core.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Disarm.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Disarm.dll new file mode 100644 index 00000000..367fa593 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Disarm.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Gee.External.Capstone.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Gee.External.Capstone.dll new file mode 100644 index 00000000..f2366d12 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Gee.External.Capstone.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Iced.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Iced.dll new file mode 100644 index 00000000..2d524f41 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Iced.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Common.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Common.dll new file mode 100644 index 00000000..617ac5df Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Common.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Generator.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Generator.dll new file mode 100644 index 00000000..eea7f6a4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Generator.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.HarmonySupport.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.HarmonySupport.dll new file mode 100644 index 00000000..3187f3e1 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.HarmonySupport.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Runtime.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Runtime.dll new file mode 100644 index 00000000..19c32370 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Il2CppInterop.Runtime.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/LibCpp2IL.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/LibCpp2IL.dll new file mode 100644 index 00000000..aa9d4c2e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/LibCpp2IL.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Mdb.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Mdb.dll new file mode 100644 index 00000000..c22998b6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Mdb.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Pdb.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Pdb.dll new file mode 100644 index 00000000..0d396b88 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Pdb.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Rocks.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Rocks.dll new file mode 100644 index 00000000..785c186e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.Rocks.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.dll new file mode 100644 index 00000000..9b78a39f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/Mono.Cecil.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/MonoMod.RuntimeDetour.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/MonoMod.RuntimeDetour.dll new file mode 100644 index 00000000..dd521007 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/MonoMod.RuntimeDetour.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/MonoMod.Utils.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/MonoMod.Utils.dll new file mode 100644 index 00000000..19b0459f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/MonoMod.Utils.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/SemanticVersioning.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/SemanticVersioning.dll new file mode 100644 index 00000000..e0216727 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/SemanticVersioning.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/StableNameDotNet.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/StableNameDotNet.dll new file mode 100644 index 00000000..11ed7506 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/StableNameDotNet.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/WasmDisassembler.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/WasmDisassembler.dll new file mode 100644 index 00000000..d9d18696 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/WasmDisassembler.dll differ diff --git a/Assets/Mod/BaseBepInEx/BepInEx/core/dobby.dll b/Assets/Mod/BaseBepInEx/BepInEx/core/dobby.dll new file mode 100644 index 00000000..9f14570b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/BepInEx/core/dobby.dll differ diff --git a/Assets/Mod/BaseBepInEx/changelog.txt b/Assets/Mod/BaseBepInEx/changelog.txt new file mode 100644 index 00000000..e3a5957a --- /dev/null +++ b/Assets/Mod/BaseBepInEx/changelog.txt @@ -0,0 +1,48 @@ +46 changes since v6.0.0-pre.2 + +Changelog (excluding merge commits): +* (5fef357) [ManlyMarco] Bump Cpp2IL to pre-release.19 (#1103) +* (995f049) [js6pak] Fix CI webhook +* (0c2a3f1) [ManlyMarco] Switch Build.csproj TargetFramework to net8.0 (#1039) +* (28559fe) [Howard Lam] Checks if target Unity binaries archive exists before attempting download in case game's network conenction is blocked (#1024) +* (35f6b1b) [itectori] include DeobfuscationMap.csv.gz in ComputeHash (#1020) +* (e1974e2) [ManlyMarco] Use joelwmale/webhook-action@2.4.1 +* (e2d5ab8) [ManlyMarco] Clean up empty files +* (544f909) [aldelaro5] Revert "Merge pull request #960 from aldelaro5/metaplugins" (#995) +* (d9b95a0) [aldelaro5] Add input abstraction layer to support games with the Input class disabled +* (af5d939) [aldelaro5] Make Logger.Listeners and Logger.Sources thread safe +* (8597ce7) [aldelaro5] Add TomlTypeConverter for UnityEngine.Rect +* (6e846fc) [aldelaro5] Preload Harmony and MonoMod +* (92f7d25) [aldelaro5] Print file write data in console +* (a5b7dab) [aldelaro5] Add error when using *nix build with Windows executable +* (b891cec) [aldelaro5] Upgrade the github workflows sdk to 8.0.x (#992) +* (726b68e) [aldelaro5] Prioritize the shallowest path for the same assembly name in the default providers (#980) +* (cdeaad7) [ManlyMarco] Use upload-artifact@v4 (#981) +* (f800cbc) [ManlyMarco] Add GlobalMetadataPath setting (#978) +* (cbd70eb) [ManlyMarco] Automatically load all interop assemblies before loading plugins (#777) +* (2474dce) [aldelaro5] Use PascalCase for the cache versioning const +* (ede28de) [aldelaro5] Make the cache version a const +* (ae4d7bb) [aldelaro5] Actually write the cache version in SaveAssemblyCache +* (d389ed8) [aldelaro5] Add versioning to CachedAssembly +* (be1beb5) [aldelaro5] Address code review round 5 +* (7315b7d) [aldelaro5] Make the default providers classes internal in their assemblies +* (6b38cee) [ManlyMarco] Fix failing to initialize if game's Data folder is just 'Data' (#975) +* (c02ad9c) [aldelaro5] Add an obsolete property for compatibility +* (e0a81df) [aldelaro5] Rename Location to prevent compilation with it +* (d44b93b) [aldelaro5] Use the IL2CPPChainLoader instance for il2cpp to preserve compatibility +* (e9337e9) [aldelaro5] Revert the removal of the il2cpp chainloader instance +* (0a4f1d6) [aldelaro5] Accomodate breaking changes in preparation for api refactors +* (5a32424) [thegu5] Bump Cpp2IL (#967) +* (24cf635) [aldelaro5] Ensure providers and plugins / patchers folders are created if they don't exist +* (4597be0) [aldelaro5] Remove files caching from CachedPluginLoadContext, make it forward the call instead +* (ba3fac9) [aldelaro5] Upgrade MonoMod to 22.7.31.1 to fix old mono preloader crashes (#973) +* (40643ff) [aldelaro5] Offload the load context caching to bepinex +* (1f182c7) [aldelaro5] Correct the symbols resolution and rename the method +* (ee5918b) [aldelaro5] Address minor code review +* (e2396d2) [aldelaro5] address code review round 3 +* (b729aee) [aldelaro5] Address code review round 3 (first part) +* (bfc539b) [aldelaro5] Address code review round 2 +* (b688bfa) [aldelaro5] Add a Source property to PluginInfo +* (d8dd404) [aldelaro5] Address code review +* (4d47c3c) [aldelaro5] Address code review +* (952c74f) [aldelaro5] Implement Patcher and plugin providers diff --git a/Assets/Mod/BaseBepInEx/doorstop_config.ini b/Assets/Mod/BaseBepInEx/doorstop_config.ini new file mode 100644 index 00000000..8af10262 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/doorstop_config.ini @@ -0,0 +1,51 @@ +# General options for Unity Doorstop +[General] + +# Enable Doorstop? +enabled = true + +# Path to the assembly to load and execute +# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()` +target_assembly = BepInEx\core\BepInEx.Unity.IL2CPP.dll + +# If true, Unity's output log is redirected to \output_log.txt +redirect_output_log = false + +# Overrides the default boot.config file path +boot_config_override = + +# If enabled, DOORSTOP_DISABLE env var value is ignored +# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS +ignore_disable_switch = false + +# Options specific to running under Unity Mono runtime +[UnityMono] + +# Overrides default Mono DLL search path +# Sometimes it is needed to instruct Mono to seek its assemblies from a different path +# (e.g. mscorlib is stripped in original game) +# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed +# Original Managed folder is added as a secondary folder in the search path +dll_search_path_override = + +# If true, Mono debugger server will be enabled +debug_enabled = false + +# When debug_enabled is true, this option specifies whether Doorstop should initialize the debugger server +# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to false +debug_start_server = true + +# When debug_enabled is true, specifies the address to use for the debugger server +debug_address = 127.0.0.1:10000 + +# If true and debug_enabled is true, Mono debugger server will suspend the game execution until a debugger is attached +debug_suspend = false + +# Options sepcific to running under Il2Cpp runtime +[Il2Cpp] + +# Path to coreclr.dll that contains the CoreCLR runtime +coreclr_path = dotnet\coreclr.dll + +# Path to the directory containing the managed core libraries for CoreCLR (mscorlib, System, etc.) +corlib_dir = dotnet diff --git a/Assets/Mod/BaseBepInEx/dotnet/.version b/Assets/Mod/BaseBepInEx/dotnet/.version new file mode 100644 index 00000000..ccfc5527 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/dotnet/.version @@ -0,0 +1,2 @@ +0ec02c8c96e2eda06dc5b5edfdbdba0f36415082 +6.0.7 diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Bcl.AsyncInterfaces.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Bcl.AsyncInterfaces.dll new file mode 100644 index 00000000..fe6ba4c5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Bcl.AsyncInterfaces.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.CSharp.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.CSharp.dll new file mode 100644 index 00000000..0cdc3af4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.CSharp.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.DiaSymReader.Native.x86.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.DiaSymReader.Native.x86.dll new file mode 100644 index 00000000..80176a13 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.DiaSymReader.Native.x86.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.DependencyInjection.Abstractions.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.DependencyInjection.Abstractions.dll new file mode 100644 index 00000000..b4ee93da Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.DependencyInjection.Abstractions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.DependencyInjection.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.DependencyInjection.dll new file mode 100644 index 00000000..97525f7e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.DependencyInjection.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Logging.Abstractions.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Logging.Abstractions.dll new file mode 100644 index 00000000..bb27a2fc Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Logging.Abstractions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Logging.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Logging.dll new file mode 100644 index 00000000..9e2d7f94 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Logging.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Options.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Options.dll new file mode 100644 index 00000000..604b6027 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Options.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Primitives.dll new file mode 100644 index 00000000..1b2c43af Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Extensions.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.NETCore.App.deps.json b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.NETCore.App.deps.json new file mode 100644 index 00000000..4c5e97e8 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.NETCore.App.deps.json @@ -0,0 +1,984 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0/win-x86", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": {}, + ".NETCoreApp,Version=v6.0/win-x86": { + "Microsoft.NETCore.App.Runtime.win-x86/6.0.7": { + "runtime": { + "System.Private.CoreLib.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "Microsoft.VisualBasic.dll": { + "assemblyVersion": "10.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "mscorlib.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "netstandard.dll": { + "assemblyVersion": "2.1.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.AppContext.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Buffers.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ComponentModel.DataAnnotations.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Configuration.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Core.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Data.DataSetExtensions.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Data.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.Contracts.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.Debug.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.Tools.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.Tracing.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Drawing.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Dynamic.Runtime.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Globalization.Calendars.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Globalization.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Globalization.Extensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.Compression.FileSystem.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.FileSystem.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.FileSystem.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.Pipes.AccessControl.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.UnmanagedMemoryStream.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Numerics.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Numerics.Vectors.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.Emit.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.Emit.ILGeneration.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.Emit.Lightweight.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.Extensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Resources.Reader.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Resources.ResourceManager.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Extensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Handles.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Intrinsics.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Loader.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Serialization.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Serialization.Json.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Serialization.Xml.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Principal.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.SecureString.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ServiceModel.Web.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ServiceProcess.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Text.Encoding.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Text.Encoding.Extensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Overlapped.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Tasks.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Tasks.Extensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Thread.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.ThreadPool.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Timer.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Transactions.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ValueTuple.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Web.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Windows.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.Linq.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.ReaderWriter.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.Serialization.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.XDocument.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.XmlDocument.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.XmlSerializer.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.XPath.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "WindowsBase.dll": { + "assemblyVersion": "4.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "Microsoft.CSharp.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "Microsoft.VisualBasic.Core.dll": { + "assemblyVersion": "11.0.0.0", + "fileVersion": "11.100.722.32202" + }, + "Microsoft.Win32.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "Microsoft.Win32.Registry.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Collections.Concurrent.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Collections.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Collections.Immutable.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Collections.NonGeneric.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Collections.Specialized.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ComponentModel.Annotations.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ComponentModel.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ComponentModel.EventBasedAsync.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ComponentModel.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ComponentModel.TypeConverter.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Console.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Data.Common.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.DiagnosticSource.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.FileVersionInfo.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.Process.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.StackTrace.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.TextWriterTraceListener.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Diagnostics.TraceSource.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Drawing.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Formats.Asn1.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.Compression.Brotli.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.Compression.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.Compression.ZipFile.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.FileSystem.AccessControl.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.FileSystem.DriveInfo.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.FileSystem.Watcher.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.IsolatedStorage.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.MemoryMappedFiles.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.IO.Pipes.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Linq.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Linq.Expressions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Linq.Parallel.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Linq.Queryable.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Memory.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Http.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Http.Json.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.HttpListener.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Mail.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.NameResolution.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.NetworkInformation.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Ping.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Quic.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Requests.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Security.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.ServicePoint.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.Sockets.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.WebClient.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.WebHeaderCollection.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.WebProxy.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.WebSockets.Client.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Net.WebSockets.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.ObjectModel.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Private.DataContractSerialization.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Private.Uri.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Private.Xml.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Private.Xml.Linq.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.DispatchProxy.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.Metadata.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Reflection.TypeExtensions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Resources.Writer.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.CompilerServices.Unsafe.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.CompilerServices.VisualC.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.InteropServices.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.InteropServices.RuntimeInformation.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Numerics.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Serialization.Formatters.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Runtime.Serialization.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.AccessControl.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Claims.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.Algorithms.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.Cng.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.Csp.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.Encoding.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.OpenSsl.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.Primitives.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Cryptography.X509Certificates.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Security.Principal.Windows.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Text.Encoding.CodePages.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Text.Encodings.Web.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Text.Json.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Text.RegularExpressions.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Channels.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Tasks.Dataflow.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Threading.Tasks.Parallel.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Transactions.Local.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Web.HttpUtility.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + }, + "System.Xml.XPath.XDocument.dll": { + "assemblyVersion": "6.0.0.0", + "fileVersion": "6.0.722.32202" + } + }, + "native": { + "clretwrc.dll": { + "fileVersion": "6.0.722.32202" + }, + "clrjit.dll": { + "fileVersion": "6.0.722.32202" + }, + "coreclr.dll": { + "fileVersion": "6.0.722.32202" + }, + "createdump.exe": { + "fileVersion": "6.0.722.32202" + }, + "dbgshim.dll": { + "fileVersion": "6.0.722.32202" + }, + "mscordaccore.dll": { + "fileVersion": "6.0.722.32202" + }, + "mscordaccore_x86_x86_6.0.722.32202.dll": { + "fileVersion": "6.0.722.32202" + }, + "mscordbi.dll": { + "fileVersion": "6.0.722.32202" + }, + "mscorrc.dll": { + "fileVersion": "6.0.722.32202" + }, + "api-ms-win-core-console-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-console-l1-2-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-datetime-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-debug-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-errorhandling-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-fibers-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-file-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-file-l1-2-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-file-l2-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-handle-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-heap-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-interlocked-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-libraryloader-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-localization-l1-2-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-memory-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-namedpipe-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-processenvironment-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-processthreads-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-processthreads-l1-1-1.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-profile-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-rtlsupport-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-string-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-synch-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-synch-l1-2-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-sysinfo-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-timezone-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-core-util-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "API-MS-Win-core-xstate-l2-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-conio-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-convert-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-environment-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-filesystem-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-heap-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-locale-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-math-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-multibyte-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-private-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-process-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-runtime-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-stdio-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-string-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-time-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "api-ms-win-crt-utility-l1-1-0.dll": { + "fileVersion": "10.0.22000.194" + }, + "ucrtbase.dll": { + "fileVersion": "10.0.22000.194" + }, + "msquic.dll": { + "fileVersion": "1.7.0.0" + }, + "System.IO.Compression.Native.dll": { + "fileVersion": "42.42.42.42424" + }, + "hostpolicy.dll": { + "fileVersion": "6.0.722.32202" + }, + "Microsoft.DiaSymReader.Native.x86.dll": { + "fileVersion": "14.28.29715.1" + } + } + } + } + }, + "libraries": { + "Microsoft.NETCore.App.Runtime.win-x86/6.0.7": { + "type": "package", + "serviceable": true, + "sha512": "", + "path": "microsoft.netcore.app.runtime.win-x86/6.0.7" + } + }, + "runtimes": { + "win-x86": [ + "win", + "any", + "base" + ], + "win-x86-aot": [ + "win-aot", + "win-x86", + "win", + "aot", + "any", + "base" + ], + "win10-x86": [ + "win10", + "win81-x86", + "win81", + "win8-x86", + "win8", + "win7-x86", + "win7", + "win-x86", + "win", + "any", + "base" + ], + "win10-x86-aot": [ + "win10-aot", + "win10-x86", + "win10", + "win81-x86-aot", + "win81-aot", + "win81-x86", + "win81", + "win8-x86-aot", + "win8-aot", + "win8-x86", + "win8", + "win7-x86-aot", + "win7-aot", + "win7-x86", + "win7", + "win-x86-aot", + "win-aot", + "win-x86", + "win", + "aot", + "any", + "base" + ], + "win7-x86": [ + "win7", + "win-x86", + "win", + "any", + "base" + ], + "win7-x86-aot": [ + "win7-aot", + "win7-x86", + "win7", + "win-x86-aot", + "win-aot", + "win-x86", + "win", + "aot", + "any", + "base" + ], + "win8-x86": [ + "win8", + "win7-x86", + "win7", + "win-x86", + "win", + "any", + "base" + ], + "win8-x86-aot": [ + "win8-aot", + "win8-x86", + "win8", + "win7-x86-aot", + "win7-aot", + "win7-x86", + "win7", + "win-x86-aot", + "win-aot", + "win-x86", + "win", + "aot", + "any", + "base" + ], + "win81-x86": [ + "win81", + "win8-x86", + "win8", + "win7-x86", + "win7", + "win-x86", + "win", + "any", + "base" + ], + "win81-x86-aot": [ + "win81-aot", + "win81-x86", + "win81", + "win8-x86-aot", + "win8-aot", + "win8-x86", + "win8", + "win7-x86-aot", + "win7-aot", + "win7-x86", + "win7", + "win-x86-aot", + "win-aot", + "win-x86", + "win", + "aot", + "any", + "base" + ] + } +} \ No newline at end of file diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.NETCore.App.runtimeconfig.json b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.NETCore.App.runtimeconfig.json new file mode 100644 index 00000000..3d37d8e9 --- /dev/null +++ b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.NETCore.App.runtimeconfig.json @@ -0,0 +1,8 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "configProperties": { + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false + } + } +} \ No newline at end of file diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.VisualBasic.Core.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.VisualBasic.Core.dll new file mode 100644 index 00000000..68f91164 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.VisualBasic.Core.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.VisualBasic.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.VisualBasic.dll new file mode 100644 index 00000000..d07f110d Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.VisualBasic.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Win32.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Win32.Primitives.dll new file mode 100644 index 00000000..f2dd1d6b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Win32.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Win32.Registry.dll b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Win32.Registry.dll new file mode 100644 index 00000000..9c396856 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/Microsoft.Win32.Registry.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.AppContext.dll b/Assets/Mod/BaseBepInEx/dotnet/System.AppContext.dll new file mode 100644 index 00000000..d827f2b6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.AppContext.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Buffers.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Buffers.dll new file mode 100644 index 00000000..3baab5b0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Buffers.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Concurrent.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Concurrent.dll new file mode 100644 index 00000000..602f9142 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Concurrent.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Immutable.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Immutable.dll new file mode 100644 index 00000000..d26c4521 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Immutable.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Collections.NonGeneric.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.NonGeneric.dll new file mode 100644 index 00000000..dfc307e3 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.NonGeneric.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Specialized.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Specialized.dll new file mode 100644 index 00000000..052621d5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.Specialized.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Collections.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.dll new file mode 100644 index 00000000..0b956e47 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Collections.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.Annotations.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.Annotations.dll new file mode 100644 index 00000000..3f1a8fb4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.Annotations.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.DataAnnotations.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.DataAnnotations.dll new file mode 100644 index 00000000..176eef26 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.DataAnnotations.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.EventBasedAsync.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.EventBasedAsync.dll new file mode 100644 index 00000000..ce855e17 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.EventBasedAsync.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.Primitives.dll new file mode 100644 index 00000000..b10d0f31 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.TypeConverter.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.TypeConverter.dll new file mode 100644 index 00000000..9495e4e1 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.TypeConverter.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.dll new file mode 100644 index 00000000..1da8c785 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ComponentModel.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Configuration.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Configuration.dll new file mode 100644 index 00000000..42ab7cd3 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Configuration.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Console.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Console.dll new file mode 100644 index 00000000..8fc2d371 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Console.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Core.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Core.dll new file mode 100644 index 00000000..f8076d1b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Core.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Data.Common.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Data.Common.dll new file mode 100644 index 00000000..858402c8 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Data.Common.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Data.DataSetExtensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Data.DataSetExtensions.dll new file mode 100644 index 00000000..431d40b3 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Data.DataSetExtensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Data.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Data.dll new file mode 100644 index 00000000..e44f7f23 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Data.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Contracts.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Contracts.dll new file mode 100644 index 00000000..ad1a47cb Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Contracts.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Debug.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Debug.dll new file mode 100644 index 00000000..b2f3e16c Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Debug.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.DiagnosticSource.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.DiagnosticSource.dll new file mode 100644 index 00000000..e914d90b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.DiagnosticSource.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.FileVersionInfo.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.FileVersionInfo.dll new file mode 100644 index 00000000..a465fd31 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.FileVersionInfo.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Process.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Process.dll new file mode 100644 index 00000000..02126bb9 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Process.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.StackTrace.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.StackTrace.dll new file mode 100644 index 00000000..66c0986b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.StackTrace.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.TextWriterTraceListener.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.TextWriterTraceListener.dll new file mode 100644 index 00000000..f2026f3f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.TextWriterTraceListener.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Tools.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Tools.dll new file mode 100644 index 00000000..086bdcc7 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Tools.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.TraceSource.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.TraceSource.dll new file mode 100644 index 00000000..9c6ecd35 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.TraceSource.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Tracing.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Tracing.dll new file mode 100644 index 00000000..411d31ef Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Diagnostics.Tracing.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Drawing.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Drawing.Primitives.dll new file mode 100644 index 00000000..2c4e702e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Drawing.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Drawing.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Drawing.dll new file mode 100644 index 00000000..1bdd2583 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Drawing.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Dynamic.Runtime.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Dynamic.Runtime.dll new file mode 100644 index 00000000..b1d1de76 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Dynamic.Runtime.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Formats.Asn1.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Formats.Asn1.dll new file mode 100644 index 00000000..5805c93b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Formats.Asn1.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.Calendars.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.Calendars.dll new file mode 100644 index 00000000..e822aae6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.Calendars.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.Extensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.Extensions.dll new file mode 100644 index 00000000..c15e4ed7 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.Extensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.dll new file mode 100644 index 00000000..570df911 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Globalization.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.Brotli.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.Brotli.dll new file mode 100644 index 00000000..72ea29df Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.Brotli.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.FileSystem.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.FileSystem.dll new file mode 100644 index 00000000..727c186a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.FileSystem.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.Native.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.Native.dll new file mode 100644 index 00000000..7e0564a2 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.Native.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.ZipFile.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.ZipFile.dll new file mode 100644 index 00000000..75fd4359 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.ZipFile.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.dll new file mode 100644 index 00000000..d50761a2 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Compression.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.AccessControl.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.AccessControl.dll new file mode 100644 index 00000000..347fa307 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.AccessControl.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.DriveInfo.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.DriveInfo.dll new file mode 100644 index 00000000..486e13f0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.DriveInfo.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.Primitives.dll new file mode 100644 index 00000000..5ae73a98 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.Watcher.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.Watcher.dll new file mode 100644 index 00000000..7afbf12d Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.Watcher.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.dll new file mode 100644 index 00000000..21fb9e6a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.FileSystem.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.IsolatedStorage.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.IsolatedStorage.dll new file mode 100644 index 00000000..e8e80b44 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.IsolatedStorage.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.MemoryMappedFiles.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.MemoryMappedFiles.dll new file mode 100644 index 00000000..18163c61 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.MemoryMappedFiles.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Pipes.AccessControl.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Pipes.AccessControl.dll new file mode 100644 index 00000000..354e6ebb Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Pipes.AccessControl.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.Pipes.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Pipes.dll new file mode 100644 index 00000000..f42139a0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.Pipes.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.UnmanagedMemoryStream.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.UnmanagedMemoryStream.dll new file mode 100644 index 00000000..5c7d1da2 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.UnmanagedMemoryStream.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.IO.dll b/Assets/Mod/BaseBepInEx/dotnet/System.IO.dll new file mode 100644 index 00000000..70bc0bca Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.IO.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Expressions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Expressions.dll new file mode 100644 index 00000000..4bd47d1b Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Expressions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Parallel.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Parallel.dll new file mode 100644 index 00000000..991fbef0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Parallel.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Queryable.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Queryable.dll new file mode 100644 index 00000000..9f9438e9 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.Queryable.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Linq.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.dll new file mode 100644 index 00000000..3c94c9be Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Linq.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Memory.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Memory.dll new file mode 100644 index 00000000..3c45870a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Memory.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Http.Json.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Http.Json.dll new file mode 100644 index 00000000..52bd105f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Http.Json.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Http.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Http.dll new file mode 100644 index 00000000..ee09c4e5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Http.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.HttpListener.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.HttpListener.dll new file mode 100644 index 00000000..035f36e6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.HttpListener.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Mail.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Mail.dll new file mode 100644 index 00000000..7fe01969 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Mail.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.NameResolution.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.NameResolution.dll new file mode 100644 index 00000000..7bc136e4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.NameResolution.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.NetworkInformation.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.NetworkInformation.dll new file mode 100644 index 00000000..300ee7b5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.NetworkInformation.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Ping.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Ping.dll new file mode 100644 index 00000000..f7d45a38 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Ping.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Primitives.dll new file mode 100644 index 00000000..994a4c28 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Quic.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Quic.dll new file mode 100644 index 00000000..cdf7adf9 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Quic.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Requests.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Requests.dll new file mode 100644 index 00000000..67705180 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Requests.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Security.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Security.dll new file mode 100644 index 00000000..aba14c8a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Security.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.ServicePoint.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.ServicePoint.dll new file mode 100644 index 00000000..be5c068a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.ServicePoint.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.Sockets.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Sockets.dll new file mode 100644 index 00000000..3688eb79 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.Sockets.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebClient.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebClient.dll new file mode 100644 index 00000000..b75a9dfa Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebClient.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebHeaderCollection.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebHeaderCollection.dll new file mode 100644 index 00000000..5e184c7c Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebHeaderCollection.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebProxy.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebProxy.dll new file mode 100644 index 00000000..02132f48 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebProxy.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebSockets.Client.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebSockets.Client.dll new file mode 100644 index 00000000..9ed661bc Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebSockets.Client.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebSockets.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebSockets.dll new file mode 100644 index 00000000..a4e4da32 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.WebSockets.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Net.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Net.dll new file mode 100644 index 00000000..de8d7b7e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Net.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Numerics.Vectors.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Numerics.Vectors.dll new file mode 100644 index 00000000..8aa41245 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Numerics.Vectors.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Numerics.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Numerics.dll new file mode 100644 index 00000000..253f0fe0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Numerics.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ObjectModel.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ObjectModel.dll new file mode 100644 index 00000000..307e5a48 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ObjectModel.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Private.CoreLib.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Private.CoreLib.dll new file mode 100644 index 00000000..a338aa23 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Private.CoreLib.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Private.DataContractSerialization.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Private.DataContractSerialization.dll new file mode 100644 index 00000000..baa44418 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Private.DataContractSerialization.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Private.Uri.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Private.Uri.dll new file mode 100644 index 00000000..64e2d8c7 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Private.Uri.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Private.Xml.Linq.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Private.Xml.Linq.dll new file mode 100644 index 00000000..90a5e5fb Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Private.Xml.Linq.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Private.Xml.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Private.Xml.dll new file mode 100644 index 00000000..db172c4f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Private.Xml.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.DispatchProxy.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.DispatchProxy.dll new file mode 100644 index 00000000..502fade6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.DispatchProxy.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.ILGeneration.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.ILGeneration.dll new file mode 100644 index 00000000..cd79d121 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.ILGeneration.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.Lightweight.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.Lightweight.dll new file mode 100644 index 00000000..982abbc3 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.Lightweight.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.dll new file mode 100644 index 00000000..80eb5576 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Emit.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Extensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Extensions.dll new file mode 100644 index 00000000..aee132f9 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Extensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Metadata.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Metadata.dll new file mode 100644 index 00000000..6bfdf4ff Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Metadata.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Primitives.dll new file mode 100644 index 00000000..858b0fc6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.TypeExtensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.TypeExtensions.dll new file mode 100644 index 00000000..5fd6514e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.TypeExtensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.dll new file mode 100644 index 00000000..03aafa7e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Reflection.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Resources.Reader.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Resources.Reader.dll new file mode 100644 index 00000000..a51d64fe Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Resources.Reader.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Resources.ResourceManager.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Resources.ResourceManager.dll new file mode 100644 index 00000000..ef5fb45c Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Resources.ResourceManager.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Resources.Writer.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Resources.Writer.dll new file mode 100644 index 00000000..74c5a501 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Resources.Writer.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.CompilerServices.Unsafe.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.CompilerServices.Unsafe.dll new file mode 100644 index 00000000..e071689a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.CompilerServices.Unsafe.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.CompilerServices.VisualC.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.CompilerServices.VisualC.dll new file mode 100644 index 00000000..d5de92b0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.CompilerServices.VisualC.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Extensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Extensions.dll new file mode 100644 index 00000000..82989176 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Extensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Handles.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Handles.dll new file mode 100644 index 00000000..a9161837 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Handles.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.InteropServices.RuntimeInformation.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.InteropServices.RuntimeInformation.dll new file mode 100644 index 00000000..0f6961fb Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.InteropServices.RuntimeInformation.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.InteropServices.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.InteropServices.dll new file mode 100644 index 00000000..42088000 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.InteropServices.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Intrinsics.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Intrinsics.dll new file mode 100644 index 00000000..1cdbeb59 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Intrinsics.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Loader.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Loader.dll new file mode 100644 index 00000000..64240ece Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Loader.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Numerics.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Numerics.dll new file mode 100644 index 00000000..a2e785aa Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Numerics.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Formatters.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Formatters.dll new file mode 100644 index 00000000..d3d28be4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Formatters.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Json.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Json.dll new file mode 100644 index 00000000..acf7a693 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Json.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Primitives.dll new file mode 100644 index 00000000..2dc40f7f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Xml.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Xml.dll new file mode 100644 index 00000000..00aa3857 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.Xml.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.dll new file mode 100644 index 00000000..75710391 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.Serialization.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.dll new file mode 100644 index 00000000..9f383e54 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Runtime.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.AccessControl.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.AccessControl.dll new file mode 100644 index 00000000..4917c646 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.AccessControl.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Claims.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Claims.dll new file mode 100644 index 00000000..cfe98df5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Claims.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Algorithms.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Algorithms.dll new file mode 100644 index 00000000..6844325c Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Algorithms.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Cng.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Cng.dll new file mode 100644 index 00000000..56fbb8c5 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Cng.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Csp.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Csp.dll new file mode 100644 index 00000000..e4b8d171 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Csp.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Encoding.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Encoding.dll new file mode 100644 index 00000000..343332f4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Encoding.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.OpenSsl.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.OpenSsl.dll new file mode 100644 index 00000000..9a84e96e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.OpenSsl.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Primitives.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Primitives.dll new file mode 100644 index 00000000..645cc1c3 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.Primitives.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.X509Certificates.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.X509Certificates.dll new file mode 100644 index 00000000..021f4979 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Cryptography.X509Certificates.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Principal.Windows.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Principal.Windows.dll new file mode 100644 index 00000000..cdcb6f0d Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Principal.Windows.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.Principal.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Principal.dll new file mode 100644 index 00000000..74447453 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.Principal.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.SecureString.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.SecureString.dll new file mode 100644 index 00000000..67d6f150 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.SecureString.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Security.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Security.dll new file mode 100644 index 00000000..6f8af073 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Security.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ServiceModel.Web.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ServiceModel.Web.dll new file mode 100644 index 00000000..0e891ff8 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ServiceModel.Web.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ServiceProcess.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ServiceProcess.dll new file mode 100644 index 00000000..e0442387 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ServiceProcess.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.CodePages.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.CodePages.dll new file mode 100644 index 00000000..5863bc10 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.CodePages.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.Extensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.Extensions.dll new file mode 100644 index 00000000..c62d1e16 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.Extensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.dll new file mode 100644 index 00000000..e7b00eb0 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encoding.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encodings.Web.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encodings.Web.dll new file mode 100644 index 00000000..aa3bd58d Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Encodings.Web.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Text.Json.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Json.dll new file mode 100644 index 00000000..fe36969f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Text.Json.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Text.RegularExpressions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Text.RegularExpressions.dll new file mode 100644 index 00000000..ac925856 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Text.RegularExpressions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Channels.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Channels.dll new file mode 100644 index 00000000..d9585379 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Channels.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Overlapped.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Overlapped.dll new file mode 100644 index 00000000..03beaa1d Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Overlapped.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Dataflow.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Dataflow.dll new file mode 100644 index 00000000..2a739ec2 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Dataflow.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Extensions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Extensions.dll new file mode 100644 index 00000000..27de0523 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Extensions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Parallel.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Parallel.dll new file mode 100644 index 00000000..73ace52f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.Parallel.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.dll new file mode 100644 index 00000000..cf449157 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Tasks.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Thread.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Thread.dll new file mode 100644 index 00000000..9afc5201 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Thread.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.ThreadPool.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.ThreadPool.dll new file mode 100644 index 00000000..c417eb08 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.ThreadPool.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Timer.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Timer.dll new file mode 100644 index 00000000..5efe4770 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.Timer.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Threading.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.dll new file mode 100644 index 00000000..37e4f8ba Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Threading.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Transactions.Local.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Transactions.Local.dll new file mode 100644 index 00000000..7bbfc510 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Transactions.Local.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Transactions.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Transactions.dll new file mode 100644 index 00000000..c5691e66 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Transactions.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.ValueTuple.dll b/Assets/Mod/BaseBepInEx/dotnet/System.ValueTuple.dll new file mode 100644 index 00000000..3a79f86e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.ValueTuple.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Web.HttpUtility.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Web.HttpUtility.dll new file mode 100644 index 00000000..6377f891 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Web.HttpUtility.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Web.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Web.dll new file mode 100644 index 00000000..874e2dd3 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Web.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Windows.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Windows.dll new file mode 100644 index 00000000..75939b50 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Windows.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.Linq.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.Linq.dll new file mode 100644 index 00000000..31456283 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.Linq.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.ReaderWriter.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.ReaderWriter.dll new file mode 100644 index 00000000..eb906c30 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.ReaderWriter.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.Serialization.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.Serialization.dll new file mode 100644 index 00000000..97a09d31 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.Serialization.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XDocument.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XDocument.dll new file mode 100644 index 00000000..553442df Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XDocument.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XPath.XDocument.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XPath.XDocument.dll new file mode 100644 index 00000000..8c007311 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XPath.XDocument.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XPath.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XPath.dll new file mode 100644 index 00000000..0ab841ce Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XPath.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XmlDocument.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XmlDocument.dll new file mode 100644 index 00000000..b2d64005 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XmlDocument.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XmlSerializer.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XmlSerializer.dll new file mode 100644 index 00000000..76905fd4 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.XmlSerializer.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.Xml.dll b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.dll new file mode 100644 index 00000000..ae3af89f Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.Xml.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/System.dll b/Assets/Mod/BaseBepInEx/dotnet/System.dll new file mode 100644 index 00000000..3b36983a Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/System.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/WindowsBase.dll b/Assets/Mod/BaseBepInEx/dotnet/WindowsBase.dll new file mode 100644 index 00000000..6528b1e2 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/WindowsBase.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/clretwrc.dll b/Assets/Mod/BaseBepInEx/dotnet/clretwrc.dll new file mode 100644 index 00000000..dbf6a8c1 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/clretwrc.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/clrjit.dll b/Assets/Mod/BaseBepInEx/dotnet/clrjit.dll new file mode 100644 index 00000000..da0b36c9 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/clrjit.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/coreclr.dll b/Assets/Mod/BaseBepInEx/dotnet/coreclr.dll new file mode 100644 index 00000000..9fd32373 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/coreclr.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/dbgshim.dll b/Assets/Mod/BaseBepInEx/dotnet/dbgshim.dll new file mode 100644 index 00000000..1a7ed1dc Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/dbgshim.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/hostpolicy.dll b/Assets/Mod/BaseBepInEx/dotnet/hostpolicy.dll new file mode 100644 index 00000000..0dfde460 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/hostpolicy.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/mscordaccore.dll b/Assets/Mod/BaseBepInEx/dotnet/mscordaccore.dll new file mode 100644 index 00000000..a7225b55 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/mscordaccore.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/mscordaccore_x86_x86_6.0.722.32202.dll b/Assets/Mod/BaseBepInEx/dotnet/mscordaccore_x86_x86_6.0.722.32202.dll new file mode 100644 index 00000000..389b30ef Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/mscordaccore_x86_x86_6.0.722.32202.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/mscordbi.dll b/Assets/Mod/BaseBepInEx/dotnet/mscordbi.dll new file mode 100644 index 00000000..d840eb35 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/mscordbi.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/mscorlib.dll b/Assets/Mod/BaseBepInEx/dotnet/mscorlib.dll new file mode 100644 index 00000000..97dd7a39 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/mscorlib.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/mscorrc.dll b/Assets/Mod/BaseBepInEx/dotnet/mscorrc.dll new file mode 100644 index 00000000..8bd4c630 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/mscorrc.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/msquic.dll b/Assets/Mod/BaseBepInEx/dotnet/msquic.dll new file mode 100644 index 00000000..8cec5274 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/msquic.dll differ diff --git a/Assets/Mod/BaseBepInEx/dotnet/netstandard.dll b/Assets/Mod/BaseBepInEx/dotnet/netstandard.dll new file mode 100644 index 00000000..23d9101e Binary files /dev/null and b/Assets/Mod/BaseBepInEx/dotnet/netstandard.dll differ diff --git a/Assets/Mod/BaseBepInEx/winhttp.dll b/Assets/Mod/BaseBepInEx/winhttp.dll new file mode 100644 index 00000000..b0233ce6 Binary files /dev/null and b/Assets/Mod/BaseBepInEx/winhttp.dll differ diff --git a/Assets/ModNews/SChinese/FS.v1.0_20240814.txt b/Assets/ModNews/SChinese/FS.v1.0_20240814.txt index b782c516..271ac83b 100644 --- a/Assets/ModNews/SChinese/FS.v1.0_20240814.txt +++ b/Assets/ModNews/SChinese/FS.v1.0_20240814.txt @@ -80,5 +80,5 @@ 来源: Town Of Host : Enhanced ## 重制 -- 重制客户端设置:切换形象[经典豆子模式-愚人节牧马模式-愚人节长颈豆模式](仅主界面可用) +- 重制客户端设置:切换形象[经典豆子模式-愚人节牧马模式-愚人节长颈豆模式](仅主页可用) 制作: Slok \ No newline at end of file diff --git a/Assets/ModNews/SChinese/FS.v1.0_20250129.txt b/Assets/ModNews/SChinese/FS.v1.0_20250129.txt index 350d9148..c818873e 100644 --- a/Assets/ModNews/SChinese/FS.v1.0_20250129.txt +++ b/Assets/ModNews/SChinese/FS.v1.0_20250129.txt @@ -167,7 +167,7 @@ 制作: Slok ## 删除 -- 暂时删除: 主界面——官方网站按钮 +- 暂时删除: 主页——官方网站按钮 原因: 网站维护退出制作组 - 暂时删除: 愚人节地图——骷髅舰(镜像) 原因: 出现房主卡在驱逐动画的问题 diff --git a/Assets/ModNews/SChinese/FS.v1.1_20250216.txt b/Assets/ModNews/SChinese/FS.v1.1_20250216.txt index b76ebb97..e371e956 100644 --- a/Assets/ModNews/SChinese/FS.v1.1_20250216.txt +++ b/Assets/ModNews/SChinese/FS.v1.1_20250216.txt @@ -32,7 +32,7 @@ - 修复了部分玩家退出时不被标记为断连的问题 - 修复了GC报错高频出现的问题 - 修复了玩家卡在游戏启动动画“校验资源”的问题 -- 修复了原版主界面音乐音量错误的问题 +- 修复了原版主页音乐音量错误的问题 修复: Slok - 修复了模组有时因屏蔽词无法加载的问题 修复: Slok diff --git a/Assets/ModNews/SChinese/FS.v1.1_20250505.txt b/Assets/ModNews/SChinese/FS.v1.1_20250505.txt deleted file mode 100644 index d0d6f64a..00000000 --- a/Assets/ModNews/SChinese/FS.v1.1_20250505.txt +++ /dev/null @@ -1,50 +0,0 @@ -#Number:100007 -#Title:终极嫌疑 v1.1 -#SubTitle:★★★★全面优化★★★★ -#ShortTitle:★全面优化v1.1 -#Date:2025-05-05T00:00:00Z -#----------------------------- -# 简体中文 -让Kami1337无处遁形! - -## 对应官方版本 -- 基于 Among Us v16.0.2 -- 使用 BepInEx 框架 v6.0.0-be.733 - -## 更新内容 -- 修正: 5 -- 优化: 5 -- 更改: 1 -- 新增: 2 -- 重制: 0 -- 删除: 0 - -## 团队参与者 -- [Slok]("https://github.com/Slok7565/") - -## 修正 -- 修复了快速启动模式失效的问题 -- 修复了练习模式数据创建失效的问题 -- 修复了地图尸体标记错误显示的问题 -- 修复了地图尸体标记错误颜色的问题 -- 修复了万圣节地图装扮失效的问题 -修复: Slok - -## 优化 -- 重写启动加载界面以优化代码运行效率 -- 使用异步方法完成屏蔽词读取以防止进程阻塞 -- 使用异步方法完成更新检查以防止进程阻塞 -- 加密屏蔽词以防止远程封禁 -- 完全重写FAC以增强反作弊能力 -制作: Slok - -## 更改 -- 模组公告不再下载至本地,而是在启动游戏时读取 -制作: Slok - -## 新增 -- 重要资源下载失败自动退出游戏 -制作: Slok -想法: QingFeng -- 部分作弊程序或模组能够被识别 -制作: Slok \ No newline at end of file diff --git a/Assets/ModNews/SChinese/FS.v1.2_20250815.txt b/Assets/ModNews/SChinese/FS.v1.2_20250815.txt new file mode 100644 index 00000000..4ed8125c --- /dev/null +++ b/Assets/ModNews/SChinese/FS.v1.2_20250815.txt @@ -0,0 +1,147 @@ +#Number:100007 +#Title:终极嫌疑 v1.2 +#SubTitle:★★★★期待下一次的相遇!★★★★ +#ShortTitle:★版本更新v1.2 +#Date:2025-08-15T00:00:00Z +#----------------------------- +# 简体中文 +自 v1.2 正式版起,「Final Suspect 终极嫌疑」的功能开发已正式完成! +未来,本模组将主要专注于 Bug 修复与版本适配工作,或不再或极少添加新功能。 +制作团队将把精力投入到多个全新项目的开发中,包括一个全新的 Among Us 项目! +期待下一次的相遇! + +## 对应官方版本 +- 基于 Among Us v16.1.0 +- 使用 BepInEx 框架 v6.0.0-be.735 + +## 模组版本信息 +- 程序集版本号: v1.1.102 +- 版本类型: Beta +- 构建号: 3 + +## 更新内容 +- 修正: 22 +- 优化: 9 +- 更改: 14 +- 新增: 29 +- 重制: 0 +- 删除: 2 + +## 贡献者 +- [LezaiYa]("https://github.com/NewLezaiYa") +- [Elinmei]("https://github.com/linmeideli") +- [Nonalus]("https://github.com/Reborn5537/") +- [白糖咖啡]("https://space.bilibili.com/360363478?spm_id_from=333.337.0.0") + +## 修正 +- 修复了快速启动模式失效的问题 +- 修复了练习模式数据创建失效的问题 +- 修复了地图尸体标记错误显示的问题 +- 修复了地图尸体标记错误颜色的问题 +- 修复了万圣节地图装扮失效的问题 +- 修复了更新日志读取失败的问题 +- 修复了开场动画职业展示着色失效的问题 +- 修复了加载动画在新版本不工作的问题 +- 修复了加载屏蔽词时崩溃的问题 +- 修复了通讯破坏时任务栏错误显示的问题 +- 修复了大厅搜索信息失效的问题 +- 修复了Among Us左下角提示信息显示错误的问题 +- 修复了左下角提示信息中玩家名称无颜色的问题 +- 修复了按下F1显示职业介绍出现卡顿的问题 +- 修复了违规名称错误匹配的问题 +- 修复了会议召开瞬间的击杀被误判为作弊的问题 +- 修复了客户端击杀时有可能造成反作弊误判的问题 +修复: Slok +- 修复了开启「结束时自动返回大厅」时无法使用幽幽鬼影的问题 +修复: Slok +反馈: 秦始皇 +- 修复了开启「结束时自动返回大厅」时无法访问商店的问题 +修复: Slok +反馈: QingFeng +- 修复了封禁名单不工作的问题 +修复:LezaiYa +反馈:警长小蓝 +- 修复了左Shift和右Shift快捷键无法使用的问题 +修复: Elinmei +- 修复了「愚人节牧马模式」损坏的问题 +来源: Town Of Host:Enhanced + +## 优化 +- 重写启动加载界面以优化代码运行效率 +- 使用异步方法完成屏蔽词读取以防止进程阻塞 +- 使用异步方法完成更新检查以防止进程阻塞 +- 加密屏蔽词以防止远程封禁 +- 重写违禁词逻辑 +- 完全重写FAC以增强反作弊能力 +- 全面优化代码运行效率 +- 全面优化代码格式 +- 全面优化语言文件格式 +制作: Slok + +## 更改 +- 模组公告不再下载至本地,而是在启动游戏时读取 +- 不再删除冗余模组文件,而是强制终止游戏进程 +- 在模组公告加载完毕后会弹出公告窗口 +- 不再强制更改分辨率 +- 调整技能冷却颜色 +- 左下角提示信息置于顶层 +- 不再强制覆盖服务器列表 +- 不再通过添加文件以阻止语言文件更新 +- 「踢出未登录的玩家」更改为「踢出好友代码异常的玩家」 +- 「禁用反作弊」更改为「启用反作弊」 +- 结算界面复盘信息按钮根据职业颜色改变 +- 脱离团队 XtremeWave 极致狂澜 并作为Slok的模组 +制作: Slok +- 将国内Gitee下载优先于国内API下载 +制作: LezaiYa +- 允许游戏内使用「切换外观形象」 +来源: Town Of Host:Enhanced + +## 新增 +- 重要资源下载失败自动退出游戏 +- 客户端选项: 离线模式(试验性) +制作: Slok +想法: QingFeng +- 部分作弊程序或模组识别 +- 对全部RPC的日志检查以及更多RPC的作弊判断 +- 适配全分辨率UI +- 大厅搜索列表显示房主真实名称 +- 移除玩家名称结尾的空格避免重复 +- 将玩家名称中所有空格更改为下划线 +- 愚人节彩蛋 +- 任务总进度百分比显示 +- 为任务进度条重新着色 +- 启动程序加载界面&主页音乐 +- 添加github镜像源以便下载 +- 资源包: 主页风格包(含5个主页风格) +- 玩家信息显示新增所在位置 +- 客户端守护(完全防御崩溃程序的作弊程序) +- 快捷键:Shift 快速开始游戏时会终止开始音效 +- 客户端功能: 切换主页风格 +- 客户端选项: 启用客户端守护(试验性) +- 客户端功能: 资源管理 +制作: Slok +- 增加对好友代码的检测 +- 增加对NonalusAntiCheatMenu的RPC检测 +制作: Nonalus +- 为BetterAmongUs添加了RPC152检测 +- 为HostGuard添加了RPC176检测 +- 当目录存在其它模组时,将强制关闭游戏 +制作: LezaiYa +- 游戏内展示预设信息面板 +- 快捷键: 按下F2调整预设信息面板显示状态 +制作: Slok, Elinmei +- 客户端功能: 名称标识管理 +制作: Slok +参考: Town Of Next +- 会议标签系统 +制作: Slok +想法: Slok, Amongus +参考: Town Of Next + +## 删除 +- 暂时删除: 客户端功能: 我的音乐 +原因: 占用源代码资源过大 +- 删除: 客户端功能: 音频管理 +原因: 由「资源管理」取代 +决定: Slok \ No newline at end of file diff --git a/Assets/ModNews/SChinese/FeaturesIntroduction.v1.2.txt b/Assets/ModNews/SChinese/FeaturesIntroduction.v1.2.txt new file mode 100644 index 00000000..96996852 --- /dev/null +++ b/Assets/ModNews/SChinese/FeaturesIntroduction.v1.2.txt @@ -0,0 +1,136 @@ +#Number:100008 +#Title:v1.2 功能&优化介绍 +#SubTitle:★★★★我们奇奇妙妙的内容★★★★ +#ShortTitle:★v1.2 内容介绍 +#Date:2025-08-15T00:00:00Z +#----------------------------- +## 本文件涵盖注意事项与功能介绍!请务必完整阅读! +## 注意! +- 如果您看到作弊者,这说明他们曾进行过非法行为或存在于模组封禁名单中,并不代表他们的行为一定是作弊行为或他们正在作弊 +例如: 当您看见(无效的RPC:151)代表对方可能是「Better Among Us」玩家,但FS并不适配此模组的RPC,且FS始终会将发送无效RPC的玩家视为作弊 +当然,您也大可放心,因为我们独特的互认方法,您不会在他们的视野里被判定为作弊者! +- 当模组出现Bug时,请点击「客户端功能-输出日志」输出日志,将其连同Bug一起反馈! +- 除了「简体中文」与「English」之外,所有语言都由AI翻译,可能出现偏差,请及时反馈! +- 开启离线模式后,模组将停止获取屏蔽词、公告、远程资源及更新检查,也不会再下载必要资源。这可能导致您错过关键版本更新,或出现不可预见的错误。请谨慎使用! + +# 简体中文 +## 优化 - 26 +全局: +- 模组鼠标光标 +- 模组右上角图标 +主页: +- 启动游戏程序动画 +- 加载动画-职业立绘轮播 +- 主页UI布局 +- 主页背景 +在线搜索: +- 房间名称优化 +- 非官方服务器着色 +游戏中-全局: +- 延迟显示处的帧数&服务器显示 +- 模组版本信息显示 +- 聊天气泡颜色优化 +- 聊天框背景 +- 断连原因优化 +- 玩家名称查重优化 +游戏中-大厅: +- 大厅信息版背景优化 +- 设置编辑界面着色 +- 设置编辑界面背景优化 +- 设置查看界面着色 +- 等候仓增加团队Logo +游戏中-进行: +- 开场动画 +- 根据职业与阵营为小地图背景着色 +- 不同职业分别着色 +- 技能按钮文本着色 +- 冷却文本颜色 +- 会议中退出玩家显示 +游戏结算: +- 结算文本优化 + +## 功能 - 51 +全局:(设置界面-客户端功能使用) +- 输出日志 +- 清空自动日志 +- 切换原版 +- 资源管理(仅限主页使用) +- 名称标识管理(仅限主页使用) +- 切换主页风格 +加载界面: +- 自动下载远程模组资源(重要资源下载失败自动退出游戏) +主页: +- 自动检查模组版本更新 +在线搜索: +- 房主平台显示以及据此为房间名着色 +- 房间号显示 +- 房间剩余时间百分比显示 +- 更多服务器选择 +- 房主名称显示 +游戏中-全局: +- Final Anti-Cheat反作弊(FAC 2.0) +- 客户端守护(抵御对程序本体攻击的黑客) +- 违禁词检查&屏蔽 +- 解锁帧数限制(由60FPS升至165FPS) +- 自动踢出符合条件的玩家 +游戏中-大厅: +- 展示玩家平台与客户端信息 +- 人满自动开始游戏 +- 与其他本模组玩家互认 +- 显示大厅关闭时间(仅创建房间的房主) +游戏中-进行: +- 场景&会议职业显示 +- 任务进度/击杀数显示 +- 滑动鼠标滚轮使用千里眼(使用条件: 练习模式&本地游戏全局、在线游戏大厅/死亡后非守护天使) +- 死后非守护天使可见他人职业、任务进度/击杀数、位置 +- 死后非守护天使可通过小地图查看尸体及所有人位置 +- 死后可见他人死因&凶手 +- 死后可以使用复盘信息 +- 幽幽鬼影追踪为职业着色 +- 愚人节地图彩蛋 +- 会议标签系统(可以为玩家打上标签以防止忘记身份) +- 任务总进度百分比显示 +游戏结算: +- 复盘信息(职业、任务进度/击杀数、死因&凶手) +- 自动返回大厅 +快捷键-全局: +- F10: 打开游戏根目录 +- F11: 更改分辨率 +- Ctrl+F1: 日志输出 +- Ctrl+F2: 切换日志是否在游戏中输出(仅房主) +快捷键-首页: +- Tab: 隐藏UI +快捷键-聊天: +- Ctrl+C: 复制当前聊天框文本 +- Ctrl+V: 粘贴文本到聊天框 +- Ctrl+X: 剪切当前聊天文本 +- ↑: 追溯上一条自己的聊天历史 +- ↓: 追溯下一条自己的聊天历史 +快捷键-在线搜索: +- 左Shift: 加入上一个加入的房间 +- 右Shift: 加入剪贴板中的房间 +快捷键-游戏中: +- F1: 显示职业介绍及立绘 +- F2: 显示/隐藏预设信息版 +快捷键-大厅: +- Shift: 跳过开始游戏倒计时直接开始游戏(仅房主) +- C: 重置开始游戏倒计时(仅房主) + +可选设置:(设置界面-客户端选项) - 14 +- 切换外观形象[经典豆子模式 - 愚人节牧马模式 - 愚人节长颈豆模式] +- 解锁帧数限制 +- 踢出好友代码异常的玩家 +- 踢出使用违规昵称的玩家 +- 踢出被封禁的玩家 +- 屏蔽违禁词 +- 人满自动开始游戏 +- 结束时自动返回大厅 +- 禁用原版游戏音乐 +- 启用反作弊 +- 启用客户端守护(试验性) +- 展示玩家平台与客户端信息 +- 使用模组鼠标光标 +- 快速启动模式 +- 离线模式(试验性) +可选设置:(其他) +- 切换地图风格(仅限The Skeld骷髅舰, 按钮位于安保室) \ No newline at end of file diff --git a/Assets/ModNews/TChinese/FS.v1.0_20250129.txt b/Assets/ModNews/TChinese/FS.v1.0_20250129.txt index 4b67aaf4..b0ec485f 100644 --- a/Assets/ModNews/TChinese/FS.v1.0_20250129.txt +++ b/Assets/ModNews/TChinese/FS.v1.0_20250129.txt @@ -167,7 +167,7 @@ 製作: Slok ## 刪除 -- 暫時刪除: 主界面——官方網站按鈕 +- 暫時刪除: 主页——官方網站按鈕 原因: 網站維護退出製作組 - 暫時刪除: 愚人節地圖——骷髏艦(鏡像) 原因: 出現房主卡在驅逐動畫的問題 diff --git a/Assets/ModNews/TChinese/FS.v1.1_20250216.txt b/Assets/ModNews/TChinese/FS.v1.1_20250216.txt index c387ea86..09c1af9a 100644 --- a/Assets/ModNews/TChinese/FS.v1.1_20250216.txt +++ b/Assets/ModNews/TChinese/FS.v1.1_20250216.txt @@ -32,7 +32,7 @@ - 修復了部分玩家退出時不被標記為斷連的問題 - 修復了GC報錯高頻出現的問題 - 修復了玩家卡在遊戲啟動動畫「校驗資源」的問題 -- 修復了原版主界面音樂音量錯誤的問題 +- 修復了原版主页音樂音量錯誤的問題 修復: Slok - 修復了模組有時因屏蔽詞無法加載的問題 修復: Slok diff --git a/Assets/Musics/FinalSuspect.zip b/Assets/Musics/FinalSuspect.zip new file mode 100644 index 00000000..a7d0b8e0 Binary files /dev/null and b/Assets/Musics/FinalSuspect.zip differ diff --git a/Assets/Packages/MainMenuStyle/FinalSuspect-BG-MiraStudio.png b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-MiraStudio.png new file mode 100644 index 00000000..5ead6204 Binary files /dev/null and b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-MiraStudio.png differ diff --git a/Assets/Packages/MainMenuStyle/FinalSuspect-BG-MiraStudioNewYear.png b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-MiraStudioNewYear.png new file mode 100644 index 00000000..be0aacb7 Binary files /dev/null and b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-MiraStudioNewYear.png differ diff --git a/Assets/Images/FinalSuspect-BG-NewYear.png b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-NewYear.png similarity index 100% rename from Assets/Images/FinalSuspect-BG-NewYear.png rename to Assets/Packages/MainMenuStyle/FinalSuspect-BG-NewYear.png diff --git a/Assets/Packages/MainMenuStyle/FinalSuspect-BG-Security.png b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-Security.png new file mode 100644 index 00000000..c686bd54 Binary files /dev/null and b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-Security.png differ diff --git a/Assets/Packages/MainMenuStyle/FinalSuspect-BG-XtremeWave.png b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-XtremeWave.png new file mode 100644 index 00000000..3ea80c2a Binary files /dev/null and b/Assets/Packages/MainMenuStyle/FinalSuspect-BG-XtremeWave.png differ diff --git a/Assets/Sounds/.md5Pair.txt b/Assets/Sounds/.md5Pair.txt deleted file mode 100644 index 8b137891..00000000 --- a/Assets/Sounds/.md5Pair.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Assets/Sounds/Affinity.wav b/Assets/Sounds/Affinity.wav deleted file mode 100644 index 61b14836..00000000 Binary files a/Assets/Sounds/Affinity.wav and /dev/null differ diff --git a/Assets/Sounds/CountingStars.wav b/Assets/Sounds/CountingStars.wav deleted file mode 100644 index 97e64e84..00000000 Binary files a/Assets/Sounds/CountingStars.wav and /dev/null differ diff --git a/Assets/Sounds/ElegyOfFracturedVow.wav b/Assets/Sounds/ElegyOfFracturedVow.wav deleted file mode 100644 index 5d601ac2..00000000 Binary files a/Assets/Sounds/ElegyOfFracturedVow.wav and /dev/null differ diff --git a/Assets/Sounds/Fractured.wav b/Assets/Sounds/Fractured.wav deleted file mode 100644 index 479cdc14..00000000 Binary files a/Assets/Sounds/Fractured.wav and /dev/null differ diff --git a/Assets/Sounds/GongXiFaCai.wav b/Assets/Sounds/GongXiFaCai.wav deleted file mode 100644 index 0105b9c7..00000000 Binary files a/Assets/Sounds/GongXiFaCai.wav and /dev/null differ diff --git a/Assets/Sounds/Inceps_Plus_InProgress.wav b/Assets/Sounds/Inceps_Plus_InProgress.wav deleted file mode 100644 index c98283c9..00000000 Binary files a/Assets/Sounds/Inceps_Plus_InProgress.wav and /dev/null differ diff --git a/Assets/Sounds/Interlude.wav b/Assets/Sounds/Interlude.wav deleted file mode 100644 index b5e97c75..00000000 Binary files a/Assets/Sounds/Interlude.wav and /dev/null differ diff --git a/Assets/Sounds/NeverGonnaGiveYouUp.wav b/Assets/Sounds/NeverGonnaGiveYouUp.wav deleted file mode 100644 index c573e5da..00000000 Binary files a/Assets/Sounds/NeverGonnaGiveYouUp.wav and /dev/null differ diff --git a/Assets/Sounds/ReturnToSimplicity.wav b/Assets/Sounds/ReturnToSimplicity.wav deleted file mode 100644 index 0421d2f7..00000000 Binary files a/Assets/Sounds/ReturnToSimplicity.wav and /dev/null differ diff --git a/Assets/Sounds/TidalSurge.wav b/Assets/Sounds/TidalSurge.wav deleted file mode 100644 index fe53cab1..00000000 Binary files a/Assets/Sounds/TidalSurge.wav and /dev/null differ diff --git a/Assets/Sounds/TrailOfTruth.wav b/Assets/Sounds/TrailOfTruth.wav deleted file mode 100644 index fdef1845..00000000 Binary files a/Assets/Sounds/TrailOfTruth.wav and /dev/null differ diff --git a/Assets/Sounds/VestigiumSplendoris.wav b/Assets/Sounds/VestigiumSplendoris.wav deleted file mode 100644 index 52f26602..00000000 Binary files a/Assets/Sounds/VestigiumSplendoris.wav and /dev/null differ diff --git a/Assets/TODO.txt b/Assets/TODO.txt index fccbb7e6..f93bd007 100644 --- a/Assets/TODO.txt +++ b/Assets/TODO.txt @@ -1,6 +1 @@ -1.资源管理 -2.游戏内设置查看 -3.备注 -4.笔记本 -5.主界面多风格 -6. +主页多风格 diff --git a/FinalSuspect.sln b/FinalSuspect.sln index b0a83690..d0f63c19 100644 --- a/FinalSuspect.sln +++ b/FinalSuspect.sln @@ -8,16 +8,16 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Canary|Any CPU = Canary|Any CPU Release|Any CPU = Release|Any CPU + OpenBeta|Any CPU = OpenBeta|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {BB32D6D5-7915-496C-933A-A2632C78918F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB32D6D5-7915-496C-933A-A2632C78918F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB32D6D5-7915-496C-933A-A2632C78918F}.Canary|Any CPU.ActiveCfg = Canary|Any CPU - {BB32D6D5-7915-496C-933A-A2632C78918F}.Canary|Any CPU.Build.0 = Canary|Any CPU + {BB32D6D5-7915-496C-933A-A2632C78918F}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB32D6D5-7915-496C-933A-A2632C78918F}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB32D6D5-7915-496C-933A-A2632C78918F}.Release|Any CPU.Build.0 = Release|Any CPU + {BB32D6D5-7915-496C-933A-A2632C78918F}.OpenBeta|Any CPU.ActiveCfg = OpenBeta|Any CPU + {BB32D6D5-7915-496C-933A-A2632C78918F}.OpenBeta|Any CPU.Build.0 = OpenBeta|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FinalSuspect/Attributes/GameModuleInitializerAttribute.cs b/FinalSuspect/Attributes/GameModuleInitializerAttribute.cs index 2407a072..111229a0 100644 --- a/FinalSuspect/Attributes/GameModuleInitializerAttribute.cs +++ b/FinalSuspect/Attributes/GameModuleInitializerAttribute.cs @@ -1,8 +1,9 @@ namespace FinalSuspect.Attributes; /// -/// 用于在 的后期修饰中,用于每次游戏初始化的方法 -/// 在静态方法前加上 [GameModuleInitializer],可以在游戏开始时自动调用 -/// 通过 [GameModuleInitializer(InitializePriority.High)] 可以指定调用顺序 +/// 用于在 的后期修饰中,用于每次游戏初始化的方法 +/// 在静态方法前加上 [GameModuleInitializer],可以在游戏开始时自动调用 +/// 通过 [GameModuleInitializer(InitializePriority.High)] 可以指定调用顺序 /// -public sealed class GameModuleInitializerAttribute(InitializePriority priority = InitializePriority.Normal) : InitializerAttribute(priority) { } \ No newline at end of file +public sealed class GameModuleInitializerAttribute(InitializePriority priority = InitializePriority.Normal) + : InitializerAttribute(priority); \ No newline at end of file diff --git a/FinalSuspect/Attributes/InitializerAttribute.cs b/FinalSuspect/Attributes/InitializerAttribute.cs index d5f57e41..5a26bbbb 100644 --- a/FinalSuspect/Attributes/InitializerAttribute.cs +++ b/FinalSuspect/Attributes/InitializerAttribute.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using FinalSuspect.Modules.LogHandler; namespace FinalSuspect.Attributes; @@ -10,15 +7,20 @@ namespace FinalSuspect.Attributes; public abstract class InitializerAttribute(InitializePriority priority) : Attribute { /// 所有初始化方法 + // ReSharper disable once StaticMemberInGenericType private static MethodInfo[] allInitializers; - private static LogHandler logger = Handler(nameof(InitializerAttribute)); - public InitializerAttribute() : this(InitializePriority.Normal) { } + private static readonly LogHandler logger = Handler(nameof(InitializerAttribute)); private readonly InitializePriority priority = priority; + /// 在初始化时调用的方法 private MethodInfo targetMethod; + protected InitializerAttribute() : this(InitializePriority.Normal) + { + } + private static void FindInitializers() { var initializers = new HashSet>(32); @@ -34,24 +36,24 @@ private static void FindInitializers() { // 获取 InitializerAttribute var attribute = method.GetCustomAttribute>(); - if (attribute != null) - { - // 如果获取到了,则注册 - attribute.targetMethod = method; - initializers.Add(attribute); - } + if (attribute == null) continue; + // 如果获取到了,则注册 + attribute.targetMethod = method; + initializers.Add(attribute); } } + // 将找到的初始化方法按照优先级排序并转换为数组 - allInitializers = initializers.OrderBy(initializer => initializer.priority).Select(initializer => initializer.targetMethod).ToArray(); + allInitializers = + [ + .. initializers.OrderBy(initializer => initializer.priority).Select(initializer => initializer.targetMethod) + ]; } + public static void InitializeAll() { // 在首次初始化时查找初始化方法 - if (allInitializers == null) - { - FindInitializers(); - } + if (allInitializers == null) FindInitializers(); if (allInitializers == null) return; foreach (var initializer in allInitializers) @@ -61,16 +63,21 @@ public static void InitializeAll() } } } + public enum InitializePriority { /// 最高优先级,首先执行 VeryHigh, + /// 在默认值之前执行 High, + /// 默认值 Normal, + /// 在默认值之后执行 Low, + /// 最低优先级,最后执行 - VeryLow, + VeryLow } \ No newline at end of file diff --git a/FinalSuspect/Attributes/PluginModuleInitializerAttribute.cs b/FinalSuspect/Attributes/PluginModuleInitializerAttribute.cs index 9f7027b6..8d7ccac6 100644 --- a/FinalSuspect/Attributes/PluginModuleInitializerAttribute.cs +++ b/FinalSuspect/Attributes/PluginModuleInitializerAttribute.cs @@ -1,8 +1,9 @@ namespace FinalSuspect.Attributes; /// -/// 用于在 中启动时初始化的方法 -/// 在静态方法前面加上 [PluginModuleInitializer],可以在启动时自动调用 -/// 可以使用 [PluginModuleInitializer(InitializePriority.High)] 来指定调用顺序 +/// 用于在 中启动时初始化的方法 +/// 在静态方法前面加上 [PluginModuleInitializer],可以在启动时自动调用 +/// 可以使用 [PluginModuleInitializer(InitializePriority.High)] 来指定调用顺序 /// -public sealed class PluginModuleInitializerAttribute(InitializePriority priority = InitializePriority.Normal) : InitializerAttribute(priority) { } \ No newline at end of file +public sealed class PluginModuleInitializerAttribute(InitializePriority priority = InitializePriority.Normal) + : InitializerAttribute(priority); \ No newline at end of file diff --git a/FinalSuspect/Modules/ClientOptions/ClientActionItem.cs b/FinalSuspect/ClientActions/ClientActionItem.cs similarity index 84% rename from FinalSuspect/Modules/ClientOptions/ClientActionItem.cs rename to FinalSuspect/ClientActions/ClientActionItem.cs index feee6c85..1089fc83 100644 --- a/FinalSuspect/Modules/ClientOptions/ClientActionItem.cs +++ b/FinalSuspect/ClientActions/ClientActionItem.cs @@ -4,16 +4,12 @@ using UnityEngine.UI; using Object = UnityEngine.Object; -namespace FinalSuspect.Modules.ClientOptions; +namespace FinalSuspect.ClientActions; public class ClientActionItem { - public ToggleButtonBehaviour ToggleButton { get; private set; } - protected Action OnClickAction { get; set; } - - public static SpriteRenderer CustomBackground { get; set; } - public static ToggleButtonBehaviour ModOptionsButton { get; set; } private static int numItems; + private string Name; protected ClientActionItem(string name, OptionsMenuBehaviour optionsMenuBehaviour) { @@ -22,7 +18,7 @@ protected ClientActionItem(string name, OptionsMenuBehaviour optionsMenuBehaviou var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; // 在生成第一个按钮时同时生成背景 - if (CustomBackground == null) + if (!CustomBackground) { numItems = 0; CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); @@ -47,10 +43,7 @@ protected ClientActionItem(string name, OptionsMenuBehaviour optionsMenuBehaviou PassiveButton leaveButton = null; foreach (var button in selectableButtons) { - if (button == null) - { - continue; - } + if (!button) continue; switch (button.name) { @@ -62,15 +55,16 @@ protected ClientActionItem(string name, OptionsMenuBehaviour optionsMenuBehaviou break; } } - + var generalTab = mouseMoveToggle.transform.parent.parent.parent; ModOptionsButton = Object.Instantiate(mouseMoveToggle, generalTab); var pos = leaveButton?.transform.localPosition; - ModOptionsButton.transform.localPosition = pos != null ? pos.Value + new Vector3(1.24f, 0f, 0f) : new Vector3(1.24f, -2.4f, 1f); + ModOptionsButton.transform.localPosition = + pos != null ? pos.Value + new Vector3(1.24f, 0f, 0f) : new Vector3(1.24f, -2.4f, 1f); ModOptionsButton.name = "FinalSuspect Options"; ModOptionsButton.Text.text = GetString("FinalSuspectOptions"); - ModOptionsButton.Background.color = ColorHelper.ClientOptionColor; + ModOptionsButton.Background.color = ColorHelper.FSClientOptionColor; var modOptionsPassiveButton = ModOptionsButton.GetComponent(); modOptionsPassiveButton.OnClick = new Button.ButtonClickedEvent(); modOptionsPassiveButton.OnClick.AddListener(new Action(() => @@ -84,10 +78,11 @@ protected ClientActionItem(string name, OptionsMenuBehaviour optionsMenuBehaviou ToggleButton.transform.localPosition = new Vector3( // 基于当前选项数量计算位置 numItems % 2 == 0 ? -1.3f : 1.3f, + // ReSharper disable once PossibleLossOfFraction 2.2f - 0.5f * (numItems / 2), -6f); ToggleButton.name = name; - ToggleButton.Text.text = GetString(name); + Rename(name); ToggleButton.Background.color = Color.white; var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); @@ -99,8 +94,14 @@ protected ClientActionItem(string name, OptionsMenuBehaviour optionsMenuBehaviou } } + public ToggleButtonBehaviour ToggleButton { get; } + protected Action OnClickAction { get; set; } + + public static SpriteRenderer CustomBackground { get; set; } + public static ToggleButtonBehaviour ModOptionsButton { get; set; } + /// - /// 在 Mod 选项界面中添加一个可执行操作的按钮 + /// 在 Mod 选项界面中添加一个可执行操作的按钮 /// /// 按钮标签的翻译键和按钮对象名称 /// 点击时触发的 Action @@ -121,4 +122,12 @@ private void OnClick() { OnClickAction?.Invoke(); } + + protected void Rename(string name = null) + { + if (name != null) + Name = name; + name ??= Name; + ToggleButton.Text.text = GetString("ClientOption." + name); + } } \ No newline at end of file diff --git a/FinalSuspect/ClientActions/ClientActionsPatch.cs b/FinalSuspect/ClientActions/ClientActionsPatch.cs new file mode 100644 index 00000000..b7797c25 --- /dev/null +++ b/FinalSuspect/ClientActions/ClientActionsPatch.cs @@ -0,0 +1,325 @@ +using System; +using System.IO; +using BepInEx.Configuration; +using FinalSuspect.ClientActions.FeatureItems; +using FinalSuspect.ClientActions.FeatureItems.MainMenuStyle; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; +using FinalSuspect.ClientActions.FeatureItems.NameTag; +using FinalSuspect.ClientActions.FeatureItems.Resources; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Features; +using FinalSuspect.Patches.System; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace FinalSuspect.ClientActions; + +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))] +public static class OptionsMenuBehaviourStartPatch +{ + private static ClientOptionItem _unlockFPS; + private static ClientOptionItem _switchOutfitType; + private static ClientOptionItem _kickPlayerWithAbnormalFriendCode; + private static ClientOptionItem _kickPlayerWithDenyName; + private static ClientOptionItem _kickPlayerInBanList; + private static ClientOptionItem _spamDenyWord; + private static ClientOptionItem _autoStartGame; + private static ClientOptionItem _autoEndGame; + + private static ClientOptionItem _disableVanillaSound; + private static ClientOptionItem _enableFac; + private static ClientOptionItem _enableGuardian; + private static ClientOptionItem _showPlayerInfo; + private static ClientOptionItem _useModCursor; + private static ClientOptionItem _fastLaunchMode; + private static ClientOptionItem _offlineMode; + + private static ClientOptionItem _versionCheat; + private static ClientOptionItem _godMode; + private static ClientOptionItem _noGameEnd; + + private static ClientFeatureItem _clearAutoLogs; + private static ClientFeatureItem _dumpLog; + private static ClientFeatureItem _unloadMod; + private static ClientFeatureItem _mainMenuStyleBtn; + + private static ClientFeatureItem _resourceBtn; + + private static ClientFeatureItem _myMusicBtn; + private static ClientFeatureItem _nameTagBtn; + + + private static bool _reseted; + public static bool Recreate; + public static OptionsMenuBehaviour Instance { get; private set; } + + public static void Postfix(OptionsMenuBehaviour __instance) + { + if (!__instance.DisableMouseMovement) return; + Instance = __instance; + + if (!_reseted || !DebugModeManager.IsDebugMode) + { + _reseted = true; + Main.VersionCheat.Value = false; + Main.GodMode.Value = false; + Main.NoGameEnd.Value = false; + } + + if (Recreate) + { + ClientActionItem.ModOptionsButton.gameObject.SetActive(false); + Object.Destroy(ClientActionItem.ModOptionsButton); + Object.Destroy(ClientActionItem.CustomBackground); + ClientFeatureItem.ModOptionsButton.gameObject.SetActive(false); + Object.Destroy(ClientFeatureItem.ModOptionsButton); + Object.Destroy(ClientFeatureItem.CustomBackground); + + Object.Destroy(ModUnloaderScreen.Popup); + Object.Destroy(MainMenuStylePanel.CustomBackground); + Object.Destroy(ResourcesPanel.CustomBackground); + Object.Destroy(MyMusicPanel.CustomBackground); + Object.Destroy(NameTagPanel.CustomBackground); + + ClientActionItem.ModOptionsButton = null; + ClientActionItem.CustomBackground = null; + + ClientFeatureItem.ModOptionsButton = null; + ClientFeatureItem.CustomBackground = null; + + ModUnloaderScreen.Popup = null; + MainMenuStylePanel.CustomBackground = null; + ResourcesPanel.CustomBackground = null; + MyMusicPanel.CustomBackground = null; + NameTagPanel.CustomBackground = null; + } + + CreateOptionItem(ref _unlockFPS, "UnlockFPS", Main.UnlockFPS, __instance, UnlockFPSButtonToggle); + CreateOptionItem(ref _switchOutfitType, "SwitchOutfitType", Main.SwitchOutfitType, __instance, SwitchMode); + CreateOptionItem(ref _kickPlayerWithAbnormalFriendCode, "KickPlayerWithAbnormalFriendCode", + Main.KickPlayerWithAbnormalFriendCode, __instance); + CreateOptionItem(ref _kickPlayerInBanList, "KickPlayerInBanList", Main.KickPlayerInBanList, __instance); + CreateOptionItem(ref _kickPlayerWithDenyName, "KickPlayerWithDenyName", Main.KickPlayerWithDenyName, + __instance); + CreateOptionItem(ref _spamDenyWord, "SpamDenyWord", Main.SpamDenyWord, __instance); + CreateOptionItem(ref _enableFac, "EnableFAC", Main.EnableFAC, __instance); + CreateOptionItem(ref _enableGuardian, "EnableGuardian", Main.EnableGuardian, __instance); + CreateOptionItem(ref _autoStartGame, "AutoStartGame", Main.AutoStartGame, __instance, AutoStartButtonToggle); + CreateOptionItem(ref _autoEndGame, "AutoEndGame", Main.AutoEndGame, __instance); + //CreateOptionItem(ref PrunkMode, "PrunkMode", Main.PrunkMode, __instance); + CreateOptionItem(ref _disableVanillaSound, "DisableVanillaSound", Main.DisableVanillaSound, __instance, () => + { + if (Main.DisableVanillaSound.Value) + AudioPlayer.StopPlayVanilla(); + else + AudioPlayer.StartPlayVanilla(); + }); + CreateOptionItem(ref _showPlayerInfo, "ShowPlayerInfo", Main.ShowPlayerInfo, __instance); + CreateOptionItem(ref _useModCursor, "UseModCursor", Main.UseModCursor, __instance, SetCursor); + CreateOptionItem(ref _fastLaunchMode, "FastLaunchMode", Main.FastLaunchMode, __instance); + CreateOptionItem(ref _offlineMode, "OfflineMode", Main.OfflineMode, __instance, (() => + { + __instance.Close(); + CustomPopup.Show(GetString("ClientOption.OfflineMode"), GetString("UpdateResult.Succeed_Text"), + [(GetString(StringNames.ExitGame), Application.Quit)]); + })); + if (DebugModeManager.IsDebugMode) + { + CreateOptionItem(ref _versionCheat, "VersionCheat", Main.VersionCheat, __instance); + CreateOptionItem(ref _godMode, "GodMode", Main.GodMode, __instance); + CreateOptionItem(ref _noGameEnd, "NoGameEnd", Main.NoGameEnd, __instance); + } + + CreateFeatureItem(ref _dumpLog, "DumpLog", () => { DumpLog(); }, __instance); + CreateFeatureItem(ref _clearAutoLogs, "ClearAutoLogs", () => + { + ClearAutoLogs(); + SetFeatureItemDisabled(_clearAutoLogs); + }, __instance); + CreateFeatureItem(ref _unloadMod, "UnloadMod", ModUnloaderScreen.Show, __instance); + + + CreateFeatureItem(ref _mainMenuStyleBtn, "MainMenuStyleManager", + () => { MainMenuStylePanel.CustomBackground?.gameObject.SetActive(true); }, __instance); + CreateFeatureItem(ref _resourceBtn, "ResourceManager", + () => { ResourcesPanel.CustomBackground?.gameObject.SetActive(true); }, __instance); + CreateFeatureItem(ref _myMusicBtn, "SoundOption", + () => { MyMusicPanel.CustomBackground?.gameObject.SetActive(true); }, __instance); + CreateFeatureItem(ref _nameTagBtn, "NameTagManager", + () => { NameTagPanel.CustomBackground?.gameObject.SetActive(true); }, __instance); + + SetFeatureItemTextAndColor(_mainMenuStyleBtn, "MainMenuStyleManager"); + SetFeatureItemTextAndColor(_resourceBtn, "ResourceManager"); + SetFeatureItemTextAndColor(_myMusicBtn, "MyMusic"); + SetFeatureItemTextAndColor(_nameTagBtn, "NameTagManager"); + + if (!IsNotJoined) + { + SetFeatureItemDisabled_Menu(_resourceBtn); + SetFeatureItemDisabled_Menu(_mainMenuStyleBtn); + } + + if (Directory.GetFiles(GetLogFolder(true).FullName).Length <= 0) + SetFeatureItemDisabled(_clearAutoLogs); + + AudioManager.ReloadTag(); + + MainMenuStylePanel.Init(__instance); + ResourcesPanel.Init(__instance); + MyMusicPanel.Init(__instance); + NameTagPanel.Init(__instance); + + + if (!ModUnloaderScreen.Popup) + ModUnloaderScreen.Init(__instance); + Recreate = false; + } + + private static void CreateOptionItem(ref ClientOptionItem item, string name, ConfigEntry value, + OptionsMenuBehaviour instance, Action toggleAction = null) + { + if (Recreate) + { + Object.Destroy(item.ToggleButton.gameObject); + item = null; + } + + if (item == null || !item.ToggleButton) item = ClientOptionItem.Create(name, value, instance, toggleAction); + } + + /*private static void CreateActionItem(ref ClientActionItem item, string name, Action action, OptionsMenuBehaviour instance) + { + if (recreate) + { + Object.Destroy(item.ToggleButton.gameObject); + item = null; + } + + if (item == null || !item.ToggleButton) + { + item = ClientActionItem.Create(name, action, instance); + } + }*/ + + private static void CreateFeatureItem(ref ClientFeatureItem item, string name, Action action, + OptionsMenuBehaviour instance) + { + if (Recreate) + { + Object.Destroy(item.ToggleButton.gameObject); + item = null; + } + + if (item == null || !item.ToggleButton) item = ClientFeatureItem.Create(name, action, instance); + } + + private static void SetFeatureItemTextAndColor(ClientFeatureItem item, string text) + { + item.ToggleButton.Text.text = GetString("ClientFeature." + text); + item.ToggleButton.GetComponent().enabled = true; + item.ToggleButton.Background.color = ColorHelper.FSClientFeatureColor; + } + + /*private static void SetOptionItemDisabled(ClientOptionItem_Boolean item) + { + item.ToggleButton.Text.text += $"\n|{GetString("OnlyAvailableInMainMenu")}|"; + item.ToggleButton.GetComponent().enabled = false; + item.ToggleButton.Background.color = ColorHelper.ClientOptionColor_CanNotUse; + }*/ + + // private static void SetOptionItemDisabled_Menu(ClientOptionItem item) + // { + // item.Rename(); + // item.ToggleButton.Text.text += $"\n|{GetString("Tip.OnlyAvailableInMainMenu")}|"; + // item.ToggleButton.GetComponent().enabled = false; + // item.ToggleButton.Background.color = ColorHelper.ClientOptionColor_CanNotUse; + // } + + private static void SetFeatureItemDisabled_Menu(ClientFeatureItem item) + { + item.ToggleButton.Text.text += $"\n|{GetString("Tip.OnlyAvailableInMainMenu")}|"; + SetFeatureItemDisabled(item); + } + + private static void SetFeatureItemDisabled(ClientFeatureItem item) + { + item.ToggleButton.GetComponent().enabled = false; + item.ToggleButton.Background.color = ColorHelper.FSClientFeatureColor_CanNotUse; + } + + /*private static void SetFeatureItemEnable(ClientFeatureItem item) + { + item.ToggleButton.GetComponent().enabled = true; + item.ToggleButton.Background.color = ColorHelper.ClientFeatureColor; + }*/ + + private static void UnlockFPSButtonToggle() + { + Application.targetFrameRate = Main.UnlockFPS.Value ? 165 : 60; + SendInGame(string.Format(GetString("Notification.FPSSetTo"), Application.targetFrameRate)); + } + + private static void SwitchMode() + { + foreach (var pc in Main.AllPlayerControls) + { + pc.MyPhysics.SetBodyType(pc.BodyType); + if (pc.BodyType == PlayerBodyTypes.Normal) + pc.cosmetics.currentBodySprite.BodySprite.transform.localScale = new Vector3(0.5f, 0.5f, 1f); + } + } + + private static void AutoStartButtonToggle() + { + if (Main.AutoStartGame.Value == false && IsCountDown) GameStartManager.Instance.ResetStartState(); + } + + public static void SetCursor() + { + try + { + var sprite = LoadSprite("Cursor.png"); + Cursor.SetCursor(Main.UseModCursor.Value ? sprite.texture : null, Vector2.zero, CursorMode.Auto); + } + catch + { + Main.UseModCursor.Value = false; + } + } +} + +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] +public static class OptionsMenuBehaviourClosePatch +{ + public static void Postfix() + { + ClientActionItem.CustomBackground?.gameObject.SetActive(false); + ClientFeatureItem.CustomBackground?.gameObject.SetActive(false); + ModUnloaderScreen.Hide(); + MainMenuStylePanel.Hide(); + ResourcesPanel.Hide(); + MyMusicPanel.Hide(); + NameTagPanel.Hide(); + } +} + +[HarmonyPatch(typeof(LanguageSetter), nameof(LanguageSetter.SetLanguage))] +public static class LanguageSetterSetLanguagePatch +{ + public static void Postfix() + { + OptionsMenuBehaviourStartPatch.Recreate = true; + try + { + Object.Destroy(VersionShowerStartPatch.VisitText); + } + catch + { + /* ignored */ + } + + VersionShowerStartPatch.VisitText = null; + VersionShowerStartPatch.CreateVisitText(null); + OptionsMenuBehaviourStartPatch.Postfix(OptionsMenuBehaviourStartPatch.Instance); + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/ClientOptions/ClientFeatureItem.cs b/FinalSuspect/ClientActions/ClientFeatureItem.cs similarity index 79% rename from FinalSuspect/Modules/ClientOptions/ClientFeatureItem.cs rename to FinalSuspect/ClientActions/ClientFeatureItem.cs index 0673f163..dbe9ab32 100644 --- a/FinalSuspect/Modules/ClientOptions/ClientFeatureItem.cs +++ b/FinalSuspect/ClientActions/ClientFeatureItem.cs @@ -4,25 +4,20 @@ using UnityEngine.UI; using Object = UnityEngine.Object; -namespace FinalSuspect.Modules.ClientOptions; +namespace FinalSuspect.ClientActions; public class ClientFeatureItem { - public ToggleButtonBehaviour ToggleButton { get; set; } - public Action OnClickAction { get; protected set; } - - public static SpriteRenderer CustomBackground { get; set; } - public static ToggleButtonBehaviour ModOptionsButton { get; set; } private static int numItems; - protected ClientFeatureItem(string name, OptionsMenuBehaviour optionsMenuBehaviour) + private ClientFeatureItem(string name, OptionsMenuBehaviour optionsMenuBehaviour) { try { var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; // 1つ目のボタンの生成時に背景も生成 - if (CustomBackground == null) + if (!CustomBackground) { numItems = 0; CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); @@ -49,28 +44,22 @@ protected ClientFeatureItem(string name, OptionsMenuBehaviour optionsMenuBehavio for (var i = 0; i < selectableButtons.Length; i++) { var button = selectableButtons[i]; - if (button == null) - { - continue; - } + if (button == null) continue; if (button.name == "LeaveGameButton") - { leaveButton = button.GetComponent(); - } - else if (button.name == "ReturnToGameButton") - { - returnButton = button.GetComponent(); - } + else if (button.name == "ReturnToGameButton") returnButton = button.GetComponent(); } + var generalTab = mouseMoveToggle.transform.parent.parent.parent; ModOptionsButton = Object.Instantiate(mouseMoveToggle, generalTab); var pos = leaveButton?.transform.localPosition; - ModOptionsButton.transform.localPosition = pos != null ? pos.Value - new Vector3(1.3f, 0f, 0f) : new Vector3(-1.3f, -2.4f, 1f); + ModOptionsButton.transform.localPosition = + pos != null ? pos.Value - new Vector3(1.3f, 0f, 0f) : new Vector3(-1.3f, -2.4f, 1f); ModOptionsButton.name = "FinalSuspectFeatures Options"; ModOptionsButton.Text.text = GetString("FinalSuspectFeatures"); - ModOptionsButton.Background.color = ColorHelper.ClientFeatureColor; + ModOptionsButton.Background.color = ColorHelper.FSClientFeatureColor; var modOptionsPassiveButton = ModOptionsButton.GetComponent(); modOptionsPassiveButton.OnClick = new Button.ButtonClickedEvent(); modOptionsPassiveButton.OnClick.AddListener(new Action(() => @@ -78,14 +67,9 @@ protected ClientFeatureItem(string name, OptionsMenuBehaviour optionsMenuBehavio CustomBackground.gameObject.SetActive(true); })); - if (leaveButton != null) - { - leaveButton.transform.localPosition = new Vector3(-1.35f, -2.411f, -1f); - } - if (returnButton != null) - { - returnButton.transform.localPosition = new Vector3(1.35f, -2.411f, -1f); - } + if (leaveButton) leaveButton.transform.localPosition = new Vector3(-1.35f, -2.411f, -1f); + + if (returnButton) returnButton.transform.localPosition = new Vector3(1.35f, -2.411f, -1f); } // ボタン生成 @@ -93,11 +77,12 @@ protected ClientFeatureItem(string name, OptionsMenuBehaviour optionsMenuBehavio ToggleButton.transform.localPosition = new Vector3( // 現在のオプション数を基に位置を計算 numItems % 2 == 0 ? -1.3f : 1.3f, + // ReSharper disable once PossibleLossOfFraction 2.2f - 0.5f * (numItems / 2), -6f); ToggleButton.name = name; - ToggleButton.Text.text = GetString(name); - ToggleButton.Background.color = ColorHelper.ClientFeatureColor_ClickType; + ToggleButton.Text.text = GetString("ClientFeature." + name); + ToggleButton.Background.color = ColorHelper.FSClientFeatureColor_ClickType; var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); passiveButton.OnClick.AddListener((Action)OnClick); @@ -108,8 +93,14 @@ protected ClientFeatureItem(string name, OptionsMenuBehaviour optionsMenuBehavio } } + public ToggleButtonBehaviour ToggleButton { get; set; } + public Action OnClickAction { get; protected set; } + + public static SpriteRenderer CustomBackground { get; set; } + public static ToggleButtonBehaviour ModOptionsButton { get; set; } + /// - /// Modオプション画面に何かアクションを起こすボタンを追加します + /// Modオプション画面に何かアクションを起こすボタンを追加します /// /// ボタンラベルの翻訳キーとボタンのオブジェクト名 /// クリック時に発火するアクション @@ -126,7 +117,7 @@ public static ClientFeatureItem Create( }; } - public void OnClick() + private void OnClick() { try { diff --git a/FinalSuspect/ClientActions/ClientOptionItem.cs b/FinalSuspect/ClientActions/ClientOptionItem.cs new file mode 100644 index 00000000..240419a1 --- /dev/null +++ b/FinalSuspect/ClientActions/ClientOptionItem.cs @@ -0,0 +1,103 @@ +using System; +using BepInEx.Configuration; +using FinalSuspect.Helpers; +using UnityEngine; + +namespace FinalSuspect.ClientActions; + +public sealed class ClientOptionItem : ClientActionItem +{ + private ClientOptionItem( + string name, + ConfigEntry config, + OptionsMenuBehaviour optionsMenuBehaviour) + : base( + name, + optionsMenuBehaviour) + { + Config = config; + UpdateToggle(); + } + + private ConfigEntry Config { get; } + + /// + /// Modオプション画面にconfigのトグルを追加します + /// + /// ボタンラベルの翻訳キーとボタンのオブジェクト名 + /// 対応するconfig + /// OptionsMenuBehaviourのインスタンス + /// クリック時に追加で発火するアクション.configが変更されたあとに呼ばれる + /// 作成したアイテム + public static ClientOptionItem Create( + string name, + ConfigEntry config, + OptionsMenuBehaviour optionsMenuBehaviour, + Action additionalOnClickAction = null) + { + var item = new ClientOptionItem(name, config, optionsMenuBehaviour); + item.OnClickAction = () => + { + switch (config.Value) + { + case bool: + config.Value = (T)(object)!(bool)(object)config.Value; + break; + case not null when typeof(T).IsEnum: + var allValues = (T[])Enum.GetValues(typeof(T)); + if (allValues.Length == 0) break; + var currentIndex = Array.IndexOf(allValues, config.Value); + if (currentIndex < 0) + currentIndex = 0; + else + currentIndex = (currentIndex + 1) % allValues.Length; + config.Value = allValues[currentIndex]; + item.ToggleButton.Text.text += $"\n|{GetString(config.Value.ToString())}|"; + break; + } + + item.UpdateToggle(); + additionalOnClickAction?.Invoke(); + }; + return item; + } + + private void UpdateToggle() + { + if (!ToggleButton) return; + + var color = ColorHelper.FSClientOptionColor_Disable; + switch (Config.Value) + { + case bool value: + color = value + ? ColorHelper.FSClientOptionColor + : ColorHelper.FSClientOptionColor_Disable; + break; + case not null when typeof(T).IsEnum: + var allValues = (T[])Enum.GetValues(typeof(T)); + if (allValues.Length == 0) break; + var currentIndex = Array.IndexOf(allValues, Config.Value); + + var baseColor = ColorHelper.FSClientOptionColor; + var factor = allValues.Length > 1 + ? currentIndex / (float)(allValues.Length - 1) + : 0f; + var newRed = (byte)Mathf.Clamp(baseColor.r - (byte)(factor * 70), 0, 255); + color = new Color32( + newRed, + baseColor.g, + baseColor.b, + baseColor.a + ); + + Config.Value = allValues[currentIndex]; + Rename(); + ToggleButton.Text.text += $"\n|{GetString($"Value.{Config.Value.ToString()}")}|"; + break; + } + + ToggleButton.Background.color = color; + ToggleButton.Rollover?.ChangeOutColor(color); + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/MainMenuStyle/MainMenuStyleManager.cs b/FinalSuspect/ClientActions/FeatureItems/MainMenuStyle/MainMenuStyleManager.cs new file mode 100644 index 00000000..0484e5b2 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/MainMenuStyle/MainMenuStyleManager.cs @@ -0,0 +1,105 @@ +using System.IO; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; +using FinalSuspect.Modules.Resources; +using UnityEngine; + +namespace FinalSuspect.ClientActions.FeatureItems.MainMenuStyle; + +public abstract class MainMenuStyleManager +{ + public static readonly List MainMenuStyles = + [ + new( + "MiraHQ", + true, + [ + new Color(0.5216f, 1f, 0.9490f, 0.8f), + new Color(0.5216f, 0.7765f, 1f, 0.8f), + new Color(0.7294f, 0.6353f, 1.0f, 0.8f), + new Color(0.0235f, 0f, 0.8f, 0.8f) + ]), + new( + "Security", + true, + [ + new Color(1f, 0.524f, 0.549f, 0.8f), + new Color(1f, 0.825f, 0.686f, 0.8f), + new Color(0.526f, 1f, 0.792f, 0.8f), + new Color(0.526f, 0.731f, 1f, 0.8f) + ], + 2), + new( + "NewYear", + false, + [ + new Color(0.8f, 0.251f, 0.1f, 0.8f), + new Color(1f, 1f, 0.286f, 0.8f), + new Color(0.7f, 0.7765f, 0.245f, 0.8f), + new Color(1f, 0.324f, 0.211f, 0.8f), + ]), + new( + "MiraStudio", + false, + [ + new Color(0.9f, 0.551f, 0.9f, 0.8f), + new Color(0.9f, 0.951f, 0.5f, 0.8f), + new Color(0.8f, 0.251f, 0.1f, 0.8f), + new Color(0.526f, 0.731f, 1f, 0.8f) + ]), + new( + "XtremeWave", + false, + [ + new Color(0.0235f, 0.6f, 1f, 0.8f), + new Color(0.526f, 0.731f, 1f, 0.8f), + new Color(0.7294f, 0.6353f, 1.0f, 0.8f), + new Color(0.9f, 0.551f, 0.9f, 0.8f), + ]), + //new( + //"WhenLookingBackAtTheEnd", + //false, + //[]) + ]; + + public class MainMenuStyle( + string BGName, + bool starFieldActive, + List mainUIColors, + int starGenDire = -2, + SupportedMusics supportedMusic = SupportedMusics.FinalSuspect__Slok) + { + public readonly List MainUIColors = mainUIColors; + private CurrentState _currentState = CurrentState.NotFound; + public bool Applied => CurrentState == CurrentState.Applied; + public bool StarFieldActive { get; } = starFieldActive; + public int StarGenDire { get; } = starGenDire; + public SupportedMusics MainMenuMusic { get; } = supportedMusic; + + public CurrentState CurrentState + { + get + { + if (_currentState != CurrentState.NotFound) + return _currentState; + + return File.Exists(GetLocalFilePath(FileType.Images, $"FinalSuspect-BG-{BGName}.png")) + ? CurrentState.NotApply + : CurrentState.NotFound; + } + set => _currentState = value; + } + + public string Title => GetString($"MainMenuStyle.Title_{BGName}"); + public string Author => GetString($"MainMenuStyle.Author_{BGName}"); + public string Description => GetString($"MainMenuStyle.Description_{BGName}"); + public Sprite PreviewSprite => LoadSprite($"FinalSuspect-BG-{BGName}-Preview.png", 450f); + public Sprite Sprite => LoadSprite($"FinalSuspect-BG-{BGName}.png", 179f); + } +} + +public enum CurrentState +{ + NotFound, + NotApply, + Applied +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/MainMenuStyle/MainMenuStylePanel.cs b/FinalSuspect/ClientActions/FeatureItems/MainMenuStyle/MainMenuStylePanel.cs new file mode 100644 index 00000000..d3d8b137 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/MainMenuStyle/MainMenuStylePanel.cs @@ -0,0 +1,297 @@ +using System; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; +using FinalSuspect.Helpers; +using FinalSuspect.Patches.System; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using static FinalSuspect.ClientActions.FeatureItems.MainMenuStyle.MainMenuStyleManager; +using Object = UnityEngine.Object; + +namespace FinalSuspect.ClientActions.FeatureItems.MainMenuStyle; + +public static class MainMenuStylePanel +{ + private static ToggleButtonBehaviour _applyButton; + private static TextMeshPro _titleText; + private static TextMeshPro _authorText; + private static TextMeshPro _descriptionText; + private static SpriteRenderer _previewImage; + public static SpriteRenderer CustomBackground { get; set; } + public static List Items { get; private set; } = []; + private static int CurrentPage { get; set; } = Main.CurrentStyleId.Value + 1; + private static int TotalPageCount => MainMenuStyles.Count; + + public static void Hide() => CustomBackground?.gameObject.SetActive(false); + + public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) + { + if (CustomBackground != null) return; + + var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + CustomBackground = CreateBackground(optionsMenuBehaviour); + + CreateAuthorText(optionsMenuBehaviour); + CreatePreviewImage(); + CreateTitleText(optionsMenuBehaviour); + CreateCloseButton(mouseMoveToggle); + CreateApplyButton(mouseMoveToggle); + CreateHelpText(optionsMenuBehaviour); + CreateDescriptionText(optionsMenuBehaviour); + CreatePageNavigationButtons(mouseMoveToggle); + var currentBackground = MainMenuStyles[Main.CurrentStyleId.Value]; + currentBackground.CurrentState = CurrentState.Applied; + Refresh(currentBackground); + } + + private static SpriteRenderer CreateBackground(OptionsMenuBehaviour options) + { + var bg = Object.Instantiate(options.Background, options.transform); + bg.name = "Main Menu Style Panel Background"; + bg.transform.localScale = new Vector3(0.9f, 0.9f, 1f); + bg.transform.localPosition += Vector3.back * 18; + bg.gameObject.SetActive(false); + return bg; + } + + private static void CreateCloseButton(ToggleButtonBehaviour template) + { + var closeButton = Object.Instantiate(template, CustomBackground.transform); + closeButton.transform.localPosition = new Vector3(1.3f, -2.43f, -6f); + closeButton.name = "Close"; + closeButton.Text.text = GetString("Close"); + closeButton.Background.color = Color.red; + + var closePassiveButton = closeButton.GetComponent(); + closePassiveButton.OnClick = new Button.ButtonClickedEvent(); + closePassiveButton.OnClick.AddListener(new Action(() => CustomBackground.gameObject.SetActive(false))); + } + + private static void CreateApplyButton(ToggleButtonBehaviour template) + { + _applyButton = Object.Instantiate(template, CustomBackground.transform); + _applyButton.transform.localPosition = new Vector3(1.3f, -1.88f, -6f); + _applyButton.name = "ApplyButton"; + _applyButton.Text.text = GetString("MainMenuStyle.Apply"); + _applyButton.Background.color = IsNotJoined ? Palette.White : Palette.DisabledGrey; + + var button = _applyButton.GetComponent(); + button.OnClick = new Button.ButtonClickedEvent(); + button.OnClick.AddListener(new Action(() => + { + var id = CurrentPage - 1; + Main.CurrentStyleId.Value = id; + var style = MainMenuStyles[id]; + MainMenuStyles.Where(x => x.Applied).Do(x => x.CurrentState = CurrentState.NotApply); + style.CurrentState = CurrentState.Applied; + Refresh(style); + var sr = ModMainMenuManager.FinalSuspect_Background.GetComponent(); + + sr.sprite = style.Sprite; + if (id == 3) + { + var rd = HashRandom.Next(0, 100); + if (rd < 5) + sr.sprite = LoadSprite("FinalSuspect-BG-MiraStudioNewYear.png", 179f); + } + + ModMainMenuManager.Starfield.SetActive(style.StarFieldActive); + var starGen = ModMainMenuManager.Starfield.GetComponent(); + starGen.SetDirection(new Vector2(0, style.StarGenDire)); + + var __instance = DestroyableSingleton.Instance; + Color shade = new(0f, 0f, 0f, 0f); + var standardActiveSprite = __instance.newsButton.activeSprites.GetComponent().sprite; + var minorActiveSprite = __instance.quitButton.activeSprites.GetComponent().sprite; + AwakeFriendCodeUIPatch.Prefix(); + var friendsButton = ModMainMenuManager.FriendsButton.GetComponent(); + Dictionary, (Sprite, Color, Color, Color, Color)> mainButtons = new() + { + { + [ + __instance.playButton, + __instance.inventoryButton, + __instance.shopButton + ], + (standardActiveSprite, style.MainUIColors[0], shade, Color.white, Color.white) + }, + { + [ + __instance.newsButton, + __instance.myAccountButton, + __instance.settingsButton + ], + (minorActiveSprite, style.MainUIColors[1], shade, Color.white, Color.white) + }, + { + [ + __instance.creditsButton, + __instance.quitButton, + ModMainMenuManager.InviteButton.GetComponent(), + ModMainMenuManager.GithubButton.GetComponent() + ], + (minorActiveSprite, style.MainUIColors[2], shade, Color.white, Color.white) + }, + { + [friendsButton], + (minorActiveSprite, style.MainUIColors[3], shade, Color.white, Color.white) + } + }; + foreach (var kvp in mainButtons) + kvp.Key.Do(passiveButton => + { + FormatButtonColor(__instance, passiveButton, kvp.Value.Item2, kvp.Value.Item3, kvp.Value.Item4, + kvp.Value.Item5); + }); + var lastAudio = FinalMusic.musics.FirstOrDefault(x => x.PlayAsMainMenuMusic); + var audio = FinalMusic.musics.FirstOrDefault(x => x.CurrentAudio == style.MainMenuMusic); + + if (lastAudio == null || audio == null) return; + if (lastAudio != audio) + SoundManager.Instance.StopAllSound(); + AudioPlayer.Play(audio, true); + })); + button.enabled = IsNotJoined; + } + + private static void CreateHelpText(OptionsMenuBehaviour optionsMenuBehaviour) + { + var helpText = Object.Instantiate( + optionsMenuBehaviour.DisableMouseMovement.Text, + CustomBackground.transform + ); + helpText.name = "HelpText"; + helpText.transform.localPosition = new Vector3(-1.25f, -2.15f, -5f); + helpText.transform.localScale = Vector3.one; + + var tmp = helpText.GetComponent(); + tmp.text = GetString("Tip.MainMenuStyleHelp"); + helpText.GetComponent().sizeDelta = new Vector2(2.45f, 1f); + } + + private static void CreateTitleText(OptionsMenuBehaviour optionsMenuBehaviour) + { + _titleText = Object.Instantiate( + optionsMenuBehaviour.DisableMouseMovement.Text, + CustomBackground.transform + ); + _titleText.name = "TitleText"; + _titleText.transform.localPosition = new Vector3(-0.05f, 2.55f, -5f); + _titleText.transform.localScale = Vector3.one; + + var tmp = _titleText.GetComponent(); + tmp.text = "Title"; + tmp.alignment = TextAlignmentOptions.TopLeft; + tmp.fontStyle = FontStyles.Bold; + //tmp.autoSizeTextContainer = true; + _titleText.GetComponent().sizeDelta = new Vector2(5f, 0.3f); + } + + private static void CreateAuthorText(OptionsMenuBehaviour optionsMenuBehaviour) + { + _authorText = Object.Instantiate( + optionsMenuBehaviour.DisableMouseMovement.Text, + CustomBackground.transform + ); + _authorText.name = "AuthorText"; + _authorText.transform.localPosition = new Vector3(0.75f, -1.25f, -5f); + _authorText.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f); + + var tmp = _authorText.GetComponent(); + tmp.text = "Author"; + tmp.alignment = TextAlignmentOptions.BottomRight; + tmp.fontStyle = FontStyles.Bold; + _authorText.GetComponent().sizeDelta = new Vector2(4f, 1f); + } + + private static void CreateDescriptionText(OptionsMenuBehaviour optionsMenuBehaviour) + { + _descriptionText = Object.Instantiate( + optionsMenuBehaviour.DisableMouseMovement.Text, + CustomBackground.transform + ); + _descriptionText.name = "DescriptionText"; + _descriptionText.transform.localPosition = new Vector3(-0.05f, -0.2f, -5f); + _descriptionText.transform.localScale = Vector3.one; + + var tmp = _descriptionText.GetComponent(); + tmp.text = + "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; + tmp.alignment = TextAlignmentOptions.TopLeft; + tmp.fontStyle = FontStyles.Italic; + tmp.fontSizeMin = tmp.fontSizeMax = tmp.fontSize = 1.25f; + + _descriptionText.GetComponent().sizeDelta = new Vector2(4.2f, 0.2f); + } + + private static void CreatePreviewImage() + { + _previewImage = ObjectHelper.CreateSpriteRenderer( + "FinalSuspect-BG-Preview", + "FinalSuspect-BG-MiraHQ.png", + 450f, + new Vector3(0, 1.1f, -5f), + CustomBackground.transform + ); + _previewImage.color = Color.white; + _previewImage.gameObject.layer = 5; + _previewImage.transform.localScale = Vector3.one; + } + + private static void CreatePageNavigationButtons(ToggleButtonBehaviour template) + { + var prevButton = Object.Instantiate(template, CustomBackground.transform); + prevButton.transform.localPosition = new Vector3(-2.4f, 1.1f, -6f); + prevButton.name = "PreviousPageButton"; + prevButton.Text.text = "←"; + prevButton.Background.color = Color.white; + prevButton.Background.size = new Vector2(0.4f, 0.4f); + prevButton.transform.FindChild("ButtonHighlight").gameObject.GetComponent().size = + new Vector2(0.55f, 0.55f); + prevButton.GetComponent().size = new Vector2(0.39f, 0.39f); + + var prevPassiveButton = prevButton.GetComponent(); + prevPassiveButton.OnClick = new Button.ButtonClickedEvent(); + prevPassiveButton.OnClick.AddListener(new Action(() => + { + CurrentPage = CurrentPage - 1 <= 0 ? TotalPageCount : CurrentPage - 1; + Refresh(MainMenuStyles[CurrentPage - 1]); + })); + + // 下一页按钮 + var nextButton = Object.Instantiate(template, CustomBackground.transform); + nextButton.transform.localPosition = new Vector3(2.4f, 1.1f, -6f); + nextButton.name = "NextPageButton"; + nextButton.Text.text = "→"; + nextButton.Background.color = Color.white; + nextButton.Background.size = new Vector2(0.4f, 0.4f); + nextButton.transform.FindChild("ButtonHighlight").gameObject.GetComponent().size = + new Vector2(0.55f, 0.55f); + nextButton.GetComponent().size = new Vector2(0.39f, 0.39f); + + var nextPassiveButton = nextButton.GetComponent(); + nextPassiveButton.OnClick = new Button.ButtonClickedEvent(); + nextPassiveButton.OnClick.AddListener(new Action(() => + { + CurrentPage = CurrentPage % TotalPageCount + 1; + Refresh(MainMenuStyles[CurrentPage - 1]); + })); + } + + private static void Refresh(MainMenuStyleManager.MainMenuStyle style) + { + _titleText.text = style.Title; + _authorText.text = $"{GetString("Author")}:{style.Author}"; + _descriptionText.text = style.Description; + _previewImage.sprite = style.PreviewSprite; + _applyButton.Background.color = style.CurrentState switch + { + CurrentState.NotFound => _applyButton.Text.color = Palette.DisabledGrey, + CurrentState.NotApply => _applyButton.Text.color = ColorHelper.FSClientFeatureColor, + CurrentState.Applied => _applyButton.Text.color = ColorHelper.FSColor, + _ => _applyButton.Background.color + }; + _applyButton.GetComponent().enabled = style.CurrentState != CurrentState.NotFound; + _applyButton.Text.text = GetString($"MainMenuStyle.{style.CurrentState}"); + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/ClientOptions/ModUnloaderScreen.cs b/FinalSuspect/ClientActions/FeatureItems/ModUnloaderScreen.cs similarity index 80% rename from FinalSuspect/Modules/ClientOptions/ModUnloaderScreen.cs rename to FinalSuspect/ClientActions/FeatureItems/ModUnloaderScreen.cs index f633c6ab..36dd9bf1 100644 --- a/FinalSuspect/Modules/ClientOptions/ModUnloaderScreen.cs +++ b/FinalSuspect/ClientActions/FeatureItems/ModUnloaderScreen.cs @@ -6,14 +6,14 @@ using UnityEngine.UI; using Object = UnityEngine.Object; -namespace FinalSuspect.Modules.ClientOptions; +namespace FinalSuspect.ClientActions.FeatureItems; public static class ModUnloaderScreen { public static SpriteRenderer Popup { get; set; } - public static TextMeshPro WarnText { get; private set; } - public static ToggleButtonBehaviour CancelButton { get; private set; } - public static ToggleButtonBehaviour UnloadButton { get; private set; } + private static TextMeshPro WarnText { get; set; } + private static ToggleButtonBehaviour CancelButton { get; set; } + private static ToggleButtonBehaviour UnloadButton { get; set; } public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) { @@ -72,22 +72,21 @@ public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) public static void Show() { - if (Popup != null) - { - Popup.gameObject.SetActive(true); + if (!Popup) return; + Popup.gameObject.SetActive(true); - if (AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Started) - { - WarnText.text = GetString("CannotUnloadDuringGame"); - UnloadButton.gameObject.SetActive(false); - } - else - { - WarnText.text = GetString("UnloadWarning"); - UnloadButton.gameObject.SetActive(true); - } + if (AmongUsClient.Instance.GameState == InnerNetClient.GameStates.Started) + { + WarnText.text = GetString("Tip.CannotUnloadDuringGame"); + UnloadButton.gameObject.SetActive(false); + } + else + { + WarnText.text = GetString("Tip.UnloadWarning"); + UnloadButton.gameObject.SetActive(true); } } + public static void Hide() { Popup?.gameObject.SetActive(false); diff --git a/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioLoader.cs b/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioLoader.cs new file mode 100644 index 00000000..a9057d96 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioLoader.cs @@ -0,0 +1,220 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Object = UnityEngine.Object; + +// ReSharper disable RedundantAssignment + +namespace FinalSuspect.ClientActions.FeatureItems.MyMusic; + +public static class AudioLoader +{ + static AudioLoader() + { + Warmup(); + } + + private static void Warmup() + { + var dummyBytes = new byte[2]; + ConvertBytesToFloats(dummyBytes); + + var warmupClip = AudioClip.Create("Warmup", 1, 1, 44100, false); + warmupClip.SetData(new float[] { 0 }, 0); + Object.Destroy(warmupClip); + } + + public static async Task LoadAudioClipAsync(string filePath) + { + if (!File.Exists(filePath)) + { + Debug.LogError("File does not exist: " + filePath); + return null; + } + + byte[] audioData; + try + { + audioData = await ReadAllBytesAsync(filePath); + } + catch (Exception e) + { + Debug.LogError("Failed to read file: " + filePath + "\n" + e.Message); + return null; + } + + try + { + // 优先尝试解析WAV文件头 + if (TryParseWavHeader(audioData, out var audioInfo)) + { + return await CreateClipFromPcm( + audioInfo.data, + audioInfo.sampleRate, + audioInfo.channels, + audioInfo.bitDepth + ); + } + // 尝试其他格式或默认处理 + else + { + Debug.LogWarning("Unrecognized format, attempting default processing"); + return await CreateClipFromRaw(audioData); + } + } + finally + { + // 确保及时释放内存 + audioData = null; + } + } + + private static async Task CreateClipFromPcm( + byte[] pcmData, + int sampleRate, + int channels, + int bitDepth) + { + if (bitDepth != 16) + { + Debug.LogError($"Unsupported bit depth: {bitDepth}. Only 16-bit supported"); + return null; + } + + var floatData = await Task.Run(() => ConvertBytesToFloats(pcmData)); + pcmData = null; // 立即释放PCM数据 + + var samplesPerChannel = floatData.Length / channels; + var audioClip = AudioClip.Create("LoadedAudioClip", samplesPerChannel, channels, sampleRate, false); + audioClip.SetData(floatData, 0); + return audioClip; + } + + private static async Task CreateClipFromRaw(byte[] rawData) + { + // 默认参数(双声道/44.1kHz) + const int defaultChannels = 2; + const int defaultSampleRate = 44100; + + var floatData = await Task.Run(() => ConvertBytesToFloats(rawData)); + rawData = null; // 立即释放原始数据 + + var samplesPerChannel = floatData.Length / defaultChannels; + var audioClip = + AudioClip.Create("LoadedAudioClip", samplesPerChannel, defaultChannels, defaultSampleRate, false); + audioClip.SetData(floatData, 0); + return audioClip; + } + + private static bool TryParseWavHeader(byte[] data, + out (byte[] data, int sampleRate, int channels, int bitDepth) audioInfo) + { + audioInfo = default; + + // 基本WAV文件检查(RIFF头) + if (data.Length < 44 || + Encoding.ASCII.GetString(data, 0, 4) != "RIFF" || + Encoding.ASCII.GetString(data, 8, 4) != "WAVE") + { + return false; + } + + try + { + // 查找"fmt "块 + var fmtIndex = FindChunk(data, "fmt "); + if (fmtIndex < 0) return false; + + _ = BitConverter.ToInt32(data, fmtIndex + 4); + int format = BitConverter.ToInt16(data, fmtIndex + 8); + if (format != 1) return false; // 仅支持PCM格式 + + int channels = BitConverter.ToInt16(data, fmtIndex + 10); + var sampleRate = BitConverter.ToInt32(data, fmtIndex + 12); + int bitDepth = BitConverter.ToInt16(data, fmtIndex + 22); + + // 查找"data"块 + var dataIndex = FindChunk(data, "data"); + if (dataIndex < 0) return false; + + var dataSize = BitConverter.ToInt32(data, dataIndex + 4); + var dataStart = dataIndex + 8; + + // 提取纯音频数据 + var audioData = new byte[dataSize]; + Buffer.BlockCopy(data, dataStart, audioData, 0, dataSize); + + audioInfo = (audioData, sampleRate, channels, bitDepth); + return true; + } + catch + { + return false; + } + } + + private static int FindChunk(byte[] data, string chunkId) + { + const int headerSize = 12; // RIFF头大小 + var index = headerSize; + + while (index < data.Length - 8) + { + var id = Encoding.ASCII.GetString(data, index, 4); + var size = BitConverter.ToInt32(data, index + 4); + + if (id == chunkId) + { + return index; + } + + index += 8 + size; // 移动到下一个区块 + } + + return -1; + } + + private static async Task ReadAllBytesAsync(string filePath) + { + await using var sourceStream = new FileStream( + filePath, + FileMode.Open, + FileAccess.Read, + FileShare.Read, + bufferSize: 4096, + useAsync: true + ); + + var buffer = new byte[sourceStream.Length]; + _ = await sourceStream.ReadAsync(buffer, 0, (int)sourceStream.Length); + return buffer; + } + + private static float[] ConvertBytesToFloats(byte[] audioBytes) + { + if (audioBytes.Length % 2 != 0) + { + throw new ArgumentException("Audio byte array must be even length (16-bit samples)"); + } + + var floatCount = audioBytes.Length / 2; + var floatData = new float[floatCount]; + + unsafe + { + fixed (byte* bytePtr = audioBytes) + fixed (float* floatPtr = floatData) + { + var src = (short*)bytePtr; + for (var i = 0; i < floatCount; i++) + { + floatPtr[i] = src[i] / 32768.0f; + } + } + } + + return floatData; + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioManager.cs b/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioManager.cs new file mode 100644 index 00000000..4af46279 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioManager.cs @@ -0,0 +1,244 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Features.CheckingandBlocking; +using FinalSuspect.Modules.Resources; +using UnityEngine; + +namespace FinalSuspect.ClientActions.FeatureItems.MyMusic; + +#nullable enable +public static class AudioManager +{ + public static List CustomAudios = []; + + public static void ReloadTag(bool official = true) + { + CustomAudios = []; +#nullable disable + if (official) + { + Init(); + return; + } + + try + { + var files = Directory.GetFiles(GetLocalPath(LocalType.Resources) + "Musics"); + + foreach (var filePath in files) + { + var fileName = Path.GetFileName(filePath); + if (EnumHelper.GetAllNames().Skip(1).Any(x => fileName.Contains(x))) + continue; + + if (string.IsNullOrWhiteSpace(fileName)) + continue; + + FinalMusic.CreateMusic(fileName); + Info($"Audio Loaded: {fileName}", "AudioManager"); + } + } + catch (Exception ex) + { + Error("Load Audios Failed\n" + ex, "AudioManager", false); + } + } + + private static void Init() + { + FinalMusic.InitializeAll(); + } + + public static bool ConvertExtension(ref string path) + { + if (path == null) return false; + List extensions = [".zip", ".wav", ".flac", ".aiff", ".mp3", ".aac", ".ogg", ".m4a"]; + + while (!File.Exists(path)) + { + var currentPath = path; + var extensionsArray = extensions.ToArray(); + if (extensionsArray.Length == 0) return false; + var matchingKey = extensions.FirstOrDefault(currentPath.Contains); + if (matchingKey is null) return false; + var currentIndex = Array.IndexOf(extensionsArray, matchingKey); + if (currentIndex == -1) return false; + + var nextIndex = (currentIndex + 1) % extensionsArray.Length; + path = path.Replace(matchingKey, extensionsArray[nextIndex]); + extensions.Remove(matchingKey); + } + + return true; + } + + public static void PlaySound(byte playerID, Sounds sound) + { + if (PlayerControl.LocalPlayer.PlayerId != playerID) return; + switch (sound) + { + case Sounds.KillSound: + SoundManager.Instance.PlaySound(PlayerControl.LocalPlayer.KillSfx, false); + break; + case Sounds.TaskComplete: + SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskCompleteSound, + false); + break; + case Sounds.TaskUpdateSound: + SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskUpdateSound, + false); + break; + case Sounds.ImpTransform: + SoundManager.Instance.PlaySound( + DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx, false, 0.8f); + break; + case Sounds.Yeehawfrom: + SoundManager.Instance.PlaySound( + DestroyableSingleton.Instance.HnSLocalYeehawSfx, false, 0.8f); + break; + default: + throw new ArgumentOutOfRangeException(nameof(sound), sound, null); + } + } +} + +public enum SupportedMusics +{ + UnOfficial, + FinalSuspect__Slok, + + // ## World Music + GongXiFaCai__Andy_Lau, + NeverGonnaGiveYouUp__Rick_Astley, + CountingStars__One_Republic, + + // ## Mod Music + // 专辑 + ChasingDawn__Slok, + ReturnToSimplicity2__Slok, + + // + Affinity__Slok, + TidalSurge__Slok, + ReturnToSimplicity__Slok, + + // 这里是EmberVeins的Demo曲 + TrailOfTruth__Slok, + Interlude__Slok, + Fractured__Slok, // 这首会有大用 + StruggleAgainstFadingFlame__Slok, + ElegyOfFracturedVow__Slok, + VestigiumSplendoris__Slok +} + +public enum AudiosStates +{ + NotExist, + IsDownLoading, + Exist, + IsPlaying, + DownLoadSucceedNotice, + DownLoadFailureNotice, + IsLoading +} + +public class FinalMusic +{ + public static readonly List musics = []; + + private static readonly object finalMusicsLock = new(); + public string Author; + public AudioClip Clip; + + public SupportedMusics CurrentAudio; + public AudiosStates CurrentAudioStates; + public string FileName; + public AudiosStates LastAudioStates; + + public string Name; + public string Path; + + public bool PlayAsMainMenuMusic; + + public bool UnOfficial; + + + public static void InitializeAll() + { + foreach (var file in EnumHelper.GetAllValues().ToList()) CreateMusic(music: file); + } + + public static void CreateMusic(string name = "", SupportedMusics music = SupportedMusics.UnOfficial) + { + var mus = new FinalMusic(); + mus.Create(name, music); + } + + public static async Task LoadClip(SupportedMusics music = SupportedMusics.UnOfficial) + { + var mus = musics.FirstOrDefault(x => x.CurrentAudio == music); + if (mus != null) + await mus.Load(); + } + + private async Task Load() + { + if (CurrentAudioStates != AudiosStates.Exist) return; + var task = AudioLoader.LoadAudioClipAsync(Path); + _ = new MainThreadTask(() => + { + LastAudioStates = CurrentAudioStates = AudiosStates.IsLoading; + MyMusicPanel.RefreshTagList(); + }, "Update Audio States"); + await task; + _ = new MainThreadTask(() => + { + if (task.Result) + Clip = task.Result; + LastAudioStates = CurrentAudioStates = Clip ? AudiosStates.Exist : AudiosStates.NotExist; + MyMusicPanel.RefreshTagList(); + }, "Update Audio States"); + } + + private void Create(string name, SupportedMusics music) + { + if (music != SupportedMusics.UnOfficial) + { + var Part = music.ToString().Split("__"); + FileName = Part[0]; + Name = GetString($"Mus.{Part[0]}"); + Author = Part[1].Replace("_", " "); + } + else + { + AudioManager.CustomAudios.Remove(name); + AudioManager.CustomAudios.Add(name); + FileName = Name = name; + Author = ""; + } + + UnOfficial = music == SupportedMusics.UnOfficial; + CurrentAudio = music; + Path = GetResourceFilesPath(FileType.Musics, FileName + ".wav"); + CurrentAudioStates = LastAudioStates = + AudioManager.ConvertExtension(ref Path) ? AudiosStates.Exist : AudiosStates.NotExist; + + lock (finalMusicsLock) + { + var file = musics.Find(x => x.FileName == FileName); + if (file != null) + { + file.Path = Path; + if (file.CurrentAudioStates is AudiosStates.DownLoadFailureNotice or AudiosStates.DownLoadSucceedNotice + || CurrentAudioStates is AudiosStates.NotExist) + file.CurrentAudioStates = file.LastAudioStates = CurrentAudioStates; + } + else if (Name != string.Empty) + { + musics.Add(this); + } + } + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioPlayer.cs b/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioPlayer.cs new file mode 100644 index 00000000..d38ab3e5 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/MyMusic/AudioPlayer.cs @@ -0,0 +1,214 @@ +using UnityEngine; +using Object = UnityEngine.Object; + +namespace FinalSuspect.ClientActions.FeatureItems.MyMusic; + +public static class AudioPlayer +{ + public static async void Play(FinalMusic audio, bool asMainMenuMusic = false) + { + try + { + if (audio.CurrentAudioStates is AudiosStates.NotExist or AudiosStates.IsPlaying) return; + if (!Constants.ShouldPlaySfx()) return; + + _ = new MainThreadTask(() => { StopPlayMod(true); }, "Playing Sfx"); + + await FinalMusic.LoadClip(audio.CurrentAudio); + + _ = new MainThreadTask(() => + { + foreach (var file in FinalMusic.musics.Where(file => file.FileName == audio.FileName)) + { + file.CurrentAudioStates = AudiosStates.IsPlaying; + file.PlayAsMainMenuMusic = asMainMenuMusic; + } + + AudioManager.ReloadTag(); + MyMusicPanel.RefreshTagList(); + SoundManager.Instance.CrossFadeSound(audio.FileName, audio.Clip, 0.7f); + Msg($"播放声音:{audio.Name}", "CustomSounds"); + }, "Playing Sfx"); + } + catch + { + /* ignored */ + } + } + + public static void StopPlayMod(bool playNew = false) + { + FinalMusic.musics.Do(x => + { + x.Clip = null; + x.CurrentAudioStates = x.LastAudioStates; + x.PlayAsMainMenuMusic = false; + SoundManager.Instance.StopNamedSound(x.FileName); + }); + _ = new MainThreadTask(MyMusicPanel.RefreshTagList, "Refresh Tag List"); + if (Main.DisableVanillaSound.Value || playNew) + StopPlayVanilla(); + else + StartPlayVanilla(); + } + + public static void StopPlayVanilla() + { + SoundManager.Instance.StopNamedSound("MapTheme"); + SoundManager.Instance.StopNamedSound("MainBG"); + } + + public static void StartPlayVanilla() + { + var isPlaying = FinalMusic.musics.Any(x => x.CurrentAudioStates == AudiosStates.IsPlaying); + if (isPlaying) return; + if (IsLobby) + SoundManager.Instance.CrossFadeSound("MapTheme", LobbyBehaviour.Instance.MapTheme, 0.07f); + } + + /*public static void AutoPlay(string sound, string name) + { + Play(sound); + MusicNow = name; + MusicPlaybackCompletedHandler(); + } + + public static string MusicNow = ""; + private static void MusicPlaybackCompletedHandler() + { + var rd = IRandom.Instance; + List mus = new(); + foreach (var audio in FinalMusic.musics) + { + var music = audio.FileName; + mus.Add(music); + } + if (MyMusicPanel.PlayMode == 2) + { + for (int i = 0; i < 10; i++) + { + var select = mus[rd.Next(0, mus.Count)]; + var path = @$"Final Suspect_Data/Resources/Audios/{select}.wav"; + if (ConvertExtension(ref path)) + StartPlayWait(path); + else + i--; + } + + } + else if (MyMusicPanel.PlayMode == 3) + { + var musicn = mus.IndexOf(MusicNow); + for (int i = 0; i < 10; i++) + { + int index = musicn; + if (index > mus.Count - 2) + index = -1; + var select = mus[index + 1]; + var path = @$"Final Suspect_Data/Resources/Audios/{select}.wav"; + if (ConvertExtension(ref path)) + { + StartPlayWait(path); + musicn++; + + } + else + i--; + } + + } + new LateTask(() => + { + MusicPlaybackCompletedHandler(); + }, 40f, "AddMusic"); + } + public static void StartPlayOnce(string path) => PlaySound(@$"{path}", 0, 1); 第3个形参,换为9,连续播放 + + public static void StartPlayInAmongUs(FinalMusic audio) + { + if (audio.Clip != null) + { + StopPlay(); + SoundManager.Instance.CrossFadeSound(audio.Name, audio.Clip, 0.5f); + } + else + { + Panel.Delete(audio); + } + }*/ +} + +[HarmonyPatch(typeof(SoundManager), nameof(SoundManager.PlaySoundImmediate))] +[HarmonyPatch(typeof(SoundManager), nameof(SoundManager.PlaySound))] +public class PlaySoundPatch +{ + public static bool Prefix(SoundManager __instance, [HarmonyArgument(0)] AudioClip clip, + [HarmonyArgument(1)] bool loop) + { + var isPlaying = FinalMusic.musics.Any(x => x.CurrentAudioStates == AudiosStates.IsPlaying); + var disableVanilla = Main.DisableVanillaSound.Value; + return !(isPlaying || disableVanilla) || !loop; + } +} + +[HarmonyPatch(typeof(SoundManager), nameof(SoundManager.PlayDynamicSound))] +[HarmonyPatch(typeof(SoundManager), nameof(SoundManager.PlayNamedSound))] +public class PlayDynamicAndNamedSoundPatch +{ + public static bool Prefix([HarmonyArgument(0)] string name, [HarmonyArgument(1)] AudioClip clip, + [HarmonyArgument(2)] bool loop) + { + var isPlaying = FinalMusic.musics.Any(x => x.CurrentAudioStates == AudiosStates.IsPlaying); + var isModMusic = FinalMusic.musics.Any(x => x.FileName == name); + var disableVanilla = Main.DisableVanillaSound.Value; + return !(isPlaying || disableVanilla) || !loop || isModMusic; + } +} + +[HarmonyPatch(typeof(SoundManager), nameof(SoundManager.CrossFadeSound))] +public class CrossFadeSoundPatch +{ + public static bool Prefix([HarmonyArgument(0)] string name, [HarmonyArgument(2)] float maxVolume) + { + var isPlaying = FinalMusic.musics.Any(x => x.CurrentAudioStates == AudiosStates.IsPlaying); + var isModMusic = FinalMusic.musics.Any(x => x.FileName == name); + var disableVanilla = Main.DisableVanillaSound.Value; + return !(isPlaying || disableVanilla) || isModMusic; + } +} + +[HarmonyPatch(typeof(SoundManager), nameof(SoundManager.StopAllSound))] +public class StopAllSoundPatch +{ + public static bool Prefix(SoundManager __instance) + { + for (var i = __instance.soundPlayers.Count - 1; i >= 0; i--) + { + var matchingMusic = FinalMusic.musics.FirstOrDefault(x => x.Clip == __instance.soundPlayers[i].Player.clip); + if (matchingMusic != null) + { + if (!matchingMusic.PlayAsMainMenuMusic) continue; + AudioPlayer.StopPlayMod(); + } + + Object.Destroy(__instance.soundPlayers[i].Player); + __instance.soundPlayers.RemoveAt(i); + } + + var keysToRemove = new List(); + foreach (var (key, value) in __instance.allSources) + { + if (FinalMusic.musics.Any(x => x.Clip == key && !x.PlayAsMainMenuMusic)) + continue; + + value.volume = 0f; + value.Stop(); + Object.Destroy(value); + keysToRemove.Add(key); + } + + foreach (var key in keysToRemove) __instance.allSources.Remove(key); + + return false; + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Panels/MyMusicPanel.cs b/FinalSuspect/ClientActions/FeatureItems/MyMusic/MyMusicPanel.cs similarity index 76% rename from FinalSuspect/Modules/Panels/MyMusicPanel.cs rename to FinalSuspect/ClientActions/FeatureItems/MyMusic/MyMusicPanel.cs index 3433a0f1..5e88137a 100644 --- a/FinalSuspect/Modules/Panels/MyMusicPanel.cs +++ b/FinalSuspect/ClientActions/FeatureItems/MyMusic/MyMusicPanel.cs @@ -1,38 +1,39 @@ using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using FinalSuspect.Helpers; -using FinalSuspect.Modules.SoundInterface; using TMPro; using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; -namespace FinalSuspect.Modules.Panels; +namespace FinalSuspect.ClientActions.FeatureItems.MyMusic; +[SuppressMessage("ReSharper", "PossibleLossOfFraction")] public static class MyMusicPanel { + private static int numItems; + + public static int PlayMode; public static SpriteRenderer CustomBackground { get; set; } public static List Items { get; private set; } public static OptionsMenuBehaviour OptionsMenuBehaviourNow { get; private set; } public static int CurrentPage { get; private set; } = 1; public static int ItemsPerPage => 7; - public static int TotalPageCount => (XtremeMusic.musics.Count + ItemsPerPage - 1) / ItemsPerPage; + public static int TotalPageCount => (FinalMusic.musics.Count + ItemsPerPage - 1) / ItemsPerPage; - private static int numItems; - public static int PlayMode; //public static ToggleButtonBehaviour ChangePlayMode { get; private set; } public static void Hide() { - if (CustomBackground != null) + if (CustomBackground) CustomBackground?.gameObject.SetActive(false); } + public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) { - var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; OptionsMenuBehaviourNow = optionsMenuBehaviour; - if (CustomBackground == null) + if (!CustomBackground) { CurrentPage = 1; numItems = 0; @@ -50,39 +51,38 @@ public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) closeButton.Background.color = Color.red; var closePassiveButton = closeButton.GetComponent(); closePassiveButton.OnClick = new Button.ButtonClickedEvent(); - closePassiveButton.OnClick.AddListener(new Action(() => - { - - CustomBackground.gameObject.SetActive(false); - })); + closePassiveButton.OnClick.AddListener(new Action(() => { CustomBackground.gameObject.SetActive(false); })); var stopButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); stopButton.transform.localPosition = new Vector3(1.3f, -1.88f, -16f); var stopButtonScale = stopButton.transform.localScale; stopButton.transform.localScale = stopButtonScale; stopButton.name = "stopButton"; - stopButton.Text.text = GetString("Stop"); + stopButton.Text.text = GetString("MusPlay.Stop"); stopButton.Background.color = Color.white; var stopPassiveButton = stopButton.GetComponent(); stopPassiveButton.OnClick = new Button.ButtonClickedEvent(); - stopPassiveButton.OnClick.AddListener(new Action(CustomSoundsManager.StopPlayMod)); + stopPassiveButton.OnClick.AddListener(new Action(() => AudioPlayer.StopPlayMod())); AddPageNavigationButton(optionsMenuBehaviour); - var helpText = Object.Instantiate(optionsMenuBehaviour.DisableMouseMovement.Text, CustomBackground.transform); + var helpText = + Object.Instantiate(optionsMenuBehaviour.DisableMouseMovement.Text, CustomBackground.transform); helpText.name = "Help Text"; helpText.transform.localPosition = new Vector3(-1.25f, -2.15f, -15f); helpText.transform.localScale = new Vector3(1f, 1f, 1f); var helpTextTMP = helpText.GetComponent(); - helpTextTMP.text = GetString("CustomSoundHelp"); + helpTextTMP.text = GetString("Tip.MyMusic"); helpText.gameObject.GetComponent().sizeDelta = new Vector2(2.45f, 1f); //AddChangePlayModeButton(optionsMenuBehaviour); } + RefreshTagList(); } - static void AddPageNavigationButton(OptionsMenuBehaviour optionsMenuBehaviour) + + private static void AddPageNavigationButton(OptionsMenuBehaviour optionsMenuBehaviour) { var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; var nextPageButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); @@ -103,10 +103,11 @@ static void AddPageNavigationButton(OptionsMenuBehaviour optionsMenuBehaviour) { CurrentPage = 1; } - - RefreshTagList() ; + + RefreshTagList(); })); } + /*static void AddChangePlayModeButton(OptionsMenuBehaviour optionsMenuBehaviour) { //var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; @@ -133,25 +134,33 @@ static void AddPageNavigationButton(OptionsMenuBehaviour optionsMenuBehaviour) }*/ public static void RefreshTagList() { - Items?.Do(Object.Destroy); - Items = []; - numItems = 0; - var optionsMenuBehaviour = OptionsMenuBehaviourNow; - var startIndex = (CurrentPage - 1) * ItemsPerPage; - - var count = 0; - foreach (var audio in XtremeMusic.musics.Skip(startIndex)) + try { - if (count >= ItemsPerPage) + Items?.Do(Object.Destroy); + Items = []; + numItems = 0; + var optionsMenuBehaviour = OptionsMenuBehaviourNow; + var startIndex = (CurrentPage - 1) * ItemsPerPage; + + var count = 0; + foreach (var audio in FinalMusic.musics.Skip(startIndex)) { - break; - } + if (count >= ItemsPerPage) + { + break; + } - RefreshTags(optionsMenuBehaviour, audio); - count++; + RefreshTags(optionsMenuBehaviour, audio); + count++; + } + } + catch + { + /* ignored */ } } - public static void RefreshTags(OptionsMenuBehaviour optionsMenuBehaviour, XtremeMusic audio) + + public static void RefreshTags(OptionsMenuBehaviour optionsMenuBehaviour, FinalMusic audio) { try { @@ -168,15 +177,15 @@ public static void RefreshTags(OptionsMenuBehaviour optionsMenuBehaviour, Xtreme var ToggleButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); ToggleButton.transform.localPosition = new Vector3(offsetX, offsetY, offsetZ); ToggleButton.name = "Btn-" + filename; - ToggleButton.Text.text = $"{name}{(author != string.Empty ? $" -{author}" : "")}"; ToggleButton.Background.color = Color.white; - numItems++; + numItems++; offsetX = numItems % 2 == 0 ? -1.3f : 1.3f; offsetY = 2.2f - 0.5f * (numItems / 2); offsetZ = -6f; - var previewText = Object.Instantiate(optionsMenuBehaviour.DisableMouseMovement.Text, CustomBackground.transform); + var previewText = + Object.Instantiate(optionsMenuBehaviour.DisableMouseMovement.Text, CustomBackground.transform); previewText.transform.localPosition = new Vector3(offsetX, offsetY, offsetZ); previewText.fontSize = ToggleButton.Text.fontSize; previewText.name = "PreText-" + filename; @@ -185,48 +194,51 @@ public static void RefreshTags(OptionsMenuBehaviour optionsMenuBehaviour, Xtreme string preview; var enable = false; - switch (audio.CurrectAudioStates) + switch (audio.CurrentAudioStates) { case AudiosStates.IsPlaying: - preview = GetString("Playing"); - color = ColorHelper.ModColor32; + preview = GetString("Tip.Playing"); + color = ColorHelper.FSColor; break; case AudiosStates.IsDownLoading: color = ColorHelper.DownloadYellow; - preview = GetString("DownloadingAudios"); + preview = GetString("Tip.Downloading"); break; case AudiosStates.IsLoading: - color = ColorHelper.ClientOptionColor; - preview = GetString("Parsing"); + color = ColorHelper.FSClientOptionColor; + preview = GetString("Tip.Parsing"); break; case AudiosStates.DownLoadSucceedNotice: case AudiosStates.Exist: - color = audio.UnOfficial ? Color.green : ColorHelper.ClientFeatureColor; - preview = GetString("CanPlay"); + color = audio.UnOfficial ? Color.green : ColorHelper.FSClientFeatureColor; + preview = GetString("MusPlay.CanPlay"); enable = true; break; case AudiosStates.NotExist: case AudiosStates.DownLoadFailureNotice: default: { - color = ColorHelper.ClientFeatureColor_CanNotUse; - preview = GetString("NoFound"); + color = ColorHelper.FSClientFeatureColor_CanNotUse; + preview = GetString("MusPlay.NoFound"); break; } } - previewText.text = preview; + previewText.text = $"{name}{(author != string.Empty ? $" -{author}" : "")}"; ToggleButton.Background.color = color; ToggleButton.GetComponent().enabled = enable; + ToggleButton.Text.text = preview; var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); passiveButton.OnClick.AddListener(new Action(OnClick)); + void OnClick() { Info($"Try To Play {filename}:{path}", "MyMusicPanel"); - CustomSoundsManager.Play(audio); + AudioPlayer.Play(audio); } + Items.Add(ToggleButton.gameObject); Items.Add(previewText.gameObject); } diff --git a/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagEditMenu.cs b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagEditMenu.cs new file mode 100644 index 00000000..f9ebd16a --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagEditMenu.cs @@ -0,0 +1,468 @@ +using System; +using System.Globalization; +using AmongUs.Data; +using FinalSuspect.Helpers; +using Il2CppSystem.IO; +using Newtonsoft.Json; +using TMPro; +using UnityEngine; +using static FinalSuspect.ClientActions.FeatureItems.NameTag.NameTagManager; +using Component = FinalSuspect.ClientActions.FeatureItems.NameTag.NameTagManager.Component; +using File = System.IO.File; +using Path = System.IO.Path; + +namespace FinalSuspect.ClientActions.FeatureItems.NameTag; + +public static class NameTagEditMenu +{ + public enum ComponentType + { + DisplayName, + Title, + Prefix, + Suffix, + Name, + LastTag + } + + // UI 常量 + private const float ButtonSpacing = 1.8f; + private const float ButtonRowHeight = 0.5f; + private const float ButtonStartX = -3.8f; + private const float ButtonStartY = 2.1f; + + // 数据 + private static string FriendCode; + private static NameTagManager.NameTag CacheTag; + + private static ComponentType CurrentComponent; + + // UI 元素 + public static GameObject Menu { get; private set; } + private static Dictionary ComponentButtons { get; } = new(); + public static GameObject Preview { get; private set; } + public static GameObject Text_Enter { get; private set; } + public static GameObject Size_Enter { get; private set; } + public static GameObject Color1_Enter { get; private set; } + public static GameObject Color2_Enter { get; private set; } + public static GameObject Color3_Enter { get; private set; } + + public static void Hide() + { + Menu?.SetActive(false); + } + + public static void Toggle(string friendCode, bool? on = null) + { + on ??= Menu == null || !Menu.activeSelf; + if (!on.Value) + { + Hide(); + return; + } + + if (Menu == null) Init(); + if (Menu == null) return; + + Menu.SetActive(true); + FriendCode = friendCode; + CacheTag = friendCode != null && AllExternalNameTags.TryGetValue(friendCode, out var tag) + ? DeepClone(tag) + : new NameTagManager.NameTag(); + + LoadComponent(GetComponent(CacheTag, ComponentType.DisplayName)); + SetButtonHighlight(ComponentType.DisplayName); + CurrentComponent = ComponentType.DisplayName; + UpdatePreview(); + } + + private static void SetButtonHighlight(ComponentType componentType) + { + foreach (var button in ComponentButtons.Values) + { + var text = button.transform.Find("Text_TMP").GetComponent(); + text.color = Palette.DisabledGrey; + } + + if (ComponentButtons.TryGetValue(componentType, out var activeButton)) + { + var activeText = activeButton.transform.Find("Text_TMP").GetComponent(); + activeText.color = ColorHelper.FSColor; + } + } + + private static void LoadComponent(Component com, bool name = false) + { + Text_Enter.GetComponent().enabled = !name; + Text_Enter.GetComponent().SetText(!name ? com?.Text ?? "" : GetString("CanNotEdit")); + Size_Enter.GetComponent() + .SetText((com?.SizePercentage ?? 100).ToString(CultureInfo.CurrentCulture)); + + Color1_Enter.GetComponent().Clear(); + Color2_Enter.GetComponent().Clear(); + Color3_Enter.GetComponent().Clear(); + + if (com?.Gradient?.IsValid ?? false) + for (var i = 0; i < Mathf.Min(3, com.Gradient.Colors.Count); i++) + { + var color = com.Gradient.Colors[i]; + var textBox = i switch + { + 0 => Color1_Enter.GetComponent(), + 1 => Color2_Enter.GetComponent(), + 2 => Color3_Enter.GetComponent(), + _ => null + }; + textBox?.SetText(ColorUtility.ToHtmlStringRGBA(color)[..6]); + } + else if (com?.TextColor != null) + Color1_Enter.GetComponent().SetText( + ColorUtility.ToHtmlStringRGBA(com.TextColor.Value)[..6]); + } + + private static void UpdatePreview() + { + if (!Menu.activeSelf || CacheTag == null || Preview == null) return; + var displayName = CacheTag.Apply(null, true); + Preview.GetComponent().text = displayName.title; + } + + private static void SaveToCache(ComponentType type) + { + var com = new Component(); + var text = Text_Enter.GetComponent().text.Trim(); + if (text != "" && type != ComponentType.Name) com.Text = text; + + var size = Size_Enter.GetComponent().text.Trim(); + if (size != "" && float.TryParse(size, out var sizef)) com.SizePercentage = sizef; + + List colors = new(); + AddColorIfValid(Color1_Enter, colors); + AddColorIfValid(Color2_Enter, colors); + AddColorIfValid(Color3_Enter, colors); + + if (colors.Count > 1) com.Gradient = new ColorGradient(colors.ToArray()); + else if (colors.Count == 1) com.TextColor = colors[0]; + com.Spaced = default; + + switch (type) + { + case ComponentType.Title: CacheTag.Title = com; break; + case ComponentType.Prefix: CacheTag.Prefix = com; break; + case ComponentType.Suffix: CacheTag.Suffix = com; break; + case ComponentType.Name: CacheTag.Name = com; break; + case ComponentType.DisplayName: CacheTag.DisplayName = com; break; + case ComponentType.LastTag: CacheTag.LastTag = com; break; + } + } + + private static void AddColorIfValid(GameObject input, List colors) + { + var colorHex = input.GetComponent().text.Trim(); + if (!string.IsNullOrEmpty(colorHex) + && ColorUtility.DoTryParseHtmlColor("#" + colorHex, out var color)) + colors.Add(color); + } + + private static bool SaveToFile(string friendCode, NameTagManager.NameTag tag) + { + if (string.IsNullOrEmpty(friendCode)) return false; + + StringWriter sw = new(); + JsonWriter writer = new JsonTextWriter(sw); + writer.WriteStartObject(); + var components = new Dictionary + { + ["Title"] = tag.Title, + ["Prefix"] = tag.Prefix, + ["Suffix"] = tag.Suffix, + ["Name"] = tag.Name, + ["DisplayName"] = tag.DisplayName, + ["LastTag"] = tag.LastTag + }; + foreach (var (name, com) in components) + { + if (com == null) continue; + + writer.WritePropertyName(name); + writer.WriteStartObject(); + + if (com.Text != null && name != "Name") + { + writer.WritePropertyName("Text"); + writer.WriteValue(com.Text); + } + + if (com.SizePercentage != null) + { + writer.WritePropertyName("SizePercentage"); + writer.WriteValue(com.SizePercentage.ToString()); + } + + if (com.Gradient != null && com.Gradient.IsValid) + { + var colors = string.Join(",", + com.Gradient.Colors.Select(c => "#" + ColorUtility.ToHtmlStringRGBA(c)[..6])); + writer.WritePropertyName("Gradient"); + writer.WriteValue(colors); + } + else if (com.TextColor != null) + { + writer.WritePropertyName("Color"); + writer.WriteValue("#" + ColorUtility.ToHtmlStringRGBA(com.TextColor.Value)[..6]); + } + + if (name != "Title" && name != "Name") + { + writer.WritePropertyName("Spaced"); + writer.WriteValue(com.Spaced.ToString()); + } + + writer.WriteEndObject(); + } + + writer.WriteEndObject(); + + var fileName = Path.Combine(TAGS_DIRECTORY_PATH, friendCode.Trim() + ".json"); + File.WriteAllText(fileName, sw.ToString()); + return true; + } + + public static void Init() + { + Menu = UiHelper.CreateBaseWindow( + "Name Tag Edit Menu", + NameTagPanel.CustomBackground.transform.parent, + -30f, + 1.4f + ); + + var closeButton = UiHelper.CreateCloseButton(Menu.transform, () => Toggle(null, false)); + closeButton.transform.localPosition = new Vector3(4.9f, 2.5f, -1f) * GetResolutionOffset(); + CreateComponentButtons(); + CreateActionButtons(); + CreatePreviewSection(); + CreateInputFields(); + UiHelper.HideTemplateObjects(Menu.transform); + } + + private static void CreateComponentButtons() + { + // 第一行按钮:DisplayName, Title, Prefix + CreateComponentButton(ComponentType.DisplayName, 0, 0); + CreateComponentButton(ComponentType.Title, 1, 0); + CreateComponentButton(ComponentType.Prefix, 2, 0); + + // 第二行按钮:Suffix, Name, LastTag + CreateComponentButton(ComponentType.Suffix, 0, 1); + CreateComponentButton(ComponentType.Name, 1, 1); + CreateComponentButton(ComponentType.LastTag, 2, 1); + } + + private static void CreateComponentButton(ComponentType type, int col, int row) + { + var x = ButtonStartX + col * ButtonSpacing; + var y = ButtonStartY - row * ButtonRowHeight; + var offset = GetResolutionOffset(); + + var button = UiHelper.CreateButton( + Menu.transform.Find("Button Prefab").gameObject, + Menu.transform, + new Vector3(x, y, 0) * offset, + type.ToString(), + offset + ); + + button.name = $"Edit{type}Button"; + button.transform.Find("Background").localScale = new Vector3(0.85f, 0.85f, 1f); + + button.GetComponent().OnClick.AddListener((Action)(() => + { + SaveToCache(CurrentComponent); + LoadComponent(GetComponent(CacheTag, type), type == ComponentType.Name); + SetButtonHighlight(type); + CurrentComponent = type; + })); + + ComponentButtons[type] = button; + } + + private static void CreateActionButtons() + { + var offset = GetResolutionOffset(); + + var previewButton = UiHelper.CreateButton( + Menu.transform.Find("Button Prefab").gameObject, + Menu.transform, + new Vector3(1.2f * offset, -2.5f * offset, 0f), + "RefreshPreview", + offset + ); + previewButton.name = "RefreshPreviewButton"; + + var previewPassive = previewButton.GetComponent(); + if (previewPassive != null) + { + previewPassive.OnClick.RemoveAllListeners(); + previewPassive.OnClick.AddListener((Action)(() => + { + SaveToCache(CurrentComponent); + UpdatePreview(); + })); + } + + // 保存按钮 + var saveButton = UiHelper.CreateButton( + Menu.transform.Find("Button Prefab").gameObject, + Menu.transform, + new Vector3(3.5f * offset, -2.5f * offset, 0f), + "SaveAndClose", + offset + ); + saveButton.name = "SaveAndExitButton"; + + var savePassive = saveButton.GetComponent(); + if (savePassive != null) + { + savePassive.OnClick.RemoveAllListeners(); + savePassive.OnClick.AddListener((Action)(() => + { + SaveToCache(CurrentComponent); + if (SaveToFile(FriendCode, CacheTag)) + { + ReloadTag(FriendCode); + NameTagPanel.RefreshTagList(); + } + + Toggle(null, false); + })); + } + + // 删除按钮 + var deleteButton = UiHelper.CreateButton( + Menu.transform.Find("Button Prefab").gameObject, + Menu.transform, + new Vector3(-3.5f * offset, -2.5f * offset, 0f), + GetString("Delete"), + offset, + false + ); + deleteButton.name = "DeleteButton"; + + // 设置删除按钮文本为红色 + var deleteText = deleteButton.transform.Find("Text_TMP")?.GetComponent(); + if (deleteText != null) deleteText.color = Color.red; + + var deletePassive = deleteButton.GetComponent(); + if (deletePassive != null) + { + deletePassive.OnClick.RemoveAllListeners(); + deletePassive.OnClick.AddListener((Action)(() => + { + if (string.IsNullOrEmpty(FriendCode)) return; + + var fileName = Path.Combine(TAGS_DIRECTORY_PATH, $"{FriendCode.Trim()}.json"); + if (File.Exists(fileName)) + try + { + File.Delete(fileName); + ReloadTag(FriendCode); + NameTagPanel.RefreshTagList(); + Toggle(null, false); + } + catch (Exception ex) + { + Error($"Delete name tag failed: {ex}", "NameTagEditMenu"); + } + })); + } + } + + private static void CreatePreviewSection() + { + var offset = GetResolutionOffset(); + + Preview = UiHelper.CreateText( + Menu.transform.Find("Title Prefab").gameObject, + Menu.transform, + new Vector3(0f, 1.2f * offset, 0f), + DataManager.player.Customization.Name, + 0.6f + ); + Preview.name = "Preview Text"; + } + + private static void CreateInputFields() + { + var offset = GetResolutionOffset(); + + // 文本输入区域 + Text_Enter = UiHelper.CreateInputField( + Menu.transform, + new Vector3(-2.9f * offset, 0f * offset, 0f), + true + ); + Text_Enter.name = "Edit Text Enter Box"; + + // 尺寸输入区域 + Size_Enter = UiHelper.CreateInputField( + Menu.transform, + new Vector3(-2.9f * offset, -1.2f * offset, 0f), + false + ); + Size_Enter.name = "Edit Size Enter Box"; + + // 颜色输入区域 + Color1_Enter = UiHelper.CreateInputField( + Menu.transform, + new Vector3(1.95f * offset, -0f * offset, 0f), + true + ); + Color1_Enter.name = "Edit Color 1 Enter Box"; + + Color2_Enter = UiHelper.CreateInputField( + Menu.transform, + new Vector3(1.95f * offset, -0.6f * offset, 0f), + true + ); + Color2_Enter.name = "Edit Color 2 Enter Box"; + + Color3_Enter = UiHelper.CreateInputField( + Menu.transform, + new Vector3(1.95f * offset, -1.2f * offset, 0f), + true + ); + Color3_Enter.name = "Edit Color 3 Enter Box"; + + // 创建标签文本 + CreateLabel(GetString("Tip.TextContent"), new Vector3(-2.95f * offset, 0f, 0f)); + CreateLabel(GetString("Tip.TextSizeDescription"), new Vector3(-2.95f * offset, -1.2f * offset, 0f)); + CreateLabel(GetString("Tip.TextColorDescription"), new Vector3(1.95f * offset, 0.2f, 0f)); + } + + private static void CreateLabel(string text, Vector3 position) + { + var label = UiHelper.CreateText( + Menu.transform.Find("Info Prefab").gameObject, + Menu.transform, + position, + text, + 1f + ); + label.name = $"Label_{text[..Mathf.Min(10, text.Length)]}"; + } + + private static Component GetComponent(NameTagManager.NameTag tag, ComponentType type) + { + return type switch + { + ComponentType.DisplayName => tag.DisplayName, + ComponentType.Title => tag.Title, + ComponentType.Prefix => tag.Prefix, + ComponentType.Suffix => tag.Suffix, + ComponentType.Name => tag.Name, + ComponentType.LastTag => tag.LastTag, + _ => null + }; + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagManager.cs b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagManager.cs new file mode 100644 index 00000000..2b554317 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagManager.cs @@ -0,0 +1,293 @@ +using System; +using System.IO; +using System.Text; +using AmongUs.Data; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using FinalSuspect.Modules.Resources; +using Il2CppSystem.Linq; +using Newtonsoft.Json.Linq; +using UnityEngine; + +namespace FinalSuspect.ClientActions.FeatureItems.NameTag; + +public static class NameTagManager +{ + public static readonly string TAGS_DIRECTORY_PATH = GetLocalPath(LocalType.NameTag); + private static Dictionary NameTags = new(); + public static IReadOnlyDictionary AllNameTags => NameTags; + + public static IReadOnlyDictionary AllInternalNameTags => + AllNameTags.Where(t => t.Value.Isinternal).ToDictionary(x => x.Key, x => x.Value); + + public static IReadOnlyDictionary AllExternalNameTags => + AllNameTags.Where(t => !t.Value.Isinternal).ToDictionary(x => x.Key, x => x.Value); + + public static NameTag DeepClone(NameTag tag) + { + return new NameTag + { + Title = CloneCom(tag.Title), + Prefix = CloneCom(tag.Prefix), + Suffix = CloneCom(tag.Suffix), + Name = CloneCom(tag.Name), + DisplayName = CloneCom(tag.DisplayName), + LastTag = CloneCom(tag.LastTag) + }; + + static Component CloneCom(Component com) + { + return com == null + ? null + : new Component + { + Text = com.Text, + SizePercentage = com.SizePercentage, + TextColor = com.TextColor, + Gradient = com.Gradient != null ? new ColorGradient(com.Gradient.Colors.ToArray()) : null, + Spaced = com.Spaced + }; + } + } + + public static (string title, string prefix, string suffix, string name, string displayName, string lastTag) + ApplyFor(PlayerControl player) + { + var a = AllNameTags.TryGetValue(player.FriendCode, out var tag); + + return a + ? tag.Apply(player.GetDataName()) + : ("", "", "", "", "", ""); + } + + public static void ReloadTag(string friendCode) + { + if (friendCode == null) + { + Init(); + return; + } + + NameTags.Remove(friendCode); + var path = Path.Combine(TAGS_DIRECTORY_PATH, $"{friendCode}.json"); + if (File.Exists(path)) + try + { + ReadTagsFromFile(path); + } + catch (Exception ex) + { + Error($"Load Tag From: {path} Failed\n{ex}", "NameTagManager", false); + } + } + + public static void Init() + { + NameTags = new Dictionary(); + + if (!Directory.Exists(TAGS_DIRECTORY_PATH)) + Directory.CreateDirectory(TAGS_DIRECTORY_PATH); + + foreach (var file in Directory.EnumerateFiles(TAGS_DIRECTORY_PATH, "*.json", SearchOption.AllDirectories)) + { + if (file.Contains("template", StringComparison.OrdinalIgnoreCase)) continue; + + try + { + ReadTagsFromFile(file); + } + catch (Exception ex) + { + Error($"Load Tag From: {file} Failed\n{ex}", "NameTagManager", false); + } + } + } + + public static void ReadTagsFromFile(string path) + { + var text = File.ReadAllText(path); + var obj = JObject.Parse(text); + var tag = GetTagFromJObject(obj); + var friendCode = Path.GetFileNameWithoutExtension(path); + + if (tag != null && !string.IsNullOrEmpty(friendCode)) + { + NameTags[friendCode] = tag; + Info($"Name Tag Loaded: {friendCode}", "NameTagManager"); + } + } + + public static NameTag GetTagFromJObject(JObject obj) + { + var tag = new NameTag(); + var componentMap = new Dictionary> + { + ["Title"] = token => tag.Title = GetComponent(token), + ["Prefix"] = token => tag.Prefix = GetComponent(token), + ["Suffix"] = token => tag.Suffix = GetComponent(token), + ["Name"] = token => tag.Name = GetComponent(token), + ["DisplayName"] = token => tag.DisplayName = GetComponent(token), + ["LastTag"] = token => tag.LastTag = GetComponent(token) + }; + + foreach (var prop in obj.Properties().ToList()) + if (componentMap.TryGetValue(prop.Name, out var action)) + action(prop.Value); + + return tag; + } + + private static Component GetComponent(JToken token) + { + if (token == null) return null; + return new Component + { + Text = token["Text"]?.ToString(), + SizePercentage = ParseSize(token["SizePercentage"]?.ToString()), + TextColor = ParseColor(token["Color"]?.ToString()), + Gradient = ParseGradient(token["Gradient"]?.ToString()), + Spaced = token["Spaced"]?.ToString()?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false + }; + } + + private static float? ParseSize(string str) + { + return float.TryParse(str, out var size) ? size : 90f; + } + + private static Color32? ParseColor(string str) + { + if (string.IsNullOrEmpty(str)) return null; + if (!str.StartsWith("#")) str = "#" + str; + return ColorUtility.TryParseHtmlString(str, out var color) ? color : null; + } + + private static ColorGradient ParseGradient(string str) + { + if (string.IsNullOrEmpty(str)) return null; + + var colors = new List(); + foreach (var colorStr in str.Split(',', ',')) + { + var trimmed = colorStr.Trim(); + if (string.IsNullOrEmpty(trimmed)) continue; + + var formatted = trimmed.StartsWith("#") ? trimmed : "#" + trimmed; + if (ColorUtility.TryParseHtmlString(formatted, out var color)) + colors.Add(color); + } + + return colors.Count >= 2 ? new ColorGradient(colors.ToArray()) : null; + } + + public class NameTag + { + public bool Isinternal { get; set; } = false; + public Component DisplayName { get; set; } + public Component Title { get; set; } + public Component Prefix { get; set; } + public Component Suffix { get; set; } + public Component Name { get; set; } + public Component LastTag { get; set; } + + public (string title, string prefix, string suffix, string name, string displayName, string lastTag) + Apply(string name, bool preview = false) + { + if (Name != null && Name.Text != "") name = Name.Generate(false); + + if (name == "") + name = DataManager.player.Customization.Name; + else if (name != "" && Name is { Text: "" }) + Name.Text = name; + + + if (!preview) + return ( + Title?.Generate(false) ?? "", + Prefix?.Generate(false) ?? "", + Suffix?.Generate(false) ?? "", + name, + DisplayName?.Generate(false) ?? "", + LastTag?.Generate(false) ?? "" + ); + + + name = $"{Prefix?.Generate()}{name}{Suffix?.Generate()}"; + var dp = DisplayName?.Generate(false); + var title = dp.RemoveHtmlTags() == "" ? "" : $"({dp})"; + return ($"{name}{title}", "", "", "", "", ""); + } + } + + public class Component + { + public float? SizePercentage { get; set; } = 90f; + public string Text { get; set; } + public Color32? TextColor { get; set; } + public ColorGradient Gradient { get; set; } + public bool Spaced { get; set; } = true; + + public string Generate(bool applySpace = true, bool applySize = true) + { + if (string.IsNullOrEmpty(Text)) return ""; + + var result = Text; + if (Gradient is { IsValid: true }) + result = Gradient.Apply(result); + else if (TextColor != null) + result = StringHelper.ColorString(TextColor.Value, result); + + if (Spaced && applySpace) + result = $" {result} "; + if (SizePercentage != null && applySize) + result = $"{result}"; + + return result; + } + } + + public class ColorGradient + { + public ColorGradient(params Color[] colors) + { + Colors = new List(colors); + Spacing = Colors.Count > 1 ? 1f / (Colors.Count - 1) : 0f; + } + + public List Colors { get; } + private float Spacing { get; } + + public bool IsValid => Colors.Count >= 2; + + public string Apply(string input) + { + if (input.Length == 0) return input; + if (input.Length == 1) return StringHelper.ColorString(Colors[0], input); + + var step = 1f / (input.Length - 1); + var sb = new StringBuilder(); + + for (var i = 0; i < input.Length; i++) + { + var color = Evaluate(step * i); + sb.Append(StringHelper.ColorString(color, input[i].ToString())); + } + + return sb.ToString(); + } + + public Color Evaluate(float percent) + { + percent = Mathf.Clamp01(percent); + var indexLow = Mathf.FloorToInt(percent / Spacing); + + if (indexLow >= Colors.Count - 1) + return Colors[^1]; + + var indexHigh = indexLow + 1; + var t = (percent - indexLow * Spacing) / Spacing; + + return Color.Lerp(Colors[indexLow], Colors[indexHigh], t); + } + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagNewWindow.cs b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagNewWindow.cs new file mode 100644 index 00000000..8bdcf416 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagNewWindow.cs @@ -0,0 +1,142 @@ +using System; +using System.Text.RegularExpressions; +using TMPro; +using UnityEngine; + +namespace FinalSuspect.ClientActions.FeatureItems.NameTag; + +public static class NameTagNewWindow +{ + private static readonly Regex FriendCodeRegex = new("^[a-z]+#[0-9]{4}$", RegexOptions.Compiled); + + public static GameObject Window { get; private set; } + public static GameObject Info { get; private set; } + public static GameObject EnterBox { get; private set; } + public static GameObject ConfirmButton { get; private set; } + + public static void Open() + { + try + { + if (NameTagEditMenu.Menu?.activeSelf ?? false) return; + } + catch + { + /* ignored */ + } + + if (Window == null) Init(); + Window?.SetActive(true); + EnterBox?.GetComponent()?.Clear(); + } + + public static void Init() + { + Window = UiHelper.CreateBaseWindow( + "New Name Tag Window", + NameTagPanel.CustomBackground.transform.parent, + -10, + 0.7f + ); + + CreateCloseButton(); + CreateInfoText(); + CreateInputField(); + CreateConfirmButton(); + + UiHelper.HideTemplateObjects(Window.transform); + } + + private static void CreateCloseButton() + { + var closeButton = UiHelper.CreateCloseButton(Window.transform, () => Window.SetActive(false)); + closeButton.transform.localPosition = new Vector3(2.4f, 1.2f, -1f) * GetResolutionOffset(); + } + + private static void CreateInfoText() + { + Info = UiHelper.CreateText( + Window.transform.Find("Info Prefab").gameObject, + Window.transform, + new Vector3(0f, 0.1f, 0f) * GetResolutionOffset(), + GetString("Tip.PleaseEnterFriendCode"), + 1f + ); + Info.name = "Enter Friend Code Description"; + } + + private static void CreateInputField() + { + EnterBox = UiHelper.CreateInputField( + Window.transform, + new Vector3(0f, -0.04f, 0f) * GetResolutionOffset(), + true + ); + EnterBox.name = "Enter Friend Code Box"; + + var enterBoxTBT = EnterBox.GetComponent(); + enterBoxTBT.AllowEmail = false; + enterBoxTBT.AllowSymbols = true; + enterBoxTBT.AllowPaste = true; + } + + private static void CreateConfirmButton() + { + ConfirmButton = UiHelper.CreateButton( + Window.transform.Find("Button Prefab").gameObject, + Window.transform, + new Vector3(0, -0.8f, 0f) * GetResolutionOffset(), + GetString(StringNames.Confirm), + GetResolutionOffset(), + false + ); + ConfirmButton.name = "Confirm Button"; + + ConfirmButton.GetComponent().OnClick.AddListener((Action)OnConfirmClicked); + } + + private static void OnConfirmClicked() + { + var input = EnterBox.GetComponent().text; + var code = NormalizeFriendCode(input); + var infoTmp = Info.GetComponent(); + + if (!FriendCodeRegex.IsMatch(code)) + { + ShowError(infoTmp, GetString("Tip.FriendCodeIncorrect")); + return; + } + + if (NameTagManager.AllNameTags.ContainsKey(code)) + { + ShowError(infoTmp, GetString("Tip.FriendCodeAlreadyExist")); + return; + } + + Window.SetActive(false); + NameTagEditMenu.Toggle(code, true); + } + + private static string NormalizeFriendCode(string input) + { + return input.ToLower() + .Replace("-", "#") + .Replace("—", "#") + .Replace(" ", string.Empty) + .Trim(); + } + + private static void ShowError(TextMeshPro text, string message) + { + ConfirmButton.SetActive(false); + text.text = message; + text.color = message.Contains("Incorrect") ? Color.red : Color.blue; + + _ = new LateTask(() => + { + text.text = GetString("Tip.PleaseEnterFriendCode"); + text.color = Color.white; + ConfirmButton.SetActive(true); + }, 1.2f, "Reactivate Enter Box"); + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagPanel.cs b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagPanel.cs new file mode 100644 index 00000000..0126937d --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/NameTag/NameTagPanel.cs @@ -0,0 +1,193 @@ +using System; +using FinalSuspect.Helpers; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using static FinalSuspect.ClientActions.FeatureItems.NameTag.NameTagManager; +using Object = UnityEngine.Object; + +namespace FinalSuspect.ClientActions.FeatureItems.NameTag; + +public static class NameTagPanel +{ + private static int numItems; + private static ToggleButtonBehaviour ButtonTemplate; + public static SpriteRenderer CustomBackground { get; set; } + public static List Items { get; private set; } = []; + public static int CurrentPage { get; private set; } = 1; + public static int ItemsPerPage => 8; + + public static int TotalPageCount => + (AllNameTags.Count(kv => !kv.Value.Isinternal) + ItemsPerPage - 1) / ItemsPerPage; + + public static void Hide() + { + CustomBackground?.gameObject.SetActive(false); + } + + public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) + { + if (CustomBackground != null) return; + + var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + CustomBackground = CreateBackground(optionsMenuBehaviour); + ButtonTemplate = mouseMoveToggle; + + CreateCloseButton(mouseMoveToggle); + CreateNewButton(mouseMoveToggle); + CreateHelpText(optionsMenuBehaviour); + CreatePageNavigationButtons(mouseMoveToggle); + + ReloadTag(null); + RefreshTagList(); + } + + private static SpriteRenderer CreateBackground(OptionsMenuBehaviour options) + { + var bg = Object.Instantiate(options.Background, options.transform); + bg.name = "Name Tag Panel Background"; + bg.transform.localScale = new Vector3(0.9f, 0.9f, 1f); + bg.transform.localPosition += Vector3.back * 18; + bg.gameObject.SetActive(false); + return bg; + } + + private static void CreateCloseButton(ToggleButtonBehaviour template) + { + var closeButton = Object.Instantiate(template, CustomBackground.transform); + closeButton.transform.localPosition = new Vector3(1.3f, -2.43f, -6f); + closeButton.name = "Close"; + closeButton.Text.text = GetString("Close"); + closeButton.Background.color = Color.red; + + var closePassiveButton = closeButton.GetComponent(); + closePassiveButton.OnClick = new Button.ButtonClickedEvent(); + closePassiveButton.OnClick.AddListener(new Action(() => CustomBackground.gameObject.SetActive(false))); + } + + private static void CreateNewButton(ToggleButtonBehaviour template) + { + var newButton = Object.Instantiate(template, CustomBackground.transform); + newButton.transform.localPosition = new Vector3(1.3f, -1.88f, -6f); + newButton.name = "New Tag"; + newButton.Text.text = GetString("NameTag.NewNameTag"); + newButton.Background.color = IsNotJoined ? Palette.White : Palette.DisabledGrey; + + var newPassiveButton = newButton.GetComponent(); + newPassiveButton.OnClick = new Button.ButtonClickedEvent(); + newPassiveButton.OnClick.AddListener(new Action(NameTagNewWindow.Open)); + newPassiveButton.enabled = IsNotJoined; + } + + private static void CreateHelpText(OptionsMenuBehaviour optionsMenuBehaviour) + { + var helpText = Object.Instantiate( + optionsMenuBehaviour.DisableMouseMovement.Text, + CustomBackground.transform + ); + + helpText.name = "Help Text"; + helpText.transform.localPosition = new Vector3(-1.25f, -2.15f, -5f); + helpText.transform.localScale = new Vector3(1f, 1f, 1f); + + var helpTextTMP = helpText.GetComponent(); + helpTextTMP.text = GetString("Tip.CustomNameTagHelp"); + helpText.gameObject.GetComponent().sizeDelta = new Vector2(2.45f, 1f); + } + + private static void CreatePageNavigationButtons(ToggleButtonBehaviour template) + { + var prevButton = Object.Instantiate(template, CustomBackground.transform); + prevButton.transform.localPosition = new Vector3(-1.3f, -1.33f, -6f); + prevButton.name = "PreviousPageButton"; + prevButton.Text.text = GetString("PreviousPage"); + prevButton.Background.color = Color.white; + + var prevPassiveButton = prevButton.GetComponent(); + prevPassiveButton.OnClick = new Button.ButtonClickedEvent(); + prevPassiveButton.OnClick.AddListener(new Action(() => + { + CurrentPage = CurrentPage - 1 <= 0 ? TotalPageCount : CurrentPage - 1; + RefreshTagList(); + })); + + // 下一页按钮 + var nextButton = Object.Instantiate(template, CustomBackground.transform); + nextButton.transform.localPosition = new Vector3(1.3f, -1.33f, -6f); + nextButton.name = "NextPageButton"; + nextButton.Text.text = GetString("NextPage"); + nextButton.Background.color = Color.white; + + var nextPassiveButton = nextButton.GetComponent(); + nextPassiveButton.OnClick = new Button.ButtonClickedEvent(); + nextPassiveButton.OnClick.AddListener(new Action(() => + { + CurrentPage = CurrentPage % TotalPageCount + 1; + RefreshTagList(); + })); + } + + public static void RefreshTagList() + { + try + { + Items?.Do(Object.Destroy); + Items = []; + numItems = 0; + + var startIndex = (CurrentPage - 1) * ItemsPerPage; + var count = 0; + + foreach (var nameTag in AllNameTags) + { + if (nameTag.Value.Isinternal) continue; + if (count++ < startIndex) continue; + if (numItems >= ItemsPerPage) break; + + CreateTagItem(nameTag.Key, nameTag.Value); + numItems++; + } + } + catch (Exception ex) + { + Error($"RefreshTagList failed: {ex}", "NameTagPanel"); + } + } + + private static void CreateTagItem(string key, NameTagManager.NameTag value) + { + if (ButtonTemplate == null || CustomBackground == null) return; + + var posY = 2.2f - 0.5f * numItems; + + var button = Object.Instantiate(ButtonTemplate, CustomBackground.transform); + button.transform.localPosition = new Vector3(-1.3f, posY, -4f); + button.name = "Btn-" + key; + button.Text.text = key; + button.Background.color = + IsNotJoined + ? ColorHelper.FSClientFeatureColor + : ColorHelper.FSClientFeatureColor_CanNotUse; + + var passiveButton = button.GetComponent(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.OnClick.AddListener(new Action(() => NameTagEditMenu.Toggle(key, true))); + passiveButton.enabled = IsNotJoined; + + var previewText = Object.Instantiate( + button.Text.gameObject, + CustomBackground.transform + ); + + previewText.name = "PreText-" + key; + previewText.transform.localPosition = new Vector3(1.3f, posY, -6f); + + var previewTMP = previewText.GetComponent(); + previewTMP.text = value.Apply(null, true).title ?? GetString("PreviewNotAvailable"); + previewTMP.fontSize = 1.2f; + previewTMP.alignment = TextAlignmentOptions.Center; + + Items.Add(button.gameObject); + Items.Add(previewText); + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/NameTag/UiHelper.cs b/FinalSuspect/ClientActions/FeatureItems/NameTag/UiHelper.cs new file mode 100644 index 00000000..f1b3cb61 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/NameTag/UiHelper.cs @@ -0,0 +1,146 @@ +using System; +using FinalSuspect.Helpers; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using Object = UnityEngine.Object; + +namespace FinalSuspect.ClientActions.FeatureItems.NameTag; + +public static class UiHelper +{ + private static Transform TempCB; + + public static GameObject CreateBaseWindow(string name, Transform parent, float zOffset, float scale) + { + var template = AccountManager.Instance.transform.Find("InfoTextBox").gameObject; + var window = Object.Instantiate(template, parent); + window.name = name; + window.transform.localPosition += Vector3.forward * zOffset; + + var offset = GetResolutionOffset(); + var background = window.transform.Find("Background"); + if (background != null) + background.localScale = background.localScale * scale * offset; + + // 移除不需要的按钮 + var button2 = window.transform.Find("Button2"); + if (button2 != null) Object.Destroy(button2.gameObject); + + // 设置预制体的名字 + var title = window.transform.Find("TitleText_TMP"); + if (title != null) title.gameObject.name = "Title Prefab"; + var info = window.transform.Find("InfoText_TMP"); + if (info != null) info.gameObject.name = "Info Prefab"; + var button1 = window.transform.Find("Button1"); + if (button1 != null) button1.gameObject.name = "Button Prefab"; + + return window; + } + + public static GameObject CreateCloseButton(Transform parent, Action onClick) + { + var template = parent.parent.Find("CloseButton"); + if (template != null) + TempCB = template; + template ??= TempCB; + if (template == null) return null; + + var button = Object.Instantiate(template, parent).gameObject; + button.name = "CloseButton"; + button.transform.localScale = Vector3.one; + + var passiveButton = button.GetComponent(); + if (passiveButton != null) + { + passiveButton.OnClick.RemoveAllListeners(); + passiveButton.OnClick.AddListener(new Action(() => onClick?.Invoke())); + } + + return button; + } + + public static GameObject CreateButton(GameObject template, Transform parent, Vector3 position, string text, + float scale, bool getString = true) + { + var button = Object.Instantiate(template, parent); + button.name = $"Button_{text}"; + + button.transform.localPosition = position; + button.transform.localScale = new Vector3(scale, scale, 1f); + + var passiveButton = button.GetComponent(); + if (passiveButton != null) + passiveButton.OnClick = new Button.ButtonClickedEvent(); + + var textComp = button.transform.Find("Text_TMP")?.GetComponent(); + if (textComp == null) return button; + if (getString) + text = "NameTag." + text; + textComp.text = getString ? GetString(text) : text; + if (EnumHelper.GetAllNames().Contains(text.Replace("NameTag.", "")) && + text.Replace("NameTag.", "") != "DisplayName") + { + textComp.text += $"({GetString("Disable")})"; + passiveButton.enabled = false; + } + + return button; + } + + public static GameObject CreateText(GameObject template, Transform parent, Vector3 position, string text, + float fontSize) + { + var textObj = Object.Instantiate(template, parent); + textObj.name = $"Text_{text.Substring(0, Mathf.Min(10, text.Length))}"; + + textObj.transform.localPosition = position; + + var textComp = textObj.GetComponent(); + if (textComp != null) + { + textComp.text = text; + textComp.fontSize = fontSize; + } + + return textObj; + } + + public static GameObject CreateInputField(Transform parent, Vector3 position, bool allowSymbols) + { + var template = AccountManager.Instance.transform + .Find("PremissionRequestWindow/GuardianEmailConfirm").gameObject; + if (template == null) return null; + + var input = Object.Instantiate(template, parent); + input.name = "InputField"; + + input.transform.localPosition = position; + input.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f); + + var textBox = input.GetComponent(); + if (textBox != null) + { + textBox.AllowPaste = true; + textBox.allowAllCharacters = true; + textBox.AllowEmail = true; + textBox.AllowSymbols = allowSymbols; + } + + var emailBehaviour = input.GetComponent(); + if (emailBehaviour != null) + Object.Destroy(emailBehaviour); + + return input; + } + + public static void HideTemplateObjects(Transform parent) + { + var templates = new[] { "Title Prefab", "Info Prefab", "Button Prefab", "Enter Box Prefab" }; + foreach (var name in templates) + { + var obj = parent.Find(name); + if (obj != null) obj.gameObject.SetActive(false); + } + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/Resources/ResourcesManager.cs b/FinalSuspect/ClientActions/FeatureItems/Resources/ResourcesManager.cs new file mode 100644 index 00000000..6d15c84a --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/Resources/ResourcesManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using FinalSuspect.Helpers; + +namespace FinalSuspect.ClientActions.FeatureItems.Resources; + +public static class ResourcesManager +{ + public static readonly Dictionary> AllResources = new(); + + public static async Task CheckForResources() + { + foreach (var url in GetInfoFileUrlList(true)) + { + var task = GetAllResources(url + "fs_resources.json"); + await task; + if (!task.Result) continue; + break; + } + } + + private static async Task GetAllResources(string url) + { + try + { + var task = JsonHelper.GetJsonStringAsync(url); + await task; + var (result, succeed) = task.Result; + if (!succeed) return false; + + var data = JsonSerializer.Deserialize>>(result); + foreach (var kvp in data) AllResources.Add(kvp.Key, kvp.Value); + + return true; + } + catch (Exception ex) + { + Error($"Exception: {ex.Message}", "Check Resources"); + return false; + } + } +} \ No newline at end of file diff --git a/FinalSuspect/ClientActions/FeatureItems/Resources/ResourcesPanel.cs b/FinalSuspect/ClientActions/FeatureItems/Resources/ResourcesPanel.cs new file mode 100644 index 00000000..847038b0 --- /dev/null +++ b/FinalSuspect/ClientActions/FeatureItems/Resources/ResourcesPanel.cs @@ -0,0 +1,228 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Features; +using FinalSuspect.Modules.Resources; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using static FinalSuspect.ClientActions.FeatureItems.Resources.ResourcesManager; +using Object = UnityEngine.Object; + +namespace FinalSuspect.ClientActions.FeatureItems.Resources; + +public static class ResourcesPanel +{ + private static int numItems; + + private static readonly Dictionary PackageStates = new(); + public static SpriteRenderer CustomBackground { get; set; } + private static GameObject Slider { get; set; } + private static Dictionary Items { get; set; } + + public static void Hide() + { + if (CustomBackground != null) + CustomBackground?.gameObject.SetActive(false); + } + + public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) + { + var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + if (!IsNotJoined) return; + + if (CustomBackground == null) + { + numItems = 0; + CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); + CustomBackground.name = "Resource Manager"; + CustomBackground.transform.localScale = new Vector3(0.9f, 0.9f, 1f); + CustomBackground.transform.localPosition += Vector3.back * 18; + CustomBackground.gameObject.SetActive(false); + + var closeButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); + closeButton.transform.localPosition = new Vector3(1.3f, -2.43f, -16f); + closeButton.name = "Close"; + closeButton.Text.text = GetString("Close"); + closeButton.Background.color = Color.red; + var closePassiveButton = closeButton.GetComponent(); + closePassiveButton.OnClick = new Button.ButtonClickedEvent(); + closePassiveButton.OnClick.AddListener(new Action(() => { CustomBackground.gameObject.SetActive(false); })); + + if (CustomPopup.InfoTMP != null) + { + var helpText = Object.Instantiate(CustomPopup.InfoTMP.gameObject, CustomBackground.transform); + helpText.name = "Help Text"; + helpText.transform.localPosition = new Vector3(-1.25f, -2.15f, -15f); + helpText.transform.localScale = new Vector3(1f, 1f, 1f); + var helpTextTMP = helpText.GetComponent(); + helpTextTMP.text = GetString("Tip.ResourceManager"); + helpText.gameObject.GetComponent().sizeDelta = new Vector2(2.45f, 1f); + } + + var sliderTemplate = AccountManager.Instance.transform + .FindChild("MainSignInWindow/SignIn/AccountsMenu/Accounts/Slider").gameObject; + if (sliderTemplate != null && Slider == null) + { + Slider = Object.Instantiate(sliderTemplate, CustomBackground.transform); + Slider.name = "Slider"; + Slider.transform.localPosition = new Vector3(0f, 0.5f, -11f); + Slider.transform.localScale = new Vector3(1f, 1f, 1f); + Slider.GetComponent().size = new Vector2(5f, 4f); + var scroller = Slider.GetComponent(); + scroller.ScrollWheelSpeed = 0.3f; + var mask = Slider.transform.FindChild("Mask"); + mask.transform.localScale = new Vector3(4.9f, 3.92f, 1f); + } + } + + RefreshTagList(); + } + + public static void RefreshTagList() + { + if (!IsNotJoined) return; + numItems = 0; + var scroller = Slider.GetComponent(); + scroller.Inner.gameObject.ForEachChild((Action)DestroyObj); + + var numberSetter = AccountManager.Instance.transform.FindChild("DOBEnterScreen/EnterAgePage/MonthMenu/Months") + .GetComponent(); + var buttonPrefab = numberSetter.ButtonPrefab.gameObject; + + Items?.Values.Do(Object.Destroy); + Items = new Dictionary(); + + foreach (var (packageName, fileList) in AllResources) + { + PackageStates.TryAdd(packageName, CurrentState.None); + numItems++; + + var button = Object.Instantiate(buttonPrefab, scroller.Inner); + button.transform.localPosition = new Vector3(-1f, 1.6f - 0.6f * numItems, -10.5f); + button.transform.localScale = new Vector3(1.2f, 1.2f, 1.2f); + button.name = "Btn-" + packageName; + + var renderer = button.GetComponent(); + var rollover = button.GetComponent(); + + var previewText = + Object.Instantiate(button.transform.GetChild(0).GetComponent(), button.transform); + previewText.transform.SetLocalX(1.9f); + previewText.fontSize = 1f; + previewText.name = "PreText-" + packageName; + + Object.Destroy(button.GetComponent()); + Object.Destroy(button.GetComponent()); + + string buttonText; + Color buttonColor; + var enable = true; + var preview = $"{GetString($"Package.{packageName}")}"; + + if (fileList.All(name => + { + var type = GetType(name); + var path = GetLocalFilePath(type, name); + return File.Exists(path); + })) + PackageStates[packageName] = CurrentState.Complete; + + var state = PackageStates[packageName]; + switch (state) + { + case CurrentState.IsDownloading: + buttonText = GetString("Tip.Downloading"); + buttonColor = ColorHelper.DownloadYellow; + enable = false; + break; + case CurrentState.DownLoadSucceeded: + case CurrentState.DownLoadFailed: + var succeed = state == CurrentState.DownLoadSucceeded; + buttonText = GetString($"Tip.{state}"); + buttonColor = succeed ? Color.cyan : Palette.Brown; + enable = false; + break; + case CurrentState.Complete: + buttonText = GetString("Tip.PackageExists"); + buttonColor = ColorHelper.CompleteGreen; + enable = false; + break; + default: + buttonText = GetString("Download"); + buttonColor = Color.green; + break; + } + + var passiveButton = button.GetComponent(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.OnClick.AddListener(new Action(() => + { + PackageStates[packageName] = CurrentState.IsDownloading; + RefreshTagList(); + + var downloadTasks = (from fileName in fileList + let type = GetType(fileName) + select ResourcesDownloader.StartDownloadAsPackage(packageName, type, fileName)).ToList(); + var allSucceeded = false; + Task.Run(async () => + { + try + { + var results = await Task.WhenAll(downloadTasks); + allSucceeded = results.All(success => success); + } + finally + { + PackageStates[packageName] = + allSucceeded ? CurrentState.DownLoadSucceeded : CurrentState.DownLoadFailed; + _ = new MainThreadTask(RefreshTagList, "Notice"); + _ = new LateTask(() => + { + PackageStates[packageName] = CurrentState.None; + RefreshTagList(); + MyMusicPanel.RefreshTagList(); + }, 3F, "Refresh Tag List"); + } + }); + })); + + button.transform.GetChild(0).GetComponent().text = buttonText; + rollover.OutColor = renderer.color = buttonColor; + button.GetComponent().enabled = enable; + previewText.text = preview; + Items.Add(packageName, button); + } + + scroller.SetYBoundsMin(0f); + scroller.SetYBoundsMax(0.6f * numItems); + return; + + static void DestroyObj(GameObject obj) + { + if (obj.name.StartsWith("AccountButton")) Object.Destroy(obj); + } + + static FileType GetType(string fileName) + { + var type = Path.GetExtension(fileName) switch + { + ".jpg" or ".png" => FileType.Images, + ".wav" or ".zip" => FileType.Musics, + _ => FileType.Unknown + }; + return type; + } + } + + private enum CurrentState + { + None, + IsDownloading, + DownLoadSucceeded, + DownLoadFailed, + Complete + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Core/DllChecker.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Core/DllChecker.cs new file mode 100644 index 00000000..777f557d --- /dev/null +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Core/DllChecker.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using BepInEx; +using BepInEx.Unity.IL2CPP; +using UnityEngine; + +namespace FinalSuspect.DataHandling.FinalAntiCheat.Core; + +internal static class DllChecker +{ + internal static void Init() + { + // SM的文件名是写死的 + string[] SuspiciousFiles = ["SickoMenu.dll", "version.dll"]; + // 获取当前Dll启动目录 + var DirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + // 获取游戏根目录 + var AmongUsPath = Environment.CurrentDirectory; + // 针对基于BepInEx注入检测 + if (DirectoryPath != null) + foreach (var path in Directory.EnumerateFiles(DirectoryPath, "*.*")) + { + var fileName = Path.GetFileName(path); + + if (fileName is "FinalSuspect.dll" or "PolarNight.dll") continue; + Error($"检测到非法/模组文件: {fileName}!游戏将被强制终止。", "FAC"); + Application.Quit(1); + } + + // 针对基于version注入检测 + foreach (var fileName in SuspiciousFiles) + { + var fullPath = Path.Combine(AmongUsPath, fileName); + + if (!File.Exists(fullPath)) continue; + Error($"检测到非法文件: {fileName}!游戏将被强制终止。", "FAC"); + Application.Quit(1); + } + } +} + +[HarmonyPatch(typeof(IL2CPPChainloader), nameof(IL2CPPChainloader.LoadPlugin))] +public static class DisableOtherPlugins +{ + public static bool Prefix([HarmonyArgument(0)] PluginInfo pluginInfo, [HarmonyArgument(1)] Assembly pluginAssembly) + { + return pluginInfo.Metadata.GUID + is "com.sinai.unityexplorer" + or "cn.slok.polarnight"; + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Core/FAC.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Core/FAC.cs index 17eae4ca..a8d82bec 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Core/FAC.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Core/FAC.cs @@ -1,44 +1,42 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; -using Hazel; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Patches.Game_Vanilla; +using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Core; public static class FAC { - public static int DeNum; + private static int DeNum; public static long _lastHandleCheater = -1; - private static List LobbyDeadBodies = []; - private static readonly List _handlers = new(); + public static readonly List _handlers = []; static FAC() { - foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(IRpcHandler).IsAssignableFrom(t) && !t.IsAbstract)) + foreach (var type in Assembly.GetExecutingAssembly().GetTypes() + .Where(t => typeof(IRpcHandler).IsAssignableFrom(t) && !t.IsAbstract)) { var handler = (IRpcHandler)Activator.CreateInstance(type); + if (handler == null) continue; var rpcTypes = handler.TargetRpcs; var activehandler = new RpcHandlers(rpcTypes); activehandler.Handlers.Add(handler); - + _handlers.Add(activehandler); } } - public static void Init() + public static void Init_FAC() { DeNum = 0; - LobbyDeadBodies = []; } public static void WarnHost(int denum = 1) { DeNum += denum; - if (ErrorText.Instance == null) return; + if (!ErrorText.Instance) return; ErrorText.Instance.CheatDetected = DeNum > 3; ErrorText.Instance.SBDetected = DeNum > 10; if (ErrorText.Instance.CheatDetected) @@ -47,74 +45,89 @@ public static void WarnHost(int denum = 1) ErrorText.Instance.Clear(); } - public static bool ReceiveRpc(PlayerControl pc, byte callId, MessageReader reader, out bool notify, out string reason, out bool ban) + public static bool ReceiveRpc(PlayerControl pc, byte callId, MessageReader reader, out bool notify, + out string reason, out bool ban) { notify = true; reason = "Hacking"; ban = false; - - if (Main.DisableFAC.Value || pc == null || reader == null || pc.AmOwner) + + if (!Main.EnableFAC.Value || !pc || reader == null || pc.AmOwner) return false; try { if (pc.GetCheatData().HandleIncomingRpc(callId)) { + NotificationPopperPatch.NotificationPop(AmongUsClient.Instance.AmHost + ? string.Format(GetString("CheatDetected.Overload"), pc.GetColoredName()) + : string.Format(GetString("CheatDetected.Overload_NotHost"), pc.GetColoredName())); ban = true; + notify = false; return true; } var sr = MessageReader.Get(reader); - var handlers = _handlers.FirstOrDefault(x => x.TargetRpcs.Contains(callId))?.Handlers; - - if (handlers != null) - foreach (var handler in handlers) + foreach (var handler in _handlers.Where(handlers => handlers.TargetRpcs.Contains(callId)) + .SelectMany(handlers => handlers.Handlers)) + { + if (!Enum.IsDefined(typeof(RpcCalls), callId)) { - if (!Enum.IsDefined(typeof(RpcCalls), callId)) + notify = false; + if (handler.HandleInvalidRPC(pc, sr, ref notify, ref reason, ref ban)) { - notify = false; - if (handler.HandleInvalidRPC(pc, sr, ref notify, ref reason, ref ban)) - { - ban = true; - if (reason == "Hacking") - reason = GetString("Unknown"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("FAC.CheatDetected.UsingCheat"), pc.GetDataName(), reason)); - return true; - } - NotificationPopperPatch.NotificationPop(string.Format(GetString("FAC.CheatDetected.MayUseCheat"), pc.GetDataName(), reason)); - return false; - } - if (handler.HandleAll(pc, sr, ref notify, ref reason, ref ban)) - return true; - if (IsLobby && handler.HandleLobby(pc, sr, ref notify, ref reason, ref ban)) - { - if (AmongUsClient.Instance.AmHost) return true; - NotificationPopperPatch.NotificationPop(GetString("Warning.RoomBroken")); - notify = false; + ban = true; + if (reason == "Hacking") + reason = GetString("Unknown"); + NotificationPopperPatch.NotificationPop( + string.Format(GetString("CheatDetected.UseCheat"), + pc.GetColoredName(), + reason)); return true; } - return (IsInGame && handler.HandleGame_All(pc, sr, ref notify, ref reason, ref ban)) - || (IsInTask && handler.HandleGame_InTask(pc, sr, ref notify, ref reason, ref ban)) - || (IsMeeting && handler.HandleGame_InMeeting(pc, sr, ref notify, ref reason, ref ban)); + + if (reason == "Hacking") + reason = GetString("Unknown"); + NotificationPopperPatch.NotificationPop( + string.Format(GetString("CheatDetected.MayUseCheat"), + pc.GetColoredName(), + reason)); + return false; } + + if (handler.HandleAll(pc, sr, ref notify, ref reason, ref ban)) + return true; + + if (IsLobby && handler.HandleLobby(pc, sr, ref notify, ref reason, ref ban)) + { + if (AmongUsClient.Instance.AmHost) return true; + NotificationPopperPatch.NotificationPop(GetString("Warning.RoomBroken")); + notify = false; + return true; + } + + return (IsInGame && handler.HandleGame_All(pc, sr, ref notify, ref reason, ref ban)) + || (IsInTask && handler.HandleGame_InTask(pc, sr, ref notify, ref reason, ref ban)) + || (IsInMeeting && handler.HandleGame_InMeeting(pc, sr, ref notify, ref reason, ref ban)); + } } catch (Exception e) { Fatal(e.ToString(), "FAC"); } + WarnHost(-1); return false; } - public static void HandleCheat(PlayerControl pc, string text) => - NotificationPopperPatch.NotificationPop(string.Format(text, pc.GetDataName())); + public static void HandleCheat(PlayerControl pc, string text) + { + NotificationPopperPatch.NotificationPop(string.Format(text, pc.GetColoredName())); + } public static void Dispose(byte id) { - foreach (var handler in _handlers) - { - handler.Handlers.Do(x => x.Dispose(id)); - } + foreach (var handler in _handlers) handler.Handlers.Do(x => x.Dispose(id)); } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Core/Git.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Core/Git.cs new file mode 100644 index 00000000..fe34cb08 --- /dev/null +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Core/Git.cs @@ -0,0 +1,21 @@ +using System.IO; +using UnityEngine; + +[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] +// ReSharper disable once CheckNamespace +internal static class Git +{ + public static void Prefix(MainMenuManager __instance) + { + // 获取当前Dll启动目录 + var DirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + // 针对基于BepInEx注入检测 + if (DirectoryPath == null) return; + foreach (var path in Directory.EnumerateFiles(DirectoryPath, "*.*")) + { + var fileName = Path.GetFileName(path); + + if (fileName is not "FinalSuspect.dll" and not "PolarNight.dll") Application.Quit(1); + } + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Core/PlayerCheatData.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Core/PlayerCheatData.cs index 3b4eaac0..839db89b 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Core/PlayerCheatData.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Core/PlayerCheatData.cs @@ -1,31 +1,59 @@ using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Modules.Features.CheckingandBlocking; using FinalSuspect.Patches.Game_Vanilla; using InnerNet; namespace FinalSuspect.DataHandling.FinalAntiCheat.Core; -public class PlayerCheatData +public class PlayerCheatData : IDisposable { - public bool IsSuspectCheater { get; private set; } - public ClientData ClientData { get; } - public string FriendCode => ClientData.FriendCode; - public string Puid => ClientData.GetHashedPuid(); - private readonly PlayerControl _player; + private readonly Dictionary _rpcRecords = new(); + public PlayerCheatData(PlayerControl player) { _player = player; ClientData = _player.GetClient(); } - public void MarkAsCheater() => IsSuspectCheater = true; + public bool IsSuspectCheater { get; private set; } + public bool IsHacker { get; private set; } + public ClientData ClientData { get; private set; } + public string FriendCode => ClientData?.FriendCode ?? string.Empty; + public string Puid => ClientData?.GetHashedPuid() ?? string.Empty; + public bool InComingOverloaded { get; private set; } + + public void Dispose() + { + IsSuspectCheater = false; + ClientData = null; + InComingOverloaded = false; + _rpcRecords.Clear(); + } + + public void MarkAsCheater() + { + if (IsSuspectCheater) return; + IsSuspectCheater = true; + Warn($"Suspect Cheater: {_player.GetFinalData().Name}," + + $"FriendCode: {FriendCode}," + + $"Puid: {Puid},", + "FAC"); + } + + public void MarkAsHacker() + { + if (IsHacker) return; + IsHacker = true; + Warn($"Overload Hacker: {_player.GetFinalData().Name}," + + $"FriendCode: {FriendCode}," + + $"Puid: {Puid},", + "FAC"); + } - public void HandleLobbyPosition() + private void HandleLobbyPosition() { if (!IsLobby) return; var pos = _player.GetTruePosition(); @@ -33,71 +61,70 @@ public void HandleLobbyPosition() MarkAsCheater(); } - public void HandleBan() + private void HandleBan() { if (ClientData.IsFACPlayer() || ClientData.IsBannedPlayer()) MarkAsCheater(); } - - public void HandleSuspectCheater() + + private void HandleSuspectCheater() { - if (Main.DisableFAC.Value || !IsSuspectCheater || _lastHandleCheater != -1 && _lastHandleCheater + 1 >= GetTimeStamp()) return; + if (!Main.EnableFAC.Value || !IsSuspectCheater || + (_lastHandleCheater != -1 && _lastHandleCheater + 1 >= GetTimeStamp())) return; _lastHandleCheater = GetTimeStamp(); if (!AmongUsClient.Instance.AmHost) { - NotificationPopperPatch.NotificationPop(string.Format(GetString("Warning.Cheater_NotHost"), - _player.GetDataName())); + NotificationPopperPatch.NotificationPop(string.Format(GetString("CheatDetected.Cheater_NotHost"), + _player.GetColoredName())); return; } - - NotificationPopperPatch.NotificationPop(string.Format(GetString("Warning.Cheater"), - _player.GetDataName())); - KickPlayer(_player.PlayerId, false, "Suspect Cheater"); - } - - private static readonly Regex ValidFormatRegex = new( - @"^[A-Za-z]+#\d{4}$", - RegexOptions.Compiled - ); - private void HandleFriendCode() - { - if (!ValidFormatRegex.IsMatch(FriendCode) && Main.KickPlayerWhoFriendCodeNotExist.Value) - MarkAsCheater(); + KickPlayer(_player.PlayerId, false, "Cheater"); } - - private readonly Dictionary _rpcRecords = new(); - - private struct RpcRecord + + private void HandleHacker() { - public long LastReceivedTime; - public int Count; + if (!Main.EnableGuardian.Value || !IsHacker || + (_lastHandleCheater != -1 && _lastHandleCheater + 1 >= GetTimeStamp())) return; + _lastHandleCheater = GetTimeStamp(); + if (!AmongUsClient.Instance.AmHost) + { + NotificationPopperPatch.NotificationPop(string.Format(GetString("CheatDetected.Overload_NotHost"), + _player.GetColoredName())); + return; + } + + KickPlayer(_player.PlayerId, false, "Overload"); } - + public bool HandleIncomingRpc(byte rpcId) { + if (InComingOverloaded) return true; var currentTime = GetCurrentTimestamp(); - + if (_rpcRecords.TryGetValue(rpcId, out var record)) { var timeDiff = currentTime - record.LastReceivedTime; - + if (timeDiff > 1000) { record.Count = 1; + record.LastReceivedTime = currentTime; } else { record.Count++; - - if (record.Count > 10) + + if (record.Count > record.MaxiCount) { MarkAsCheater(); record.Count = 0; + InComingOverloaded = true; + Warn($"InComingRpc Overloaded: {_player.GetDataName()}", "FAC"); return true; } } - record.LastReceivedTime = currentTime; + _rpcRecords[rpcId] = record; } else @@ -105,28 +132,39 @@ public bool HandleIncomingRpc(byte rpcId) _rpcRecords[rpcId] = new RpcRecord { LastReceivedTime = currentTime, - Count = 1 + Count = 1, + MaxiCount = _handlers.Where(handlers => handlers.TargetRpcs.Contains(rpcId)) + .SelectMany(handlers => handlers.Handlers) + .Where(handler => handler.Condition(_player)) + .Select(handler => handler.MaxiReceivedNumPerSecond()) + .Prepend(5) + .Max() }; } + return false; } - - private static long GetCurrentTimestamp() - { - return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } public void HandleCheatData() { try { + ClientData ??= _player.GetClient(); HandleBan(); HandleLobbyPosition(); HandleSuspectCheater(); + HandleHacker(); } - catch + catch { /* ignored */ } } + + private struct RpcRecord + { + public long LastReceivedTime; + public int Count; + public int MaxiCount; + } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Core/RpcHandlers.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Core/RpcHandlers.cs index 82d6b6e1..941e47fe 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Core/RpcHandlers.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Core/RpcHandlers.cs @@ -1,10 +1,9 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; namespace FinalSuspect.DataHandling.FinalAntiCheat.Core; public class RpcHandlers(List targetRpcs) { - public readonly List TargetRpcs = targetRpcs; public readonly List Handlers = []; + public readonly List TargetRpcs = targetRpcs; } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/AmongUsMenu.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/AmongUsMenu.cs index e0f8ba68..8cc2f42c 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/AmongUsMenu.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/AmongUsMenu.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,7 +10,7 @@ public class AmongUsMenu : IRpcHandler unchecked((byte)42069), 101 ]; - + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/BetterAmongUs.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/BetterAmongUs.cs index fdb4cb87..cf722ce2 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/BetterAmongUs.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/BetterAmongUs.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Invalid; -public class BetterAmongUs: IRpcHandler +public class BetterAmongUs : IRpcHandler { public List TargetRpcs => [ - 150, - 151 + 151, + 152 ]; - + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/HostGuard.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/HostGuard.cs new file mode 100644 index 00000000..c142f674 --- /dev/null +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/HostGuard.cs @@ -0,0 +1,19 @@ +using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using Hazel; + +namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Invalid; + +public class HostGuard : IRpcHandler +{ + public List TargetRpcs => + [ + 176 + ]; + + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, + ref bool notify, ref string reason, ref bool ban) + { + reason = "HostGuard"; + return false; + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/KillNetWork.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/KillNetWork.cs index 215c6d0c..6088d567 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/KillNetWork.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/KillNetWork.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Invalid; + public class KillNetWork : IRpcHandler { public List TargetRpcs => @@ -10,7 +10,7 @@ public class KillNetWork : IRpcHandler 119, 250 ]; - + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/MalumMenu.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/MalumMenu.cs index 21c9e877..7f16b731 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/MalumMenu.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/MalumMenu.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -10,7 +9,7 @@ public class MalumMenu : IRpcHandler [ // Noting ]; - + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/NonalusAntiCheatMenu.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/NonalusAntiCheatMenu.cs new file mode 100644 index 00000000..baaadb43 --- /dev/null +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/NonalusAntiCheatMenu.cs @@ -0,0 +1,20 @@ +using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using Hazel; + +namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Invalid; + +public class NonalusAntiCheatMenu : IRpcHandler +{ + public List TargetRpcs => + [ + unchecked((byte)1337), + unchecked((byte)5537) + ]; + + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, + ref bool notify, ref string reason, ref bool ban) + { + reason = "NonalusAntiCheatMenu"; + return true; + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/SickoMenu.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/SickoMenu.cs index 4b7240eb..767f99f2 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/SickoMenu.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/SickoMenu.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,7 +10,7 @@ public class SickoMenu : IRpcHandler 168, unchecked((byte)420) ]; - + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/YuMenu.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/YuMenu.cs index 8f8b3d3d..ca8da83b 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/YuMenu.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Invalid/YuMenu.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -10,7 +9,7 @@ public class YuMenu : IRpcHandler [ unchecked((byte)520) ]; - + public bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ExiledHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ExiledHandler.cs index 09dc47bd..d01daf74 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ExiledHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ExiledHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,7 +10,7 @@ public class ExiledHandler : IRpcHandler [ (byte)RpcCalls.Exiled ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/HostRpcHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/HostRpcHandler.cs new file mode 100644 index 00000000..a2bff191 --- /dev/null +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/HostRpcHandler.cs @@ -0,0 +1,26 @@ +using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; + +// 6 +public class HostRpcHandler : IRpcHandler +{ + public List TargetRpcs => + [ + (byte)RpcCalls.SetName, + (byte)RpcCalls.SetTasks, + (byte)RpcCalls.SetStartCounter, + (byte)RpcCalls.SyncSettings + ]; + + public int MaxiReceivedNumPerSecond() + { + return 999; + } + + public bool Condition(PlayerControl player) + { + return player.IsHost(); + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/MurderPlayerHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/MurderPlayerHandler.cs index 3e7a4a6b..26504d7a 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/MurderPlayerHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/MurderPlayerHandler.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; +using InnerNet; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -11,13 +11,18 @@ public class MurderPlayerHandler : IRpcHandler public List TargetRpcs => [ (byte)RpcCalls.MurderPlayer, - (byte)RpcCalls.CheckMurder, + (byte)RpcCalls.CheckMurder ]; - public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, + public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { - return !sender.IsImpostor(); + var target = reader.ReadNetObject(); + var resultFlags = (MurderResultFlags)reader.ReadInt32(); + + return !sender.IsImpostor() + && sender != target + && (resultFlags != MurderResultFlags.DecisionByHost || !sender.IsHost()); } public bool HandleLobby(PlayerControl sender, MessageReader reader, diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/OutfitStrHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/OutfitStrHandler.cs index 37aef6dd..c5c79d09 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/OutfitStrHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/OutfitStrHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -13,9 +12,9 @@ public class OutfitStrHandler : IRpcHandler (byte)RpcCalls.SetSkinStr, (byte)RpcCalls.SetPetStr, (byte)RpcCalls.SetVisorStr, - (byte)RpcCalls.SetNamePlateStr, + (byte)RpcCalls.SetNamePlateStr ]; - + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PetHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PetHandler.cs index 4398c071..fe49358c 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PetHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PetHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -17,6 +16,11 @@ public class PetHandler : IRpcHandler public bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { - return !sender.cosmetics.HasPetEquipped() || reader.Length < 4; + return false; + } + + public int MaxiReceivedNumPerSecond() + { + return 20; } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PhantomHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PhantomHandler.cs index 4261764f..e2d9c02d 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PhantomHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/PhantomHandler.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using AmongUs.GameOptions; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -14,15 +13,15 @@ public class PhantomHandler : IRpcHandler (byte)RpcCalls.CheckVanish, (byte)RpcCalls.StartVanish, (byte)RpcCalls.CheckAppear, - (byte)RpcCalls.StartAppear, + (byte)RpcCalls.StartAppear ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ProtectHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ProtectHandler.cs index 148c47d3..e7092033 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ProtectHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ProtectHandler.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using FinalSuspect.DataHandling.FinalAntiCheat.Core; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,15 +9,15 @@ public class ProtectHandler : IRpcHandler public List TargetRpcs => [ (byte)RpcCalls.ProtectPlayer, - (byte)RpcCalls.CheckProtect, + (byte)RpcCalls.CheckProtect ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_InMeeting(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatHandler.cs index e7bff339..46bdd417 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatHandler.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -9,17 +9,17 @@ public class SendChatHandler : IRpcHandler { public List TargetRpcs => [ - (byte)RpcCalls.SendChat, + (byte)RpcCalls.SendChat ]; - public bool HandleAll(PlayerControl sender, MessageReader reader, + public bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { var text = reader.ReadString(); return text.Length > 100; } - - public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, + + public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return sender.IsAlive(); diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatNoteHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatNoteHandler.cs index 6a8ac21f..3e456747 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatNoteHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendChatNoteHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,13 +10,13 @@ public class SendChatNoteHandler : IRpcHandler [ (byte)RpcCalls.SendChatNote ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendQuickChatHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendQuickChatHandler.cs index a5f4caba..10a01595 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendQuickChatHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SendQuickChatHandler.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -9,20 +9,17 @@ namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; public class SendQuickChatHandler : IRpcHandler { private static readonly Dictionary _records = new(); - + public List TargetRpcs => [ - (byte)RpcCalls.SendQuickChat, + (byte)RpcCalls.SendQuickChat ]; - public bool HandleAll(PlayerControl sender, MessageReader reader, + public bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { var current = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - if (!_records.TryGetValue(sender.PlayerId, out var record)) - { - record = (current, 0); - } + if (!_records.TryGetValue(sender.PlayerId, out var record)) record = (current, 0); if (current - record.timestamp < 3) { @@ -30,14 +27,12 @@ record = (current, 0); if (record.count > 1) { if (AmongUsClient.Instance.AmHost) - { - HandleCheat(sender, GetString("Warning.SendQuickChat")); - } - else if (!OtherModHost) - { - HandleCheat(sender, GetString("Warning.SendQuickChat_NotHost")); - } - Warn($"{sender.GetDataName()}({sender.GetCheatData().FriendCode})({sender.GetCheatData().Puid})一秒内多次发送快捷消息", "FAC"); + HandleCheat(sender, GetString("CheatDetected.SendQuickChat")); + else if (!OtherModHost) HandleCheat(sender, GetString("CheatDetected.SendQuickChat_NotHost")); + + Warn( + $"{sender.GetDataName()}({sender.GetCheatData().FriendCode})({sender.GetCheatData().Puid}) 一秒内多次发送快捷消息", + "FAC"); ban = true; notify = false; return true; @@ -51,8 +46,8 @@ record = (current, 0); _records[sender.PlayerId] = record; return false; } - - public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, + + public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return sender.IsAlive(); diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetColorHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetColorHandler.cs index 2471563c..62613972 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetColorHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetColorHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -10,10 +9,10 @@ public class SetColorHandler : IRpcHandler public List TargetRpcs => [ (byte)RpcCalls.CheckColor, - (byte)RpcCalls.SetColor, + (byte)RpcCalls.SetColor ]; - public bool HandleGame_All(PlayerControl sender, MessageReader reader, + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetLevelHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetLevelHandler.cs index 962339b7..8ca17658 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetLevelHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetLevelHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,7 +10,7 @@ public class SetLevelHandler : IRpcHandler [ (byte)RpcCalls.SetLevel ]; - + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetNameHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetNameHandler.cs index cf3acfab..19b8300c 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetNameHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetNameHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -8,38 +7,39 @@ namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; public class SetNameHandler : IRpcHandler { private static readonly Dictionary _counters = new(); - + public List TargetRpcs => [ (byte)RpcCalls.CheckName, - (byte)RpcCalls.SetName, + (byte)RpcCalls.SetName ]; - public bool HandleAll(PlayerControl sender, MessageReader reader, + public bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { _counters.TryAdd(sender.PlayerId, 0); if (++_counters[sender.PlayerId] <= 3) return false; if (AmongUsClient.Instance.AmHost) { - HandleCheat(sender, GetString("Warning.SetName")); + HandleCheat(sender, GetString("CheatDetected.SetName")); WarnHost(); } else if (!OtherModHost) { - HandleCheat(sender, GetString("Warning.SetName_NotHost")); + HandleCheat(sender, GetString("CheatDetected.SetName_NotHost")); } + ban = true; notify = false; return true; } - - public bool HandleGame_All(PlayerControl sender, MessageReader reader, + + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public void Dispose(byte id) { _counters.Remove(id); diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetRoleHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetRoleHandler.cs index a3fc68da..861e5e09 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetRoleHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetRoleHandler.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; using AmongUs.GameOptions; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -12,16 +12,16 @@ public class SetRoleHandler : IRpcHandler [ (byte)RpcCalls.SetRole ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { - return sender.GetXtremeData().RoleAssgined && !IsGhost((RoleTypes)reader.ReadUInt16()); + return sender.GetFinalData().RoleAssigned && !IsGhost((RoleTypes)reader.ReadUInt16()); } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetScannerHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetScannerHandler.cs index 3ba85455..fdd204d0 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetScannerHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetScannerHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,13 +10,13 @@ public class SetScannerHandler : IRpcHandler [ (byte)RpcCalls.SetScanner ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_InMeeting(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetStartCounterHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetStartCounterHandler.cs index 51fe796d..310bd7ba 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetStartCounterHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SetStartCounterHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,10 +10,12 @@ public class SetStartCounterHandler : IRpcHandler [ (byte)RpcCalls.SetStartCounter ]; - + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } + + public int MaxiReceivedNumPerSecond() => 20; } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ShapeShifterHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ShapeShifterHandler.cs index 416bd2cc..ecf94b30 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ShapeShifterHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/ShapeShifterHandler.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using AmongUs.GameOptions; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -13,15 +12,15 @@ public class ShapeShifterHandler : IRpcHandler [ (byte)RpcCalls.Shapeshift, (byte)RpcCalls.CheckShapeshift, - (byte)RpcCalls.RejectShapeshift, + (byte)RpcCalls.RejectShapeshift ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SnapToHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SnapToHandler.cs new file mode 100644 index 00000000..f5d543c9 --- /dev/null +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SnapToHandler.cs @@ -0,0 +1,16 @@ +using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; + +namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; + +public class SnapToHandler : IRpcHandler +{ + public List TargetRpcs => + [ + (byte)RpcCalls.SnapTo + ]; + + public int MaxiReceivedNumPerSecond() + { + return 30; + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/StartMeetingHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/StartMeetingHandler.cs index c160cc07..baf23e3a 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/StartMeetingHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/StartMeetingHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -10,9 +9,9 @@ public class StartMeetingHandler : IRpcHandler public List TargetRpcs => [ (byte)RpcCalls.ReportDeadBody, - (byte)RpcCalls.StartMeeting, + (byte)RpcCalls.StartMeeting ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SystemHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SystemHandler.cs index f028cf21..14a27c49 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SystemHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/SystemHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -11,13 +10,13 @@ public class SystemHandler : IRpcHandler (byte)RpcCalls.CloseDoorsOfType, (byte)RpcCalls.UpdateSystem ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } - + public bool HandleGame_InMeeting(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/TaskHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/TaskHandler.cs index 52cfebef..d5fbf17e 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/TaskHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/TaskHandler.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -11,10 +11,22 @@ public class TaskHandler : IRpcHandler [ (byte)RpcCalls.CompleteTask ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { return true; } + + public bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return sender.IsImpostor(); + } + + public bool HandleGame_InMeeting(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return true; + } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VentHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VentHandler.cs index 987ee02d..ef31a127 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VentHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VentHandler.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using AmongUs.GameOptions; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Handlers.Valid; @@ -12,12 +11,14 @@ public class VentHandler : IRpcHandler public List TargetRpcs => [ (byte)RpcCalls.EnterVent, - (byte)RpcCalls.ExitVent, + (byte)RpcCalls.ExitVent ]; - public bool HandleAll(PlayerControl sender, MessageReader reader, + public bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { - return !sender.IsImpostor() && sender.GetRoleType() != RoleTypes.Engineer; + return !sender.IsImpostor() && + sender.GetRoleType() is not RoleTypes.Engineer and not RoleTypes.CrewmateGhost + and not RoleTypes.GuardianAngel; } -} +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VoteHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VoteHandler.cs index 06f76c73..8f94238d 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VoteHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Handlers/Valid/VoteHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; using Hazel; @@ -12,9 +11,9 @@ public class VoteHandler : IRpcHandler (byte)RpcCalls.VotingComplete, (byte)RpcCalls.CastVote, (byte)RpcCalls.ClearVote, - (byte)RpcCalls.AddVote, + (byte)RpcCalls.AddVote ]; - + public bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) { diff --git a/FinalSuspect/DataHandling/FinalAntiCheat/Interfaces/IRpcHandler.cs b/FinalSuspect/DataHandling/FinalAntiCheat/Interfaces/IRpcHandler.cs index e384ba11..5c3f7857 100644 --- a/FinalSuspect/DataHandling/FinalAntiCheat/Interfaces/IRpcHandler.cs +++ b/FinalSuspect/DataHandling/FinalAntiCheat/Interfaces/IRpcHandler.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Hazel; namespace FinalSuspect.DataHandling.FinalAntiCheat.Interfaces; @@ -7,23 +6,53 @@ public interface IRpcHandler { List TargetRpcs { get; } - bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) => - true; - - bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) => - false; - - bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) => - false; - - bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) => - false; - - bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason,ref bool ban) => - false; - - bool HandleGame_InMeeting(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, ref bool ban) => - false; - - void Dispose(byte id) { } + bool HandleInvalidRPC(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return true; + } + + bool HandleAll(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return false; + } + + bool HandleLobby(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return false; + } + + bool HandleGame_All(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return false; + } + + bool HandleGame_InTask(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return false; + } + + bool HandleGame_InMeeting(PlayerControl sender, MessageReader reader, ref bool notify, ref string reason, + ref bool ban) + { + return false; + } + + int MaxiReceivedNumPerSecond() + { + return 5; + } + + bool Condition(PlayerControl player) + { + return true; + } + + void Dispose(byte id) + { + } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalGameData/FinalGameData.cs b/FinalSuspect/DataHandling/FinalGameData/FinalGameData.cs new file mode 100644 index 00000000..24c9bc53 --- /dev/null +++ b/FinalSuspect/DataHandling/FinalGameData/FinalGameData.cs @@ -0,0 +1,31 @@ +using FinalSuspect.Attributes; +using UnityEngine; + +namespace FinalSuspect.DataHandling.FinalGameData; + +public static partial class FinalGameData +{ + public static string LastResultText; + public static string LastRoomCode; + public static string LastServer; + public static string LastGameData; + public static string LastGameResult; + public static Color LastLocalPlayerRoleColor; + public static bool JoinedCompleted; + public static bool IntroDestroyed; + + [GameModuleInitializer] + public static void Init() + { + IntroDestroyed = false; + LastResultText = LastGameData = LastGameResult = LastRoomCode = LastServer = ""; + } +} + +public enum VanillaDeathReason +{ + None, + Exile, + Kill, + Disconnect +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/XtremeGameData.cs b/FinalSuspect/DataHandling/FinalGameData/GameStates.cs similarity index 63% rename from FinalSuspect/DataHandling/XtremeGameData.cs rename to FinalSuspect/DataHandling/FinalGameData/GameStates.cs index b66a03af..4a5d9b9e 100644 --- a/FinalSuspect/DataHandling/XtremeGameData.cs +++ b/FinalSuspect/DataHandling/FinalGameData/GameStates.cs @@ -1,16 +1,17 @@ -using System; -using System.Linq; +using System; using AmongUs.GameOptions; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using InnerNet; -namespace FinalSuspect.DataHandling; +namespace FinalSuspect.DataHandling.FinalGameData; -public static partial class XtremeGameData +public partial class FinalGameData { public static class GameStates { - public static bool InGame { private get; set; } + private static bool InGame { get; set; } + private static bool InMeeting { get; set; } + public static bool OtherModHost { get @@ -40,24 +41,25 @@ public static bool OtherModHost public static bool IsLocalGame => AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame; public static bool IsFreePlay => AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay; public static bool IsInTask => IsInGame && !MeetingHud.Instance; - public static bool IsMeeting => IsInGame && MeetingHud.Instance; + public static bool IsInMeeting => IsInGame && MeetingHud.Instance && InMeeting; + + public static bool IsVoting => IsInMeeting && + MeetingHud.Instance.state is MeetingHud.VoteStates.Voted + or MeetingHud.VoteStates.NotVoted; public static bool IsCountDown => GameStartManager.InstanceExists && GameStartManager.Instance.startState == GameStartManager.StartingStates.Countdown; - public static bool IsShip => ShipStatus.Instance != null; + public static bool IsShip => ShipStatus.Instance; public static bool IsCanMove => PlayerControl.LocalPlayer?.CanMove is true; public static bool IsDead => PlayerControl.LocalPlayer?.Data?.IsDead is true && !IsLobby; - public static bool IsNormalGame => GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.Normal or GameModes.NormalFools; + public static bool IsNormalGame => + GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.Normal or GameModes.NormalFools; - public static bool IsHideNSeek => GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.HideNSeek or GameModes.SeekFools; - - public static bool MapIsActive(MapNames name) - { - return (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == name; - } + public static bool IsHideNSeek => + GameOptionsManager.Instance.CurrentGameOptions.GameMode is GameModes.HideNSeek or GameModes.SeekFools; public static bool IsVanillaServer { @@ -71,22 +73,20 @@ public static bool IsVanillaServer regionInfo.Servers.All(serverInfo => serverInfo.Ip.EndsWith(Domain, StringComparison.Ordinal)); } } - } - public static bool OtherModClient(this PlayerControl player) => OtherModClient(player.PlayerId) || player.Data.OwnerId == -2 && !IsFinalSuspect(player.PlayerId); - public static bool OtherModClient(byte id) => PlayerVersion.playerVersion.TryGetValue(id, out var ver) && Main.ForkId != ver.forkId; + public static void UpdateGameState_IsInGame(bool inGame) + { + InGame = inGame; + } - public static bool ModClient(this PlayerControl player) => ModClient(player.PlayerId); - public static bool ModClient(byte id) => PlayerVersion.playerVersion.ContainsKey(id); - - public static bool IsFinalSuspect(this PlayerControl pc) => IsFinalSuspect(pc.PlayerId); - public static bool IsFinalSuspect(byte id) => PlayerVersion.playerVersion.TryGetValue(id, out var ver) && Main.ForkId == ver.forkId; -} + public static void UpdateGameState_IsInMeeting(bool inMeeting) + { + InMeeting = inMeeting; + } -public enum VanillaDeathReason -{ - None, - Exile, - Kill, - Disconnect + public static bool MapIsActive(MapNames name) + { + return (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == name; + } + } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalGameData/PlayerVersion.cs b/FinalSuspect/DataHandling/FinalGameData/PlayerVersion.cs new file mode 100644 index 00000000..7f7ed69c --- /dev/null +++ b/FinalSuspect/DataHandling/FinalGameData/PlayerVersion.cs @@ -0,0 +1,14 @@ +using System; + +namespace FinalSuspect.DataHandling.FinalGameData; + +public static partial class FinalGameData +{ + public class PlayerVersion(Version ver, string tag_str, string forkId) + { + public static Dictionary playerVersion = new(); + public readonly string forkId = forkId; + public readonly string tag = tag_str; + public readonly Version version = ver; + } +} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/XtremeLocalHandling.cs b/FinalSuspect/DataHandling/FinalLocalHandling.cs similarity index 59% rename from FinalSuspect/DataHandling/XtremeLocalHandling.cs rename to FinalSuspect/DataHandling/FinalLocalHandling.cs index df0ca096..3e798098 100644 --- a/FinalSuspect/DataHandling/XtremeLocalHandling.cs +++ b/FinalSuspect/DataHandling/FinalLocalHandling.cs @@ -1,6 +1,7 @@ -using System.Linq; +using FinalSuspect.ClientActions.FeatureItems.NameTag; +using FinalSuspect.DataHandling.FinalGameData; using FinalSuspect.Helpers; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Modules.Features.CheckingandBlocking; using TMPro; using UnityEngine; @@ -9,33 +10,23 @@ namespace FinalSuspect.DataHandling; [HarmonyPatch] -public static class XtremeLocalHandling +public static class FinalLocalHandling { private static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); private static readonly int AddColor = Shader.PropertyToID("_AddColor"); public static string CheckAndGetNameWithDetails( - this PlayerControl player, - out Color topcolor, - out Color bottomcolor, + byte id, + out Color topcolor, + out Color bottomcolor, out string toptext, out string bottomtext, bool topswap = false) { - return CheckAndGetNameWithDetails(player.PlayerId, out topcolor, out bottomcolor, out toptext, out bottomtext, topswap); - } - public static string CheckAndGetNameWithDetails( - byte id, - out Color topcolor, - out Color bottomcolor, - out string toptext, - out string bottomtext, - bool topswap = false) - { - var data = XtremePlayerData.GetXtremeDataById(id); + var data = GetFinalDataById(id); var player = data.Player; - var name = IsInTask - ? player.GetRealName() + var name = IsInTask + ? player.GetRealName() : data.Name ?? player.GetRealName(); topcolor = Color.white; bottomcolor = Color.white; @@ -44,175 +35,248 @@ public static string CheckAndGetNameWithDetails( data.GetLobbyText(ref topcolor, ref bottomcolor, ref toptext, ref bottomtext); data.GetGameText(ref topcolor, ref toptext, topswap); SpamManager.CheckSpam(ref name); - if (!player.GetCheatData().IsSuspectCheater || Main.DisableFAC.Value) return name; + var ap = NameTagManager.ApplyFor(player).displayName; + name += ap.RemoveHtmlTags() == "" ? "" : $" ({ap})"; + if (!player.GetCheatData().IsSuspectCheater && !player.GetCheatData().IsHacker || + !Main.EnableFAC.Value) return name; topcolor = ColorHelper.FaultColor; - toptext = toptext.CheckAndAppendText(GetString("Cheater")); + toptext = toptext.CheckAndAppendText(GetString("Id.Cheater")); return name; } - private static void GetLobbyText(this XtremePlayerData data, ref Color topcolor, ref Color bottomcolor, ref string toptext, ref string bottomtext) + private static void GetLobbyText(this FinalPlayerData data, ref Color topcolor, ref Color bottomcolor, + ref string toptext, ref string bottomtext) { if (!IsLobby) return; var player = data.Player; - - if (player.IsHost()) toptext = toptext.CheckAndAppendText(GetString("Host")); - if (XtremeGameData.PlayerVersion.playerVersion.TryGetValue(player.PlayerId, out var ver) && ver != null) + if (player.IsHost()) toptext = toptext.CheckAndAppendText(GetString("Id.Host")); + if (GetPlayerVersion(player.GetClientId(), out var ver)) { if (Main.ForkId != ver.forkId) { toptext = toptext.CheckAndAppendText($"{ver.forkId}"); topcolor = ColorHelper.UnmatchedColor; } - else if (Main.version.CompareTo(ver.version) == 0 && ver.tag == $"{Main.GitCommit}({Main.GitBranch})") - { - topcolor = ColorHelper.ModColor32; - } - else if (Main.version.CompareTo(ver.version) == 0 && - ver.tag != $"{Main.GitCommit}({Main.GitBranch})") - { - toptext = toptext.CheckAndAppendText($"{ver.tag}"); - topcolor = Color.yellow; - } else { - toptext = toptext.CheckAndAppendText($"v{ver.version}"); - topcolor = Color.red; + switch (Main.version.CompareTo(ver.version)) + { + case 0 when ver.tag == $"{Main.GitCommit}({Main.GitBranch})": + topcolor = ColorHelper.FSColor; + break; + case 0 when ver.tag != $"{Main.GitCommit}({Main.GitBranch})": + toptext = toptext.CheckAndAppendText($"{ver.tag}"); + topcolor = Color.yellow; + break; + default: + toptext = toptext.CheckAndAppendText($"v{ver.version}"); + topcolor = Color.red; + break; + } } } else { - if (player.IsLocalPlayer()) topcolor = ColorHelper.ModColor32; + if (player.IsLocalPlayer()) topcolor = ColorHelper.FSColor; else if (player.IsHost()) topcolor = ColorHelper.HostNameColor; else topcolor = ColorHelper.ClientlessColor; } - if (Main.ShowPlayerInfo.Value) - { - bottomtext = bottomtext.CheckAndAppendText($"{player.GetPlatform()} {player.GetClient().FriendCode}"); - bottomcolor = ColorHelper.DownloadYellow; - } + if (!Main.ShowPlayerInfo.Value) return; + bottomtext = bottomtext.CheckAndAppendText($"{player.GetPlatform()} {player.GetClient().FriendCode}"); + bottomcolor = ColorHelper.DownloadYellow; } - private static string GetPlatform(this PlayerControl player) - { - try - { - var color = ""; - var name = ""; - string text; - switch (player.GetClient().PlatformData.Platform) - { - case Platforms.StandaloneEpicPC: - color = "#905CDA"; - name = "Itch"; - break; - case Platforms.StandaloneSteamPC: - color = "#4391CD"; - name = "Steam"; - break; - case Platforms.StandaloneMac: - color = "#e3e3e3"; - name = "Mac."; - break; - case Platforms.StandaloneWin10: - color = "#0078d4"; - name = GetString("MicrosoftStore"); - break; - case Platforms.StandaloneItch: - color = "#E35F5F"; - name = "Itch"; - break; - case Platforms.IPhone: - color = "#e3e3e3"; - name = GetString("IPhone"); - break; - case Platforms.Android: - color = "#1EA21A"; - name = GetString("Android"); - break; - case Platforms.Switch: - name = "NintendoSwitch"; - break; - case Platforms.Xbox: - color = "#07ff00"; - name = "Xbox"; - break; - case Platforms.Playstation: - color = "#0014b4"; - name = "PlayStation"; - break; - } - - if (color != "" && name != "") - text = $"{name}"; - else - text = name; - return text; - } - catch - { - return ""; - } - } - private static void GetGameText(this XtremePlayerData data, ref Color color, ref string roleText ,bool topswap) + private static void GetGameText(this FinalPlayerData data, ref Color color, ref string roleText, bool topswap) { if (!IsInGame) return; if (!Main.EnableFinalSuspect.Value) return; - - var roleType = XtremePlayerData.GetRoleById(data.PlayerId); + + var roleType = GetRoleById(data.PlayerId); var player = data.Player; + var roleTag = data.RoleTag; if (CanSeeTargetRole(player, out var bothImp)) { color = GetRoleColor(roleType); - if (!topswap) - roleText = $"{GetRoleString(roleType.ToString())} {GetProgressText(player)} {GetVitalText(player.PlayerId, docolor:CanSeeOthersRole())}"; - else - roleText = $"{GetVitalText(player.PlayerId, docolor:CanSeeOthersRole())} {GetProgressText(player)} {GetRoleString(roleType.ToString())}"; + roleText = !topswap + ? $"{GetRoleString(roleType.ToString())} {GetProgressText(player)} {GetVitalText(player.PlayerId, doColor: CanSeeOthersRole())} " + : $"{GetVitalText(player.PlayerId, doColor: CanSeeOthersRole())} {GetProgressText(player)} {GetRoleString(roleType.ToString())}"; + } + else if (roleTag.TagColor != Color.white || roleTag.TagStr != "" || roleTag.Room != "") + { + color = data.RoleTag.TagColor; + roleText = !topswap + ? $"[{data.RoleTag.TagStr} " + StringHelper.ColorString(ColorHelper.ClientlessColor, + $"{data.RoleTag.Room}") + "]" + : "[" + StringHelper.ColorString(ColorHelper.ClientlessColor, $"{data.RoleTag.Room}") + + $" {data.RoleTag.TagStr}]"; } else if (bothImp) { color = Palette.ImpostorRed; } - if (player.GetXtremeData().IsDisconnected) - { - color = Color.gray; - } + if (player.GetFinalData().IsDisconnected) color = Color.gray; } - public static string CheckAndAppendText(this string toptext, string extratext) + private static string CheckAndAppendText(this string toptext, string extratext) { if (toptext != "") - toptext += " "; + toptext += "\n"; toptext += extratext; return toptext; } + + #region HUD + + public static void SetVentOutlineColor(Vent __instance, ref bool mainTarget) + { + if (!Main.EnableFinalSuspect.Value) return; + var color = PlayerControl.LocalPlayer.GetRoleColor(); + __instance.myRend.material.SetColor(OutlineColor, color); + __instance.myRend.material.SetColor(AddColor, mainTarget ? color : Color.clear); + } + + public static void ShowMap(MapBehaviour map, MapOptions opts) + { + if (!Main.EnableFinalSuspect.Value) return; + foreach (var data in FinalPlayerData.AllPlayerData) + if (data.IsDisconnected) + { + data.Rend.gameObject.SetActive(false); + } + else + { + if (opts.Mode == MapOptions.Modes.CountOverlay) + { + data.Rend.enabled = opts.ShowLivePlayerPosition; + } + else + { + data.Player.SetPlayerMaterialColors(data.Rend); + data.Player.SetPlayerMaterialColors(data.Rend_DeadBody); + data.Rend.gameObject.SetActive(true); + UpdateMap(); + } + } + + var roleType = PlayerControl.LocalPlayer.Data.Role.Role; + var color = GetRoleColor(roleType); + var mode = opts.Mode; + switch (mode) + { + case MapOptions.Modes.CountOverlay: + color = Palette.AcceptedGreen; + break; + case MapOptions.Modes.Sabotage: + color = Palette.DisabledGrey; + break; + } + + map.ColorControl.SetColor(color); + } + + public static void UpdateMap() + { + if (!Main.EnableFinalSuspect.Value) return; + foreach (var data in FinalPlayerData.AllPlayerData) + { + var player = data.Player; + data.Rend_DeadBody?.gameObject.SetActive(CanSeeTargetRole(player, out _) && + player.GetFinalData().RealDeathReason is VanillaDeathReason.Kill); + if (data.IsDisconnected || !CanSeeTargetRole(player, out _) || player.IsLocalPlayer()) + { + data.Rend.gameObject.SetActive(false); + continue; + } + + var vector = player.transform.position; + if (MeetingHud.Instance && data.PreMeetingPosition != null) + vector = data.PreMeetingPosition.Value; + else if (data.PreMeetingPosition != null) data.PreMeetingPosition = null; + + vector /= ShipStatus.Instance.MapScale; + vector.x *= Mathf.Sign(ShipStatus.Instance.transform.localScale.x); + vector.z = -1f; + data.Rend.transform.localPosition = vector; + data.Rend.gameObject.SetActive(true); + + if (data.IsDead) + data.Rend.color = Color.white.AlphaMultiplied(0.6f); + else if (data.Rend_DeadBody) + data.Rend_DeadBody.transform.localPosition = vector; + } + } + + public static bool GetHauntFilterText(HauntMenuMinigame __instance) + { + if (!Main.EnableFinalSuspect.Value) return true; + var role = __instance.HauntTarget.GetRoleType(); + var color = GetRoleColor(role); + __instance.NameText.color = __instance.FilterText.color = color; + __instance.FilterText.text = GetRoleName(role); + return false; + } + + public static void GetChatBubbleText(byte playerId, ref string name, ref Color32 bgcolor, ref Color namecolor) + { + try + { + if (!Main.EnableFinalSuspect.Value) + { + namecolor = Color.white; + return; + } + + var player = GetPlayerById(playerId); + name = player.CheckAndGetNameWithDetails(out namecolor, out _, out var toptext, out _, + player.IsLocalPlayer()); + toptext = toptext.Replace("\n", " "); + if (player.IsLocalPlayer()) + name = $"{toptext} " + name; + else + name += $" {toptext}"; + + if (!player.IsAlive()) + bgcolor = new Color32(255, 0, 0, 120); + } + catch + { + /* ignored */ + } + } + + #endregion + #region FixedUpdate - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate)), HarmonyPostfix] + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + [HarmonyPostfix] public static void OnFixedUpdate(PlayerControl __instance) { Main.EnableFinalSuspect.Value = !OtherModHost; - if (__instance == null) return; + if (!__instance) return; try { - var name = __instance.CheckAndGetNameWithDetails(out var topcolor, out var bottomcolor, out var toptext, out var bottomtext); + var name = __instance.CheckAndGetNameWithDetails(out var topcolor, out var bottomcolor, out var toptext, + out var bottomtext); if (Main.EnableFinalSuspect.Value) - { + { DisconnectSync(__instance); DeathSync(__instance); } - + var topTextTransform = __instance.cosmetics.nameText.transform.Find("TopText"); var topText = topTextTransform.GetComponent(); topText.enabled = true; topText.text = toptext; topText.color = topcolor; topText.transform.SetLocalY(0.2f); - + var bottomTextTransform = __instance.cosmetics.nameText.transform.Find("BottomText"); var bottomText = bottomTextTransform.GetComponent(); bottomText.enabled = true; @@ -223,32 +287,33 @@ public static void OnFixedUpdate(PlayerControl __instance) __instance.cosmetics.nameText.text = name; __instance.cosmetics.nameText.color = topcolor; - + __instance.GetCheatData().HandleCheatData(); } catch { - var create = (IsFreePlay || - __instance.GetRealName() != "Player(Clone)" && IsLobby) - && XtremePlayerData.AllPlayerData.All(data => data.PlayerId != __instance.PlayerId); - if (create) XtremePlayerData.CreateDataFor(__instance); + var create = ( + IsFreePlay || (__instance.GetRealName() != "Player(Clone)" && IsLobby) + ) + && FinalPlayerData.AllPlayerData.All(data => data.PlayerId != __instance.PlayerId); + if (create) FinalPlayerData.CreateDataFor(__instance); } } private static void DisconnectSync(PlayerControl pc) { if (!IsInTask || IsFreePlay) return; - var data = pc.GetXtremeData(); + var data = pc.GetFinalData(); var currectlyDisconnect = pc.Data.Disconnected && !data.IsDisconnected; var Task_NotAssgin = data.TotalTaskCount == 0 && !data.IsImpostor; var Role_NotAssgin = data.RoleWhenAlive == null; - if (pc.GetXtremeData().IsDisconnected) + if (pc.GetFinalData().IsDisconnected) { pc.Data.Disconnected = true; pc.Data.IsDead = true; } - + if (!currectlyDisconnect && !Task_NotAssgin && !Role_NotAssgin) return; pc.SetDisconnected(); pc.SetDeathReason(VanillaDeathReason.Disconnect, Task_NotAssgin || Role_NotAssgin); @@ -256,22 +321,21 @@ private static void DisconnectSync(PlayerControl pc) private static void DeathSync(PlayerControl pc) { - if (!IsInTask || pc.GetXtremeData().IsDead) return; + if (!IsInTask || pc.GetFinalData().IsDead) return; if (pc.Data.IsDead) pc.SetDead(); } - + #endregion - + #region MeetingHud [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] [HarmonyPostfix] - [HarmonyPriority(Priority.First)] + [HarmonyPriority(Priority.HigherThanNormal)] public static void OnMeetingStart(MeetingHud __instance) { if (!Main.EnableFinalSuspect.Value) return; foreach (var pva in __instance.playerStates) - { try { pva.ColorBlindName.transform.localPosition -= new Vector3(1.35f, 0f, 0f); @@ -289,18 +353,14 @@ public static void OnMeetingStart(MeetingHud __instance) pva.NameText.text = name; pva.NameText.color = color; - if (toptext.Length > 0) - { - roleTextMeeting.text = toptext; - roleTextMeeting.color = color; - roleTextMeeting.enabled = true; - } + roleTextMeeting.text = toptext; + roleTextMeeting.color = color; + roleTextMeeting.enabled = toptext.Length > 0; } catch { /* ignored */ } - } } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] @@ -310,7 +370,6 @@ public static void MeetingHudUpdate(MeetingHud __instance) { if (!Main.EnableFinalSuspect.Value) return; foreach (var pva in __instance.playerStates) - { try { var name = CheckAndGetNameWithDetails(pva.TargetPlayerId, out var color, out _, out var toptext, out _); @@ -321,139 +380,15 @@ public static void MeetingHudUpdate(MeetingHud __instance) pva.NameText.text = name; pva.NameText.color = color; - if (toptext.Length > 0) - { - roleTextMeeting.text = toptext; - roleTextMeeting.color = color; - roleTextMeeting.enabled = true; - } + roleTextMeeting.text = toptext; + roleTextMeeting.color = color; + roleTextMeeting.enabled = toptext.Length > 0; } catch { /* ignored */ } - } } #endregion - - #region HUD - - public static void SetVentOutlineColor(Vent __instance, ref bool mainTarget) - { - if (!Main.EnableFinalSuspect.Value) return; - var color = PlayerControl.LocalPlayer.GetRoleColor(); - __instance.myRend.material.SetColor(OutlineColor, color); - __instance.myRend.material.SetColor(AddColor, mainTarget ? color : Color.clear); - } - - #endregion - - public static void ShowMap(MapBehaviour map, MapOptions opts) - { - if (!Main.EnableFinalSuspect.Value) return; - foreach (var data in XtremePlayerData.AllPlayerData) - { - if (data.IsDisconnected) - data.Rend.gameObject.SetActive(false); - else - { - if (opts.Mode == MapOptions.Modes.CountOverlay) - data.Rend.enabled = opts.ShowLivePlayerPosition; - else - { - data.Player.SetPlayerMaterialColors(data.Rend); - data.Player.SetPlayerMaterialColors(data.Deadbodyrend); - data.Rend.gameObject.SetActive(true); - UpdateMap(); - } - } - } - - var roleType = PlayerControl.LocalPlayer.Data.Role.Role; - var color = GetRoleColor(roleType); - var mode = opts.Mode; - switch (mode) - { - case MapOptions.Modes.CountOverlay: - color = Palette.AcceptedGreen; - break; - case MapOptions.Modes.Sabotage: - color = Palette.DisabledGrey; - break; - } - map.ColorControl.SetColor(color); - } - public static void UpdateMap() - { - if (!Main.EnableFinalSuspect.Value) return; - foreach (var data in XtremePlayerData.AllPlayerData) - { - var player = data.Player; - data.Deadbodyrend?.gameObject.SetActive(CanSeeTargetRole(player, out _) && player.GetXtremeData().RealDeathReason is VanillaDeathReason.Kill); - if (data.IsDisconnected || !CanSeeTargetRole(player, out _) || player.IsLocalPlayer()) - { - data.Rend.gameObject.SetActive(false); - continue; - } - - var vector = player.transform.position; - if (MeetingHud.Instance && data.PreMeetingPosition != null) - { - vector = data.PreMeetingPosition.Value; - } - else if (data.PreMeetingPosition != null) - { - data.PreMeetingPosition = null; - } - - vector /= ShipStatus.Instance.MapScale; - vector.x *= Mathf.Sign(ShipStatus.Instance.transform.localScale.x); - vector.z = -1f; - data.Rend.transform.localPosition = vector; - data.Rend.gameObject.SetActive(true); - - if (data.IsDead) - data.Rend.color = Color.white.AlphaMultiplied(0.6f); - else if (data.Deadbodyrend != null) - data.Deadbodyrend.transform.localPosition = vector; - } - } - - public static bool GetHauntFilterText(HauntMenuMinigame __instance) - { - if (!Main.EnableFinalSuspect.Value) return true; - if (__instance.HauntTarget == null) return true; - var role = __instance.HauntTarget.GetRoleType(); - var color = GetRoleColor(role); - __instance.NameText.color = __instance.FilterText.color = color; - __instance.FilterText.text = GetRoleName(role); - return false; - } - - public static void GetChatBubbleText(byte playerId, ref string name, ref Color32 bgcolor, ref Color namecolor) - { - try - { - if (!Main.EnableFinalSuspect.Value) - { - namecolor = Color.white; - return; - } - var player = GetPlayerById(playerId); - name = player.CheckAndGetNameWithDetails(out namecolor, out _, out var toptext, out _, player.IsLocalPlayer()); - toptext = toptext.Replace("\n", " "); - if (player.IsLocalPlayer()) - name = $"{toptext} " + name; - else - name += $" {toptext}"; - - if (!player.IsAlive()) - bgcolor = new Color32(255, 0, 0, 120); - } - catch - { - /* ignored */ - } - } } \ No newline at end of file diff --git a/FinalSuspect/DataHandling/FinalPlayerData.cs b/FinalSuspect/DataHandling/FinalPlayerData.cs new file mode 100644 index 00000000..b91bc97e --- /dev/null +++ b/FinalSuspect/DataHandling/FinalPlayerData.cs @@ -0,0 +1,226 @@ +using System; +using AmongUs.GameOptions; +using FinalSuspect.Attributes; +using FinalSuspect.DataHandling.FinalAntiCheat.Core; +using FinalSuspect.DataHandling.FinalGameData; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using FinalSuspect.Modules.Features.DisplayedRoleTag; +using UnityEngine; + +namespace FinalSuspect.DataHandling; + +public class FinalPlayerData : IDisposable +{ + ///////////////FUNCTIONS\\\\\\\\\\\\\\\ + + public void AdjustPlayerId() + { + PlayerId = Player.PlayerId; + } + + public void SetName(string name) + { + Name = name; + } + + public void SetDead() + { + IsDead = true; + Info($"Set Death For {Player.GetNameWithRole()}", "Data"); + } + + public void SetDisconnected() + { + if (IsLobby) + { + Dispose(); + AllPlayerData.Remove(this); + return; + } + + Info($"Set Disconnect For {Player.GetNameWithRole()}", "Data"); + IsDisconnected = true; + SetDead(); + SetDeathReason(VanillaDeathReason.Disconnect); + } + + public void SetRole(RoleTypes role) + { + if (!RoleAssigned) + { + RoleWhenAlive = role; + SetAsImp(IsImpostor(role)); + } + else + { + SetDead(); + RoleAfterDeath = role; + } + + RoleAssigned = !IsFreePlay; + Info("Set Role For Player: " + Name + " => " + role, "SetRole"); + } + + public void SetDeathReason(VanillaDeathReason deathReason, bool focus = false) + { + if ((IsDead && RealDeathReason == VanillaDeathReason.None) || focus) + RealDeathReason = deathReason; + Info($"Set Death Reason For {Player.GetNameWithRole()}; Death Reason: {deathReason}", "Data"); + } + + public void SetRealKiller(FinalPlayerData killer) + { + SetDead(); + SetDeathReason(VanillaDeathReason.Kill); + killer.UpdateProcess(); + RealKiller = killer; + Info($"Set Real Killer For {Player.GetNameWithRole()}, Killer: {killer.Player.GetNameWithRole()}", "Data"); + } + + public void SetTaskTotalCount(int count) + { + TotalTaskCount = count; + } + + public void UpdateProcess() + { + ProcessInt++; + } + + private void SetAsImp(bool isimp) + { + IsImpostor = isimp; + } + + [GameModuleInitializer] + public static void InitializeAll() + { + DisposeAll(); + AllPlayerData = []; + if (IsFreePlay) + foreach (var data in GameData.Instance.AllPlayers) + CreateDataFor(data.Object); + else + foreach (var pc in PlayerControl.AllPlayerControls) + CreateDataFor(pc); + } + + public static void CreateDataFor(PlayerControl player, string playername = null) + { + try + { + var colorId = player.Data.DefaultOutfit.ColorId; + playername ??= player.GetRealName(); + playername = playername.TrimEnd(); + playername = playername.Replace(" ", "_"); + + var existingNames = new HashSet(AllPlayerData.Select(data => data.Name)); + var baseName = playername; + var suffix = 0; + + while (existingNames.Contains(playername)) + { + suffix++; + playername = $"{baseName}_{suffix}"; + + if (suffix <= 1000) continue; + playername = $"{baseName}_{Guid.NewGuid().ToString("N")[..4]}"; + break; + } + + AllPlayerData.Add(new FinalPlayerData(player, playername, colorId)); + Info( + $"Creating FinalPlayerData For {player.GetClient().PlayerName ?? "Playername null"}({player.GetClient().FriendCode ?? "Friendcode null"})", + "Data"); + } + catch + { + /* ignored */ + } + } + + #region PLAYER_INFO + + public static List AllPlayerData; + public PlayerControl Player { get; private set; } + + public string Name { get; private set; } + public int ColorId { get; private set; } + public byte PlayerId { get; private set; } + public uint NetId { get; private set; } + + public bool IsImpostor { get; private set; } + public bool IsDead { get; private set; } + public bool DeathByDisconnected => RealDeathReason == VanillaDeathReason.Disconnect; + public bool IsDisconnected { get; private set; } + + public RoleTypes? RoleWhenAlive { get; private set; } + public RoleTypes? RoleAfterDeath { get; private set; } + public bool RoleAssigned { get; private set; } + + public VanillaDeathReason RealDeathReason { get; private set; } + public FinalPlayerData RealKiller { get; private set; } + + public int ProcessInt { get; private set; } + public int TotalTaskCount { get; private set; } + public bool TaskCompleted => TotalTaskCount == ProcessInt; + + public DisplayerRoleTag RoleTag { get; private set; } + + public PlayerCheatData CheatData { get; private set; } + + private FinalPlayerData(PlayerControl player, string playername, int colorid) + { + Player = player; + Name = playername; + ColorId = colorid; + CheatData = new PlayerCheatData(player); + PlayerId = player.PlayerId; + NetId = player.NetId; + IsImpostor = IsDead = RoleAssigned = false; + ProcessInt = TotalTaskCount = 0; + RealDeathReason = VanillaDeathReason.None; + RealKiller = null; + RoleTag = new DisplayerRoleTag(); + } + + public SpriteRenderer Rend { get; set; } + public SpriteRenderer Rend_DeadBody { get; set; } + public Vector3? PreMeetingPosition { get; set; } + public string PreMeetingRoomName { get; set; } + + #endregion + +#pragma warning disable CA1816 + public void Dispose() + { + Info($"Disposing FinalPlayerData For {Name}", "Data"); + Player = null; + CheatData.Dispose(); + CheatData = null; + Name = null; + ColorId = -1; + IsImpostor = IsDead = RoleAssigned = false; + ProcessInt = TotalTaskCount = -1; + RealDeathReason = VanillaDeathReason.None; + RealKiller = null; + Rend_DeadBody = Rend = null; + PreMeetingPosition = null; + RoleTag = null; + PreMeetingRoomName = null; + } + + public static void DisposeAll() + { + try + { + AllPlayerData.Do(data => data.Dispose()); + AllPlayerData.Clear(); + } + catch + { + /* ignored */ + } + } +} +#pragma warning restore CA1816 \ No newline at end of file diff --git a/FinalSuspect/DataHandling/PlayerVersion.cs b/FinalSuspect/DataHandling/PlayerVersion.cs deleted file mode 100644 index 5291cf2b..00000000 --- a/FinalSuspect/DataHandling/PlayerVersion.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace FinalSuspect.DataHandling; - -public static partial class XtremeGameData -{ - public class PlayerVersion(Version ver, string tag_str, string forkId) - { - public static Dictionary playerVersion = new(); - public readonly Version version = ver; - public readonly string tag = tag_str; - public readonly string forkId = forkId; - - public PlayerVersion(string ver, string tag_str, string forkId) : this(Version.Parse(ver), tag_str, forkId) { } - } -} \ No newline at end of file diff --git a/FinalSuspect/DataHandling/XtremePlayerData.cs b/FinalSuspect/DataHandling/XtremePlayerData.cs deleted file mode 100644 index 915484fd..00000000 --- a/FinalSuspect/DataHandling/XtremePlayerData.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AmongUs.GameOptions; -using FinalSuspect.Attributes; -using FinalSuspect.DataHandling.FinalAntiCheat; -using FinalSuspect.DataHandling.FinalAntiCheat.Core; -using FinalSuspect.Helpers; -using FinalSuspect.Modules.Core.Game; -using UnityEngine; - -namespace FinalSuspect.DataHandling; - -public class XtremePlayerData : IDisposable -{ - #region PLAYER_INFO - - public static List AllPlayerData; - public PlayerControl Player { get; private set; } - - public string Name { get; private set; } - public int ColorId { get; private set; } - public byte PlayerId { get; private set; } - - public bool IsImpostor { get; private set; } - public bool IsDead { get; private set; } - public bool DeathByDisconnected => RealDeathReason == VanillaDeathReason.Disconnect; - public bool IsDisconnected { get; private set; } - - public RoleTypes? RoleWhenAlive { get; private set; } - public RoleTypes? RoleAfterDeath { get; private set; } - public bool RoleAssgined { get; private set; } - - public VanillaDeathReason RealDeathReason { get; private set; } - public XtremePlayerData RealKiller { get; private set; } - - //public int ProcessInt { get; private set; } - public int TotalTaskCount { get; private set; } - public int CompleteTaskCount { get; private set; } - public bool TaskCompleted => TotalTaskCount == CompleteTaskCount; - public int KillCount { get; private set; } - - public PlayerCheatData CheatData { get; private set; } - - private XtremePlayerData(PlayerControl player, string playername, int colorid) - { - Player = player; - Name = playername; - ColorId = colorid; - CheatData = new PlayerCheatData(player); - PlayerId = player.PlayerId; - IsImpostor = IsDead = RoleAssgined = false; - CompleteTaskCount = KillCount = TotalTaskCount = 0; - RealDeathReason = VanillaDeathReason.None; - RealKiller = null; - } - - - public SpriteRenderer Rend { get; set; } - public SpriteRenderer Deadbodyrend { get; set; } - public Vector3? PreMeetingPosition { get; set; } - - #endregion - - ///////////////FUNCTIONS\\\\\\\\\\\\\\\ - - public static XtremePlayerData GetXtremeDataById(byte id) - { - try - { - return AllPlayerData.FirstOrDefault(data => data.PlayerId == id); - } - catch - { - return null; - } - } - - public static PlayerControl GetPlayerById(byte id) => GetXtremeDataById(id).Player; - public static string GetPlayerNameById(byte id) => GetXtremeDataById(id).Name; - - public static RoleTypes GetRoleById(byte id) - { - var data = GetXtremeDataById(id); - var dead = data?.IsDead ?? false; - RoleTypes nullrole; - - if (dead && !IsFreePlay) - { - nullrole = data.IsImpostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; - } - else - { - nullrole = GetPlayerById(id).Data.Role.Role; - } - var role = (dead ? data.RoleAfterDeath : data?.RoleWhenAlive) ?? nullrole; - return role; - } - - public void AdjustPlayerId() - { - PlayerId = Player.PlayerId; - } - public static int GetLongestNameByteCount() => AllPlayerData.Select(data => data.Name.GetByteCount()).OrderByDescending(byteCount => byteCount).FirstOrDefault(); - - public void SetName(string name) => Name = name; - public void SetDead() - { - IsDead = true; - Info($"Set Death For {Player.GetNameWithRole()}", "Data"); - } - - public void SetDisconnected() - { - if (IsLobby) - { - Dispose(); - AllPlayerData.Remove(this); - return; - } - Info($"Set Disconnect For {Player.GetNameWithRole()}", "Data"); - IsDisconnected = true; - SetDead(); - SetDeathReason(VanillaDeathReason.Disconnect); - } - - public void SetRole(RoleTypes role) - { - if (!RoleAssgined) - { - RoleWhenAlive = role; - SetIsImp(IsImpostor(role)); - } - else - { - SetDead(); - RoleAfterDeath = role; - } - RoleAssgined = !IsFreePlay; - Info("Set Role For Player: " + Name + " => " + role, "SetRole"); - } - - public void SetDeathReason(VanillaDeathReason deathReason, bool focus = false) - { - if (IsDead && RealDeathReason == VanillaDeathReason.None || focus) - RealDeathReason = deathReason; - Info($"Set Death Reason For {Player.GetNameWithRole()}; Death Reason: {deathReason}", "Data"); - } - - public void SetRealKiller(XtremePlayerData killer) - { - SetDead(); - SetDeathReason(VanillaDeathReason.Kill); - killer.KillCount++; - RealKiller = killer; - Info($"Set Real Killer For {Player.GetNameWithRole()}, Killer: {killer.Player.GetNameWithRole()}", "Data"); - } - - public void SetTaskTotalCount(int count) => TotalTaskCount = count; - public void CompleteTask() => CompleteTaskCount++; - public void SetIsImp(bool isimp) => IsImpostor = isimp; - - [GameModuleInitializer] - public static void InitializeAll() - { - DisposeAll(); - AllPlayerData = []; - if (IsFreePlay) - { - foreach (var data in GameData.Instance.AllPlayers) - { - CreateDataFor(data.Object); - } - } - else - { - foreach (var pc in PlayerControl.AllPlayerControls) - { - CreateDataFor(pc); - } - } - } - - public static void CreateDataFor(PlayerControl player, string playername = null) - { - try - { - var colorId = player.Data.DefaultOutfit.ColorId; - playername ??= player.GetRealName(); - - AllPlayerData.Add(new XtremePlayerData(player, playername, colorId)); - Info($"Creating XtremePlayerData For {player.GetClient().PlayerName ?? "Playername null"}({player.GetClient().FriendCode ?? "Friendcode null"})", "Data"); - } - catch - { - /* ignored */ - } - } -#pragma warning disable CA1816 - public void Dispose() - { - Info($"Disposing XtremePlayerData For {Name}", "Data"); - Player = null; - CheatData = null; - Name = null; - ColorId = -1; - IsImpostor = IsDead = RoleAssgined = false; - CompleteTaskCount = KillCount = TotalTaskCount = 0; - RealDeathReason = VanillaDeathReason.None; - RealKiller = null; - Deadbodyrend = Rend = null; - PreMeetingPosition = null; - } - - public static void DisposeAll() - { - try - { - AllPlayerData.Do(data => data.Dispose()); - AllPlayerData.Clear(); - } - catch - { - /* ignored */ - } - } -} -#pragma warning restore CA1816 - -public static class XtremePlayerDataExtensions -{ - public static XtremePlayerData GetXtremeData(this PlayerControl pc) - { - try - { - return XtremePlayerData.GetXtremeDataById(pc.PlayerId); - } - catch - { - try - { - return XtremePlayerData.AllPlayerData.FirstOrDefault(data => data.Player == pc); - } - catch - { - return null; - } - } - } - - public static PlayerCheatData GetCheatData(this PlayerControl pc) - { - try - { - return GetCheatDataById(pc.PlayerId); - } - catch - { - try - { - return pc.GetXtremeData().CheatData; - } - catch - { - return null; - } - } - } - - public static bool IsAlive(this PlayerControl pc) => pc?.GetXtremeData()?.IsDead == false || !IsInGame; - - public static string GetDataName(this PlayerControl pc) - { - try - { - return XtremePlayerData.GetPlayerNameById(pc.PlayerId); - } - catch - { - return null; - } - } - - public static void SetDead(this PlayerControl pc) => pc.GetXtremeData().SetDead(); - public static void SetDisconnected(this PlayerControl pc) => pc.GetXtremeData().SetDisconnected(); - public static void SetRole(this PlayerControl pc, RoleTypes role) => pc.GetXtremeData().SetRole(role); - - public static void SetDeathReason(this PlayerControl pc, VanillaDeathReason deathReason, bool focus = false) - => pc.GetXtremeData().SetDeathReason(deathReason, focus); - - public static void SetRealKiller(this PlayerControl pc, PlayerControl killer) - { - if (pc.GetXtremeData().RealKiller != null || !pc.Data.IsDead) return; - pc.GetXtremeData().SetRealKiller(killer.GetXtremeData()); - } - - public static void SetTaskTotalCount(this PlayerControl pc, int TaskTotalCount) => pc.GetXtremeData().SetTaskTotalCount(TaskTotalCount); - public static void OnCompleteTask(this PlayerControl pc) => pc.GetXtremeData().CompleteTask(); -} \ No newline at end of file diff --git a/FinalSuspect/FinalSuspect.csproj b/FinalSuspect/FinalSuspect.csproj index de451315..602bd521 100644 --- a/FinalSuspect/FinalSuspect.csproj +++ b/FinalSuspect/FinalSuspect.csproj @@ -1,67 +1,98 @@  - - net6.0 - latest - - Your best functional tool for Among Us. - XtremeWave - - false - false - false - - Debug;Canary;Release - ..\Export - MSB3246, CA2211, IDE0044, IDE0028, IDE0300 - true - - $([System.DateTime]::Now.ToString("yyyyMMdd")) - - - MSB3246, CA2211, IDE0044, IDE0028, IDE0300 - - - MSB3246, CA2211, IDE0044, IDE0028, IDE0300 - - - MSB3246, CA2211, IDE0044, IDE0028, IDE0300 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + net6.0 + latest + + Your best functional tool Mod for Among Us. + Slok7565 + + false + false + false + + Debug;OpenBeta;Release; + ..\Export + MSB3246, CA2211, IDE0044, IDE0028, IDE0300 + true + + $([System.DateTime]::Now.ToString("yyyyMMdd")) + + disable + + + MSB3246, CA2211, IDE0044, IDE0028, IDE0300 + + + MSB3246, CA2211, IDE0044, IDE0028, IDE0300 + + + MSB3246, CA2211, IDE0044, IDE0028, IDE0300 + + + + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + ..\Assets\Mod\BaseBepInEx + + <_MainCsContent>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\main.cs')) + + <_VersionHeadPattern>private const string DisplayedVersion_Head = "([^"]+)"; + $([System.Text.RegularExpressions.Regex]::Match($(_MainCsContent), $(_VersionHeadPattern)).Groups[1].Value) + + <_VersionTypePattern>private const VersionTypes DisplayedVersion_Type = VersionTypes.([^;]+); + $([System.Text.RegularExpressions.Regex]::Match($(_MainCsContent), $(_VersionTypePattern)).Groups[1].Value) + + <_TestCreationPattern>private const int DisplayedVersion_TestCreation = (\d+); + $([System.Text.RegularExpressions.Regex]::Match($(_MainCsContent), $(_TestCreationPattern)).Groups[1].Value) + + $(DisplayedVersion_Head)_$(CompileDate) + $(DisplayedVersion_Head)_$(CompileDate)_$(DisplayedVersion_Type)_$(DisplayedVersion_TestCreation) + + ..\Assets\Mod\FinalSuspect v$(DisplayedVersion) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FinalSuspect/FodyWeavers.xml b/FinalSuspect/FodyWeavers.xml index 47715521..d3cf7e2f 100644 --- a/FinalSuspect/FodyWeavers.xml +++ b/FinalSuspect/FodyWeavers.xml @@ -1,7 +1,7 @@ - - - - YamlDotNet - - + + + + YamlDotNet + + \ No newline at end of file diff --git a/FinalSuspect/GlobalUsings.cs b/FinalSuspect/GlobalUsings.cs index 64d105f3..6343f92f 100644 --- a/FinalSuspect/GlobalUsings.cs +++ b/FinalSuspect/GlobalUsings.cs @@ -2,12 +2,15 @@ //爱了爱了 //古希腊掌管using的神 +global using System.Collections.Generic; +global using System.Linq; +global using System.Reflection; global using FinalSuspect.DataHandling; global using FinalSuspect.Modules.Core.Plugin; global using HarmonyLib; global using static FinalSuspect.DataHandling.FinalAntiCheat.Core.FAC; -global using static FinalSuspect.DataHandling.XtremeGameData.GameStates; +global using static FinalSuspect.DataHandling.FinalGameData.FinalGameData.GameStates; global using static FinalSuspect.Modules.Core.Game.Utils; global using static FinalSuspect.Modules.Core.Plugin.Translator; -global using static FinalSuspect.Modules.Core.Plugin.XtremeLogger; +global using static FinalSuspect.Modules.Core.Plugin.FinalLogger; global using static FinalSuspect.Modules.Resources.PathManager; \ No newline at end of file diff --git a/FinalSuspect/Helpers/ColorHelper.cs b/FinalSuspect/Helpers/ColorHelper.cs index 30c667bd..0f42a3a6 100644 --- a/FinalSuspect/Helpers/ColorHelper.cs +++ b/FinalSuspect/Helpers/ColorHelper.cs @@ -4,6 +4,33 @@ namespace FinalSuspect.Helpers; public static class ColorHelper { + private const float MarkerSat = 1f; + private const float MarkerVal = 1f; + private const float MarkerAlpha = 0.2f; + + public const string AuthorColorHex = "#cdfffd"; + public const string FSColorHex = "#cecdfd"; + + public static readonly Color32 AuthorColor = new(205, 255, 253, 255); + public static readonly Color32 FSColor = new(206, 205, 253, 255); + public static readonly Color32 HalfYellow = new(255, 255, 25, 160); + public static readonly Color32 HalfFSColor = new(206, 205, 253, 160); + public static readonly Color32 FaultColor = new(229, 115, 115, 255); + public static readonly Color32 UnmatchedColor = new(191, 255, 185, 255); + public static readonly Color32 HostNameColor = new(177, 255, 231, 255); + public static readonly Color32 ClientlessColor = new(225, 224, 179, 255); + public static readonly Color32 DownloadYellow = new(252, 255, 152, 255); + public static readonly Color32 CompleteGreen = new(185, 255, 181, 255); + + public static readonly Color32 FSClientOptionColor = new(150, 149, 227, 255); + public static readonly Color32 FSClientOptionColor_Disable = new(61, 60, 97, 255); + public static readonly Color32 FSClientOptionColor_CanNotUse = new(90, 89, 108, 255); + public static readonly Color32 FSClientFeatureColor = new(191, 149, 227, 255); + public static readonly Color32 FSClientFeatureColor_ClickType = new(219, 207, 227, 255); + public static readonly Color32 FSClientFeatureColor_CanNotUse = new(102, 89, 97, 255); + + public static readonly Color32 ImpostorRedPale = new(255, 90, 90, 255); + /// 将颜色转换为荧光笔颜色 /// 颜色 /// 是否将颜色调整为最大亮度。如果希望较暗的颜色保持不变,请传入 false @@ -13,48 +40,21 @@ public static Color ToMarkingColor(this Color color, bool bright = true) var markingColor = Color.HSVToRGB(h, MarkerSat, bright ? MarkerVal : v).SetAlpha(MarkerAlpha); return markingColor; } - + public static Color HexToColor(string hex) { _ = ColorUtility.TryParseHtmlString(hex, out var color); return color; } - + public static string ColorToHex(Color color) { Color32 color32 = color; return $"{color32.r:X2}{color32.g:X2}{color32.b:X2}{color32.a:X2}"; } - private const float MarkerSat = 1f; - private const float MarkerVal = 1f; - private const float MarkerAlpha = 0.2f; - - public const string TeamColor = "#cdfffd"; - public const string ModColor = "#cecdfd"; - - public static readonly Color32 TeamColor32 = new(205, 255, 253, 255); - public static readonly Color32 ModColor32 = new(206, 205, 253, 255); - public static readonly Color32 HalfYellow = new(255, 255, 25, 160); - public static readonly Color32 HalfModColor32 = new(206, 205, 253, 160); - public static readonly Color32 FaultColor = new(229, 115, 115, 255); - public static readonly Color32 UnmatchedColor = new(191, 255, 185, 255); - public static readonly Color32 HostNameColor = new(177, 255, 231, 255); - public static readonly Color32 ClientlessColor = new(225, 224, 179, 255); - public static readonly Color32 DownloadYellow = new(252, 255, 152, 255); - public static readonly Color32 LoadCompleteGreen = new(185, 255, 181, 255); - - public static readonly Color32 ClientOptionColor = new(150, 149, 227, 255); - public static readonly Color32 ClientOptionColor_Disable = new(61, 60, 97, 255); - public static readonly Color32 ClientOptionColor_CanNotUse = new(90, 89, 108, 255); - public static readonly Color32 ClientFeatureColor = new(191, 149, 227, 255); - public static readonly Color32 ClientFeatureColor_ClickType = new(219, 207, 227, 255); - public static readonly Color32 ClientFeatureColor_CanNotUse = new(102, 89, 97, 255); - - public static readonly Color32 ImpostorRedPale = new(255, 90, 90, 255); - /// - /// Darkness: 按1的比例混合黑色与原色。负值则与白色混合。 + /// Darkness: 按1的比例混合黑色与原色。负值则与白色混合。 /// public static Color ShadeColor(this Color color, float Darkness = 0) { @@ -66,4 +66,73 @@ public static Color ShadeColor(this Color color, float Darkness = 0) var B = (color.b + Weight) / (Darkness + 1); return new Color(R, G, B, color.a); } + + private static void ColorToHSV(Color color, out float hue /*, out float saturation, out float value*/) + { + var max = Mathf.Max(color.r, Mathf.Max(color.g, color.b)); + var min = Mathf.Min(color.r, Mathf.Min(color.g, color.b)); + var delta = max - min; + + hue = 0f; + //saturation = 0f; + //value = max; + + if (delta != 0) + { + if (Mathf.Approximately(max, color.r)) + { + hue = (color.g - color.b) / delta; + } + else if (Mathf.Approximately(max, color.g)) + { + hue = 2 + (color.b - color.r) / delta; + } + else + { + hue = 4 + (color.r - color.g) / delta; + } + + hue *= 60; + if (hue < 0) hue += 360; + } + + //if (max != 0) + //{ + //saturation = delta / max; + //} + } + + private static Color HSVToColor(float hue, float saturation, float value) + { + var i = Mathf.FloorToInt(hue / 60) % 6; + var f = hue / 60 - Mathf.Floor(hue / 60); + var p = value * (1 - saturation); + var q = value * (1 - f * saturation); + var t = value * (1 - (1 - f) * saturation); + + return i switch + { + 0 => new Color(value, t, p), + 1 => new Color(q, value, p), + 2 => new Color(p, value, t), + 3 => new Color(p, q, value), + 4 => new Color(t, p, value), + _ => new Color(value, p, q) + }; + } + + public static Color ConvertToLightGray(Color color) + { + ColorToHSV(color, out var hue /*, out _, out _*/); + return HSVToColor(hue, 0f, 0.9f); + } + + public static Color GetColorByPercentage(float percentage) + { + return new Color( + r: Mathf.Clamp01(0.6f + percentage * 0.008f), // 0.6->1.0 + g: Mathf.Clamp01(1.0f - percentage * 0.01f), // 1.0->0.0 + b: Mathf.Clamp01(0.6f - percentage * 0.006f) // 0.6->0.0 + ); + } } \ No newline at end of file diff --git a/FinalSuspect/Helpers/EnumHelper.cs b/FinalSuspect/Helpers/EnumHelper.cs index 482be938..2a0cad05 100644 --- a/FinalSuspect/Helpers/EnumHelper.cs +++ b/FinalSuspect/Helpers/EnumHelper.cs @@ -5,16 +5,55 @@ namespace FinalSuspect.Helpers; public static class EnumHelper { /// - /// 获取枚举的所有值 + /// 获取枚举的所有值 /// /// 要获取值的枚举类型 /// T 类型的所有值 - public static T[] GetAllValues() where T : Enum => Enum.GetValues(typeof(T)) as T[]; + public static T[] GetAllValues() where T : Enum + { + return Enum.GetValues(typeof(T)) as T[]; + } /// - /// 获取枚举的所有名称 + /// 获取枚举的所有名称 /// /// 要获取名称的枚举类型 /// T 类型的所有值的名称 - public static string[] GetAllNames() where T : Enum => Enum.GetNames(typeof(T)); + public static string[] GetAllNames() where T : Enum + { + return Enum.GetNames(typeof(T)); + } + + /// 添加枚举值(返回新值,原值不变) + public static T AddFlag(this T value, T flag) where T : Enum + { + // 仅支持整数基类型(byte、sbyte、short、ushort、int、uint、long、ulong) + dynamic v = value; + dynamic f = flag; + return (T)(v | f); + } + + /// 移除枚举值(返回新值,原值不变) + public static T RemoveFlag(this T value, T flag) where T : Enum + { + dynamic v = value; + dynamic f = flag; + return (T)(v & ~f); + } + + /// + /// 根据 决定添加或移除某个标志位。 + /// true 时调用 AddFlag,false 时调用 RemoveFlag。 + /// + public static T AddOrRemoveFlag(this T value, T flag, bool addFlag) where T : Enum + { + return addFlag ? value.AddFlag(flag) : value.RemoveFlag(flag); + } + + public static bool HasAnyFlag(this T value, T flags) where T : Enum + { + var v = (int)(object)value; + var f = (int)(object)flags; + return (v & f) != 0; + } } \ No newline at end of file diff --git a/FinalSuspect/Helpers/IL2CPPHelper.cs b/FinalSuspect/Helpers/IL2CPPHelper.cs new file mode 100644 index 00000000..865fe714 --- /dev/null +++ b/FinalSuspect/Helpers/IL2CPPHelper.cs @@ -0,0 +1,13 @@ +using Il2CppInterop.Runtime.InteropTypes; + +namespace FinalSuspect.Helpers; + +public static class IL2CPPHelper +{ + public static bool TryCast(this Il2CppObjectBase obj, out T casted) + where T : Il2CppObjectBase + { + casted = obj.TryCast(); + return casted != null; + } +} \ No newline at end of file diff --git a/FinalSuspect/Helpers/JsonHelper.cs b/FinalSuspect/Helpers/JsonHelper.cs new file mode 100644 index 00000000..fad828a1 --- /dev/null +++ b/FinalSuspect/Helpers/JsonHelper.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace FinalSuspect.Helpers; + +public static class JsonHelper +{ + public static async Task<(string, bool)> GetJsonStringAsync(string url) + { + string result; + if (url.StartsWith("file:///")) + { + result = await File.ReadAllTextAsync(url[8..]); + } + else + { + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; + + var handler = new HttpClientHandler + { + AllowAutoRedirect = true, + UseDefaultCredentials = true, + Proxy = null, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + + using HttpClient client = new(handler); + + client.DefaultRequestHeaders.UserAgent.ParseAdd( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + + "AppleWebKit/537.36 (KHTML, like Gecko) " + + "Chrome/127.0 Safari/537.36"); + + client.DefaultRequestHeaders.Add("Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.9"); + client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); + client.DefaultRequestHeaders.Add("Cache-Control", "no-cache"); + client.DefaultRequestHeaders.Add("Referer", "https://gitee.com"); + + using var response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseContentRead); + if (!response.IsSuccessStatusCode) + { + Error($"Failed [{url}]: {response.StatusCode}", "Get Json Failed"); + return ("", false); + } + + result = await response.Content.ReadAsStringAsync(); + result = result.Replace("\r", string.Empty).Replace("\n", string.Empty).Trim(); + } + + return (result, true); + } +} \ No newline at end of file diff --git a/FinalSuspect/Helpers/ObjectHelper.cs b/FinalSuspect/Helpers/ObjectHelper.cs index 7da354a2..3e8652fe 100644 --- a/FinalSuspect/Helpers/ObjectHelper.cs +++ b/FinalSuspect/Helpers/ObjectHelper.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using Il2CppSystem; using UnityEngine; using Object = UnityEngine.Object; @@ -12,33 +10,53 @@ public static IEnumerable Flatten(this IEnumerable> collect { return collection.SelectMany(x => x); } + /// - /// 销毁对象的组件 + /// 销毁对象的组件 /// public static void DestroyTranslator(this GameObject obj) { - if (obj == null) return; - obj.ForEachChild((Action)(x =>DestroyTranslator(x))); + if (!obj) return; + obj.ForEachChild((Action)(x => DestroyTranslator(x))); TextTranslatorTMP[] translator = obj.GetComponentsInChildren(true); translator?.Do(Object.Destroy); } + /// - /// 销毁对象的 组件 + /// 销毁对象的 组件 /// - public static void DestroyTranslator(this MonoBehaviour obj) => obj?.gameObject.DestroyTranslator(); + public static void DestroyTranslator(this MonoBehaviour obj) + { + obj?.gameObject.DestroyTranslator(); + } - public static GameObject CreateObject(string objName, Transform parent, Vector3 localPosition, int? layer = null) + private static GameObject CreateObject(string objName, Transform parent, Vector3 localPosition, int? layer = null) { var obj = new GameObject(objName); obj.transform.SetParent(parent); obj.transform.localPosition = localPosition; obj.transform.localScale = new Vector3(1f, 1f, 1f); if (layer.HasValue) obj.layer = layer.Value; - else if (parent != null) obj.layer = parent.gameObject.layer; + else if (parent) obj.layer = parent.gameObject.layer; return obj; } - public static T CreateObject(string objName, Transform parent, Vector3 localPosition, int? layer = null) where T : Component + + private static T CreateObject(string objName, Transform parent, Vector3 localPosition, int? layer = null) + where T : Component { return CreateObject(objName, parent, localPosition, layer).AddComponent(); } + + public static SpriteRenderer CreateSpriteRenderer(string name, string spriteName, float pixelsPerUnit, + Vector3 position, Transform parent = null) + { + var renderer = CreateObject(name, null, position); + if (parent != null) + renderer.gameObject.transform.SetParent(parent); + renderer.gameObject.transform.localPosition = position; + renderer.sprite = LoadSprite(spriteName, pixelsPerUnit); + renderer.color = Color.clear; + + return renderer; + } } \ No newline at end of file diff --git a/FinalSuspect/Helpers/ResourcesHelper.cs b/FinalSuspect/Helpers/ResourcesHelper.cs index 4c1fbbd0..da746ade 100644 --- a/FinalSuspect/Helpers/ResourcesHelper.cs +++ b/FinalSuspect/Helpers/ResourcesHelper.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace FinalSuspect.Helpers; public static class ResourcesHelper @@ -11,32 +9,44 @@ public static class ResourcesHelper "FinalSuspect-Logo.png", "FinalSuspect-Logo-Blurred.png", "LastResult-BG.png", - "TeamLogo.png" + "AuthorLogo1.png", + ]; + + public static List PreReadyRemoteMusicList = + [ + "FinalSuspect.zip" ]; public static List RemoteImageList = [ - "CI_Crewmate.png", - "CI_CrewmateGhost.png", + "CI_Crewmate.png", + "CI_CrewmateGhost.png", "CI_Engineer.png", - "CI_GuardianAngel.png", - "CI_HnSEngineer.png", + "CI_GuardianAngel.png", + "CI_HnSEngineer.png", "CI_HnSImpostor.png", - "CI_Impostor.png", - "CI_ImpostorGhost.png", + "CI_Impostor.png", + "CI_ImpostorGhost.png", "CI_Noisemaker.png", - "CI_Phantom.png", - "CI_Scientist.png", + "CI_Phantom.png", + "CI_Scientist.png", "CI_Shapeshifter.png", - "CI_Tracker.png", - "Cursor.png", - "DleksBanner.png", - "DleksBanner-Wordart.png", - "DleksButton.png", - "FinalSuspect-BG-MiraHQ.jpg", - "FinalSuspect-BG-NewYear.png", + "CI_Tracker.png", + "Cursor.png", + "FinalSuspect-BG-MiraHQ.png", + "FinalSuspect-BG-MiraHQ-Preview.png", + "FinalSuspect-BG-NewYear-Preview.png", + "FinalSuspect-BG-Security-Preview.png", + "FinalSuspect-BG-MiraStudio-Preview.png", + "FinalSuspect-BG-XtremeWave-Preview.png", + //"FinalSuspect-BG-WhenLookingBackAtTheEnd-Preview.png", "ModStamp.png", - "RightPanelCloseButton.png" + "RightPanelCloseButton.png", + "AuthorLogo2.png", + "EditTag.png", + "Plate_Clear.png", + "Plate_Category.png", + "Plate_Content.png", ]; public static List RemoteDependList = @@ -45,15 +55,15 @@ public static class ResourcesHelper "YamlDotNet.xml" ]; - public static List RemoteModNewsList = + public static readonly List RemoteModNewsList = [ "FeaturesIntroduction.v1.0.txt", - "FS.v1.0_20250129.txt", + "FS.v1.0_20250129.txt", "FeaturesIntroduction.v1.1.txt", - "FS.v1.1_20250216.txt", + "FS.v1.1_20250216.txt", "FS.v1.1_20250412.txt", "FS.v1.1_20250501.txt", - "FS.v1.1_20250505.txt" + "FS.v1.2_20250815.txt" ]; #endregion diff --git a/FinalSuspect/Helpers/StringHelper.cs b/FinalSuspect/Helpers/StringHelper.cs index f4bf8d66..39c04d5c 100644 --- a/FinalSuspect/Helpers/StringHelper.cs +++ b/FinalSuspect/Helpers/StringHelper.cs @@ -19,13 +19,32 @@ public static string Mark(this string self, Color color, bool bright = true) var markingColorCode = ColorUtility.ToHtmlStringRGBA(markingColor); return $"{self}"; } + /// - /// 计算使用SJIS编码时的字节数 + /// 计算使用SJIS编码时的字节数 /// - public static int GetByteCount(this string self) => shiftJIS.GetByteCount(self); + public static int GetByteCount(this string self) + { + return shiftJIS.GetByteCount(self); + } + + public static string RemoveHtmlTags(this string str) + { + return Regex.Replace(str, "<[^>]*?>", string.Empty); + } + + public static string RemoveHtmlTagsExcept(this string str, string exceptionLabel) + { + return Regex.Replace(str, "<(?!/*" + exceptionLabel + ")[^>]*?>", string.Empty); + } - public static string RemoveHtmlTags(this string str) => Regex.Replace(str, "<[^>]*?>", string.Empty); - public static string RemoveHtmlTagsExcept(this string str, string exceptionLabel) => Regex.Replace(str, "<(?!/*" + exceptionLabel + ")[^>]*?>", string.Empty); - public static string RemoveColorTags(this string str) => Regex.Replace(str, "", ""); - public static string ColorString(Color32 color, string str) => $"{str}"; + public static string RemoveColorTags(this string str) + { + return Regex.Replace(str, "", ""); + } + + public static string ColorString(Color32 color, string str) + { + return $"{str}"; + } } \ No newline at end of file diff --git a/FinalSuspect/Modules/ClientOptions/ClientOptionItem.cs b/FinalSuspect/Modules/ClientOptions/ClientOptionItem.cs deleted file mode 100644 index 7c8ba648..00000000 --- a/FinalSuspect/Modules/ClientOptions/ClientOptionItem.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using BepInEx.Configuration; -using FinalSuspect.Helpers; -using UnityEngine; - -namespace FinalSuspect.Modules.ClientOptions; - -public sealed class ClientOptionItem_Boolean : ClientActionItem -{ - public ConfigEntry Config { get; private set; } - - private ClientOptionItem_Boolean( - string name, - ConfigEntry config, - OptionsMenuBehaviour optionsMenuBehaviour) - : base( - name, - optionsMenuBehaviour) - { - Config = config; - UpdateToggle(); - } - - /// - /// Modオプション画面にconfigのトグルを追加します - /// - /// ボタンラベルの翻訳キーとボタンのオブジェクト名 - /// 対応するconfig - /// OptionsMenuBehaviourのインスタンス - /// クリック時に追加で発火するアクション.configが変更されたあとに呼ばれる - /// 作成したアイテム - public static ClientOptionItem_Boolean Create( - string name, - ConfigEntry config, - OptionsMenuBehaviour optionsMenuBehaviour, - Action additionalOnClickAction = null) - { - var item = new ClientOptionItem_Boolean(name, config, optionsMenuBehaviour); - item.OnClickAction = () => - { - config.Value = !config.Value; - item.UpdateToggle(); - additionalOnClickAction?.Invoke(); - }; - return item; - } - - public void UpdateToggle() - { - if (ToggleButton == null) return; - - var color = Config.Value ? ColorHelper.ClientOptionColor : ColorHelper.ClientOptionColor_Disable; - ToggleButton.Background.color = color; - ToggleButton.Rollover?.ChangeOutColor(color); - } -} -public sealed class ClientOptionItem_String : ClientActionItem -{ - public ConfigEntry Config { get; private set; } - public string Name {get; private set;} - private ClientOptionItem_String( - string name, - string showingName, - ConfigEntry config, - string[] selections, - OptionsMenuBehaviour optionsMenuBehaviour) - : base( - showingName, - optionsMenuBehaviour) - { - Name = name; - Config = config; - UpdateToggle(selections); - } - - /// - /// Modオプション画面にconfigのトグルを追加します - /// - /// ボタンラベルの翻訳キーとボタンのオブジェクト名 - /// - /// 対応するconfig - /// OptionsMenuBehaviourのインスタンス - /// - /// クリック時に追加で発火するアクション.configが変更されたあとに呼ばれる - /// 作成したアイテム - public static ClientOptionItem_String Create( - string name, - string showingName, - ConfigEntry config, - OptionsMenuBehaviour optionsMenuBehaviour, - string[] selections, - Action additionalOnClickAction = null) - { - var item = new ClientOptionItem_String(name, showingName, config, selections, optionsMenuBehaviour); - item.OnClickAction = () => - { - var currentIndex = Array.IndexOf(selections, config.Value); - - if (currentIndex == -1) - { - Error("wrong index", "ClientOptionItem_String"); - return; - } - - var nextIndex = (currentIndex + 1) % selections.Length; - showingName = - config.Value = selections[nextIndex]; - item.UpdateToggle(selections); - item.UpdateName(showingName); - additionalOnClickAction?.Invoke(); - }; - return item; - } - - public void UpdateToggle(string[] selections) - { - if (ToggleButton == null) return; - - var color = Config.Value == selections[0] ? Palette.Purple : Color.magenta; - if (Config.Value == "HorseMode") - color = Color.gray; - ToggleButton.Background.color = color; - ToggleButton.Rollover?.ChangeOutColor(color); - } - public void UpdateName(string name) - { - if (ToggleButton == null) return; - - ToggleButton.Text.text = GetString(name); - if (Config.Value == "HorseMode") - ToggleButton.Text.text += $"({GetString("Broken")})"; - } -} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/ExtendedPlayerControl.cs b/FinalSuspect/Modules/Core/Game/ExtendedPlayerControl.cs deleted file mode 100644 index 7aad25c4..00000000 --- a/FinalSuspect/Modules/Core/Game/ExtendedPlayerControl.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Linq; -using AmongUs.GameOptions; -using FinalSuspect.Helpers; -using InnerNet; -using UnityEngine; - -namespace FinalSuspect.Modules.Core.Game; - -static class ExtendedPlayerControl -{ - public static ClientData GetClient(this PlayerControl player) - { - try - { - var client = AmongUsClient.Instance.allClients.ToArray().Where(cd => cd.Character.PlayerId == player.PlayerId).FirstOrDefault(); - return client; - } - catch - { - return null; - } - } - public static int GetClientId(this PlayerControl player) - { - if (player == null) return -1; - var client = player.GetClient(); - return client == null ? -1 : client.Id; - } - public static RoleTypes GetRoleType(this PlayerControl player) - { - return GetRoleType(player.PlayerId); - } - public static RoleTypes GetRoleType(byte id) - { - return XtremePlayerData.GetRoleById(id); - } - public static bool IsImpostor(this PlayerControl pc) - { - if (IsLobby) return false; - return pc.GetRoleType() switch - { - RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.ImpostorGhost => true, - _ => false, - }; - } - public static string GetNameWithRole(this PlayerControl player, bool forUser = false) - { - var ret = $"{player?.Data?.PlayerName}{(IsInGame ? - $"({GetRoleName(player.GetRoleType())})" : "")}"; - return forUser ? ret : ret.RemoveHtmlTags(); - } - public static Color GetRoleColor(this PlayerControl player) - { - return Utils.GetRoleColor(player.GetRoleType()); - } - public static string GetRealName(this PlayerControl player, bool isMeeting = false) - { - string trynull = null; - try - { - trynull = player.GetXtremeData() != null ? player?.GetDataName() : null; - } - catch - { - /* ignored */ - } - - var nullname = trynull; - return (isMeeting ? player?.Data?.PlayerName : player?.name) ?? nullname; - } - public static bool IsLocalPlayer(this PlayerControl player) => PlayerControl.LocalPlayer == player; - public static bool IsHost(this PlayerControl player) - { - try - { - return AmongUsClient.Instance.GetHost().Id == player.GetClient().Id; - } - catch - { - return false; - } - } -} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Cheat.cs b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Cheat.cs new file mode 100644 index 00000000..594cc0c0 --- /dev/null +++ b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Cheat.cs @@ -0,0 +1,52 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using FinalSuspect.Modules.Features.CheckingandBlocking; +using InnerNet; + +namespace FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +public static class _Cheat +{ + public static bool IsFACPlayer(this PlayerControl player) + { + return player?.GetClient()?.IsFACPlayer() ?? false; + } + + public static bool IsBannedPlayer(this PlayerControl player) + { + return player?.GetClient()?.IsBannedPlayer() ?? false; + } + + public static bool IsBannedPlayer(this ClientData player) + { + return BanManager.CheckBanStatus(player?.FriendCode, player?.GetHashedPuid()); + } + + public static void MarkAsCheater(this PlayerControl pc) + { + pc.GetFinalData().CheatData.MarkAsCheater(); + } + + public static void MarkAsHacker(this PlayerControl pc) + { + pc.GetFinalData().CheatData.MarkAsHacker(); + } + + public static string GetHashedPuid(this PlayerControl player) + { + return player.GetClient().GetHashedPuid(); + } + + public static string GetHashedPuid(this ClientData player) + { + if (player == null) return null; + var puid = player.ProductUserId; + if (string.IsNullOrEmpty(puid)) return puid; + + using var sha256 = SHA256.Create(); + var sha256Hash = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(puid))).Replace("-", "") + .ToLower(); + return string.Concat(sha256Hash.AsSpan(0, 5), sha256Hash.AsSpan(sha256Hash.Length - 4)); + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_ClientData.cs b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_ClientData.cs new file mode 100644 index 00000000..4ea90537 --- /dev/null +++ b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_ClientData.cs @@ -0,0 +1,104 @@ +using InnerNet; + +namespace FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +public static class _ClientData +{ + public static ClientData GetClient(this PlayerControl player) + { + try + { + var client = AmongUsClient.Instance.allClients + .ToArray().FirstOrDefault(cd => cd.Character.PlayerId == player.PlayerId); + return client; + } + catch + { + return null; + } + } + + public static int GetClientId(this PlayerControl player) + { + if (!player) return -1; + var client = player.GetClient(); + return client?.Id ?? -1; + } + + public static bool IsHost(this PlayerControl player) + { + try + { + return AmongUsClient.Instance.GetHost().Id == player.GetClient().Id; + } + catch + { + return false; + } + } + + public static string GetPlatform(this PlayerControl player) + { + try + { + var color = ""; + var name = ""; + string text; + switch (player.GetClient().PlatformData.Platform) + { + case Platforms.StandaloneEpicPC: + color = "#905CDA"; + name = "Epic"; + break; + case Platforms.StandaloneSteamPC: + color = "#4391CD"; + name = "Steam"; + break; + case Platforms.StandaloneMac: + color = "#e3e3e3"; + name = "Mac."; + break; + case Platforms.StandaloneWin10: + color = "#0078d4"; + name = GetString("Platform.MicrosoftStore"); + break; + case Platforms.StandaloneItch: + color = "#E35F5F"; + name = "Itch"; + break; + case Platforms.IPhone: + color = "#e3e3e3"; + name = GetString("Platform.IPhone"); + break; + case Platforms.Android: + color = "#1EA21A"; + name = GetString("Platform.Android"); + break; + case Platforms.Switch: + name = "NintendoSwitch"; + break; + case Platforms.Xbox: + color = "#07ff00"; + name = "Xbox"; + break; + case Platforms.Playstation: + color = "#0014b4"; + name = "PlayStation"; + break; + case Platforms.Unknown: + default: + break; + } + + if (color != "" && name != "") + text = $"{name}"; + else + text = name; + return text; + } + catch + { + return ""; + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Data.cs b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Data.cs new file mode 100644 index 00000000..089262c9 --- /dev/null +++ b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Data.cs @@ -0,0 +1,138 @@ +using AmongUs.GameOptions; +using FinalSuspect.DataHandling.FinalAntiCheat.Core; +using FinalSuspect.DataHandling.FinalGameData; +using FinalSuspect.Helpers; +using UnityEngine; + +namespace FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +public static class _Data +{ + public static string GetRealName(this PlayerControl player, bool isMeeting = false) + { + if (player == null) return null; + + string dataName = null; + try + { + var data = player.GetFinalData(); + if (data != null) + dataName = player.GetDataName(); + } + catch + { + /* ignored */ + } + + var realName = isMeeting ? player.Data?.PlayerName : player.name; + return realName ?? dataName; + } + + public static string CheckAndGetNameWithDetails( + this PlayerControl player, + out Color topcolor, + out Color bottomcolor, + out string toptext, + out string bottomtext, + bool topswap = false) + { + return FinalLocalHandling.CheckAndGetNameWithDetails(player.PlayerId, out topcolor, out bottomcolor, + out toptext, out bottomtext, + topswap); + } + + public static FinalPlayerData GetFinalData(this PlayerControl pc) + { + try + { + return GetFinalDataById(pc.PlayerId); + } + catch + { + try + { + return FinalPlayerData.AllPlayerData.FirstOrDefault(data => data.Player == pc); + } + catch + { + return null; + } + } + } + + public static PlayerCheatData GetCheatData(this PlayerControl pc) + { + try + { + return GetCheatDataById(pc.PlayerId); + } + catch + { + try + { + return pc.GetFinalData().CheatData; + } + catch + { + return null; + } + } + } + + public static string GetDataName(this PlayerControl pc) + { + try + { + return GetPlayerNameById(pc.PlayerId); + } + catch + { + return null; + } + } + + public static string GetColoredName(this PlayerControl pc) + { + try + { + var data = GetFinalDataById(pc.PlayerId); + return StringHelper.ColorString(Palette.PlayerColors[data.ColorId], data.Name); + } + catch + { + return null; + } + } + + public static void SetDead(this PlayerControl pc) + { + pc.GetFinalData().SetDead(); + } + + public static void SetDisconnected(this PlayerControl pc) + { + pc.GetFinalData().SetDisconnected(); + FinalPlayerData.AllPlayerData.Do(_data => _data.AdjustPlayerId()); + } + + public static void SetRole(this PlayerControl pc, RoleTypes role) + { + pc.GetFinalData().SetRole(role); + } + + public static void SetDeathReason(this PlayerControl pc, VanillaDeathReason deathReason, bool focus = false) + { + pc.GetFinalData().SetDeathReason(deathReason, focus); + } + + public static void SetRealKiller(this PlayerControl pc, PlayerControl killer) + { + if (pc.GetFinalData().RealKiller != null || !pc.Data.IsDead) return; + pc.GetFinalData().SetRealKiller(killer.GetFinalData()); + } + + public static void SetTaskTotalCount(this PlayerControl pc, int TaskTotalCount) + { + pc.GetFinalData().SetTaskTotalCount(TaskTotalCount); + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Events.cs b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Events.cs new file mode 100644 index 00000000..c2d9e207 --- /dev/null +++ b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Events.cs @@ -0,0 +1,9 @@ +namespace FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +public static class _Events +{ + public static void OnCompleteTask(this PlayerControl pc) + { + pc.GetFinalData().UpdateProcess(); + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_GamePlayer.cs b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_GamePlayer.cs new file mode 100644 index 00000000..da8eb456 --- /dev/null +++ b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_GamePlayer.cs @@ -0,0 +1,62 @@ +using FinalSuspect.Helpers; + +namespace FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +public static class _GamePlayer +{ + public static bool IsLocalPlayer(this PlayerControl player) + { + return PlayerControl.LocalPlayer == player; + } + + public static bool IsAlive(this PlayerControl pc) + { + return pc?.GetFinalData()?.IsDead == false || !IsInGame; + } + + public static bool OtherModClient(this PlayerControl player) + { + return Utils.OtherModClient(player.GetClientId()) || + (player.Data.OwnerId == -2 + && !Utils.IsFinalSuspect(player.GetClientId()) + && !IsFreePlay + && !IsLocalGame); + } + + public static bool ModClient(this PlayerControl player) + { + return Utils.ModClient(player.GetClientId()); + } + + public static bool IsFinalSuspect(this PlayerControl player) + { + return Utils.IsFinalSuspect(player.GetClientId()); + } + + public static bool IsDev(this PlayerControl pc) + { + return Utils.IsDev(pc.FriendCode); + } + + public static PlainShipRoom GetPlainShipRoom(this PlayerControl pc) + { + var Rooms = ShipStatus.Instance.AllRooms; + return Rooms?.Where(room => room.roomArea).FirstOrDefault(room => pc.Collider.IsTouching(room.roomArea)); + } + + public static string GetPlainShipRoomName(this PlayerControl pc) + { + try + { + if (IsInMeeting) + return pc.GetFinalData().PreMeetingRoomName; + var roomStr = pc.GetPlainShipRoom().RoomId.ToString(); + var roomName = StringHelper.ColorString(ColorHelper.ClientlessColor, $"({GetString(roomStr)})"); + return roomName; + } + catch + { + return ""; + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Role.cs b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Role.cs new file mode 100644 index 00000000..b0acbeb6 --- /dev/null +++ b/FinalSuspect/Modules/Core/Game/PlayerControlExtension/_Role.cs @@ -0,0 +1,35 @@ +using AmongUs.GameOptions; +using FinalSuspect.Helpers; +using UnityEngine; + +namespace FinalSuspect.Modules.Core.Game.PlayerControlExtension; + +public static class _Role +{ + public static RoleTypes GetRoleType(this PlayerControl player) + { + return Utils.GetRoleType(player.PlayerId); + } + + public static bool IsImpostor(this PlayerControl pc) + { + if (IsLobby) return false; + return pc.GetRoleType() switch + { + RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.ImpostorGhost => true, + _ => false + }; + } + + public static string GetNameWithRole(this PlayerControl player, bool forUser = false) + { + var ret = $"{player?.Data?.PlayerName}{(IsInGame ? + $"({GetRoleName(player.GetRoleType())})" : "")}"; + return forUser ? ret : ret.RemoveHtmlTags(); + } + + public static Color GetRoleColor(this PlayerControl player) + { + return Utils.GetRoleColor(player.GetRoleType()); + } +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Game/ServerAddManager.cs b/FinalSuspect/Modules/Core/Game/ServerAddManager.cs index eca69d05..89ae2e3b 100644 --- a/FinalSuspect/Modules/Core/Game/ServerAddManager.cs +++ b/FinalSuspect/Modules/Core/Game/ServerAddManager.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using FinalSuspect.Attributes; +using FinalSuspect.Attributes; using FinalSuspect.Helpers; using FinalSuspect.Patches.System; using UnityEngine; @@ -9,12 +7,11 @@ namespace FinalSuspect.Modules.Core.Game; public static class ServerAddManager { - private static ServerManager serverManager = DestroyableSingleton.Instance; + private static readonly ServerManager serverManager = DestroyableSingleton.Instance; [PluginModuleInitializer] public static void Init() { - serverManager.AvailableRegions = ServerManager.DefaultRegions; List regionInfos = [ CreateHttp("au-us.niko233.me", "Niko233(NA)", 443, true), @@ -24,14 +21,15 @@ public static void Init() if (IsChineseUser) { - regionInfos.Add(CreateHttp("au-cn.niko233.me", "Niko233(CN)", 443, true)); - - regionInfos.Add(CreateHttp("nb.8w.fan", "新猫服[宁波]", 443, true)); - regionInfos.Add(CreateHttp("bj.8w.fan", "新猫服[北京]", 443, true)); - regionInfos.Add(CreateHttp("player.fangkuai.fun", "方块宿迁私服", 443, true)); - regionInfos.Add(CreateHttp("auhk.fangkuai.fun", "方块香港私服", 443, true)); - + regionInfos.Add(CreateHttp("au-cn.niko233.me", "Niko233(CN)", 443, + true)); + regionInfos.Add(CreateHttp("player.fangkuai.fun", "方块宿迁私服", + 443, + true)); + regionInfos.Add(CreateHttp("auhk.fangkuai.fun", "方块香港私服", 443, + true)); } + regionInfos.Add(CreateHttp("au-as.duikbo.at", "Modded Asia (MAS)", 443, true)); regionInfos.Add(CreateHttp("www.aumods.org", "Modded NA (MNA)", 443, true)); regionInfos.Add(CreateHttp("au-eu.duikbo.at", "Modded EU (MEU)", 443, true)); @@ -42,6 +40,7 @@ public static void Init() SetServerName(defaultRegion.Name); } + public static void SetServerName(string serverName = "") { if (serverName == "") serverName = ServerManager.Instance.CurrentRegion.Name; @@ -59,29 +58,26 @@ public static void SetServerName(string serverName = "") "Niko233(AS)" => "Niko[AS]", "Niko233(EU)" => "Niko[EU]", "Niko233(CN)" => "Niko[CN]", - "XtremeWave[HongKong]" => "XW[HK]", - _ => serverName, + _ => serverName }; - if ((TranslationController.Instance?.currentLanguage?.languageID ?? SupportedLangs.SChinese) is SupportedLangs.SChinese or SupportedLangs.TChinese) - { + if ((TranslationController.Instance?.currentLanguage?.languageID ?? SupportedLangs.SChinese) is + SupportedLangs.SChinese or SupportedLangs.TChinese) name = name switch { "Asia" => "亚服", "Europe" => "欧服", "North America" => "北美服", "NA" => "北美服", - "XW[HK]" => "XW[香港]", - _ => name, + _ => name }; - } var color = GetServerColor(serverName); //Cloud.ServerName = name; PingTrackerUpdatePatch.ServerName = StringHelper.ColorString(color, name); } - public static Color GetServerColor(string serverName) + private static Color GetServerColor(string serverName) { var color = serverName switch { @@ -91,21 +87,20 @@ public static Color GetServerColor(string serverName) "Modded Asia (MAS)" => new Color32(255, 132, 0, 255), "Modded NA (MNA)" => new Color32(255, 132, 0, 255), "Modded EU (MEU)" => new Color32(255, 132, 0, 255), - "新猫服[宁波]" => new Color32(0, 255, 0, 255), - "新猫服[北京]" => new Color32(153, 0, 204, 255), "方块宿迁私服" => new Color32(0, 255, 255, 255), "方块香港私服" => new Color32(0, 255, 255, 255), "Niko233(NA)" => new Color32(255, 224, 0, 255), "Niko233(AS)" => new Color32(255, 224, 0, 255), "Niko233(EU)" => new Color32(255, 224, 0, 255), "Niko233(CN)" => new Color32(255, 224, 0, 255), - "XtremeWave[HongKong]" => ColorHelper.TeamColor32, - _ => new Color32(255, 255, 255, 255), + _ => new Color32(255, 255, 255, 255) }; + return color; } - public static IRegionInfo CreateHttp(string ip, string name, ushort port, bool ishttps) + + private static IRegionInfo CreateHttp(string ip, string name, ushort port, bool ishttps) { var serverIp = (ishttps ? "https://" : "http://") + ip; var serverInfo = new ServerInfo(name, serverIp, port, false); diff --git a/FinalSuspect/Modules/Core/Game/Utils.cs b/FinalSuspect/Modules/Core/Game/Utils.cs index 38d09566..cd01acdc 100644 --- a/FinalSuspect/Modules/Core/Game/Utils.cs +++ b/FinalSuspect/Modules/Core/Game/Utils.cs @@ -1,16 +1,14 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Text; using AmongUs.GameOptions; -using FinalSuspect.DataHandling.FinalAntiCheat; using FinalSuspect.DataHandling.FinalAntiCheat.Core; +using FinalSuspect.DataHandling.FinalGameData; using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Modules.Resources; -using FinalSuspect.Patches.System; -using Il2CppInterop.Runtime.InteropTypes; +using FinalSuspect.Patches.Game_Vanilla; using InnerNet; using UnityEngine; @@ -19,13 +17,26 @@ namespace FinalSuspect.Modules.Core.Game; public static class Utils { private static readonly DateTime timeStampStartTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + + private static readonly Dictionary cachedPlayers = new(); public static long TimeStamp => (long)(DateTime.Now.ToUniversalTime() - timeStampStartTime).TotalSeconds; - public static long GetTimeStamp(DateTime? dateTime = null) => (long)((dateTime ?? DateTime.Now).ToUniversalTime() - timeStampStartTime).TotalSeconds; - public static ClientData GetClientById(int id) + + public static long GetTimeStamp(DateTime? dateTime = null) + { + return (long)((dateTime ?? DateTime.Now).ToUniversalTime() - timeStampStartTime).TotalSeconds; + } + + public static float GetResolutionOffset() + { + return (float)Screen.width / Screen.height / (16f / 9f); + } + + private static ClientData GetClientById(int id) { try { - var client = AmongUsClient.Instance.allClients.ToArray().Where(cd => cd.Id == id).FirstOrDefault(); + var client = AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Id == id); return client; } catch @@ -33,67 +44,120 @@ public static ClientData GetClientById(int id) return null; } } - public static string GetRoleName(RoleTypes role, bool forUser = true) - { - return GetRoleString(Enum.GetName(typeof(RoleTypes), role), forUser); - } - public static Color GetRoleColor(RoleTypes role) - { - Main.roleColors.TryGetValue(role, out var hexColor); - _ = ColorUtility.TryParseHtmlString(hexColor, out var c); - return c; - } - public static string GetRoleColorCode(RoleTypes role) - { - Main.roleColors.TryGetValue(role, out var hexColor); - return hexColor; - } - public static string GetRoleInfoForVanilla(this RoleTypes role, bool InfoLong = false) - { - if (role is RoleTypes.Crewmate or RoleTypes.Impostor) - InfoLong = false; - - var text = role.ToString(); - - var Info = "Blurb" + (InfoLong ? "Long" : ""); - - if (!IsNormalGame) text = "HnS" + text; - - return GetString($"{text}{Info}"); - } - public static void KickPlayer(int clientId, bool ban, string reason = "") + // ReSharper disable once RedundantAssignment + public static void KickPlayer(int clientId, bool ban, string reason = "", KickLevel level = KickLevel.Notification) { - Info($"try to kick {GetClientById(clientId)?.Character?.GetRealName()}", "Kick"); + if (OnPlayerLeftPatch.ClientsProcessed.Contains(clientId)) return; + var client = GetClientById(clientId); + Info($"try to kick {client?.Character?.GetRealName()} Due to {reason}", "Kick Player"); + var _player = FinalPlayerData.AllPlayerData.FirstOrDefault(p => p.CheatData?.ClientData?.Id == clientId) + ?.Player; try { +#if DEBUG + ban = false; +#endif OnPlayerLeftPatch.Add(clientId); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse AmongUsClient.Instance.KickPlayer(clientId, ban); + if (level != KickLevel.None) + NotificationPopperPatch.NotificationPop(string.Format(GetString($"{level}.{reason}"), + _player ? _player.GetColoredName() : client?.PlayerName)); } - catch + catch { /* ignored */ } } - public static void KickPlayer(byte playerId, bool ban, string reason = "") + + public static void KickPlayer(byte playerId, bool ban, string reason = "", + KickLevel level = KickLevel.CheatDetected) { try { - KickPlayer(GetPlayerById(playerId).GetClient().Id, ban, reason); + KickPlayer(GetPlayerById(playerId).GetClient().Id, ban, reason, level); } - catch + catch { /* ignored */ } - } + public static string PadRightV2(this object text, int num) { - var bc = 0; var t = text.ToString(); - foreach (var c in t!) bc += Encoding.GetEncoding("UTF-8").GetByteCount(c.ToString()) == 1 ? 1 : 2; + var bc = t!.Sum(c => Encoding.GetEncoding("UTF-8").GetByteCount(c.ToString()) == 1 ? 1 : 2); return t.PadRight(Mathf.Max(num - (bc - t.Length), 0)); } + + /// + /// 乱数の簡易的なヒストグラムを取得する関数 + /// 生成した乱数を格納したint配列 + /// ヒストグラムの倍率 大量の乱数を扱う場合、この値を下げることをお勧めします。 + /// + public static bool AmDev() + { + return IsDev(EOSManager.Instance.FriendCode); + } + + public static bool IsDev(string friendCode) + { + return friendCode + is "teamelder#5856" //Slok + or "cloakhazy#9133"; //LezaiYa + } + + public static PlayerControl GetPlayerById(int playerId) + { + return GetPlayerById((byte)playerId); + } + + public static PlayerControl GetPlayerById(byte playerId) + { + if (cachedPlayers.TryGetValue(playerId, out var cachedPlayer) && cachedPlayer) return cachedPlayer; + + var player = Main.AllPlayerControls.FirstOrDefault(pc => pc.PlayerId == playerId); + cachedPlayers[playerId] = player; + return player; + } + + public static void ExecuteWithTryCatch(this Action action, bool Log = false) + { + try + { + action(); + } + catch (Exception ex) + { + if (Log) Error(ex.ToString(), "Execute With Try Catch"); + } + } + + public static void FormatButtonColor(MainMenuManager __instance, PassiveButton button, Color inActiveColor, + Color activeColor, Color inActiveTextColor, Color activeTextColor) + { + button.activeSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); + button.inactiveSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); + var activeRenderer = button.activeSprites.GetComponent(); + var inActiveRenderer = button.inactiveSprites.GetComponent(); + activeRenderer.sprite = __instance.quitButton.activeSprites.GetComponent().sprite; + inActiveRenderer.sprite = __instance.quitButton.activeSprites.GetComponent().sprite; + activeRenderer.color = activeColor.a == 0f + ? new Color(inActiveColor.r, inActiveColor.g, inActiveColor.b, 1f) + : activeColor; + inActiveRenderer.color = inActiveColor; + button.activeTextColor = activeTextColor; + button.inactiveTextColor = inActiveTextColor; + } + + public static long GetCurrentTimestamp() + { + return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + } + + #region Log Out Put + public static DirectoryInfo GetLogFolder(bool auto = false) { var folder = Directory.CreateDirectory($"{Application.persistentDataPath}/FinalSuspect/Logs"); @@ -101,42 +165,44 @@ public static DirectoryInfo GetLogFolder(bool auto = false) { folder = Directory.CreateDirectory($"{folder.FullName}/AutoLogs"); } + return folder; } + public static void DumpLog(bool popup = false) { var logs = GetLogFolder(); var filename = CopyLog(logs.FullName); OpenDirectory(filename); - if (PlayerControl.LocalPlayer != null) - { - var t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"); - if (popup) - HudManager.Instance.ShowPopUp(string.Format(GetString("Message.DumpfileSaved"), $"FinalSuspect - v{Main.DisplayedVersion}-{t}.log")); - else - AddChatMessage(string.Format(GetString("Message.DumpfileSaved"), $"FinalSuspect - v{Main.DisplayedVersion}-{t}.log")); - } + if (!PlayerControl.LocalPlayer) return; + var t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"); + if (popup) + HudManager.Instance.ShowPopUp(string.Format(GetString("Message.DumpfileSaved"), + $"FinalSuspect - v{Main.DisplayedVersion}-{t}.log")); + else + AddChatMessage(string.Format(GetString("Message.DumpfileSaved"), + $"FinalSuspect - v{Main.DisplayedVersion}-{t}.log")); } public static void ClearAutoLogs() { - foreach (var f in Directory.GetFiles(GetLogFolder(true).FullName + "/Final Suspect-logs")) File.Delete(f); + foreach (var f in Directory.GetFiles(GetLogFolder(true).FullName)) File.Delete(f); } - + public static void SaveNowLog() { var logs = GetLogFolder(true); logs.EnumerateFiles().Where(f => f.CreationTime < DateTime.Now.AddDays(-7)).ToList().ForEach(f => f.Delete()); CopyLog(logs.FullName); } - + public static string CopyLog(string path) { - var f = $"{path}/Final Suspect-logs/"; + var f = $"{path}"; var t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"); var fileName = $"{f}FinalSuspect-v{Main.DisplayedVersion}-{t}.log"; if (!Directory.Exists(f)) Directory.CreateDirectory(f); - FileInfo file = new(@$"{Environment.CurrentDirectory}/BepInEx/LogOutput.log"); + FileInfo file = new($"{Environment.CurrentDirectory}/BepInEx/LogOutput.log"); var logFile = file.CopyTo(fileName); return logFile.FullName; } @@ -146,40 +212,21 @@ public static void OpenDirectory(string path) Process.Start("Explorer.exe", $"/select,{path}"); } - public static string SummaryTexts(byte id) + private static void AddChatMessage(string text, string title = "") { - var thisdata = XtremePlayerData.GetXtremeDataById(id); - - var builder = new StringBuilder(); - var longestNameByteCount = XtremePlayerData.GetLongestNameByteCount(); - - var pos = Math.Min((float)longestNameByteCount / 2 + 1.5f, 11.5f); - - var colorId = thisdata.ColorId; - builder.Append(StringHelper.ColorString(Palette.PlayerColors[colorId], thisdata.Name)); - pos += 1.5f; - builder.AppendFormat("", pos).Append(GetProgressText(id)).Append(""); - pos += 4.5f; - - builder.AppendFormat("", pos).Append(GetVitalText(id, true)).Append(""); - pos += DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.English ? 14f : 10.5f; - - builder.AppendFormat("", pos); - - var oldrole = thisdata.RoleWhenAlive ?? RoleTypes.Crewmate; - var newrole = thisdata.RoleAfterDeath ?? (thisdata.IsImpostor? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost); - builder.Append(StringHelper.ColorString(GetRoleColor(oldrole), GetString($"{oldrole}"))); + if (!AmongUsClient.Instance.AmHost) return; + var player = PlayerControl.LocalPlayer; + var name = player.Data.PlayerName; + player.SetName(title + '\0'); + DestroyableSingleton.Instance?.Chat?.AddChat(player, text); + player.SetName(name); + } - if (thisdata.IsDead && newrole != oldrole) - { - builder.Append($"=> {StringHelper.ColorString(GetRoleColor(newrole), GetRoleString($"{newrole}"))}"); - } - builder.Append(""); + #endregion - return builder.ToString(); - } + #region Sprite - public static Dictionary CachedSprites = new(); + private static readonly Dictionary CachedSprites = new(); public static Sprite LoadSprite(string file, float pixelsPerUnit = 1f) { @@ -187,7 +234,8 @@ public static Sprite LoadSprite(string file, float pixelsPerUnit = 1f) { if (CachedSprites.TryGetValue(file + pixelsPerUnit, out var sprite)) return sprite; var texture = LoadTextureFromResources(file); - sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); + sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), + pixelsPerUnit); sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor; return CachedSprites[file + pixelsPerUnit] = sprite; } @@ -195,6 +243,7 @@ public static Sprite LoadSprite(string file, float pixelsPerUnit = 1f) { Error($"读入Texture失败:{file}", "LoadImage"); } + return null; } @@ -206,13 +255,10 @@ public static Texture2D LoadTextureFromResources(string file) { if (!File.Exists(path)) goto InDLL; - + var fileData = File.ReadAllBytes(path); var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false); - if (texture.LoadImage(fileData)) - { - return texture; - } + if (texture.LoadImage(fileData)) return texture; Warn($"无法读取图片:{path}", "LoadTexture"); } @@ -220,6 +266,7 @@ public static Texture2D LoadTextureFromResources(string file) { Warn($"读入Texture失败:{path} - {ex.Message}", "LoadTexture"); } + InDLL: /*path = "FinalSuspect.Resources.Images." + file; @@ -239,51 +286,9 @@ public static Texture2D LoadTextureFromResources(string file) return null; } - /// - /// 乱数の簡易的なヒストグラムを取得する関数 - /// 生成した乱数を格納したint配列 - /// ヒストグラムの倍率 大量の乱数を扱う場合、この値を下げることをお勧めします。 - /// - - public static bool TryCast(this Il2CppObjectBase obj, out T casted) - where T : Il2CppObjectBase - { - casted = obj.TryCast(); - return casted != null; - } - - //private const string ActiveSettingsSize = "70%"; - //private const string ActiveSettingsLineHeight = "55%"; + #endregion - public static bool AmDev() => IsDev(EOSManager.Instance.FriendCode); - public static bool IsDev(this PlayerControl pc) => IsDev(pc.FriendCode); - public static bool IsDev(string friendCode) => friendCode - is "teamelder#5856" //Slok - or "cloakhazy#9133"; //LezaiYa - - public static void AddChatMessage(string text, string title = "") - { - if (!AmongUsClient.Instance.AmHost) return; - var player = PlayerControl.LocalPlayer; - var name = player.Data.PlayerName; - player.SetName(title + '\0'); - DestroyableSingleton.Instance?.Chat?.AddChat(player, text); - player.SetName(name); - } - - private static Dictionary cachedPlayers = new(); - - public static PlayerControl GetPlayerById(int playerId) => GetPlayerById((byte)playerId); - public static PlayerControl GetPlayerById(byte playerId) - { - if (cachedPlayers.TryGetValue(playerId, out var cachedPlayer) && cachedPlayer != null) - { - return cachedPlayer; - } - var player = Main.AllPlayerControls.Where(pc => pc.PlayerId == playerId).FirstOrDefault(); - cachedPlayers[playerId] = player; - return player; - } + #region Game Play public static string GetProgressText(PlayerControl pc = null) { @@ -293,43 +298,36 @@ public static string GetProgressText(PlayerControl pc = null) var comms = IsActive(SystemTypes.Comms); var text = GetProgressText(pc.PlayerId, comms); - return enable? text : ""; + return enable ? text : ""; } private static string GetProgressText(byte playerId, bool comms = false) { - return GetTaskProgressText(playerId, comms); - } - - public static string GetTaskProgressText(byte playerId, bool comms = false) - { - var data = XtremePlayerData.GetXtremeDataById(playerId); + var data = GetFinalDataById(playerId); if (!IsNormalGame) { - if (data.IsImpostor) - { - var KillColor = Palette.ImpostorRed; - return StringHelper.ColorString(KillColor, $"({GetString("KillCount")}: {data.KillCount})"); - } - return ""; + if (!data.IsImpostor) return ""; + var KillColor = Palette.ImpostorRed; + return StringHelper.ColorString(KillColor, $"({GetString("KillCount")}: {data.ProcessInt})"); } - + if (data.IsImpostor) { var KillColor = data.IsDisconnected ? Color.gray : Palette.ImpostorRed; - return StringHelper.ColorString(KillColor, $"({GetString("KillCount")}: {data.KillCount})"); + return StringHelper.ColorString(KillColor, $"({GetString("KillCount")}: {data.ProcessInt})"); } + var NormalColor = data.TaskCompleted ? Color.green : Color.yellow; var TextColor = comms || data.IsDisconnected ? Color.gray : NormalColor; - var Completed = comms ? "?" : $"{data.CompleteTaskCount}"; + var Completed = comms ? "?" : $"{data.ProcessInt}"; return StringHelper.ColorString(TextColor, $"({Completed}/{data.TotalTaskCount})"); } - - public static string GetVitalText(byte playerId, bool summary = false, bool docolor = true) + + public static string GetVitalText(byte playerId, bool summary = false, bool doColor = true) { - var data = XtremePlayerData.GetXtremeDataById(playerId); + var data = GetFinalDataById(playerId); if (!data.IsDead || data.RealDeathReason is VanillaDeathReason.None) return ""; - + var deathReason = GetString("DeathReason." + data.RealDeathReason); var color = Palette.CrewmateBlue; switch (data.RealDeathReason) @@ -339,97 +337,106 @@ public static string GetVitalText(byte playerId, bool summary = false, bool doco break; case VanillaDeathReason.Kill: color = Palette.ImpostorRed; - var killercolor = Palette.PlayerColors[data.RealKiller.ColorId]; + var killerColor = Palette.PlayerColors[data.RealKiller.ColorId]; if (summary) - deathReason += $"<={StringHelper.ColorString(killercolor, data.RealKiller.Name)}"; - else if (docolor) - deathReason = StringHelper.ColorString(killercolor, deathReason); + deathReason += $"<={StringHelper.ColorString(killerColor, data.RealKiller.Name)}"; + else if (doColor) + deathReason = StringHelper.ColorString(killerColor, deathReason); break; case VanillaDeathReason.Exile: color = Palette.Purple; break; } + if (!summary) deathReason = "(" + deathReason + ")"; - - deathReason = StringHelper.ColorString(color, deathReason) ; + + deathReason = StringHelper.ColorString(color, deathReason); return deathReason; } + public static bool IsActive(SystemTypes type) { if (!IsNormalGame) return false; - if (!ShipStatus.Instance.Systems.ContainsKey(type)) - { - return false; - } + if (!ShipStatus.Instance.Systems.ContainsKey(type)) return false; + int mapId = Main.NormalOptions.MapId; switch (type) { case SystemTypes.Electrical: { var SwitchSystem = ShipStatus.Instance.Systems[type].Cast(); - return SwitchSystem != null && SwitchSystem.IsActive; + return SwitchSystem is { IsActive: true }; } case SystemTypes.Reactor: { if (mapId == 2) return false; var ReactorSystemType = ShipStatus.Instance.Systems[type].Cast(); - return ReactorSystemType != null && ReactorSystemType.IsActive; + return ReactorSystemType is { IsActive: true }; } - case SystemTypes.Laboratory: + case SystemTypes.Laboratory: { if (mapId != 2) return false; var ReactorSystemType = ShipStatus.Instance.Systems[type].Cast(); - return ReactorSystemType != null && ReactorSystemType.IsActive; + return ReactorSystemType is { IsActive: true }; } case SystemTypes.LifeSupp: { if (mapId is 2 or 4) return false; var LifeSuppSystemType = ShipStatus.Instance.Systems[type].Cast(); - return LifeSuppSystemType != null && LifeSuppSystemType.IsActive; + return LifeSuppSystemType is { IsActive: true }; } case SystemTypes.Comms: { if (mapId is 1 or 5) { var HqHudSystemType = ShipStatus.Instance.Systems[type].Cast(); - return HqHudSystemType != null && HqHudSystemType.IsActive; + return HqHudSystemType is { IsActive: true }; } + var HudOverrideSystemType = ShipStatus.Instance.Systems[type].Cast(); - return HudOverrideSystemType != null && HudOverrideSystemType.IsActive; + return HudOverrideSystemType is { IsActive: true }; } case SystemTypes.HeliSabotage: { var HeliSabotageSystem = ShipStatus.Instance.Systems[type].Cast(); - return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; + return HeliSabotageSystem && HeliSabotageSystem.IsActive; } case SystemTypes.MushroomMixupSabotage: { - var mushroomMixupSabotageSystem = ShipStatus.Instance.Systems[type].TryCast(); - return mushroomMixupSabotageSystem != null && mushroomMixupSabotageSystem.IsActive; + var mushroomMixupSabotageSystem = + ShipStatus.Instance.Systems[type].TryCast(); + return mushroomMixupSabotageSystem && mushroomMixupSabotageSystem.IsActive; } default: return false; } } + + public static RoleTypes GetRoleType(byte id) + { + return GetRoleById(id); + } + public static bool IsImpostor(RoleTypes role) { return role switch { RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.ImpostorGhost => true, - _ => false, + _ => false }; } + public static bool IsGhost(RoleTypes role) { return role switch { RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel => true, - _ => false, + _ => false }; } - + public static bool CanSeeTargetRole(PlayerControl target, out bool bothImp) { var LocalDead = !PlayerControl.LocalPlayer.IsAlive(); @@ -439,64 +446,184 @@ public static bool CanSeeTargetRole(PlayerControl target, out bool bothImp) return target.IsLocalPlayer() || BothDeathCanSee || - bothImp && LocalDead || - Main.GodMode.Value || + (bothImp && LocalDead) || + Main.GodMode.Value || IsFreePlay; } - + public static bool CanSeeOthersRole() { if (!IsInGame) return true; if (IsFreePlay) return true; var LocalDead = !PlayerControl.LocalPlayer.IsAlive(); var IsAngel = PlayerControl.LocalPlayer.GetRoleType() is RoleTypes.GuardianAngel; - - return !IsAngel && LocalDead || - Main.GodMode.Value || + + return (!IsAngel && LocalDead) || + Main.GodMode.Value || IsFreePlay; } - public static void ExecuteWithTryCatch(this Action action, bool Log = false) + + public static string GetRoleName(RoleTypes role) + { + return GetRoleString(Enum.GetName(typeof(RoleTypes), role)); + } + + public static Color GetRoleColor(RoleTypes role) + { + Main.roleColors.TryGetValue(role, out var hexColor); + _ = ColorUtility.TryParseHtmlString(hexColor, out var c); + return c; + } + + public static string GetRoleColorCode(RoleTypes role) + { + Main.roleColors.TryGetValue(role, out var hexColor); + return hexColor; + } + + public static string GetRoleInfoForVanilla(this RoleTypes role, bool InfoLong = false) + { + if (role is RoleTypes.Crewmate or RoleTypes.Impostor) + InfoLong = false; + + var text = role.ToString(); + var Info = "Blurb" + (InfoLong ? "Long" : ""); + if (IsNormalGame) return GetString($"{text}{Info}"); + + if (InfoLong) + switch (role) + { + case RoleTypes.Engineer: + return $"{GetString(StringNames.RuleOneCrewmates)}" + + $"\n{GetString(StringNames.RuleTwoCrewmates)}" + + $"\n{GetString(StringNames.RuleThreeCrewmates)}"; + case RoleTypes.Impostor: + return $"{GetString(StringNames.RuleOneImpostor)}" + + $"\n{GetString(StringNames.RuleTwoImpostor)}" + + $"\n{GetString(StringNames.RuleThreeImpostor)}"; + } + + text = "HnS" + text; + return GetString($"{text}{Info}"); + } + + public static string SummaryTexts(byte id) + { + var thisData = GetFinalDataById(id); + + var builder = new StringBuilder(); + var longestNameByteCount = GetLongestNameByteCount(); + + var pos = Math.Min((float)longestNameByteCount / 2 + 1.5f, 11.5f); + + var colorId = thisData.ColorId; + builder.Append(StringHelper.ColorString(Palette.PlayerColors[colorId], thisData.Name)); + pos += 1.5f; + builder.Append($"").Append(GetProgressText(id)).Append(""); + pos += 4.5f; + + builder.Append($"").Append(GetVitalText(id, true)).Append(""); + pos += DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.English + ? 14f + : 10.5f; + + builder.Append($""); + + var oldRole = thisData.RoleWhenAlive ?? RoleTypes.Crewmate; + var newRole = thisData.RoleAfterDeath ?? + (thisData.IsImpostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost); + builder.Append(StringHelper.ColorString(GetRoleColor(oldRole), GetRoleString($"{oldRole}"))); + + if (thisData.IsDead && newRole != oldRole) + builder.Append($"=> {StringHelper.ColorString(GetRoleColor(newRole), GetRoleString($"{newRole}"))}"); + + builder.Append(""); + + return builder.ToString(); + } + + private static int GetLongestNameByteCount() + { + return FinalPlayerData.AllPlayerData.Select(data => data.Name.GetByteCount()) + .OrderByDescending(byteCount => byteCount).FirstOrDefault(); + } + + #endregion + + #region FinalGameData + + public static bool ModClient(int id) + { + return GetPlayerVersion(id, out _); + } + + public static bool OtherModClient(int id) + { + return GetPlayerVersion(id, out var ver) && Main.ForkId != ver.forkId; + } + + public static bool IsFinalSuspect(int id) + { + return FinalGameData.PlayerVersion.playerVersion.TryGetValue(id, out var ver) && Main.ForkId == ver.forkId; + } + + public static bool GetPlayerVersion(int id, out FinalGameData.PlayerVersion ver) + { + return FinalGameData.PlayerVersion.playerVersion.TryGetValue(id, out ver) && ver != null; + } + + #endregion + + #region Final Player Data + + public static FinalPlayerData GetFinalDataById(byte id) { try { - action(); + return FinalPlayerData.AllPlayerData.FirstOrDefault(data => data.PlayerId == id); } - catch (Exception ex) + catch { - if (Log) Error(ex.ToString(), "Execute With Try Catch"); + return null; } } - public static void FormatButtonColor(MainMenuManager __instance, PassiveButton button, Color inActiveColor, Color activeColor, Color inActiveTextColor, Color activeTextColor) + public static string GetPlayerNameById(byte id) { - button.activeSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); - button.inactiveSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); - var activeRenderer = button.activeSprites.GetComponent(); - var inActiveRenderer = button.inactiveSprites.GetComponent(); - activeRenderer.sprite = __instance.quitButton.activeSprites.GetComponent().sprite; - inActiveRenderer.sprite = __instance.quitButton.activeSprites.GetComponent().sprite; - activeRenderer.color = activeColor.a == 0f - ? new Color(inActiveColor.r, inActiveColor.g, inActiveColor.b, 1f) - : activeColor; - inActiveRenderer.color = inActiveColor; - button.activeTextColor = activeTextColor; - button.inactiveTextColor = inActiveTextColor; + return GetFinalDataById(id).Name; } - public static void MarkAsCheater(this PlayerControl pc) + public static RoleTypes GetRoleById(byte id) { - pc.GetXtremeData().CheatData.MarkAsCheater(); + var data = GetFinalDataById(id); + var dead = data?.IsDead ?? false; + RoleTypes nullRole; + if (dead && !IsFreePlay) + nullRole = data.IsImpostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + else + nullRole = GetFinalDataById(id).Player.Data.Role.Role; + var role = (dead ? data.RoleAfterDeath : data?.RoleWhenAlive) ?? nullRole; + return role; } public static PlayerCheatData GetCheatDataById(byte id) { try { - return XtremePlayerData.GetXtremeDataById(id)?.CheatData; + return GetFinalDataById(id)?.CheatData; } catch { return null; } } + + #endregion +} + +public enum KickLevel +{ + None, + Notification, + CheatDetected } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/DebugModeManager.cs b/FinalSuspect/Modules/Core/Plugin/DebugModeManager.cs index 81875a9e..6545c1b2 100644 --- a/FinalSuspect/Modules/Core/Plugin/DebugModeManager.cs +++ b/FinalSuspect/Modules/Core/Plugin/DebugModeManager.cs @@ -2,19 +2,17 @@ namespace FinalSuspect.Modules.Core.Plugin; public static class DebugModeManager { - // これが有効の時、通常のゲームに支障のないデバッグ機能(詳細ログ・ゲーム外でのデバッグ表示など)が有効化される。 - // また、ゲーム内オプションでデバッグモードを有効化することができる。 - public static bool AmDebugger { get; private set; } = + // 当此项启用时,将激活不影响正常游戏的调试功能(详细日志/游戏外调试显示等)。 + // 同时,可在游戏内选项菜单中启用调试模式。 + private static bool AmDebugger { get; set; } #if DEBUG - true; -#else - false; + = true; #endif public static bool IsDebugMode => AmDebugger; public static void Auth(HashAuth auth, string input) { - // AmDebugger = デバッグビルドである || デバッグキー認証が通った + // AmDebugger = 启用调试版本 || 通过调试密钥认证 AmDebugger = AmDebugger || auth.CheckString(input); } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/Debugger.cs b/FinalSuspect/Modules/Core/Plugin/Debugger.cs index 384672ed..6db8d336 100644 --- a/FinalSuspect/Modules/Core/Plugin/Debugger.cs +++ b/FinalSuspect/Modules/Core/Plugin/Debugger.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Http; @@ -9,7 +8,7 @@ namespace FinalSuspect.Modules.Core.Plugin; -class Webhook +internal class Webhook { public static void Send(string text) { @@ -26,28 +25,45 @@ public static void Send(string text) awaiter.GetResult(); } } -class XtremeLogger + +internal static class FinalLogger { - public static bool isEnable; - public static List disableList = []; - public static List sendToGameList = []; + private static bool isEnable; + private static readonly List disableList = []; + private static readonly List sendToGameList = []; public static bool isDetail = false; public static bool isAlsoInGame = false; - public static void Enable() => isEnable = true; - public static void Disable() => isEnable = false; + + public static void Enable() + { + isEnable = true; + } + + public static void Disable() + { + isEnable = false; + } + public static void Enable(string tag, bool toGame = false) { disableList.Remove(tag); if (toGame && !sendToGameList.Contains(tag)) sendToGameList.Add(tag); else sendToGameList.Remove(tag); } - public static void Disable(string tag) { if (!disableList.Contains(tag)) disableList.Add(tag); } + + public static void Disable(string tag) + { + if (!disableList.Contains(tag)) disableList.Add(tag); + } + public static void SendInGame(string text) { if (!isEnable) return; NotificationPopperPatch.NotificationPop(text); } - private static void SendToFile(string text, LogLevel level = LogLevel.Info, string tag = "", bool escapeCRLF = true, int lineNumber = 0, string fileName = "") + + private static void SendToFile(string text, LogLevel level = LogLevel.Info, string tag = "", bool escapeCRLF = true, + int lineNumber = 0, string fileName = "") { if (!isEnable || disableList.Contains(tag)) return; var logger = Main.Logger; @@ -56,13 +72,14 @@ private static void SendToFile(string text, LogLevel level = LogLevel.Info, stri if (escapeCRLF) text = text.Replace("\r", "\\r").Replace("\n", "\\n"); var log_text = $"[{t}][{tag}]{text}"; - if (isDetail && DebugModeManager.AmDebugger) + if (isDetail && DebugModeManager.IsDebugMode) { StackFrame stack = new(2); var className = stack.GetMethod()?.ReflectedType?.Name ?? "NullClass"; var memberName = stack.GetMethod()?.Name ?? "NullMember"; log_text = $"[{t}][{className}.{memberName}({Path.GetFileName(fileName)}:{lineNumber})][{tag}]{text}"; } + switch (level) { case LogLevel.Info: @@ -83,32 +100,67 @@ private static void SendToFile(string text, LogLevel level = LogLevel.Info, stri case LogLevel.Debug: logger.LogFatal(log_text); break; + case LogLevel.None: + case LogLevel.All: default: logger.LogWarning("Error:Invalid LogLevel"); logger.LogInfo(log_text); break; } } - public static void Test(object content, string tag = "======= Test =======", bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => - SendToFile(content.ToString(), LogLevel.Debug, tag, escapeCRLF, lineNumber, fileName); - public static void Info(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => + + public static void Test(object content = null, string tag = "======= Test =======", bool escapeCRLF = true, + [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") + { + SendToFile((content ?? "Test Message").ToString(), LogLevel.Debug, tag, escapeCRLF, lineNumber, fileName); + } + + public static void Info(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string fileName = "") + { SendToFile(text, LogLevel.Info, tag, escapeCRLF, lineNumber, fileName); - public static void Warn(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => + } + + public static void Warn(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string fileName = "") + { SendToFile(text, LogLevel.Warning, tag, escapeCRLF, lineNumber, fileName); - public static void Error(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => + } + + public static void Error(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string fileName = "") + { SendToFile(text, LogLevel.Error, tag, escapeCRLF, lineNumber, fileName); - public static void Fatal(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => + } + + public static void Fatal(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string fileName = "") + { SendToFile(text, LogLevel.Fatal, tag, escapeCRLF, lineNumber, fileName); - public static void Msg(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => + } + + public static void Msg(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string fileName = "") + { SendToFile(text, LogLevel.Message, tag, escapeCRLF, lineNumber, fileName); - public static void Exception(Exception ex, string tag, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") => + } + + public static void Exception(Exception ex, string tag, [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string fileName = "") + { SendToFile(ex.ToString(), LogLevel.Error, tag, false, lineNumber, fileName); + } + public static void CurrentMethod([CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") { StackFrame stack = new(1); - Msg($"\"{stack.GetMethod()?.ReflectedType?.Name}.{stack.GetMethod()?.Name}\" Called in \"{Path.GetFileName(fileName)}({lineNumber})\"", "Method"); + Msg( + $"\"{stack.GetMethod()?.ReflectedType?.Name}.{stack.GetMethod()?.Name}\" Called in \"{Path.GetFileName(fileName)}({lineNumber})\"", + "Method"); } public static LogHandler.LogHandler Handler(string tag) - => new(tag); + { + return new LogHandler.LogHandler(tag); + } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/ErrorText.cs b/FinalSuspect/Modules/Core/Plugin/ErrorText.cs index c46bf2ea..f14fc9e3 100644 --- a/FinalSuspect/Modules/Core/Plugin/ErrorText.cs +++ b/FinalSuspect/Modules/Core/Plugin/ErrorText.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using TMPro; using UnityEngine; @@ -7,29 +5,35 @@ namespace FinalSuspect.Modules.Core.Plugin; public class ErrorText : MonoBehaviour { - #region Singleton - public static ErrorText Instance + public TextMeshPro Text; + public Camera Camera; + public Vector3 TextOffset = new(0, 0.3f, -1000f); + + public bool CheatDetected; + public bool SBDetected; + private readonly List AllErrors = []; + + public void Update() { - get - { - return _instance; - } + AllErrors.ForEach(err => err.IncreaseTimer()); + var ToRemove = AllErrors.Where(err => err.ErrorLevel <= 1 && 30f < err.Timer); + var errorDatas = ToRemove.ToList(); + if (!errorDatas.Any()) return; + AllErrors.RemoveAll(errorDatas.Contains); + UpdateText(); } - private static ErrorText _instance; - private void Awake() + public void LateUpdate() { - if (_instance != null) - { - Destroy(gameObject); - } - else - { - _instance = this; - DontDestroyOnLoad(this); - } + if (!Text.enabled) return; + + if (!Camera) + Camera = !HudManager.InstanceExists ? Camera.main : HudManager.Instance.PlayerCam.GetComponent(); + if (Camera) + transform.position = + AspectPosition.ComputeWorldPosition(Camera, AspectPosition.EdgeAlignments.Top, TextOffset); } - #endregion + public static void Create(TextMeshPro baseText) { var Text = Instantiate(baseText); @@ -44,45 +48,20 @@ public static void Create(TextMeshPro baseText) Text.outlineColor = Color.black; Text.alignment = TextAlignmentOptions.Top; } - public TextMeshPro Text; - public Camera Camera; - public List AllErrors = []; - public Vector3 TextOffset = new(0, 0.3f, -1000f); - public void Update() - { - AllErrors.ForEach(err => err.IncreaseTimer()); - var ToRemove = AllErrors.Where(err => err.ErrorLevel <= 1 && 30f < err.Timer); - var errorDatas = ToRemove.ToList(); - if (errorDatas.Any()) - { - AllErrors.RemoveAll(errorDatas.Contains); - UpdateText(); - } - } - public void LateUpdate() - { - if (!Text.enabled) return; - if (Camera == null) - Camera = !HudManager.InstanceExists ? Camera.main : HudManager.Instance.PlayerCam.GetComponent(); - if (Camera != null) - { - transform.position = AspectPosition.ComputeWorldPosition(Camera, AspectPosition.EdgeAlignments.Top, TextOffset); - } - } public void AddError(ErrorCode code) { var error = new ErrorData(code); //if (0 < error.ErrorLevel) // Error($"エラー発生: {error}: {error.Message}", "ErrorText"); - if (!AllErrors.Any(e => e.Code == code)) - { + if (AllErrors.All(e => e.Code != code)) //まだ出ていないエラー AllErrors.Add(error); - } + UpdateText(); } + public void UpdateText() { var text = ""; @@ -92,6 +71,7 @@ public void UpdateText() text += $"{err}: {err.Message}\n"; if (maxLevel < err.ErrorLevel) maxLevel = err.ErrorLevel; } + if (maxLevel == 0) { Text.enabled = false; @@ -100,27 +80,28 @@ public void UpdateText() { text += $"{GetString($"ErrorLevel{maxLevel}")}"; if (CheatDetected) - text = SBDetected ? GetString("FAC.CheatDetected.HighLevel") : GetString("FAC.CheatDetected.LowLevel"); + text = SBDetected ? GetString("CheatDetected.HighLevel") : GetString("FAC.CheatDetected.LowLevel"); Text.enabled = true; } + if (IsInGame && maxLevel != 3 && !CheatDetected) text += $"\n{GetString("TerminateCommand")}: Shift+L+Enter"; Text.text = text; } + public void Clear() { AllErrors.RemoveAll(err => err.ErrorLevel != 3); UpdateText(); } - public class ErrorData + private class ErrorData { public readonly ErrorCode Code; - public readonly int ErrorType1; - public readonly int ErrorType2; public readonly int ErrorLevel; - public float Timer { get; private set; } - public string Message => GetString(ToString()); + private readonly int ErrorType1; + private readonly int ErrorType2; + public ErrorData(ErrorCode code) { Code = code; @@ -129,33 +110,60 @@ public ErrorData(ErrorCode code) ErrorLevel = (int)code - (int)code / 10 * 10; Timer = 0f; } + + public float Timer { get; private set; } + public string Message => GetString(ToString()); + public override string ToString() { // ERR-xxx-yyy-z return $"ERR-{ErrorType1:000}-{ErrorType2:000}-{ErrorLevel:0}"; } - public void IncreaseTimer() => Timer += Time.deltaTime; + + public void IncreaseTimer() + { + Timer += Time.deltaTime; + } } - public bool CheatDetected; - public bool SBDetected; + #region Singleton + + public static ErrorText Instance { get; private set; } + + private void Awake() + { + if (Instance) + { + Destroy(gameObject); + } + else + { + Instance = this; + DontDestroyOnLoad(this); + } + } + + #endregion } + public enum ErrorCode { //xxxyyyz: ERR-xxx-yyy-z - // xxx: エラー大まかなの種類 (HUD関連, 追放処理関連など) - // yyy: エラーの詳細な種類 (BoutyHunterの処理, SerialKillerの処理など) - // z: 深刻度 - // 0: 処置不要 (非表示) - // 1: 正常に動作しなければ廃村 (一定時間で非表示) - // 2: 廃村を推奨 (廃村で非表示) - // 3: ユーザー側では対処不能 (消さない) - // ========== - // 001 Main - Main_DictionaryError = 0010003, // 001-000-3 Main Dictionary Error - OptionIDDuplicate = 001_010_3, // 001-010-3 オプションIDが重複している(DEBUGビルド時のみ) - // 002 サポート関連 - UnsupportedVersion = 002_000_1, // 002-000-1 AmongUsのバージョンが古い + // xxx: 错误大类 + // yyy: 错误细类 + // z: 严重等级 + // 0: 无需处理 (不显示) + // 1: 运行异常需终止对局 (短暂显示) + // 2: 建议终止对局 (终止后隐藏) + // 3: 用户无法处理 (需持续显示) + // ============= + // 001 主系统 + Main_DictionaryError = 0010003, // 001-000-3 主字典错误 + OptionIDDuplicate = 001_010_3, // 001-010-3 选项ID重复(仅调试版本生效) + + // 002 兼容支持 + UnsupportedVersion = 002_000_1, // 002-000-1 AmongUs版本过旧 + // ========== // 000 Test NoError = 0000000, // 000-000-0 No Error @@ -164,5 +172,5 @@ public enum ErrorCode TestError2 = 0009202, // 000-920-2 Test Error 2 TestError3 = 0009303, // 000-930-3 Test Error 3 CheatDetected = 000_666_2, // 000-666-2 疑似存在作弊玩家 - SBDetected = 000_666_1, // 000-666-1 傻逼外挂司马东西 + SBDetected = 000_666_1 // 000-666-1 傻逼外挂司马东西 } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/HashAuth.cs b/FinalSuspect/Modules/Core/Plugin/HashAuth.cs index 34aec50c..e5ea6aa2 100644 --- a/FinalSuspect/Modules/Core/Plugin/HashAuth.cs +++ b/FinalSuspect/Modules/Core/Plugin/HashAuth.cs @@ -8,7 +8,7 @@ public class HashAuth(string hashValue, string salt = null, HashAlgorithm algori private readonly HashAlgorithm algorithm = algorithm ?? SHA256.Create(); /// - /// 验证字符串是否匹配哈希值 + /// 验证字符串是否匹配哈希值 /// public bool CheckString(string value) { @@ -17,18 +17,20 @@ public bool CheckString(string value) } /// - /// 计算字符串的哈希值 + /// 计算字符串的哈希值 /// private string CalculateHash(string source) - => CalculateHash(source, salt, algorithm); + { + return CalculateHash(source, salt, algorithm); + } /// - /// 计算带盐值的哈希值 + /// 计算带盐值的哈希值 /// /// 源字符串 /// 盐值 /// 哈希算法实例 - private static string CalculateHash(string source, string salt = null, HashAlgorithm algorithm = null) + private static string CalculateHash(string source, string salt, HashAlgorithm algorithm = null) { // 初始化算法 algorithm ??= SHA256.Create(); @@ -51,12 +53,12 @@ private static string CalculateHash(string source, string salt = null, HashAlgor } /// - /// 通过未哈希值创建验证器(仅用于测试) + /// 通过未哈希值创建验证器(仅用于测试) /// /// 原始值 /// 盐值 /// - /// 此方法会同时生成哈希值并输出日志,仅用于开发测试阶段 + /// 此方法会同时生成哈希值并输出日志,仅用于开发测试阶段 /// public static HashAuth CreateByUnhashedValue(string value, string salt = null) { diff --git a/FinalSuspect/Modules/Core/Plugin/LateTask.cs b/FinalSuspect/Modules/Core/Plugin/LateTask.cs index 267a933c..49c3028c 100644 --- a/FinalSuspect/Modules/Core/Plugin/LateTask.cs +++ b/FinalSuspect/Modules/Core/Plugin/LateTask.cs @@ -1,23 +1,14 @@ using System; -using System.Collections.Generic; namespace FinalSuspect.Modules.Core.Plugin; internal class LateTask { + private static readonly List Tasks = []; + private readonly Action action; private readonly string name; private float timer; - private readonly Action action; - private static readonly List Tasks = []; - private bool Run(float deltaTime) - { - timer -= deltaTime; - if (!(timer <= 0)) return false; - action(); - return true; - } - public LateTask(Action action, float time, string name = "No Name Task") { this.action = action; @@ -27,12 +18,19 @@ public LateTask(Action action, float time, string name = "No Name Task") if (name != "") Info("\"" + name + "\" is created", "LateTask"); } - + + private bool Run(float deltaTime) + { + timer -= deltaTime; + if (!(timer <= 0)) return false; + action(); + return true; + } + public static void Update(float deltaTime) { var TasksToRemove = new List(); foreach (var task in Tasks) - { try { if (!task.Run(deltaTime)) continue; @@ -45,7 +43,7 @@ public static void Update(float deltaTime) Error($"{ex.GetType()}: {ex.Message} in \"{task.name}\"\n{ex.StackTrace}", "LateTask.Error", false); TasksToRemove.Add(task); } - } + TasksToRemove.ForEach(task => Tasks.Remove(task)); } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/MainThreadTask.cs b/FinalSuspect/Modules/Core/Plugin/MainThreadTask.cs index ecd64b92..54b5acab 100644 --- a/FinalSuspect/Modules/Core/Plugin/MainThreadTask.cs +++ b/FinalSuspect/Modules/Core/Plugin/MainThreadTask.cs @@ -1,44 +1,49 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace FinalSuspect.Modules.Core.Plugin; public class MainThreadTask { - private readonly string name; - private readonly Action action; private static readonly List Tasks = []; - + private readonly Action action; + private readonly string name; + + /// + /// 用于异步线程的类型 + /// + /// 需要转到主线程执行的行为 + /// 本次行为名称,会输出日志 public MainThreadTask(Action action, string name = "No Name Task") { this.action = action; this.name = name; - + if (name != "") Info("\"" + name + "\" is created", "Main Thread Task"); Tasks.Add(this); } + public static void Update() { var TasksToRemove = new List(); - // 创建原集合的副本用于遍历 - foreach (var task in Tasks.ToList()) // 关键修复:.ToList() 创建副本 + foreach (var task in Tasks.ToList()) { try { task.action(); if (task.name != "") Info($"\"{task.name}\" is finished", "Main Thread Task"); - TasksToRemove.Add(task); } catch (Exception ex) { - Error($"{ex.GetType()}: {ex.Message} in \"{task.name}\"\n{ex.StackTrace}", "Main Thread Task.Error", false); - TasksToRemove.Add(task); + Error($"{ex.GetType()}: {ex.Message} in \"{task.name}\"\n{ex.StackTrace}", + "Main Thread Task.Error", + false); } + + TasksToRemove.Add(task); } - // 安全移除已处理的任务 + TasksToRemove.ForEach(task => Tasks.Remove(task)); } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/ModMainMenuManager.cs b/FinalSuspect/Modules/Core/Plugin/ModMainMenuManager.cs new file mode 100644 index 00000000..d5bbeead --- /dev/null +++ b/FinalSuspect/Modules/Core/Plugin/ModMainMenuManager.cs @@ -0,0 +1,35 @@ +using UnityEngine; + +namespace FinalSuspect.Modules.Core.Plugin; + +public static class ModMainMenuManager +{ + public static MainMenuManager Instance; + + public static GameObject InviteButton; + public static GameObject GithubButton; + public static GameObject UpdateButton; + public static GameObject PlayButton; + public static GameObject FriendsButton; + public static readonly List MainMenuCustomButtons = []; + + public static bool isOnline; + public static bool ShowedBak; + public static bool ShowingPanel; + + public static GameObject ModStamp; + public static GameObject FinalSuspect_Background; + public static GameObject Ambience; + public static GameObject Starfield; + public static GameObject LeftPanel; + public static GameObject RightPanel; + public static GameObject CloseRightButton; + public static GameObject Tint; + public static GameObject Sizer; + public static GameObject AULogo; + public static GameObject BottomButtonBounds; + + public static Vector3 RightPanelOp = new(2.8f, -0.4f, -5.0f); + public static Dictionary AllButtons = new(); + public static bool Active = true; +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/RegistryManager.cs b/FinalSuspect/Modules/Core/Plugin/RegistryManager.cs index fbd60937..9478fd7e 100644 --- a/FinalSuspect/Modules/Core/Plugin/RegistryManager.cs +++ b/FinalSuspect/Modules/Core/Plugin/RegistryManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Microsoft.Win32; @@ -8,9 +7,9 @@ namespace FinalSuspect.Modules.Core.Plugin; # pragma warning disable CA1416 public static class RegistryManager { - public static RegistryKey SoftwareKeys => Registry.CurrentUser.OpenSubKey("Software", true); - public static RegistryKey Keys = SoftwareKeys.OpenSubKey("AU-FinalSuspect", true); - public static Version LastVersion; + private static RegistryKey Keys = SoftwareKeys.OpenSubKey("AU-FinalSuspect", true); + private static Version LastVersion; + private static RegistryKey SoftwareKeys => Registry.CurrentUser.OpenSubKey("Software", true); public static void Init() { @@ -19,28 +18,31 @@ public static void Init() Info("Create FinalSuspect Registry Key", "Registry Manager"); Keys = SoftwareKeys.CreateSubKey("AU-FinalSuspect", true); } + if (Keys == null) { Error("Create Registry Failed", "Registry Manager"); return; } - if (Keys.GetValue("Last launched version") is not string regLastVersion) - LastVersion = new Version(0, 0, 0); - else LastVersion = Version.Parse(regLastVersion); + LastVersion = Keys.GetValue("Last launched version") is not string regLastVersion + ? new Version(0, 0, 0) + : Version.Parse(regLastVersion); Keys.SetValue("Last launched version", Main.version.ToString()); Keys.SetValue("Path", Path.GetFullPath("./")); - List FoldersNFileToDel =[@"./TOH_DATA",]; + List FoldersNFileToDel = []; Info("上次启动的FinalSuspect版本:" + LastVersion, "Registry Manager"); - if (LastVersion < new Version(1, 0, 0)) +#if RELEASE + if (LastVersion < new Version(1, 2, 0)) { - Warn("v1.0 New Version Operation Needed", "Registry Manager"); - FoldersNFileToDel.Add(@"./BepInEx/config"); + Warn("v1.2 New Version Operation Needed", "Registry Manager"); + FoldersNFileToDel.Add("./BepInEx/config"); } +#endif FoldersNFileToDel.DoIf(Directory.Exists, p => { diff --git a/FinalSuspect/Modules/Core/Plugin/SystemEnvironment.cs b/FinalSuspect/Modules/Core/Plugin/SystemEnvironment.cs index 2d1b3cce..f16e50ab 100644 --- a/FinalSuspect/Modules/Core/Plugin/SystemEnvironment.cs +++ b/FinalSuspect/Modules/Core/Plugin/SystemEnvironment.cs @@ -8,11 +8,15 @@ public static class SystemEnvironment public static async Task SetEnvironmentVariablesAsync() { // 将最近打开的 FinalSuspect 应用程序文件夹的路径设置为用户环境变量 - await Task.Run(() => Environment.SetEnvironmentVariable("FINAL_SUSPECT_DIR_ROOT", Environment.CurrentDirectory, EnvironmentVariableTarget.User)); + await Task.Run(() => + Environment.SetEnvironmentVariable("FINAL_SUSPECT_DIR_ROOT", Environment.CurrentDirectory, + EnvironmentVariableTarget.User)); await Task.Run(() => Info("ROOT SET COMPLETE", "SetEnvironmentVariables")); // 将日志文件夹的路径设置为用户环境变量 var logFolderPath = await Task.Run(() => GetLogFolder().FullName); - await Task.Run(() => Environment.SetEnvironmentVariable("FINAL_SUSPECT_DIR_LOGS", logFolderPath, EnvironmentVariableTarget.User)); + await Task.Run(() => + Environment.SetEnvironmentVariable("FINAL_SUSPECT_DIR_LOGS", logFolderPath, + EnvironmentVariableTarget.User)); await Task.Run(() => Info("LOGS SET COMPLETE", "SetEnvironmentVariables")); } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Core/Plugin/Translator.cs b/FinalSuspect/Modules/Core/Plugin/Translator.cs index 7ebec515..83a48192 100644 --- a/FinalSuspect/Modules/Core/Plugin/Translator.cs +++ b/FinalSuspect/Modules/Core/Plugin/Translator.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Text; -using AmongUs.Data; using FinalSuspect.Helpers; using FinalSuspect.Modules.Resources; using Il2CppInterop.Runtime.InteropTypes.Arrays; @@ -15,93 +12,119 @@ namespace FinalSuspect.Modules.Core.Plugin; public static class Translator { - public static Dictionary> TranslateMaps = new(); - public const string LANGUAGE_FOLDER_NAME = LocalPath_Data + "Language"; - + private static readonly Dictionary> TranslateMaps = new(); + public static void TranslatorInit() { Info("加载语言文件...", "Translator"); LoadLangs(); - Info("加载语言文件成功", "Translator"); + Info("加载语言文件完成", "Translator"); } + public static void LoadLangs() { - var fileNames = Directory.GetFiles(GetLocalPath(LocalType.Resources) + "Languages"); - foreach (var file in fileNames) - { - var fileName = Path.GetFileName(file); - var yaml = new YamlStream(); - yaml.Load(new StringReader(new StreamReader(file).ReadToEnd())); - var mapping = (YamlMappingNode)yaml.Documents[0].RootNode; + var langDir = Path.Combine(GetLocalPath(LocalType.Resources), "Languages"); + if (!Directory.Exists(langDir)) return; + foreach (var filePath in Directory.GetFiles(langDir)) + { + var fileName = Path.GetFileNameWithoutExtension(filePath); var langId = -1; - var dic = new Dictionary(); - foreach (var entry in mapping.Children) + foreach (var lang in EnumHelper.GetAllValues()) + if (fileName == lang.ToString()) + langId = (int)lang; + + if (langId == -1) + continue; + try { - (var key, var value) = (((YamlScalarNode)entry.Key).Value, ((YamlScalarNode)entry.Value).Value); + using var reader = new StreamReader(filePath); + var yaml = new YamlStream(); + yaml.Load(new StringReader(reader.ReadToEnd())); - if (key == "LangID") + var dic = new Dictionary(); + var mapping = (YamlMappingNode)yaml.Documents[0].RootNode; + + foreach (var entry in mapping.Children) { - langId = int.Parse(value ?? string.Empty); - continue; + var keyNode = (YamlScalarNode)entry.Key; + var valueNode = (YamlScalarNode)entry.Value; + + if (keyNode.Value == "LangID") continue; + + if (!dic.TryAdd(keyNode.Value, valueNode.Value)) + Warn($"翻译文件 [{fileName}] 出现重复字符串: {keyNode.Value}", "Translator"); } - if (!dic.TryAdd(key, value)) - Warn($"翻译文件 [{fileName}] 出现重复字符串 => {key} / {value}", "Translator"); + // 更新翻译映射 + TranslateMaps[langId] = dic; + Info($"成功加载语言: {fileName} ({dic.Count} 个条目)", "Translator"); } - - if (langId != -1) + catch (Exception ex) { - TranslateMaps.Remove(langId); - TranslateMaps.Add(langId, dic); + Error($"加载语言文件失败: {Path.GetFileName(filePath)}\n{ex.Message}", "Translator"); } - else - Error($"翻译文件 [{fileName}] 没有提供语言ID", "Translator"); } - // カスタム翻訳ファイルの読み込み - if (!Directory.Exists(LANGUAGE_FOLDER_NAME)) Directory.CreateDirectory(LANGUAGE_FOLDER_NAME); - - // 翻訳テンプレートの作成 + // 处理自定义翻译 CreateTemplateFile(); - foreach (var lang in EnumHelper.GetAllValues()) + var customLangDir = Path.Combine(".", LANGUAGE_FOLDER_NAME); + + if (!Directory.Exists(customLangDir)) return; { - if (File.Exists(@$"./{LANGUAGE_FOLDER_NAME}/{lang}.dat")) - LoadCustomTranslation($"{lang}.dat", lang); + foreach (var lang in Enum.GetValues(typeof(SupportedLangs)).Cast()) + { + var customFile = Path.Combine(customLangDir, $"{lang}.dat"); + if (File.Exists(customFile)) LoadCustomTranslation(customFile, lang); + } } } - // ReSharper restore Unity.ExpensiveCode + + public static bool IsChineseUser => GetUserLangByRegion() == SupportedLangs.SChinese; + + public static bool IsChineseLanguageUser => + GetUserLangByRegion() is SupportedLangs.SChinese or SupportedLangs.TChinese; + public static string GetString(string s, Dictionary replacementDic = null, bool console = false) { var langId = TranslationController.Instance?.currentLanguage?.languageID ?? GetUserLangByRegion(); if (console) langId = SupportedLangs.SChinese; var str = GetString(s, langId); - if (replacementDic != null) - foreach (var rd in replacementDic) - str = str.Replace(rd.Key, rd.Value); - return str; + if (replacementDic == null) return str; + return replacementDic.Aggregate(str, (current, rd) => current.Replace(rd.Key, rd.Value)); } - public static string GetString(string str, SupportedLangs langId) + private static string GetString(string str, SupportedLangs langId) { var res = $""; - + try { // 在当前语言中寻找翻译 if (TranslateMaps[(int)langId].TryGetValue(str, out var trans)) + { res = trans; + } // 繁中用户寻找简中翻译替代 - else if (langId is SupportedLangs.TChinese && TranslateMaps[(int)SupportedLangs.SChinese].TryGetValue(str, out trans)) + else if (langId is SupportedLangs.TChinese && + TranslateMaps[(int)SupportedLangs.SChinese].TryGetValue(str, out trans)) + { res = "*" + trans; + } // 非中文用户寻找英语翻译替代 - else if (langId is not SupportedLangs.English and not SupportedLangs.TChinese && TranslateMaps[(int)SupportedLangs.English].TryGetValue(str, out trans)) + else if (langId is not SupportedLangs.English and not SupportedLangs.TChinese && + TranslateMaps[(int)SupportedLangs.English].TryGetValue(str, out trans)) + { res = "*" + trans; + } // 非中文用户寻找中文(原生)字符串替代 - else if (langId is not SupportedLangs.SChinese && TranslateMaps[(int)SupportedLangs.SChinese].TryGetValue(str, out trans)) + else if (langId is not SupportedLangs.SChinese && + TranslateMaps[(int)SupportedLangs.SChinese].TryGetValue(str, out trans)) + { res = "*" + trans; + } // 在游戏自带的字符串中寻找 else { @@ -117,19 +140,20 @@ public static string GetString(string str, SupportedLangs langId) Error("Error:\n" + Ex, "Translator"); } - if (langId is SupportedLangs.SChinese && DataManager.Settings.Gameplay.streamerMode) - res = res.Replace("死", "寄"); return res; } + public static string GetString(StringNames stringName) - => DestroyableSingleton.Instance.GetString(stringName, new Il2CppReferenceArray(0)); - public static string GetRoleString(string str, bool forUser = true) { - var CurrentLanguage = TranslationController.Instance?.currentLanguage?.languageID ?? SupportedLangs.English; - var lang = forUser ? CurrentLanguage : SupportedLangs.SChinese; + return DestroyableSingleton.Instance.GetString(stringName, + new Il2CppReferenceArray(0)); + } - return GetString(str, lang); + public static string GetRoleString(string str) + { + return GetString($"Role.{str}"); } + public static SupportedLangs GetUserLangByRegion() { try @@ -149,29 +173,26 @@ public static SupportedLangs GetUserLangByRegion() return SupportedLangs.English; } } - public static bool IsChineseUser => GetUserLangByRegion() == SupportedLangs.SChinese; - public static bool IsChineseLanguageUser => GetUserLangByRegion() is SupportedLangs.SChinese or SupportedLangs.TChinese; - public static void LoadCustomTranslation(string filename, SupportedLangs lang) + + private static void LoadCustomTranslation(string filename, SupportedLangs lang) { - var path = @$"./{LANGUAGE_FOLDER_NAME}/{filename}"; + var path = $"./{LANGUAGE_FOLDER_NAME}/{filename}"; if (File.Exists(path)) { Info($"加载自定义翻译文件:{filename}", "LoadCustomTranslation"); using StreamReader sr = new(path, Encoding.GetEncoding("UTF-8")); - string text; - while ((text = sr.ReadLine()) != null) + while (sr.ReadLine() is { } text) { var tmp = text.Split(":"); - if (tmp.Length > 1 && tmp[1] != "") + if (tmp.Length <= 1 || tmp[1] == "") continue; + try { - try - { - TranslateMaps[(int)lang][tmp[0]] = tmp.Skip(1).Join(delimiter: ":").Replace("\\n", "\n").Replace("\\r", "\r"); - } - catch (KeyNotFoundException) - { - Warn($"无效密钥:{tmp[0]}", "LoadCustomTranslation"); - } + TranslateMaps[(int)lang][tmp[0]] = + tmp.Skip(1).Join(delimiter: ":").Replace("\\n", "\n").Replace("\\r", "\r"); + } + catch (KeyNotFoundException) + { + Warn($"无效密钥:{tmp[0]}", "LoadCustomTranslation"); } } } @@ -185,8 +206,9 @@ private static void CreateTemplateFile() { var sb = new StringBuilder(); foreach (var title in TranslateMaps) sb.Append($"{title.Key}:\n"); - File.WriteAllText(@$"./{LANGUAGE_FOLDER_NAME}/template.dat", sb.ToString()); + File.WriteAllText($"./{LANGUAGE_FOLDER_NAME}/template.dat", sb.ToString()); } + public static void ExportCustomTranslation() { LoadLangs(); @@ -198,6 +220,7 @@ public static void ExportCustomTranslation() if (!TranslateMaps.ContainsKey((int)lang)) text = ""; sb.Append($"{kvp.Key}:{text}\n"); } - File.WriteAllText(@$"./{LANGUAGE_FOLDER_NAME}/export_{lang}.dat", sb.ToString()); + + File.WriteAllText($"./{LANGUAGE_FOLDER_NAME}/export_{lang}.dat", sb.ToString()); } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Features/CheckingandBlocking/BanManager.cs b/FinalSuspect/Modules/Features/CheckingandBlocking/BanManager.cs index 6c3295c2..d07a5693 100644 --- a/FinalSuspect/Modules/Features/CheckingandBlocking/BanManager.cs +++ b/FinalSuspect/Modules/Features/CheckingandBlocking/BanManager.cs @@ -1,60 +1,57 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; +using FinalSuspect.Helpers; using FinalSuspect.Modules.Core.Game; -using FinalSuspect.Patches.Game_Vanilla; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using InnerNet; namespace FinalSuspect.Modules.Features.CheckingandBlocking; public static class BanManager { - private static readonly string BAN_LIST_PATH = GetBanFilesPath("BanList.json"); - public static List FACList = []; - public static string GetHashedPuid(this PlayerControl player) - => player.GetClient().GetHashedPuid(); - public static string GetHashedPuid(this ClientData player) - { - if (player == null) return null; - var puid = player.ProductUserId; - if (string.IsNullOrEmpty(puid)) return puid; + public static readonly List FACList = []; + + private static readonly Regex ValidFormatRegex = new( + @"^[a-z]{7,10}#\d{4}$", + RegexOptions.Compiled + ); - using var sha256 = SHA256.Create(); - var sha256Hash = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(puid))).Replace("-", "").ToLower(); - return string.Concat(sha256Hash.AsSpan(0, 5), sha256Hash.AsSpan(sha256Hash.Length - 4)); - } public static void AddBanPlayer(ClientData player) { if (!AmongUsClient.Instance.AmHost || player == null) return; - if (player.IsBannedPlayer()) + if (!player.IsBannedPlayer()) { File.AppendAllText(BAN_LIST_PATH, $"{player.FriendCode},{player.GetHashedPuid()},{player.PlayerName}\n"); - SendInGame(string.Format(GetString("Message.AddedPlayerToBanList"), player.PlayerName)); + SendInGame(string.Format(GetString("Notification.AddedPlayerToBanList"), + StringHelper.ColorString(Palette.PlayerColors[player.ColorId], player.PlayerName))); + } + else + { + Info($"{player.FriendCode},{player.GetHashedPuid()},{player.PlayerName} 已经被加入封禁名单", "AddBanPlayer"); } } + public static void CheckDenyNamePlayer(ClientData player) { if (!AmongUsClient.Instance.AmHost || !Main.KickPlayerWithDenyName.Value) return; try { - using StreamReader sr = new(SpamManager.DENY_NAME_LIST_PATH); - string line; - while ((line = sr.ReadLine()) != null) - { - if (line == "") continue; - if (Main.AllPlayerControls.Any(p => p.IsDev() && line.Contains(p.FriendCode))) continue; - if (Regex.IsMatch(player.PlayerName, line)) - { - KickPlayer(player.Id, false, "DenyName"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("Message.KickedByDenyName"), player.PlayerName, line)); - Info($"{player.PlayerName}は名前が「{line}」に一致したためキックされました。", "Kick"); - return; - } - } + var existingNames = SpamManager.ReturnAllNewLinesInFile(DENY_NAME_LIST_PATH); + + var hasValidMatch = existingNames + .Where(name => !IsDevFriendCode(name)) + .Any(IsNameMatch); + + if (!hasValidMatch) return; + KickPlayer(player.Id, false, "KickedByDenyName"); + return; + + bool IsNameMatch(string pattern) => + !string.IsNullOrEmpty(pattern) && player.PlayerName.ToLower().Contains(pattern.ToLower()); + + bool IsDevFriendCode(string name) => + Main.AllPlayerControls.Any(p => p.IsDev() && name.Contains(p.FriendCode)); } catch (Exception ex) { @@ -62,38 +59,58 @@ public static void CheckDenyNamePlayer(ClientData player) } } - public static void CheckBanPlayer(ClientData player) + public static void CheckFriendCode(ClientData player) { - if (AmongUsClient.Instance.AmHost) - { - if (!Main.KickPlayerInBanList.Value) return; - if (player.IsBannedPlayer()) - { - KickPlayer(player.Id, true, "BanList"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("Message.BanedByBanList"), player.PlayerName)); - Info($"{player.PlayerName}は過去にBAN済みのためBANされました。", "BAN"); - } - else if (player.IsFACPlayer()) + if (!AmongUsClient.Instance.AmHost || !Main.KickPlayerWithAbnormalFriendCode.Value) return; + //用于检测是否为xxx#1145/xxx#1337的重复代码前缀 + //InnerSloth的好友代码不会出现前端重复 如果有前端重复一定是UE或者SM黑客 + var currentPrefixes = AmongUsClient.Instance.allClients + .ToArray() + .Where(c => c.Id != player.Id) + .Select(c => { - KickPlayer(player.Id, true, "FACList"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("Message.BanedByFACList"), player.PlayerName)); - Info($"{player.PlayerName}存在于FAC封禁名单", "BAN"); - } + if (string.IsNullOrEmpty(c.FriendCode)) return null; + var parts = c.FriendCode.Split('#'); + return parts.Length > 0 ? parts[0].ToLowerInvariant() : null; + }) + .Where(prefix => !string.IsNullOrEmpty(prefix)) + .ToHashSet(); + var newPrefixParts = player.FriendCode.Split('#'); + var newPrefix = newPrefixParts[0].ToLowerInvariant(); + + if (currentPrefixes.Contains(newPrefix)) + { + KickPlayer(player.Id, false, "KickedByAbnormalFriendCode"); + Info($"重复好友代码前缀的玩家 {player.PlayerName} 已被踢出", "Kick"); + } + else if (player.FriendCode == "") + { + KickPlayer(player.Id, false, "KickedByAbnormalFriendCode"); + Info($"没有好友代码的玩家 {player.PlayerName} 已被踢出。", "Kick"); + } + else if (!ValidFormatRegex.IsMatch(player.FriendCode) || newPrefixParts.Length < 1) + { + KickPlayer(player.Id, false, "KickedByAbnormalFriendCode"); + Info($"好友代码格式异常玩家 {player.PlayerName} 已被踢出。", "Kick"); } } - public static bool IsBannedPlayer(this PlayerControl player) - => player?.GetClient()?.IsBannedPlayer() ?? false; - public static bool IsBannedPlayer(this ClientData player) - => CheckBanStatus(player?.FriendCode, player?.GetHashedPuid()); + public static void CheckBanPlayer(ClientData player) + { + if (!AmongUsClient.Instance.AmHost && !Main.KickPlayerInBanList.Value) return; + if (player.IsBannedPlayer() || + DestroyableSingleton.Instance.IsPlayerBlockedUsername(player.FriendCode)) + KickPlayer(player.Id, true, "BanedByBanList", KickLevel.CheatDetected); + else if (player.IsFACPlayer()) KickPlayer(player.Id, true, "BanedByFACList", KickLevel.CheatDetected); + } + public static bool CheckBanStatus(string friendCode, string hashedPuid) { try { if (!File.Exists(BAN_LIST_PATH)) File.Create(BAN_LIST_PATH).Close(); using StreamReader sr = new(BAN_LIST_PATH); - string line; - while ((line = sr.ReadLine()) != null) + while (sr.ReadLine() is { } line) { if (string.IsNullOrWhiteSpace(line)) continue; if (!string.IsNullOrWhiteSpace(friendCode) && line.Contains(friendCode)) return true; @@ -104,20 +121,25 @@ public static bool CheckBanStatus(string friendCode, string hashedPuid) { Exception(ex, "CheckBanList"); } + return false; } - public static bool IsFACPlayer(this PlayerControl player) - => player?.GetClient()?.IsFACPlayer() ?? false; public static bool IsFACPlayer(this ClientData player) - => CheckFACStatus(player?.FriendCode, player?.GetHashedPuid()); + { + return CheckFACStatus(player?.FriendCode, player?.GetHashedPuid()); + } + public static bool CheckFACStatus(string friendCode, string hashedPuid) - => FACList.Any(line => + { + return FACList.Any(line => !string.IsNullOrWhiteSpace(friendCode) && line.Contains(friendCode) || !string.IsNullOrWhiteSpace(hashedPuid) && line.Contains(hashedPuid)); + } } + [HarmonyPatch(typeof(BanMenu), nameof(BanMenu.Select))] -class BanMenuSelectPatch +internal class BanMenuSelectPatch { public static void Postfix(BanMenu __instance, int clientId) { diff --git a/FinalSuspect/Modules/Features/CheckingandBlocking/RPC.cs b/FinalSuspect/Modules/Features/CheckingandBlocking/RPC.cs index 1eef5d16..1f2343db 100644 --- a/FinalSuspect/Modules/Features/CheckingandBlocking/RPC.cs +++ b/FinalSuspect/Modules/Features/CheckingandBlocking/RPC.cs @@ -1,10 +1,11 @@ using System; -using System.Linq; +using System.Threading; using System.Threading.Tasks; +using FinalSuspect.DataHandling.FinalGameData; using FinalSuspect.Helpers; using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Patches.Game_Vanilla; -using FinalSuspect.Patches.System; using Hazel; using InnerNet; @@ -16,83 +17,166 @@ public enum Sounds TaskComplete, TaskUpdateSound, ImpTransform, - Yeehawfrom, + Yeehawfrom } -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] -internal class PlayerControlRPCHandlerPatch +[HarmonyPatch] +internal class RPCHandlerPatch { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref byte callId, [HarmonyArgument(1)] MessageReader reader) + public static IEnumerable TargetMethods() { - if (__instance == null) return true; - if (OnPlayerLeftPatch.ClientsProcessed.Contains(__instance.PlayerId)) return false; - - Info($"{__instance.Data?.PlayerId}" + - $"({__instance.Data?.PlayerName})" + - $"{(__instance.IsHost() ? "Host" : "")}" + - $":{callId}({RPC.GetRpcName(callId)})", - "ReceiveRPC"); - - - if (XtremePlayerData.AllPlayerData.Any(data => data.PlayerId == __instance.Data?.PlayerId)) - if (ReceiveRpc(__instance, callId, reader, out var notify, out var reason, out var ban)) - { - if (!__instance.IsLocalPlayer()) - { - __instance.MarkAsCheater(); - } - - if (AmongUsClient.Instance.AmHost) - { - KickPlayer(__instance.PlayerId, ban, reason); - WarnHost(); - if (notify) - NotificationPopperPatch.NotificationPop - (string.Format(GetString("Warning.InvalidSlothRPC"), __instance.GetRealName(), $"{callId}({RPC.GetRpcName(callId)})")); - } - else if (notify) - NotificationPopperPatch.NotificationPop - (string.Format(GetString("Warning.InvalidSlothRPC_NotHost"), __instance.GetRealName(), $"{callId}({RPC.GetRpcName(callId)})")); - - return false; - } + return from type in typeof(InnerNetObject).Assembly.GetTypes() + where typeof(InnerNetObject).IsAssignableFrom(type) && !type.IsAbstract + select type.GetMethod("HandleRpc", BindingFlags.Public | BindingFlags.Instance) + into method + where method != null && method.GetBaseDefinition() != method + select method; + } + + public static bool Prefix(InnerNetObject __instance, [HarmonyArgument(0)] ref byte callId, + [HarmonyArgument(1)] MessageReader reader) + { + if (!__instance) return true; + + var player = GetPlayerFromInstance(__instance, reader); + if (!player) return true; + if (OnPlayerLeftPatch.ClientsProcessed.Contains(player.PlayerId)) return false; + + if (player.GetCheatData()?.InComingOverloaded != true) + { + var cd = player.GetCheatData(); + Info(player.Data + ? $"{player.Data.PlayerId}(" + + $"Name: {player.Data.PlayerName}/" + + $"FriendCode: {cd?.FriendCode}/" + + $"Puid: {cd?.Puid}" + + $")" + + $"{(player.IsHost() ? "Host" : "")}:{callId}({RPC.GetRpcName(callId)})" + : $"Call from {__instance.name}:{callId}({RPC.GetRpcName(callId)})", "ReceiveRPC"); + } + + HandleCheatDetection(player, callId, reader); - var subReader = MessageReader.Get(reader); var rpcType = (RpcCalls)callId; - + ProcessRpc(rpcType, player, reader); + + return true; + } + + private static PlayerControl GetPlayerFromInstance(InnerNetObject instance, MessageReader reader) + { + var player = FinalPlayerData.AllPlayerData.FirstOrDefault(x => instance.OwnerId == x.Player.OwnerId)?.Player; + if (player) return player; + + try + { + var sr = MessageReader.Get(reader); + player = sr.ReadNetObject(); + } + catch + { + /* ignored */ + } + + return player; + } + + private static void HandleCheatDetection(PlayerControl player, byte callId, MessageReader reader) + { + if (FinalPlayerData.AllPlayerData.All(data => data.PlayerId != player.Data?.PlayerId)) return; + if (!ReceiveRpc(player, callId, reader, out var notify, out var reason, out var ban)) return; + HandleCheater(player, notify, reason, ban, callId); + } + + private static void HandleCheater(PlayerControl player, bool notify, string reason, bool ban, byte callId) + { + if (!player.IsLocalPlayer()) player.MarkAsCheater(); + + if (AmongUsClient.Instance.AmHost) + { + KickPlayer(player.PlayerId, ban, reason, KickLevel.None); + WarnHost(); + if (notify) + NotificationPopperPatch.NotificationPop( + string.Format(GetString("CheatDetected.InvalidSlothRPC"), player.GetRealName(), + $"{callId}({RPC.GetRpcName(callId)})")); + } + else if (notify) + { + NotificationPopperPatch.NotificationPop( + string.Format(GetString("CheatDetected.InvalidSlothRPC_NotHost"), player.GetRealName(), + $"{callId}({RPC.GetRpcName(callId)})")); + } + } + + // 处理RPC调用的逻辑 + private static void ProcessRpc(RpcCalls rpcType, PlayerControl player, MessageReader reader) + { + var subReader = MessageReader.Get(reader); switch (rpcType) { - case RpcCalls.CheckName://CheckNameRPC - var name = subReader.ReadString(); - Info("RPC Check Name For Player: " + name, "CheckName"); - if (__instance.IsHost()) - Main.HostNickName = name; - if (XtremePlayerData.AllPlayerData.All(data => data.PlayerId != __instance.PlayerId)) - XtremePlayerData.CreateDataFor(__instance, name); + case RpcCalls.CheckName: + HandleCheckNameRpc(player, subReader); break; - case RpcCalls.SetName: //SetNameRPC - subReader.ReadUInt32(); - name = subReader.ReadString(); - Info("RPC Set Name For Player: " + __instance.GetNameWithRole() + " => " + name, "SetName"); + case RpcCalls.SetName: + HandleSetNameRpc(player, subReader); break; - case RpcCalls.SendChat: // Free chat - var text = subReader.ReadString(); - Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}:{text.RemoveHtmlTags()}", "ReceiveChat"); + case RpcCalls.SendChat: + HandleSendChatRpc(player, subReader); break; case RpcCalls.SendQuickChat: - Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}:Some message from quick chat", "ReceiveChat"); + HandleSendQuickChatRpc(player); break; case RpcCalls.StartMeeting: - var p = GetPlayerById(subReader.ReadByte()); - Info($"{__instance.GetNameWithRole()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); + HandleStartMeetingRpc(player, subReader); break; } - return true; } - - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + + private static void HandleCheckNameRpc(PlayerControl player, MessageReader reader) + { + var name = reader.ReadString(); + Info("RPC Check Name For Player: " + name, "CheckName"); + if (player.IsHost()) + Main.HostNickName = name; + if (FinalPlayerData.AllPlayerData.All(data => data.PlayerId != player.PlayerId)) + FinalPlayerData.CreateDataFor(player, name); + } + + private static void HandleSetNameRpc(PlayerControl player, MessageReader reader) + { + reader.ReadUInt32(); + var name = reader.ReadString(); + Info("RPC Set Name For Player: " + player.GetNameWithRole() + " => " + name, "SetName"); + } + + private static void HandleSendChatRpc(PlayerControl player, MessageReader reader) + { + var text = reader.ReadString(); + Info($"{player.GetNameWithRole()}:{text.RemoveHtmlTags()}", "ReceiveChat"); + } + + private static void HandleSendQuickChatRpc(PlayerControl player) + { + Info($"{player.GetNameWithRole()}:Some message from quick chat", "ReceiveChat"); + } + + private static void HandleStartMeetingRpc(PlayerControl player, MessageReader reader) { + var p = GetPlayerById(reader.ReadByte()); + Info($"{player.GetNameWithRole()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); + } + + public static void Postfix(InnerNetObject __instance, [HarmonyArgument(0)] byte callId, + [HarmonyArgument(1)] MessageReader reader) + { + if (!__instance) return; + var netId = __instance.NetId; + var player = FinalPlayerData.AllPlayerData.FirstOrDefault(x => x.NetId == netId)?.Player; + if (!player) return; + if (FinalGameData.PlayerVersion.playerVersion.ContainsKey(player.GetClientId())) return; + Info($"Create Player version for {player.GetRealName()}", "Rpc Version Check"); var rpcType = (RpcCalls)callId; switch (rpcType) { @@ -103,13 +187,15 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c var tag = reader.ReadString(); var forkId = reader.ReadString(); - XtremeGameData.PlayerVersion.playerVersion[__instance.PlayerId] = new XtremeGameData.PlayerVersion(version, tag, forkId); + var id = player.GetClientId(); + _ = RPC.RpcVersionCheck(); - if (!XtremeGameData.PlayerVersion.playerVersion.ContainsKey(__instance.PlayerId)) - RPC.RpcVersionCheck(); + FinalGameData.PlayerVersion.playerVersion[id] = + new FinalGameData.PlayerVersion(version, tag, forkId); if (Main.VersionCheat.Value && AmongUsClient.Instance.AmHost) - XtremeGameData.PlayerVersion.playerVersion[__instance.PlayerId] = XtremeGameData.PlayerVersion.playerVersion[0]; + FinalGameData.PlayerVersion.playerVersion[id] = + FinalGameData.PlayerVersion.playerVersion[id]; // Kick Unmached Player Start /*if (AmongUsClient.Instance.AmHost && tag != $"{Main.GitCommit}({Main.GitBranch})") @@ -129,68 +215,56 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte c } catch { - /* ignored */ + FinalGameData.PlayerVersion.playerVersion[player.GetClientId()] = null; } - break; - } - } -} -[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleRpc))] -internal class PlayerPhysicsRPCHandlerPatch -{ - public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] ref byte callId, [HarmonyArgument(1)] MessageReader reader) - { - if (__instance == null) return true; - var player = __instance.myPlayer; - if (OnPlayerLeftPatch.ClientsProcessed.Contains(player.PlayerId)) return false; - //Info($"{player.Data?.PlayerId}" + - // $"({player.Data?.PlayerName})" + - // $"{(player.IsHost() ? "Host" : "")}" + - // $":{callId}({RPC.GetRpcName(callId)})", - // "ReceiveRPC"); - - if (XtremePlayerData.AllPlayerData.All(data => data.PlayerId != player.Data?.PlayerId)) return true; - if (!ReceiveRpc(player, callId, reader, out var notify, out var reason, out var ban)) return true; - if (!player.IsLocalPlayer()) - { - player.MarkAsCheater(); - } - - if (AmongUsClient.Instance.AmHost) - { - KickPlayer(player.PlayerId, ban, reason); - WarnHost(); - if (notify) - NotificationPopperPatch.NotificationPop - (string.Format(GetString("Warning.InvalidSlothRPC"), player.GetRealName(), $"{callId}({RPC.GetRpcName(callId)})")); + break; } - else if (notify) - NotificationPopperPatch.NotificationPop - (string.Format(GetString("Warning.InvalidSlothRPC_NotHost"), player.GetRealName(), $"{callId}({RPC.GetRpcName(callId)})")); - - return false; } } internal static class RPC { - public static async void RpcVersionCheck() + private static CancellationTokenSource _rpcCts; // 用于取消异步操作 + + public static async Task RpcVersionCheck() { + _rpcCts?.Cancel(); + _rpcCts = new CancellationTokenSource(); + var ct = _rpcCts.Token; + try { - while (PlayerControl.LocalPlayer == null) await Task.Delay(500); + while (PlayerControl.LocalPlayer == null || AmongUsClient.Instance == null) + { + if (ct.IsCancellationRequested) return; + await Task.Delay(500, ct); + } + + if (PlayerControl.LocalPlayer == null || AmongUsClient.Instance == null) return; if (!Main.VersionCheat.Value) { - var writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)RpcCalls.CancelPet); + var writer = AmongUsClient.Instance.StartRpcImmediately( + PlayerControl.LocalPlayer.NetId, + (byte)RpcCalls.CancelPet, + SendOption.Reliable); writer.Write(Main.PluginVersion); writer.Write($"{Main.GitCommit}({Main.GitBranch})"); writer.Write(Main.ForkId); - writer.EndMessage(); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - - XtremeGameData.PlayerVersion.playerVersion[PlayerControl.LocalPlayer.PlayerId] = - new XtremeGameData.PlayerVersion(Main.PluginVersion, $"{Main.GitCommit}({Main.GitBranch})", Main.ForkId); + + if (FinalGameData.PlayerVersion.playerVersion != null) + FinalGameData.PlayerVersion.playerVersion[PlayerControl.LocalPlayer.GetClientId()] = + new FinalGameData.PlayerVersion( + Version.Parse(Main.PluginVersion), + $"{Main.GitCommit}({Main.GitBranch})", + Main.ForkId + ); + } + catch (OperationCanceledException) + { + /* ignored */ } catch { @@ -198,9 +272,15 @@ public static async void RpcVersionCheck() } } + public static void Cleanup() + { + _rpcCts?.Cancel(); + _rpcCts?.Dispose(); + } + public static void SendRpcLogger(uint targetNetId, byte callId, int targetClientId = -1) { - if (!DebugModeManager.AmDebugger) return; + if (!DebugModeManager.IsDebugMode) return; var rpcName = GetRpcName(callId); var from = targetNetId.ToString(); var target = targetClientId.ToString(); @@ -214,48 +294,25 @@ public static void SendRpcLogger(uint targetNetId, byte callId, int targetClient /* ignored */ } - Info($"FromNetID:{targetNetId}({from}) TargetClientID:{targetClientId}({target}) CallID:{callId}({rpcName})", "SendRPC"); + Info($"FromNetID:{targetNetId}({from}) TargetClientID:{targetClientId}({target}) CallID:{callId}({rpcName})", + "SendRPC"); } + public static string GetRpcName(byte callId) { string rpcName; if ((rpcName = Enum.GetName(typeof(RpcCalls), callId)) == null) - rpcName = callId + " 无效"; + rpcName = callId + "(无效)"; return rpcName; } } -[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.StartRpc))] -internal class StartRpcPatch -{ - public static void Prefix([HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId) - { - RPC.SendRpcLogger(targetNetId, callId); - } -} + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.StartRpcImmediately))] internal class StartRpcImmediatelyPatch { - public static void Prefix([HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId, [HarmonyArgument(3)] int targetClientId = -1) + public static void Prefix([HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId, + [HarmonyArgument(3)] int targetClientId = -1) { RPC.SendRpcLogger(targetNetId, callId, targetClientId); } -} -[HarmonyPatch(typeof(MessageReader), nameof(MessageReader.ReadUInt16))] -[HarmonyPatch(typeof(MessageReader), nameof(MessageReader.ReadPackedUInt32))] -[HarmonyPriority(Priority.First)] -internal class HazelPatch -{ - public static bool Prefix(MessageReader __instance) - { - return __instance.Length > 0; - } -} - -[HarmonyPatch(typeof(MessageReader), nameof(MessageReader.ReadUInt16))] -internal class HazelRBPatch -{ - public static bool Prefix(MessageReader __instance) - { - return true; - } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Features/CheckingandBlocking/SpamManager.cs b/FinalSuspect/Modules/Features/CheckingandBlocking/SpamManager.cs index 3ef0835d..828cbbe3 100644 --- a/FinalSuspect/Modules/Features/CheckingandBlocking/SpamManager.cs +++ b/FinalSuspect/Modules/Features/CheckingandBlocking/SpamManager.cs @@ -1,21 +1,19 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Net.Http; using System.Text; -using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using StringWriter = Il2CppSystem.IO.StringWriter; namespace FinalSuspect.Modules.Features.CheckingandBlocking; public static class SpamManager { - private static readonly string BANEDWORDS_FILE_PATH = LocalPath_Data + "BanWords.json"; - public static readonly string DENY_NAME_LIST_PATH = GetBanFilesPath("DenyName.json"); - public static List BanWords = []; + private static List BanWords = []; private static readonly List Targets = [ @@ -23,23 +21,19 @@ public static class SpamManager "FACList.json", $"BanWords/{GetUserLangByRegion()}.json" ]; - - //[PluginModuleInitializer] + public static async Task Init() { try { - CreateIfNotExists(); BanWords = ReturnAllNewLinesInFile(BANEDWORDS_FILE_PATH); foreach (var target in Targets) + foreach (var url in GetInfoFileUrlList()) { - foreach (var url in GetInfoFileUrlList()) - { - var task = GetConfigs(url + "Assets/Configs/" + target, target); - await task; - if (!task.Result) continue; - break; - } + var task = GetConfigs(url + "Assets/Configs/" + target, target); + await task; + if (!task.Result) continue; + break; } } catch (Exception ex) @@ -48,103 +42,46 @@ public static async Task Init() } } - private static void CreateIfNotExists() + + public static List ReturnAllNewLinesInFile(string filepath) { - if (!File.Exists(BANEDWORDS_FILE_PATH)) + if (!File.Exists(filepath)) return []; + var json = File.ReadAllText(filepath); + List sendList; + try { - try - { - if (File.Exists(@"./BanWords.json")) - File.Move(@"./BanWords.json", BANEDWORDS_FILE_PATH); - else - { - var fileName = GetUserLangByRegion().ToString(); - Warn($"Create New BanWords: {fileName}", "SpamManager"); - } - } - catch (Exception ex) - { - Exception(ex, "SpamManager"); - } + sendList = JsonConvert.DeserializeObject>(json) ?? []; } - - if (File.Exists(DENY_NAME_LIST_PATH)) return; + catch { - try - { - if (File.Exists(@"./DenyName.json")) - File.Move(@"./DenyName.json", DENY_NAME_LIST_PATH); - } - catch (Exception ex) - { - Exception(ex, "SpamManager"); - } + sendList = []; } - } - private static List ReturnAllNewLinesInFile(string filename) - { - if (!File.Exists(filename)) return []; - using StreamReader sr = new(filename, Encoding.GetEncoding("UTF-8")); - string text; - List sendList = []; - while ((text = sr.ReadLine()) != null) - if (text.Length >= 1 && text != "") - sendList.Add(text.Replace("\\n", "\n").ToLower()); return sendList; } - + private static async Task GetConfigs(string url, string name) { try { - string result; - if (url.StartsWith("file:///")) - { - try - { - // Windows 格式 - var filePath = url[8..].Replace('/', '\\'); - result = await File.ReadAllTextAsync(filePath); - } - catch (FileNotFoundException) - { - Warn($"服务器文件缺失: {url[8..]}", "SpamManager"); - return false; - } - catch (Exception ex) - { - Error($"读取本地文件失败: {ex.Message}", "SpamManager"); - return false; - } - } - else - { - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("User-Agent", "FinalSuspect" + name); - client.DefaultRequestHeaders.Add("Referer", "gitee.com"); - - using var response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseContentRead); - if (!response.IsSuccessStatusCode) - { - Error($"服务器请求失败 [{url}]: {response.StatusCode}", "SpamManager"); - return false; - } - result = await response.Content.ReadAsStringAsync(); - result = result.Replace("\r", string.Empty).Replace("\n", string.Empty).Trim(); - } + var task = JsonHelper.GetJsonStringAsync(url); + await task; + var (result, succeed) = task.Result; + if (!succeed) return false; + + var data = JObject.Parse(result); try { - var data = JObject.Parse(result); ProcessBanWords(data); ProcessDenyNames(data); ProcessFacList(data); } - catch (JsonException ex) + catch (Exception ex) { - Error($"JSON 解析失败: {ex.Message}", "SpamManager"); + Error($"JSON 解析失败: {ex.Message}\nData: {result[..Math.Min(100, result.Length)]}", "SpamManager"); return false; } + await Task.Delay(100); return true; } @@ -163,7 +100,8 @@ private static void ProcessBanWords(JObject data) .Except(BanWords, StringComparer.OrdinalIgnoreCase) .ToList(); - UpdateBanWords(newWords); + BanWords.AddRange(newWords); + Update(newWords, BANEDWORDS_FILE_PATH); } private static void ProcessDenyNames(JObject data) @@ -175,10 +113,7 @@ private static void ProcessDenyNames(JObject data) .Except(existingNames, StringComparer.OrdinalIgnoreCase) .ToList(); - if (newNames.Count > 0) - { - File.AppendAllLines(DENY_NAME_LIST_PATH, newNames); - } + Update(newNames, DENY_NAME_LIST_PATH); } private static void ProcessFacList(JObject data) @@ -195,28 +130,23 @@ private static void ProcessFacList(JObject data) private static List GetTokens(JToken token) { - if (token is not { Type: JTokenType.Array }) - { - return []; - } - + if (token is not { Type: JTokenType.Array }) return []; + var jarray = token.Cast(); var tokens = new List(); - for (var i = 0; i < jarray.Count; i++) - { - tokens.Add(jarray[i].ToString()); - } - + for (var i = 0; i < jarray.Count; i++) tokens.Add(jarray[i].ToString()); + return [ .. tokens .Select(item => item?.ToString()) - .Where(str => !string.IsNullOrEmpty(str))]; + .Where(str => !string.IsNullOrEmpty(str)) + ]; } - + private static string DecodeUnicodeEscapes(string input) { - return Regex.Replace(input, @"\\u([0-9A-Fa-f]{4})", match => + return Regex.Replace(input, @"\\u([0-9A-Fa-f]{4})", match => { try { @@ -228,7 +158,7 @@ private static string DecodeUnicodeEscapes(string input) } }); } - + private static string DecryptBase64(string cipherText) { try @@ -238,16 +168,43 @@ private static string DecryptBase64(string cipherText) } catch { - return cipherText; + return cipherText; } } - - private static void UpdateBanWords(List newWords) + + private static void Update(List newWords, string path) { if (newWords.Count == 0) return; - BanWords.AddRange(newWords); - File.AppendAllLines(BANEDWORDS_FILE_PATH, newWords); + List updateWords; + if (!File.Exists(path)) return; + + try + { + var json = File.ReadAllText(path); + updateWords = JsonConvert.DeserializeObject>(json) ?? []; + } + catch + { + updateWords = []; + } + + var allWords = updateWords.Union(newWords).ToList(); + + _ = new MainThreadTask(() => + { + StringWriter sw = new(); + JsonWriter jsonWriter = new JsonTextWriter(sw); + + jsonWriter.WriteStartArray(); + + foreach (var word in allWords) jsonWriter.WriteValue(word); + + jsonWriter.WriteEndArray(); + sw.Flush(); + + File.WriteAllText(path, sw.ToString()); + }, "Write in Ban"); } private static bool ShouldAddToFacList(string line) @@ -256,7 +213,7 @@ private static bool ShouldAddToFacList(string line) .Where(p => p.IsDev()) .Any(p => line.Contains(p.FriendCode, StringComparison.OrdinalIgnoreCase)); } - + public static void CheckSpam(ref string text) { if (!Main.SpamDenyWord.Value || BanWords.Count == 0) return; @@ -267,13 +224,13 @@ public static void CheckSpam(ref string text) var bannedWords = BanWords.Where(word => lowerText.Contains(word.ToLowerInvariant())).ToList(); if (bannedWords.Count == 0) return; - + var pattern = string.Join("|", bannedWords.Select(Regex.Escape)); - text = Regex.Replace(text, pattern, match => - $"{new string('*', match.Value.Length)}", + text = Regex.Replace(text, pattern, match => + $"{new string('*', match.Value.Length)}", RegexOptions.IgnoreCase); } - catch + catch { /* ignored */ } diff --git a/FinalSuspect/Modules/Features/CustomPopup.cs b/FinalSuspect/Modules/Features/CustomPopup.cs index 773cae9a..034d82ad 100644 --- a/FinalSuspect/Modules/Features/CustomPopup.cs +++ b/FinalSuspect/Modules/Features/CustomPopup.cs @@ -1,24 +1,24 @@ using System; -using System.Collections.Generic; -using System.Linq; using FinalSuspect.Helpers; using TMPro; using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; +#pragma warning disable CS8602 // 解引用可能出现空引用。 + namespace FinalSuspect.Modules.Features; #nullable enable public static class CustomPopup { - public static GameObject? Fill; - public static GameObject? InfoScreen; + private static GameObject? Fill; + private static GameObject? InfoScreen; - public static TextMeshPro? TitleTMP; + private static TextMeshPro? TitleTMP; public static TextMeshPro? InfoTMP; - public static PassiveButton? ActionButtonPrefab; + private static PassiveButton? ActionButtonPrefab; public static GameObject? FillTemp; public static GameObject? InfoScreenTemp; @@ -28,26 +28,26 @@ public static class CustomPopup public static PassiveButton? ActionButtonPrefabTemp; - public static List? ActionButtons; + private static List? ActionButtons; private static bool busy; + private static (string title, string info, List<(string, Action)>? buttons)? waitToShow; + + private static string waitToUpdateText = string.Empty; + /// - /// 显示一个全屏信息显示界面 + /// 显示一个全屏信息显示界面 /// /// 标题 /// 内容 /// 按钮(文字,点击事件) public static void Show(string title, string info, List<(string, Action)>? buttons) { - if (busy || Fill == null || InfoScreen == null || ActionButtonPrefab == null || TitleTMP == null || InfoTMP == null) + if (busy || !Fill || !InfoScreen || !ActionButtonPrefab || !TitleTMP || !InfoTMP) { Init(); - if (Fill == null || InfoScreen == null || ActionButtonPrefab == null || TitleTMP == null || InfoTMP == null) - { - Debug.LogError("CustomPopup not initialized properly."); - return; - } + if (!Fill || !InfoScreen || !ActionButtonPrefab || !TitleTMP || !InfoTMP) return; } busy = true; @@ -59,15 +59,14 @@ public static void Show(string title, string info, List<(string, Action)>? butto ActionButtons = []; if (buttons != null) - { - foreach (var buttonInfo in buttons.Where(b => b.Item1.Trim() is not null and not "")) + foreach (var buttonInfo in buttons.Where(b => !b.Item1.Trim().IsNullOrWhiteSpace())) { var (text, action) = buttonInfo; var button = Object.Instantiate(ActionButtonPrefab, InfoScreen.transform); - if (button == null) continue; + if (!button) continue; var tmp = button.transform.FindChild("Text_TMP")?.GetComponent(); - if (tmp == null) continue; + if (!tmp) continue; tmp.text = text; button.OnClick = new Button.ButtonClickedEvent(); @@ -81,11 +80,11 @@ public static void Show(string title, string info, List<(string, Action)>? butto button.gameObject.SetActive(true); ActionButtons?.Add(button); } - } if (ActionButtons?.Count > 1) { - var widthSum = ActionButtons.Count * (ActionButtonPrefab.gameObject.GetComponent()?.size.x ?? 0); + var widthSum = ActionButtons.Count * + (ActionButtonPrefab.gameObject.GetComponent()?.size.x ?? 0); widthSum += (ActionButtons.Count - 1) * 0.1f; var start = -Math.Abs(widthSum / 2); var each = widthSum / ActionButtons.Count; @@ -96,17 +95,22 @@ public static void Show(string title, string info, List<(string, Action)>? butto index++; } } + Fill.SetActive(true); InfoScreen.SetActive(true); busy = false; } - private static (string title, string info, List<(string, Action)>? buttons)? waitToShow; - public static void ShowLater(string title, string info, List<(string, Action)>? buttons) => waitToShow = (title, info, buttons); + public static void ShowLater(string title, string info, List<(string, Action)>? buttons) + { + waitToShow = (title, info, buttons); + } - private static string waitToUpdateText = string.Empty; - public static void UpdateTextLater(string info) => waitToUpdateText = info; + public static void UpdateTextLater(string info) + { + waitToUpdateText = info; + } public static void Update() { @@ -115,84 +119,64 @@ public static void Update() Show(waitToShow.Value.title, waitToShow.Value.info, waitToShow.Value.buttons); waitToShow = null; } - if (!string.IsNullOrEmpty(waitToUpdateText)) - { - InfoTMP?.SetText(waitToUpdateText); - waitToUpdateText = string.Empty; - } + + if (string.IsNullOrEmpty(waitToUpdateText)) return; + InfoTMP?.SetText(waitToUpdateText); + waitToUpdateText = string.Empty; } public static void Init() { var DOBScreen = AccountManager.Instance?.transform.FindChild("DOBEnterScreen"); - if (DOBScreen == null) - { - Debug.LogError("DOBEnterScreen not found!"); - return; - } + if (!DOBScreen) return; - if (Fill == null) + if (!Fill) { Fill = Object.Instantiate(DOBScreen.FindChild("Fill")?.gameObject); - if (Fill == null) - { - Debug.LogError("Failed to instantiate Fill."); - return; - } + if (!Fill) return; + FillTemp = Fill; Fill.transform.SetLocalZ(-100f); Fill.name = "FinalSuspect Info Popup Fill"; Fill.SetActive(false); } - if (InfoScreen == null) + if (!InfoScreen) { InfoScreen = Object.Instantiate(DOBScreen.FindChild("InfoPage")?.gameObject); - if (InfoScreen == null) - { - Debug.LogError("Failed to instantiate InfoScreen."); - return; - } + if (!InfoScreen) return; + InfoScreen.transform.SetLocalZ(-110f); InfoScreen.name = "FinalSuspect Info Popup Page"; InfoScreen.SetActive(false); } - if (TitleTMP == null) + if (!TitleTMP) { TitleTMP = InfoScreen.transform.FindChild("Title Text")?.GetComponent(); - if (TitleTMP == null) - { - Debug.LogError("Failed to find TitleTMP."); - return; - } + if (!TitleTMP) return; + TitleTMP.transform.localPosition = new Vector3(0f, 2.3f, 3f); TitleTMP.DestroyTranslator(); TitleTMP.text = ""; } - if (InfoTMP == null) + if (!InfoTMP) { InfoTMP = InfoScreen.transform.FindChild("InfoText_TMP")?.GetComponent(); - if (InfoTMP == null) - { - Debug.LogError("Failed to find InfoTMP."); - return; - } + if (!InfoTMP) return; + InfoTMP.GetComponent().sizeDelta = new Vector2(7f, 1.3f); InfoTMP.transform.localScale = new Vector3(1f, 1f, 1f); InfoTMP.DestroyTranslator(); InfoTMP.text = ""; } - if (ActionButtonPrefab == null) + if (!ActionButtonPrefab) { ActionButtonPrefab = InfoScreen.transform.FindChild("BackButton")?.GetComponent(); - if (ActionButtonPrefab == null) - { - Debug.LogError("Failed to find ActionButtonPrefab."); - return; - } + if (!ActionButtonPrefab) return; + ActionButtonPrefab.gameObject.name = "ActionButtonPrefab"; ActionButtonPrefab.transform.localScale = new Vector3(0.66f, 0.66f, 0.66f); ActionButtonPrefab.transform.localPosition = new Vector3(0f, -0.65f, 3f); diff --git a/FinalSuspect/Modules/Features/DisplayedRoleTag/DisplayerRoleTagHelper.cs b/FinalSuspect/Modules/Features/DisplayedRoleTag/DisplayerRoleTagHelper.cs new file mode 100644 index 00000000..a171d36e --- /dev/null +++ b/FinalSuspect/Modules/Features/DisplayedRoleTag/DisplayerRoleTagHelper.cs @@ -0,0 +1,329 @@ +using System; +using AmongUs.GameOptions; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using Object = UnityEngine.Object; + +// ReSharper disable UnusedMember.Local + +namespace FinalSuspect.Modules.Features.DisplayedRoleTag; + +public static class DisplayerRoleTagHelper +{ + private const int MaxOneScreenRole = 40; + + private static int Page; + public static TextMeshPro textTemplate; + public static GameObject selectionUI; + private static Dictionary> CategoryButtons; + private static Dictionary CategorySelectButtons; + private static CategoryType currentCategory = CategoryType.Role; + + private static readonly Dictionary identityColors = new() + { + { IdentityTypes.Hard_Cleared, "#FFD700" }, + { IdentityTypes.Silver_Clear, "#C0C0C0" }, + { IdentityTypes.Wolf_Bucket, "#B22222" }, + { IdentityTypes.No_Kill, "#94AA24" }, + { IdentityTypes.Outside_Position, "#228B22" }, + { IdentityTypes.Inside_Position, "#663399" } + }; + + private static void SelectCategory(CategoryType category, bool SetPage = true) + { + currentCategory = category; + if (SetPage) Page = 1; + + foreach (var catButton in CategoryButtons) + { + var index = 0; + foreach (var btn in catButton.Value.Where(btn => btn != null)) + { + index++; + + // 分页控制 + if (index <= (Page - 1) * MaxOneScreenRole || + Page * MaxOneScreenRole < index) + { + btn.gameObject.SetActive(false); + continue; + } + + btn.gameObject.SetActive(catButton.Key == category); + } + } + + foreach (var catSelect in CategorySelectButtons.Where(catSelect => catSelect.Value != null)) + { + catSelect.Value.color = catSelect.Value.color.SetAlpha(catSelect.Key == category ? 1f : 0.25f); + } + } + + public static void ShowSelectionPanel(MeetingHud __instance, PlayerControl pc) + { + if (selectionUI != null || + !PlayerControl.LocalPlayer.IsAlive() && + PlayerControl.LocalPlayer.GetRoleType() is not RoleTypes.GuardianAngel || + __instance.playerStates == null) return; + + try + { + var thisTag = pc.GetFinalData().RoleTag; + Page = 1; + CategoryButtons = new Dictionary>(); + CategorySelectButtons = new Dictionary(); + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(false)); + + // 创建UI容器 + var container = Object.Instantiate( + GameObject.Find("PhoneUI").transform, __instance.transform); + var to = container.gameObject.AddComponent(); + to.targetSize = 0.75f; + container.transform.localPosition = new Vector3(0, 0, -200f); + + selectionUI = container.gameObject; + + // 创建退出按钮 + var buttonTemplate = __instance.playerStates[0]!.transform.FindChild("votePlayerBase"); + var maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); + var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); + + var exitButtonParent = new GameObject("ExitButton").transform; + exitButtonParent.SetParent(container); + var exitButton = Object.Instantiate(buttonTemplate, exitButtonParent); + exitButton.FindChild("ControllerHighlight").gameObject.SetActive(false); + + var exitButtonMask = Object.Instantiate(maskTemplate, exitButtonParent); + exitButtonMask.transform.localScale = new Vector3(2.88f, 0.8f, 1f); + exitButtonMask.transform.localPosition = new Vector3(0f, 0f, 1f); + + exitButton.GetComponent().sprite = + smallButtonTemplate.GetComponent().sprite; + exitButtonParent.localPosition = new Vector3(3.88f, -2.12f, -200f); + exitButtonParent.localScale = new Vector3(0.22f, 0.9f, 1f); + exitButtonParent.transform.SetAsFirstSibling(); + + exitButton.GetComponent().OnClick = new Button.ButtonClickedEvent(); + exitButton.GetComponent().OnClick.AddListener(new Action(() => + { + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); + Object.Destroy(container.gameObject); + })); + exitButton.GetComponent(); + + + var clearButtonParent = new GameObject("ClearButton").transform; + clearButtonParent.SetParent(container); + var clearButton = Object.Instantiate(buttonTemplate, clearButtonParent); + clearButton.FindChild("ControllerHighlight").gameObject.SetActive(false); + + var clearLabel = Object.Instantiate( + textTemplate, clearButton); + var clearButtonMask = Object.Instantiate(maskTemplate, clearButtonParent); + clearButtonMask.transform.localScale = new Vector3(2.88f, 0.8f, 1f); + clearButtonMask.transform.localPosition = new Vector3(0f, 0f, 1f); + + clearButton.GetComponent().sprite = LoadSprite("Plate_Clear.png", 115f); + clearButton.GetComponent().color = Color.white; + clearButtonParent.localPosition = new Vector3(2.5f, -2.12f, -200f); + clearButtonParent.localScale = new Vector3(0.53f, 0.53f, 1f); + clearButtonParent.transform.SetAsFirstSibling(); + + clearLabel.text = GetString("Clear"); + clearLabel.color = Color.green; + clearLabel.alignment = TextAlignmentOptions.Center; + clearLabel.transform.localPosition = new Vector3(0, 0, -1f); + clearLabel.transform.localScale *= 1.6f; + clearLabel.autoSizeTextContainer = true; + clearLabel.enabled = true; + + clearButton.GetComponent().OnClick = new Button.ButtonClickedEvent(); + clearButton.GetComponent().OnClick.AddListener(new Action(() => + { + thisTag.setTag(""); + thisTag.setColor(Color.white); + thisTag.setRoom(""); + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); + Object.Destroy(container.gameObject); + })); + clearButton.GetComponent(); + + var tabCount = 0; + + foreach (var category in EnumHelper.GetAllValues()) + { + var color = category switch + { + CategoryType.Role => GetRoleColor(RoleTypes.Crewmate), + CategoryType.PlayerIdentityTag => GetIdentityColor(IdentityTypes.Hard_Cleared), + CategoryType.Room => (Color)ColorHelper.ClientlessColor, + _ => throw new ArgumentOutOfRangeException(nameof(category), category, null) + }; + + var catButtonParent = new GameObject(category + "Tab").transform; + catButtonParent.SetParent(container); + + var catButton = Object.Instantiate(buttonTemplate, catButtonParent); + catButton.FindChild("ControllerHighlight").gameObject.SetActive(false); + + Object.Instantiate(maskTemplate, catButtonParent); + var catLabel = Object.Instantiate( + textTemplate, catButton); + + catButton.GetComponent().sprite = LoadSprite("Plate_Category.png", 115f); + catButton.GetComponent().color = Color.white; + + CategorySelectButtons.Add(category, catButton.GetComponent()); + + catButtonParent.localPosition = new Vector3(-2.5f + tabCount++ * 1.73f, 2.225f, -200); + catButtonParent.localScale = new Vector3(0.53f, 0.53f, 1f); + + catLabel.text = GetString($"DisplayedRoleTag.{category}"); + catLabel.color = color; + catLabel.alignment = TextAlignmentOptions.Center; + catLabel.transform.localPosition = new Vector3(0, 0, -1f); + catLabel.transform.localScale *= 1.6f; + catLabel.autoSizeTextContainer = true; + catLabel.enabled = true; + + catButton.GetComponent().OnClick.AddListener(new Action(() => + { + if (category == currentCategory) return; + SelectCategory(category); + ReloadPage(); + })); + } + + foreach (var role in EnumHelper.GetAllValues()) + { + CreateOption(CategoryType.Role, role.ToString()); + } + + foreach (IdentityTypes identity in Enum.GetValues(typeof(IdentityTypes))) + { + CreateOption(CategoryType.PlayerIdentityTag, identity.ToString()); + } + + foreach (var room in ShipStatus.Instance.AllRooms) + { + CreateOption(CategoryType.Room, room.RoomId.ToString()); + } + + void CreateOption(CategoryType category, string value) + { + // 初始化列表(.NET 6+ 语法) + CategoryButtons.TryAdd(category, []); + if (CategoryButtons[category].Any(x => x.parent.name == value)) return; + + var color = category switch + { + CategoryType.Role => GetRoleColor(Enum.Parse(value)), + CategoryType.PlayerIdentityTag => GetIdentityColor(Enum.Parse(value)), + CategoryType.Room => (Color)ColorHelper.ClientlessColor, + _ => throw new ArgumentOutOfRangeException(nameof(category), category, null) + }; + + var displayText = category switch + { + CategoryType.Role => GetString($"{category}.{value}"), + CategoryType.PlayerIdentityTag => GetString($"{category}.{value}"), + CategoryType.Room => GetString(value), + _ => throw new ArgumentOutOfRangeException(nameof(category)) + }; + + var optionParent = new GameObject(value).transform; + optionParent.SetParent(container); + + var option = Object.Instantiate(buttonTemplate, optionParent); + option.FindChild("ControllerHighlight").gameObject.SetActive(false); + + Object.Instantiate(maskTemplate, optionParent); + var optionLabel = Object.Instantiate(textTemplate, option); + optionLabel.enabled = true; + + var spriteRenderer = option.GetComponent(); + spriteRenderer.sprite = LoadSprite("Plate_Content.png", 115f); + spriteRenderer.color = Color.white; + + var index = CategoryButtons[category].Count; + var (row, col) = (index / 5, index % 5); + optionParent.localPosition = new Vector3(-3.47f + 1.75f * col, 1.5f - 0.45f * row, -200f); + optionParent.localScale = Vector3.one * 0.55f; + + optionLabel.text = displayText; + optionLabel.color = color; + optionLabel.alignment = TextAlignmentOptions.Center; + optionLabel.transform.localPosition = new Vector3(0, 0, optionLabel.transform.localPosition.z); + optionLabel.transform.localScale *= 1.6f; + optionLabel.autoSizeTextContainer = true; + + option.GetComponent().OnClick.AddListener(new Action(() => + { + if (category == CategoryType.Room) + thisTag.setRoom($"({displayText})"); + else + { + thisTag.setColor(color); + thisTag.setTag(displayText); + } + + + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); + Object.Destroy(container.gameObject); + })); + + CategoryButtons[category].Add(option); + } + + void ReloadPage() + { + SelectCategory(currentCategory, false); + } + + SelectCategory(CategoryType.Role); + ReloadPage(); + } + catch (Exception ex) + { + Error(ex.ToString(), "SelectionUI"); + } + } + + private static Color GetIdentityColor(IdentityTypes identity) + { + identityColors.TryGetValue(identity, out var hexColor); + _ = ColorUtility.TryParseHtmlString(hexColor, out var c); + return c; + } + + private enum CategoryType + { + Role, + PlayerIdentityTag, + Room + } + + private enum IdentityTypes + { + Hard_Cleared, + Silver_Clear, + Wolf_Bucket, + No_Kill, + Outside_Position, + Inside_Position + } +} + +public class DisplayerRoleTag +{ + public string TagStr { get; private set; } = ""; + public Color TagColor { get; private set; } = Color.white; + public string Room { get; private set; } = ""; + + public void setTag(string tagStr) => TagStr = tagStr; + public void setColor(Color tagColor) => TagColor = tagColor; + public void setRoom(string room) => Room = room; +} \ No newline at end of file diff --git a/FinalSuspect/Modules/Features/InGameRoleInfoMenu.cs b/FinalSuspect/Modules/Features/InGameRoleInfoMenu.cs index 3a56ee10..80ae0640 100644 --- a/FinalSuspect/Modules/Features/InGameRoleInfoMenu.cs +++ b/FinalSuspect/Modules/Features/InGameRoleInfoMenu.cs @@ -1,6 +1,7 @@ using System.Text; +using FinalSuspect.Attributes; using FinalSuspect.Helpers; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using TMPro; using UnityEngine; @@ -8,20 +9,25 @@ namespace FinalSuspect.Modules.Features; public static class InGameRoleInfoMenu { - public static bool Showing => Fill != null && Fill.active && Menu != null && Menu.active; + private const string FirstHeaderSize = "130%"; + public const string SecondHeaderSize = "100%"; + private const string BodySize = "70%"; + private const string BlankLineSize = "30%"; - public static GameObject Fill; - public static SpriteRenderer FillSP => Fill.GetComponent(); + private static GameObject Fill; - public static GameObject Menu; + private static GameObject Menu; - public static GameObject RoleInfo; - public static GameObject RoleRoleIllustration; - public static SpriteRenderer RoleRoleIllustrationSP => RoleRoleIllustration.GetComponent(); + private static GameObject RoleInfo; + private static GameObject RoleIllustration; + public static bool Showing => Fill && Fill.active && Menu && Menu.active; + private static SpriteRenderer FillRend => Fill.GetComponent(); + private static SpriteRenderer RoleIllustrationRend => RoleIllustration.GetComponent(); - public static TextMeshPro RoleInfoTMP => RoleInfo.GetComponent(); + private static TextMeshPro RoleInfoTMP => RoleInfo.GetComponent(); - public static void Init() + [GameModuleInitializer] + private static void Init() { var DOBScreen = AccountManager.Instance.transform.FindChild("DOBEnterScreen"); @@ -30,7 +36,7 @@ public static void Init() Fill.transform.localPosition = new Vector3(0f, 0f, -980f); Fill.transform.localScale = new Vector3(20f, 10f, 1f); Fill.AddComponent().sprite = DOBScreen.FindChild("Fill").GetComponent().sprite; - FillSP.color = new Color(0f, 0f, 0f, 0.75f); + FillRend.color = new Color(0f, 0f, 0f, 0.75f); Menu = Object.Instantiate(DOBScreen.FindChild("InfoPage").gameObject, HudManager.Instance.transform.parent); Menu.name = "FinalSuspect Role Info Menu Page"; @@ -48,53 +54,55 @@ public static void Init() RoleInfoTMP.alignment = TextAlignmentOptions.Left; RoleInfoTMP.fontSize = 2f; - RoleRoleIllustration = new GameObject("Character Illustration") { layer = 5 }; - RoleRoleIllustration.transform.SetParent(Menu.transform); - RoleRoleIllustration.AddComponent(); - RoleRoleIllustration.transform.localPosition = new Vector3(2.3f, 0.8f, 4f); + RoleIllustration = new GameObject("Character Illustration") { layer = 5 }; + RoleIllustration.transform.SetParent(Menu.transform); + RoleIllustration.AddComponent(); + RoleIllustration.transform.localPosition = new Vector3(2.3f, 0.8f, 4f); + + ForceHide(); } public static void SetRoleInfoRef(PlayerControl player) { - if (player == null) return; + if (!player) return; if (!Fill || !Menu) Init(); var builder = new StringBuilder(256); builder.AppendFormat("\n", BlankLineSize); // 职业名 var role = player.Data.Role.Role; - builder.AppendFormat("{1}", FirstHeaderSize, GetRoleName(role).Color(GetRoleColor(role))); + builder.Append($"{GetRoleName(role).Color(GetRoleColor(role))}"); // 职业阵营 / 原版职业 - var roleTeam = player.IsImpostor()? "Impostor":"Crewmate"; - builder.AppendFormat(" ({1})\n", BodySize, GetString($"Type{roleTeam}")); - builder.AppendFormat("{1}\n", BodySize, player.GetRoleType().GetRoleInfoForVanilla(true) ?? ""); + var roleTeam = player.IsImpostor() ? "Imp" : "Crew"; + builder.Append($" ({GetString($"RoleType.{roleTeam}")})\n"); + builder.Append($"{player.GetRoleType().GetRoleInfoForVanilla(true) ?? ""}\n"); RoleInfoTMP.text = builder.ToString(); var HnSPrefix = ""; if (!IsNormalGame && player.IsAlive()) HnSPrefix = "HnS"; - RoleRoleIllustrationSP.sprite = LoadSprite($"CI_{HnSPrefix + role}.png", 320f); + RoleIllustrationRend.sprite = LoadSprite($"CI_{HnSPrefix + role}.png", 320f); } public static void Show() { if (!Fill || !Menu) Init(); - if (!Showing) - { - Fill?.SetActive(true); - Menu?.SetActive(true); - } + if (Showing) return; + Fill?.SetActive(true); + Menu?.SetActive(true); //HudManager.Instance?.gameObject.SetActive(false); } + public static void Hide() { - if (Showing) - { - Fill?.SetActive(false); - Menu?.SetActive(false); - } + if (!Showing) return; + Fill?.SetActive(false); + Menu?.SetActive(false); + //HudManager.Instance?.gameObject?.SetActive(true); + } + + public static void ForceHide() + { + Fill?.SetActive(false); + Menu?.SetActive(false); //HudManager.Instance?.gameObject?.SetActive(true); } - public const string FirstHeaderSize = "130%"; - public const string SecondHeaderSize = "100%"; - public const string BodySize = "70%"; - public const string BlankLineSize = "30%"; } \ No newline at end of file diff --git a/FinalSuspect/Modules/Features/Zoom.cs b/FinalSuspect/Modules/Features/Zoom.cs index a7cec81d..04601708 100644 --- a/FinalSuspect/Modules/Features/Zoom.cs +++ b/FinalSuspect/Modules/Features/Zoom.cs @@ -1,11 +1,11 @@ using System; -using System.Collections.Generic; using FinalSuspect.Attributes; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using UnityEngine; namespace FinalSuspect.Modules.Features; -// 来源:https://github.com/tugaru1975/TownOfPlus/TOPmods/Zoom.cs +// 来源:https://github.com/tugaru1975/TownOfPlus/TOPmods/Zoom.cs // 参考:https://github.com/Yumenopai/TownOfHost_Y [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] public static class Zoom @@ -16,31 +16,33 @@ public static void Postfix() { try { - var canZoom = IsShip || IsLobby || IsFreePlay ; + var canZoom = IsShip || IsLobby || IsFreePlay; - if (!canZoom || !CanSeeOthersRole()|| IsMeeting || !IsCanMove || InGameRoleInfoMenu.Showing) + if (!canZoom || !CanSeeOthersRole() || IsInMeeting || !IsCanMove || InGameRoleInfoMenu.Showing) { Flag.Run(() => { SetZoomSize(reset: true); }, "Zoom"); return; } if (Camera.main?.orthographicSize > 3.0f) ResetButtons = true; - if (Input.mouseScrollDelta.y > 0) + switch (Input.mouseScrollDelta.y) { - if (Camera.main?.orthographicSize > 3.0f) SetZoomSize(times: false); - } - - if (Input.mouseScrollDelta.y < 0) - { - if (IsDead || IsFreePlay || - DebugModeManager.AmDebugger || IsLobby || Main.GodMode.Value) + case > 0: + { + if (Camera.main?.orthographicSize > 3.0f) + SetZoomSize(); + break; + } + case < 0: { - if (Camera.main?.orthographicSize < 18.0f) - { - SetZoomSize(times: true); - } + if (IsDead || IsFreePlay || + DebugModeManager.IsDebugMode || IsLobby || Main.GodMode.Value) + if (Camera.main?.orthographicSize < 18.0f) + SetZoomSize(true); + break; } } + Flag.NewFlag("Zoom"); } catch @@ -49,9 +51,9 @@ public static void Postfix() } } - public static void SetZoomSize(bool times = false, bool reset = false) + private static void SetZoomSize(bool times = false, bool reset = false) { - if (Camera.main == null) return; + if (!Camera.main) return; var size = 1.5f; if (!times) size = 1 / size; if (reset) @@ -59,19 +61,20 @@ public static void SetZoomSize(bool times = false, bool reset = false) Camera.main.orthographicSize = 3.0f; HudManager.Instance.UICamera.orthographicSize = 3.0f; HudManager.Instance.Chat.transform.localScale = Vector3.one; - if (IsMeeting) MeetingHud.Instance.transform.localScale = Vector3.one; + if (IsInMeeting) MeetingHud.Instance.transform.localScale = Vector3.one; } else { Camera.main.orthographicSize *= size; HudManager.Instance.UICamera.orthographicSize *= size; } - DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive((reset || Mathf.Approximately(Camera.main.orthographicSize, 3.0f)) && PlayerControl.LocalPlayer.IsAlive()); - if (ResetButtons) - { - ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); - ResetButtons = false; - } + + DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive( + (reset || Mathf.Approximately(Camera.main.orthographicSize, 3.0f)) && PlayerControl.LocalPlayer.IsAlive()); + if (!ResetButtons) return; + ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, + Screen.fullScreen); + ResetButtons = false; } [GameModuleInitializer] @@ -79,27 +82,31 @@ public static void Init() { SetZoomSize(reset: true); } + public static void OnFixedUpdate() - => DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive(Mathf.Approximately(Camera.main!.orthographicSize, 3.0f) && PlayerControl.LocalPlayer.IsAlive()); + { + DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive( + Mathf.Approximately(Camera.main!.orthographicSize, 3.0f) && PlayerControl.LocalPlayer.IsAlive()); + } } public static class Flag { private static readonly List OneTimeList = []; private static readonly List FirstRunList = []; + public static void Run(Action action, string type, bool firstrun = false) { - if (OneTimeList.Contains(type) || (firstrun && !FirstRunList.Contains(type))) - { - if (!FirstRunList.Contains(type)) FirstRunList.Add(type); - OneTimeList.Remove(type); - action(); - } + if (!OneTimeList.Contains(type) && (!firstrun || FirstRunList.Contains(type))) return; + if (!FirstRunList.Contains(type)) FirstRunList.Add(type); + OneTimeList.Remove(type); + action(); } + public static void NewFlag(string type) { if (!OneTimeList.Contains(type)) OneTimeList.Add(type); - } + } public static void DeleteFlag(string type) { diff --git a/FinalSuspect/Modules/LogHandler/LogHandler.cs b/FinalSuspect/Modules/LogHandler/LogHandler.cs index a4e9e1ed..8e815023 100644 --- a/FinalSuspect/Modules/LogHandler/LogHandler.cs +++ b/FinalSuspect/Modules/LogHandler/LogHandler.cs @@ -2,20 +2,37 @@ namespace FinalSuspect.Modules.LogHandler; -class LogHandler(string tag) : ILogHandler +internal class LogHandler(string tag) : ILogHandler { public string Tag { get; } = tag; public void Info(string text) - => XtremeLogger.Info(text, Tag); + { + FinalLogger.Info(text, Tag); + } + public void Warn(string text) - => XtremeLogger.Warn(text, Tag); + { + FinalLogger.Warn(text, Tag); + } + public void Error(string text) - => XtremeLogger.Error(text, Tag); + { + FinalLogger.Error(text, Tag); + } + public void Fatal(string text) - => XtremeLogger.Fatal(text, Tag); + { + FinalLogger.Fatal(text, Tag); + } + public void Msg(string text) - => XtremeLogger.Msg(text, Tag); + { + FinalLogger.Msg(text, Tag); + } + public void Exception(Exception ex) - => XtremeLogger.Exception(ex, Tag); + { + FinalLogger.Exception(ex, Tag); + } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Panels/SoundManagementPanel.cs b/FinalSuspect/Modules/Panels/SoundManagementPanel.cs deleted file mode 100644 index f5dab48b..00000000 --- a/FinalSuspect/Modules/Panels/SoundManagementPanel.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using FinalSuspect.Modules.Features; -using FinalSuspect.Modules.Resources; -using FinalSuspect.Modules.SoundInterface; -using TMPro; -using UnityEngine; -using UnityEngine.UI; -using Object = UnityEngine.Object; - -namespace FinalSuspect.Modules.Panels; - -public static class SoundManagementPanel -{ - public static SpriteRenderer CustomBackground { get; set; } - public static GameObject Slider { get; private set; } - public static Dictionary Items { get; private set; } - - private static int numItems; - - public static void Hide() - { - if (CustomBackground != null) - CustomBackground?.gameObject.SetActive(false); - } - - public static void Init(OptionsMenuBehaviour optionsMenuBehaviour) - { - var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; - if (!IsNotJoined) return; - - if (CustomBackground == null) - { - numItems = 0; - CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); - CustomBackground.name = "Audio Management Panel Background"; - CustomBackground.transform.localScale = new Vector3(0.9f, 0.9f, 1f); - CustomBackground.transform.localPosition += Vector3.back * 18; - CustomBackground.gameObject.SetActive(false); - - var closeButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); - closeButton.transform.localPosition = new Vector3(1.3f, -2.43f, -16f); - closeButton.name = "Close"; - closeButton.Text.text = GetString("Close"); - closeButton.Background.color = Color.red; - var closePassiveButton = closeButton.GetComponent(); - closePassiveButton.OnClick = new Button.ButtonClickedEvent(); - closePassiveButton.OnClick.AddListener(new Action(() => - { - CustomBackground.gameObject.SetActive(false); - })); - - var newButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); - newButton.transform.localPosition = new Vector3(1.3f, -1.88f, -16f); - newButton.name = "New Audio"; - newButton.Text.text = GetString("NewSound"); - newButton.Background.color = Palette.White; - var newPassiveButton = newButton.GetComponent(); - newPassiveButton.OnClick = new Button.ButtonClickedEvent(); - newPassiveButton.OnClick.AddListener(new Action(SoundManagementNewWindow.Open)); - - var helpText = Object.Instantiate(CustomPopup.InfoTMP?.gameObject, CustomBackground.transform); - helpText.name = "Help Text"; - helpText.transform.localPosition = new Vector3(-1.25f, -2.15f, -15f); - helpText.transform.localScale = new Vector3(1f, 1f, 1f); - var helpTextTMP = helpText.GetComponent(); - helpTextTMP.text = GetString("CustomAudioManagementHelp"); - helpText.gameObject.GetComponent().sizeDelta = new Vector2(2.45f, 1f); - - var sliderTemplate = AccountManager.Instance.transform.FindChild("MainSignInWindow/SignIn/AccountsMenu/Accounts/Slider").gameObject; - if (sliderTemplate != null && Slider == null) - { - Slider = Object.Instantiate(sliderTemplate, CustomBackground.transform); - Slider.name = "Audio Management Slider"; - Slider.transform.localPosition = new Vector3(0f, 0.5f, -11f); - Slider.transform.localScale = new Vector3(1f, 1f, 1f); - Slider.GetComponent().size = new Vector2(5f, 4f); - var scroller = Slider.GetComponent(); - scroller.ScrollWheelSpeed = 0.3f; - var mask = Slider.transform.FindChild("Mask"); - mask.transform.localScale = new Vector3(4.9f, 3.92f, 1f); - } - } - RefreshTagList(); - } - - public static void RefreshTagList() - { - if (!IsNotJoined) return; - numItems = 0; - var scroller = Slider.GetComponent(); - scroller.Inner.gameObject.ForEachChild((Action)DestroyObj); - - var numberSetter = AccountManager.Instance.transform.FindChild("DOBEnterScreen/EnterAgePage/MonthMenu/Months").GetComponent(); - var buttonPrefab = numberSetter.ButtonPrefab.gameObject; - - Items?.Values.Do(Object.Destroy); - Items = new Dictionary(); - foreach (var audio in XtremeMusic.musics) - { - numItems++; - var filename = audio.FileName; - - var button = Object.Instantiate(buttonPrefab, scroller.Inner); - button.transform.localPosition = new Vector3(-1f, 1.6f - 0.6f * numItems, -10.5f); - button.transform.localScale = new Vector3(1.2f, 1.2f, 1.2f); - button.name = "Btn-" + filename; - - var renderer = button.GetComponent(); - var rollover = button.GetComponent(); - - var previewText = Object.Instantiate(button.transform.GetChild(0).GetComponent(), button.transform); - previewText.transform.SetLocalX(1.9f); - previewText.fontSize = 1f; - previewText.name = "PreText-" + filename; - - Object.Destroy(button.GetComponent()); - Object.Destroy(button.GetComponent()); - - string buttontext; - Color buttonColor; - var enable = true; - - var audioExist = audio.CurrectAudioStates is not AudiosStates.NotExist || SoundInterface.SoundManager.CustomAudios.Contains(filename); - var unpublished = audio.unpublished; - - switch (audio.CurrectAudioStates) - { - case AudiosStates.IsDownLoading: - buttontext = GetString("DownloadingAudios"); - buttonColor = Color.yellow; - enable = false; - break; - case AudiosStates.DownLoadSucceedNotice or AudiosStates.DownLoadFailureNotice: - { - var succeed = audio.CurrectAudioStates is AudiosStates.DownLoadSucceedNotice; - buttontext = GetString($"{audio.CurrectAudioStates}"); - buttonColor = succeed ? Color.cyan : Palette.Brown; - enable = false; - break; - } - case AudiosStates.IsPlaying: - buttontext = GetString("Playing"); - buttonColor = Color.red; - enable = false; - break; - default: - { - if (audioExist) - { - buttontext = GetString("delete"); - buttonColor = !audio.UnOfficial ? Color.red : Palette.Purple; - } - else - { - buttontext = !audio.UnOfficial ? GetString("download") : GetString("NoFound"); - buttonColor = !audio.UnOfficial ? Color.green : Color.black; - } - break; - } - } - - if (unpublished) - { - buttonColor = Palette.DisabledGrey; - enable = false; - } - - var preview = audio.Name; - - var passiveButton = button.GetComponent(); - passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.OnClick.AddListener(new Action(() => - { - if (audioExist) - { - Delete(audio); - } - else - { - audio.CurrectAudioStates = audio.LastAudioStates = AudiosStates.IsDownLoading; - RefreshTagList(); - var task = ResourcesDownloader.StartDownload(FileType.Sounds, filename + ".wav"); - task.ContinueWith(t => - { - _ = new MainThreadTask(() => - { - audio.CurrectAudioStates = audio.LastAudioStates = t.Result ? AudiosStates.DownLoadSucceedNotice : AudiosStates.DownLoadFailureNotice; - RefreshTagList(); - - _ = new LateTask(() => - { - XtremeMusic.CreateMusic(music: audio.CurrectAudio); - RefreshTagList(); - MyMusicPanel.RefreshTagList(); - },3f, "Refresh Tag List"); - }, "Download Notice"); - }); - } - })); - - button.transform.GetChild(0).GetComponent().text = buttontext; - rollover.OutColor = renderer.color = buttonColor; - button.GetComponent().enabled = enable; - previewText.text = preview; - Items.Add(filename, button); - } - - scroller.SetYBoundsMin(0f); - scroller.SetYBoundsMax(0.6f * numItems); - return; - - static void DestroyObj(GameObject obj) - { - if (obj.name.StartsWith("AccountButton")) Object.Destroy(obj); - } - } - - private static void Delete(XtremeMusic audio) - { - var sound = audio.FileName; - if (audio.UnOfficial) - DeleteSoundInName(sound); - DeleteSoundInFile(sound); - if (!audio.UnOfficial) XtremeMusic.CreateMusic(music: audio.CurrectAudio); - RefreshTagList(); - MyMusicPanel.RefreshTagList(); - } - - private static void DeleteSoundInName(string name) - { - using StreamReader sr = new(SoundInterface.SoundManager.TAGS_PATH); - - List update = []; - while (sr.ReadLine() is { } line) - { - if (string.IsNullOrWhiteSpace(line)) continue; - if (line != name) - { - update.Add(line); - } - } - - sr.Dispose(); - - File.Delete(SoundInterface.SoundManager.TAGS_PATH); - File.Create(SoundInterface.SoundManager.TAGS_PATH).Close(); - - var attributes = File.GetAttributes(SoundInterface.SoundManager.TAGS_PATH); - File.SetAttributes(SoundInterface.SoundManager.TAGS_PATH, attributes | FileAttributes.Hidden); - - using StreamWriter sw = new(SoundInterface.SoundManager.TAGS_PATH, true); - - foreach (var updateline in update) - { - sw.WriteLine(updateline); - } - - var item = XtremeMusic.musics.FirstOrDefault(x => x.Name == name); - XtremeMusic.musics.Remove(item); - } - - private static void DeleteSoundInFile(string sound) - { - var path = GetResourceFilesPath(FileType.Sounds, sound + ".wav"); - File.Delete(path); - } -} \ No newline at end of file diff --git a/FinalSuspect/Modules/Random/HashRandomWrapper.cs b/FinalSuspect/Modules/Random/HashRandomWrapper.cs index fc83196d..86b40d95 100644 --- a/FinalSuspect/Modules/Random/HashRandomWrapper.cs +++ b/FinalSuspect/Modules/Random/HashRandomWrapper.cs @@ -2,8 +2,15 @@ namespace FinalSuspect.Modules.Random; public class HashRandomWrapper : IRandom { - public int Next(int minValue, int maxValue) => HashRandom.Next(minValue, maxValue); - public int Next(int maxValue) => HashRandom.Next(maxValue); + public int Next(int minValue, int maxValue) + { + return HashRandom.Next(minValue, maxValue); + } + + public int Next(int maxValue) + { + return HashRandom.Next(maxValue); + } //public uint Next() => HashRandom.Next(); //public int FastNext(int maxValue) => HashRandom.FastNext(maxValue); } \ No newline at end of file diff --git a/FinalSuspect/Modules/Random/IRandom.cs b/FinalSuspect/Modules/Random/IRandom.cs index e89a3988..c1ae5730 100644 --- a/FinalSuspect/Modules/Random/IRandom.cs +++ b/FinalSuspect/Modules/Random/IRandom.cs @@ -1,27 +1,28 @@ using System; -using System.Collections.Generic; namespace FinalSuspect.Modules.Random; public interface IRandom { - /// 0以上maxValue未満の乱数を生成します。 - public int Next(int maxValue); - /// minValue以上maxValue未満の乱数を生成します。 - public int Next(int minValue, int maxValue); - // == static == // IRandomを実装するクラスのリスト - public static Dictionary randomTypes = new() + public static readonly Dictionary randomTypes = new() { { 0, typeof(NetRandomWrapper) }, //Default { 1, typeof(NetRandomWrapper) }, { 2, typeof(HashRandomWrapper) }, { 3, typeof(Xorshift) }, - { 4, typeof(MersenneTwister) }, + { 4, typeof(MersenneTwister) } }; public static IRandom Instance { get; private set; } + + /// 0以上maxValue未満の乱数を生成します。 + public int Next(int maxValue); + + /// minValue以上maxValue未満の乱数を生成します。 + public int Next(int minValue, int maxValue); + public static void SetInstance(IRandom instance) { if (instance != null) @@ -34,10 +35,11 @@ public static void SetInstanceById(int id) { // 現在のインスタンスがnull または 現在のインスタンスの型が指定typeと一致しない if (Instance == null || Instance.GetType() != type) - { Instance = Activator.CreateInstance(type) as IRandom ?? Instance; - } } - else Warn($"無効なID: {id}", "IRandom.SetInstanceById"); + else + { + Warn($"无效ID: {id}", "IRandom.SetInstanceById"); + } } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Random/MersenneTwister.cs b/FinalSuspect/Modules/Random/MersenneTwister.cs index b2157bc7..f674cc1d 100644 --- a/FinalSuspect/Modules/Random/MersenneTwister.cs +++ b/FinalSuspect/Modules/Random/MersenneTwister.cs @@ -27,26 +27,52 @@ namespace FinalSuspect.Modules.Random; public class MersenneTwister : IRandom { // 参考元 - public const string REFERENCE_HOMEPAGE = "http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/mt.html"; - public const string REFERENCE_SOURCE_CODE = "https://github.com/vpmedia/template-unity/blob/master/Framework/Assets/Frameworks/URandom/MersenneTwister.cs"; + public const string REFERENCE_HOMEPAGE = + "http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/mt.html"; - public MersenneTwister() : this((int)DateTime.UtcNow.Ticks) { } - public MersenneTwister(int seed) - { - Init((uint)seed); - } + public const string REFERENCE_SOURCE_CODE = + "https://github.com/vpmedia/template-unity/blob/master/Framework/Assets/Frameworks/URandom/MersenneTwister.cs"; /// - /// 数値の上限を設定 - /// これより下の値の一部は参考元のソースより拝借 + /// 数値の上限を設定 + /// これより下の値の一部は参考元のソースより拝借 /// private const int N = 624; + private const int M = 397; private const uint MatrixA = 0x9908b0df; private const uint UpperMask = 0x80000000; private const uint LowerMask = 0x7fffffff; private const uint TemperingMaskB = 0x9d2c5680; private const uint TemperingMaskC = 0xefc60000; + private readonly uint[] _mag01 = [0x0, MatrixA]; + + private readonly uint[] _mt = new uint[N]; + private short _mtItems; + + public MersenneTwister() : this((int)DateTime.UtcNow.Ticks) + { + } + + private MersenneTwister(int seed) + { + Init((uint)seed); + } + + public int Next(int minValue, int maxValue) + { + if (minValue < 0) throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be bigger than 0."); + if (maxValue < 0) throw new ArgumentOutOfRangeException(nameof(maxValue), "maxValue must be bigger than 0."); + if (minValue > maxValue) throw new ArgumentException("maxValue must be bigger than minValue."); + if (minValue == maxValue) return minValue; + + return (int)(minValue + Next() % (maxValue - minValue)); + } + + public int Next(int maxValue) + { + return Next(0, maxValue); + } private static uint ShiftU(uint y) { @@ -68,10 +94,6 @@ private static uint ShiftL(uint y) return y >> 18; } - private readonly uint[] _mt = new uint[N]; - private short _mtItems; - private readonly uint[] _mag01 = [0x0, MatrixA]; - private void Init(uint seed) { _mt[0] = seed & 0xffffffffU; @@ -83,7 +105,7 @@ private void Init(uint seed) } } - public uint Next() + private uint Next() { uint y; @@ -118,16 +140,4 @@ public uint Next() return y; } - - public int Next(int minValue, int maxValue) - { - if (minValue < 0) throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be bigger than 0."); - if (maxValue < 0) throw new ArgumentOutOfRangeException(nameof(maxValue), "maxValue must be bigger than 0."); - if (minValue > maxValue) throw new ArgumentException("maxValue must be bigger than minValue."); - if (minValue == maxValue) return minValue; - - return (int)(minValue + Next() % (maxValue - minValue)); - } - - public int Next(int maxValue) => Next(0, maxValue); } \ No newline at end of file diff --git a/FinalSuspect/Modules/Random/NetRandomWrapper.cs b/FinalSuspect/Modules/Random/NetRandomWrapper.cs index add8dca3..25591535 100644 --- a/FinalSuspect/Modules/Random/NetRandomWrapper.cs +++ b/FinalSuspect/Modules/Random/NetRandomWrapper.cs @@ -2,12 +2,26 @@ namespace FinalSuspect.Modules.Random; public class NetRandomWrapper(System.Random instance) : IRandom { - public System.Random wrapping = instance; + public NetRandomWrapper() : this(new System.Random()) + { + } - public NetRandomWrapper() : this(new System.Random()) { } - public NetRandomWrapper(int seed) : this(new System.Random(seed)) { } + public NetRandomWrapper(int seed) : this(new System.Random(seed)) + { + } - public int Next(int minValue, int maxValue) => wrapping.Next(minValue, maxValue); - public int Next(int maxValue) => wrapping.Next(maxValue); - public int Next() => wrapping.Next(); + public int Next(int minValue, int maxValue) + { + return instance.Next(minValue, maxValue); + } + + public int Next(int maxValue) + { + return instance.Next(maxValue); + } + + public int Next() + { + return instance.Next(); + } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Random/Xorshift.cs b/FinalSuspect/Modules/Random/Xorshift.cs index a8770948..6610eb5d 100644 --- a/FinalSuspect/Modules/Random/Xorshift.cs +++ b/FinalSuspect/Modules/Random/Xorshift.cs @@ -9,16 +9,10 @@ public class Xorshift(uint seed) : IRandom private uint num = seed; - public Xorshift() : this((uint)DateTime.UtcNow.Ticks) { } - - public uint Next() + public Xorshift() : this((uint)DateTime.UtcNow.Ticks) { - num ^= num << 13; - num ^= num >> 17; - num ^= num << 5; - - return num; } + public int Next(int minValue, int maxValue) { if (minValue < 0) throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be bigger than 0."); @@ -28,5 +22,18 @@ public int Next(int minValue, int maxValue) return (int)(minValue + Next() % (maxValue - minValue)); } - public int Next(int maxValue) => Next(0, maxValue); + + public int Next(int maxValue) + { + return Next(0, maxValue); + } + + private uint Next() + { + num ^= num << 13; + num ^= num >> 17; + num ^= num << 5; + + return num; + } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Resources/Downloader.cs b/FinalSuspect/Modules/Resources/Downloader.cs index 3a0b1bc3..7b702633 100644 --- a/FinalSuspect/Modules/Resources/Downloader.cs +++ b/FinalSuspect/Modules/Resources/Downloader.cs @@ -1,26 +1,58 @@ using System; using System.IO; +using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace FinalSuspect.Modules.Resources; + // 来源:Town Of Next public class HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath) : IDisposable { - private readonly string _downloadUrl = downloadUrl; - private readonly string _destinationFilePath = destinationFilePath; + public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, + double? progressPercentage); private HttpClient _httpClient; - public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); + public void Dispose() + { + _httpClient?.Dispose(); + } public event ProgressChangedHandler ProgressChanged; public async Task StartDownload() { - _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) }; + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; - using var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead); + var handler = new HttpClientHandler + { + AllowAutoRedirect = true, + UseDefaultCredentials = true, + Proxy = null, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + + _httpClient = new HttpClient(handler) + { + Timeout = TimeSpan.FromSeconds(30) + }; + + // 浏览器级请求头, 完全防止403 + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + + "AppleWebKit/537.36 (KHTML, like Gecko) " + + "Chrome/127.0.0.0 Safari/537.36"); + + _httpClient.DefaultRequestHeaders.Add("Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); + _httpClient.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.9"); + _httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); + _httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache"); + _httpClient.DefaultRequestHeaders.Add("Pragma", "no-cache"); + + using var response = await _httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); await DownloadFileFromHttpResponseMessage(response); } @@ -30,7 +62,7 @@ private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage respo var totalBytes = response.Content.Headers.ContentLength; - using var contentStream = await response.Content.ReadAsStreamAsync(); + await using var contentStream = await response.Content.ReadAsStreamAsync(); await ProcessContentStream(totalBytes, contentStream); } @@ -41,7 +73,9 @@ private async Task ProcessContentStream(long? totalDownloadSize, Stream contentS var buffer = new byte[8192]; var isMoreToRead = true; - using var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); + await using var fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, + FileShare.None, + 8192, true); do { var bytesRead = await contentStream.ReadAsync(buffer); @@ -59,8 +93,7 @@ private async Task ProcessContentStream(long? totalDownloadSize, Stream contentS if (readCount % 100 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); - } - while (isMoreToRead); + } while (isMoreToRead); } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) @@ -74,6 +107,4 @@ private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } - - public void Dispose() => _httpClient?.Dispose(); } \ No newline at end of file diff --git a/FinalSuspect/Modules/Resources/ModUpdater.cs b/FinalSuspect/Modules/Resources/ModUpdater.cs index d7c5daaf..568336b4 100644 --- a/FinalSuspect/Modules/Resources/ModUpdater.cs +++ b/FinalSuspect/Modules/Resources/ModUpdater.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Security.Cryptography; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using FinalSuspect.Modules.Features; -using FinalSuspect.Patches.System; using TMPro; using UnityEngine; @@ -20,55 +17,61 @@ public class ModUpdater public static void SetUpdateButtonStatus() { - MainMenuManagerPatch.UpdateButton.SetActive(VersionChecker.isChecked && VersionChecker.hasUpdate && (VersionChecker.firstStart || VersionChecker.forceUpdate)); - MainMenuManagerPatch.PlayButton.SetActive(!MainMenuManagerPatch.UpdateButton.activeSelf); - var buttonText = MainMenuManagerPatch.UpdateButton.transform.FindChild("FontPlacer").GetChild(0).GetComponent(); - buttonText.text = $"{(VersionChecker.CanUpdate ? GetString("updateButton") : GetString("updateNotice"))}\nv{VersionChecker.showVer ?? " ???"}"; + ModMainMenuManager.UpdateButton.SetActive(VersionChecker.IsChecked && VersionChecker.HasUpdate && + (VersionChecker.FirstStart || VersionChecker.ForceUpdate)); + ModMainMenuManager.PlayButton.SetActive(!ModMainMenuManager.UpdateButton.activeSelf); + var buttonText = ModMainMenuManager.UpdateButton.transform.FindChild("FontPlacer").GetChild(0) + .GetComponent(); + buttonText.text = + $"{(VersionChecker.CanUpdate ? GetString("UpdateRemind.updatePopup") : GetString("UpdateRemind.updateNotice"))}\nv{VersionChecker.ShowVer ?? " ???"}"; } - + public static void StartUpdate(string url = "waitToSelect") { if (url == "waitToSelect") { - CustomPopup.Show(GetString("updatePopupTitle"), GetString("updateChoseSource"), + CustomPopup.Show(GetString("UpdateRemind.updatePopup"), GetString("UpdateSource.Choose:"), [ - (GetString("updateSource.XtremeApi"), () => StartUpdate(downloadUrl_xtremeapi)), - (GetString("updateSource.Github"), () => StartUpdate(downloadUrl_github)), - (GetString("updateSource.Gitee"), () => StartUpdate(downloadUrl_gitee)), + (GetString("UpdateSource.GithubMirror"), () => StartUpdate(downloadUrl_githubMirror)), + (GetString("UpdateSource.Github"), () => StartUpdate(downloadUrl_github)), + (GetString("UpdateSource.Gitee"), () => StartUpdate(downloadUrl_gitee)), (GetString(StringNames.Cancel), SetUpdateButtonStatus) ]); return; } - var r = new Regex(@"^(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)?((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.[a-zA-Z]{2,4})(\:[0-9]+)?(/[^/][a-zA-Z0-9\.\,\?\'\\/\+&%\$#\=~_\-@]*)*$"); + var r = new Regex( + @"^(http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)?((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.[a-zA-Z]{2,4})(\:[0-9]+)?(/[^/][a-zA-Z0-9\.\,\?\'\\/\+&%\$#\=~_\-@]*)*$"); if (!r.IsMatch(url)) { - CustomPopup.ShowLater(GetString("updatePopupTitleFailed"), string.Format(GetString("updatePingFialed"), "404 Not Found"), + CustomPopup.ShowLater(GetString("UpdateResult.Failed_Title"), + string.Format(GetString("UpdateResult.Failed_Reason_NotFound"), "404 Not Found"), [(GetString(StringNames.Okay), SetUpdateButtonStatus)]); return; } - CustomPopup.Show(GetString("updatePopupTitle"), GetString("updatePleaseWait"), null); + CustomPopup.Show(GetString("UpdateRemind.updatePopup"), GetString("Tip.PleaseWait"), null); var task = DownloadDLL(url); task.ContinueWith(t => { var (done, reason) = t.Result; - var title = done ? GetString("updatePopupTitleDone") : GetString("updatePopupTitleFailed"); - var desc = done ? GetString("updateRestart") : reason; + var title = done ? GetString("updatePopupTitleDone") : GetString("UpdateResult.Failed_Title"); + var desc = done ? GetString("UpdateResult.Succeed_Text") : reason; CustomPopup.ShowLater(title, desc, [(GetString(done ? StringNames.ExitGame : StringNames.Okay), done ? Application.Quit : null)]); SetUpdateButtonStatus(); }); } + public static void DeleteOldFiles() { try { - foreach (var path in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "*.*")) + foreach (var path in Directory.EnumerateFiles( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "*.*")) { - if (path.EndsWith(Path.GetFileName(Assembly.GetExecutingAssembly().Location))) continue; - if (path.EndsWith("FinalSuspect.dll") || path.EndsWith("Downloader.dll")) continue; + if (!Path.GetExtension(path).Equals(".bak", StringComparison.OrdinalIgnoreCase)) continue; Info($"{Path.GetFileName(path)} Deleted", "DeleteOldFiles"); File.Delete(path); @@ -79,7 +82,8 @@ public static void DeleteOldFiles() Error($"清除更新残留失败\n{e}", "DeleteOldFiles"); } } - public static async Task<(bool, string)> DownloadDLL(string url) + + private static async Task<(bool, string)> DownloadDLL(string url) { File.Delete(DownloadFileTempPath); File.Create(DownloadFileTempPath).Close(); @@ -92,11 +96,12 @@ public static void DeleteOldFiles() client.ProgressChanged += OnDownloadProgressChanged; await client.StartDownload(); Thread.Sleep(100); - if (GetMD5HashFromFile(DownloadFileTempPath) != VersionChecker.md5) + if (GetMD5HashFromFile(DownloadFileTempPath) != VersionChecker.MD5) { File.Delete(DownloadFileTempPath); - return (false, GetString("updateFileMd5Incorrect")); + return (false, GetString("UpdateResult.Failed_Reason_FileMd5Incorrect")); } + var fileName = Assembly.GetExecutingAssembly().Location; File.Move(fileName, fileName + ".bak"); File.Move(DownloadFileTempPath, fileName); @@ -106,19 +111,21 @@ public static void DeleteOldFiles() { File.Delete(DownloadFileTempPath); Error($"更新失败\n{ex.Message}", "DownloadDLL", false); - return (false, GetString("downloadFailed")); + return (false, GetString("UpdateResult.Failed_Reason_Ping")); } } - private static void OnDownloadProgressChanged(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage) + + private static void OnDownloadProgressChanged(long? totalFileSize, long totalBytesDownloaded, + double? progressPercentage) { - if (progressPercentage != null) - { - var msg = $"{GetString("updateInProgress")}\n{totalFileSize / 1000}KB / {totalBytesDownloaded / 1000}KB - {(int)progressPercentage}%"; - Info(msg, "DownloadDLL"); - CustomPopup.UpdateTextLater(msg); - } + if (progressPercentage == null) return; + var msg = + $"{GetString("Tip.Updating")}\n{totalFileSize / 1000}KB / {totalBytesDownloaded / 1000}KB - {(int)progressPercentage}%"; + Info(msg, "DownloadDLL"); + CustomPopup.UpdateTextLater(msg); } - public static string GetMD5HashFromFile(string fileName) + + private static string GetMD5HashFromFile(string fileName) { try { diff --git a/FinalSuspect/Modules/Resources/PathManager.cs b/FinalSuspect/Modules/Resources/PathManager.cs index 8ef3bae3..a9f23676 100644 --- a/FinalSuspect/Modules/Resources/PathManager.cs +++ b/FinalSuspect/Modules/Resources/PathManager.cs @@ -1,27 +1,58 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using FinalSuspect.Attributes; namespace FinalSuspect.Modules.Resources; public static class PathManager { - public const string LocalPath_Data = "Final Suspect_Data/"; + private const string LocalPath_Data = "Final Suspect_Data/"; + public const string LANGUAGE_FOLDER_NAME = LocalPath_Data + "Language"; private const string DependsSavePath = "BepInEx/core/"; public const string DownloadFileTempPath = "BepInEx/plugins/FinalSuspect.dll.temp"; - public const string downloadUrl_github = "https://github.com/XtremeWave/FinalSuspect/releases/latest/download/FinalSuspect.dll"; + public const string BAN_LIST_PATH = LocalPath_Data + "BanList.txt"; + + public const string downloadUrl_github = + "https://github.com/Slok7565/FinalSuspect/releases/latest/download/FinalSuspect.dll"; + + public const string downloadUrl_githubMirror = + "https://hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect/releases/latest/download/FinalSuspect.dll"; + + public static string downloadUrl_gitee = + "https://gitee.com/LezaiYa/FinalSuspectAssets/releases/download/v{showVer}/FinalSuspect.dll"; + + public static readonly string BANEDWORDS_FILE_PATH = GetBanFilesPath("BanWords.json"); + public static readonly string DENY_NAME_LIST_PATH = GetBanFilesPath("DenyName.json"); + + + private static IReadOnlyList URLs => new List + { +#if DEBUG + "https://raw.githubusercontent.com/Slok7565/FinalSuspect_Assets/FinalAsset/", + "https://raw.githubusercontent.com/Slok7565/FinalSuspect/FinalSus/", + "https://hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect/FinalSus/", + "https://hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect_Assets/FinalAsset/", + "https://gitee.com/LezaiYa/FinalSuspectAssets/raw/main/", + $"file:///{Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop))}/", +#else + "https://raw.githubusercontent.com/Slok7565/FinalSuspect/FinalSus/", + "https://hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect/FinalSus/", + "https://hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect_Assets/FinalAsset/", + "https://gitee.com/LezaiYa/FinalSuspectAssets/raw/main/", +#endif + }; - public static string downloadUrl_gitee = "https://gitee.com/LezaiYa/FinalSuspectAssets/releases/download/v{showVer}/FinalSuspect.dll"; - public const string downloadUrl_xtremeapi = "http://121.62.28.59:1145/download/FinalSuspect/FinalSuspect.dll"; - public static string GetFile(FileType fileType, RemoteType remoteType, string file) { return GetRemoteUrl(fileType, remoteType) + file; } - public static string GetRemoteUrl(FileType fileType, RemoteType remoteType) + public static string GetPackageFile(string packageName, RemoteType remoteType, string file) + { + return "https://" + GetRemoteBase(remoteType) + "Packages/" + packageName + "/" + file; + } + + private static string GetRemoteUrl(FileType fileType, RemoteType remoteType) { return "https://" + GetRemoteBase(remoteType) + fileType + "/"; } @@ -30,11 +61,15 @@ private static string GetRemoteBase(RemoteType remoteType) { var remoteBase = remoteType switch { - RemoteType.Github => "github.com/XtremeWave/FinalSuspect/raw/FinalSus/Assets/", + RemoteType.GithubMirror => "hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect/FinalSus/Assets/", + RemoteType.GithubMirror_Assets => + "hub.gitmirror.com/https://github.com/Slok7565/FinalSuspect_Assets/FinalAsset/Assets/", + RemoteType.Github => "github.com/Slok7565/FinalSuspect/FinalSus/Assets/", + RemoteType.Github_Assets => "github.com/Slok7565/FinalSuspect_Assets/FinalAsset/Assets/", RemoteType.Gitee => "gitee.com/LezaiYa/FinalSuspectAssets/raw/main/Assets/", - RemoteType.XtremeApi => "api.xtreme.net.cn/FinalSuspect/download/Assets/", _ => "127.0.0.1" }; + return remoteBase; } @@ -43,23 +78,23 @@ public static string GetLocalFilePath(FileType fileType, string file) return fileType switch { FileType.Depends => GetLocalPath(LocalType.BepInEx) + file, - _ => GetResourceFilesPath(fileType, file), + _ => GetResourceFilesPath(fileType, file) }; } - + public static string GetLocalPath(LocalType localType) { if (localType == LocalType.BepInEx) return DependsSavePath; return LocalPath_Data + localType + "/"; } - + public static string GetResourceFilesPath(FileType fileType, string file) { return GetLocalPath(LocalType.Resources) + fileType + "/" + file; } - - public static string GetBanFilesPath(string file) + + private static string GetBanFilesPath(string file) { return GetLocalPath(LocalType.Ban) + file; } @@ -68,76 +103,63 @@ public static string GetBanFilesPath(string file) public static void InitializePaths() { CheckAndCreate(GetLocalPath(LocalType.Resources), false); - CheckAndCreate(GetLocalPath(LocalType.Resources) + "Sounds", false); + CheckAndCreate(GetLocalPath(LocalType.Resources) + "Musics", false); + CheckAndCreate(GetLocalPath(LocalType.Resources) + "SoundEffects"); CheckAndCreate(GetLocalPath(LocalType.Resources) + "Images"); - - CheckAndCreate(GetLocalPath(LocalType.Resources) + "Languages"); + CheckAndCreate(GetLocalPath(LocalType.Resources) + "Languages", false); + CheckAndCreate(LANGUAGE_FOLDER_NAME, false); + CheckAndCreate(GetLocalPath(LocalType.Ban)); - CheckAndCreate(GetLocalPath(LocalType.Bypass), false); + CheckAndCreate(BANEDWORDS_FILE_PATH, false, true); + CheckAndCreate(DENY_NAME_LIST_PATH, false, true); + + CheckAndCreate(GetLocalPath(LocalType.NameTag)); // 防止崩溃的必要措施 - CheckAndDelete(LocalPath_Data); - CheckAndDelete(DependsSavePath); + CheckAndDeleteXWR(LocalPath_Data); + CheckAndDeleteXWR(DependsSavePath); } - private static void CheckAndCreate(string path, bool hidden = true) + private static void CheckAndCreate(string path, bool hidden = true, bool isFile = false) { if (path == null) return; - - if (!Directory.Exists(path)) + + switch (isFile) { - Directory.CreateDirectory(path); + case true when !File.Exists(path): + File.Create(path); + break; + case false when !Directory.Exists(path): + Directory.CreateDirectory(path); + break; } - + var attributes = File.GetAttributes(path); File.SetAttributes(path, hidden - ? attributes | FileAttributes.Hidden + ? attributes | FileAttributes.Hidden : attributes & ~FileAttributes.Hidden); } - private static void CheckAndDelete(string targetFolder) + private static void CheckAndDeleteXWR(string targetFolder) { if (!Directory.Exists(targetFolder)) return; try { var filesToDelete = Directory.GetFiles(targetFolder, "*.xwr", SearchOption.AllDirectories); - - foreach (var file in filesToDelete) - { - File.Delete(file); - } + + foreach (var file in filesToDelete) File.Delete(file); } - catch + catch { /* ignored */ } } - - public static string GetBypassFileType(FileType fileType, BypassType bypassType) - { - return GetLocalPath(LocalType.Bypass) + $"BypassCheck_{fileType}_{bypassType}.xwc"; - } - - private static IReadOnlyList URLs => new List - { -#if RELEASE - "https://raw.githubusercontent.com/XtremeWave/FinalSuspect/FinalSus/", - "https://gitee.com/LezaiYa/FinalSuspectAssets/raw/main/", - "http://121.62.28.59:1145/FinalSuspect/download/", -#else - "https://raw.githubusercontent.com/XtremeWave/FinalSuspect/FinalSus/", - "https://raw.githubusercontent.com/XtremeWave/FinalSuspect_Dev/FS_Dev/", - "http://121.62.28.59:1145/FinalSuspect/download/", - "https://gitee.com/LezaiYa/FinalSuspectAssets/raw/main/", - $"file:///{Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop))}/", -#endif - }; - + public static IReadOnlyList GetInfoFileUrlList(bool allowDesktop = false) { var list = URLs.ToList(); - if (!allowDesktop && DebugModeManager.AmDebugger) - list.RemoveAt(3); + if (!allowDesktop && DebugModeManager.IsDebugMode) + list.RemoveAt(4); if (IsChineseUser) list.Reverse(); return list; } @@ -145,8 +167,10 @@ public static IReadOnlyList GetInfoFileUrlList(bool allowDesktop = false public enum FileType { + Unknown, Images, - Sounds, + Musics, + SoundEffects, Depends, ModNews, Languages @@ -154,9 +178,11 @@ public enum FileType public enum RemoteType { - Github, + GithubMirror, + GithubMirror_Assets, Gitee, - XtremeApi + Github_Assets, + Github, } public enum LocalType @@ -164,11 +190,5 @@ public enum LocalType Ban, Resources, BepInEx, - Bypass -} - -public enum BypassType -{ - Once, - Longterm, + NameTag } \ No newline at end of file diff --git a/FinalSuspect/Modules/Resources/ResourcesDownloader.cs b/FinalSuspect/Modules/Resources/ResourcesDownloader.cs index deb0bba5..3fad217a 100644 --- a/FinalSuspect/Modules/Resources/ResourcesDownloader.cs +++ b/FinalSuspect/Modules/Resources/ResourcesDownloader.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.IO.Compression; using System.Security.Cryptography; using System.Text.RegularExpressions; using System.Threading; @@ -7,7 +8,7 @@ namespace FinalSuspect.Modules.Resources; -public class ResourcesDownloader +public static class ResourcesDownloader { public static async Task StartDownload(FileType fileType, string file) { @@ -15,33 +16,107 @@ public static async Task StartDownload(FileType fileType, string file) switch (fileType) { case FileType.Images: - case FileType.Sounds: + case FileType.Musics: case FileType.ModNews: case FileType.Languages: + case FileType.SoundEffects: filePath = GetResourceFilesPath(fileType, file); break; case FileType.Depends: filePath = GetLocalPath(LocalType.BepInEx) + file; break; + case FileType.Unknown: default: return false; } - + + var downloadFileTempPath = filePath + ".xwr"; + + var retryTimes = IsChineseLanguageUser ? 0 : 3; + retry: + var remoteType = (RemoteType)retryTimes; + + var url = GetFile(fileType, remoteType, file); + + if (!IsValidUrl(url)) + { + Error($"Invalid URL: {url}", "Download Resources", false); + return false; + } + + File.Create(downloadFileTempPath).Close(); + + Msg("Start Downloading from: " + url, "Download Resources"); + Msg("Saving file to: " + filePath, "Download Resources"); + + try + { + using var client = new HttpClientDownloadWithProgress(url, downloadFileTempPath); + await client.StartDownload(); + Thread.Sleep(100); + File.Delete(filePath); + File.Move(downloadFileTempPath, filePath); + + if (Path.GetExtension(filePath).Equals(".zip", StringComparison.OrdinalIgnoreCase)) + { + try + { + var extractPath = Path.GetDirectoryName(filePath); + Msg($"Unzipping file: {filePath}", "Download Resources"); + if (extractPath != null) ZipFile.ExtractToDirectory(filePath, extractPath); + File.Delete(filePath); + Warn($"Unzipped successfully: {filePath}", "Download Resources"); + } + catch (Exception ex) + { + Error($"Failed to unzip file\n{ex.Message}", "Download Resources", false); + return false; + } + } + + Warn($"Succeed in {url}", "Download Resources"); + return true; + } + catch (Exception ex) + { + Error($"Failed to download\n{ex.Message}", "Download Resources", false); + File.Delete(downloadFileTempPath); + retryTimes++; + if (retryTimes < 4) + goto retry; + return false; + } + } + + public static async Task StartDownloadAsPackage(string packageName, FileType fileType, string file) + { + string filePath; + switch (fileType) + { + case FileType.Images: + case FileType.ModNews: + case FileType.Languages: + case FileType.Musics: + case FileType.SoundEffects: + filePath = GetResourceFilesPath(fileType, file); + break; + case FileType.Depends: + filePath = GetLocalPath(LocalType.BepInEx) + file; + break; + case FileType.Unknown: + default: + return false; + } + var DownloadFileTempPath = filePath + ".xwr"; var retrytimes = 0; - var remoteType = RemoteType.Github; + var remoteType = RemoteType.Github; retry: if (IsChineseLanguageUser) - remoteType = retrytimes switch - { - 0 => RemoteType.XtremeApi, - 1 => RemoteType.Gitee, - 2 => RemoteType.Github, - _ => remoteType - }; + remoteType = (RemoteType)retrytimes; - var url = GetFile(fileType, remoteType, file); + var url = GetPackageFile(packageName, remoteType, file); if (!IsValidUrl(url)) { @@ -50,7 +125,7 @@ public static async Task StartDownload(FileType fileType, string file) } File.Create(DownloadFileTempPath).Close(); - + Msg("Start Downloading from: " + url, "Download Resources"); Msg("Saving file to: " + filePath, "Download Resources"); @@ -69,21 +144,18 @@ public static async Task StartDownload(FileType fileType, string file) Error($"Failed to download\n{ex.Message}", "Download Resources", false); File.Delete(DownloadFileTempPath); retrytimes++; - if (retrytimes < 3) + if (retrytimes < 3) goto retry; return false; } } + private static bool IsValidUrl(string url) { - var pattern = @"^(https?|ftp)://[^\s/$.?#].[^\s]*$"; + const string pattern = @"^(https?|ftp)://[^\s/$.?#].[^\s]*$"; return Regex.IsMatch(url, pattern); } - /*private static void OnDownloadProgressChanged(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage) - { - var msg = $"\n{totalFileSize / 1000}KB / {totalBytesDownloaded / 1000}KB - {(int)progressPercentage}%"; - Info(msg, "Download Resources"); - }*/ + public static string GetMD5HashFromFile(string fileName) { try @@ -99,7 +171,14 @@ public static string GetMD5HashFromFile(string fileName) return ""; } } - /*public static async Task IsUrl404Async(FileType fileType, string file) + + /*private static void OnDownloadProgressChanged(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage) + { + var msg = $"\n{totalFileSize / 1000}KB / {totalBytesDownloaded / 1000}KB - {(int)progressPercentage}%"; + Info(msg, "Download Resources"); + } + + public static async Task IsUrl404Async(FileType fileType, string file) { return false; using var client = new HttpClient(); @@ -114,7 +193,7 @@ public static string GetMD5HashFromFile(string fileName) } var urlGitee = PathManager.GetFile(fileType, RemoteType.Gitee, file); - var urlApi = PathManager.GetFile(fileType, RemoteType.XtremeApi, file); + var urlApi = PathManager.GetFile(fileType, RemoteType.FinalApi, file); var response1 = await client.GetAsync(urlGitee); var response2 = await client.GetAsync(urlApi); return response1.StatusCode == HttpStatusCode.NotFound && response2.StatusCode == HttpStatusCode.NotFound; diff --git a/FinalSuspect/Modules/Resources/VersionChecker.cs b/FinalSuspect/Modules/Resources/VersionChecker.cs index 84ad660a..afa72fd2 100644 --- a/FinalSuspect/Modules/Resources/VersionChecker.cs +++ b/FinalSuspect/Modules/Resources/VersionChecker.cs @@ -1,7 +1,7 @@ using System; -using System.IO; -using System.Net.Http; using System.Threading.Tasks; +using FinalSuspect.ClientActions.FeatureItems.NameTag; +using FinalSuspect.ClientActions.FeatureItems.Resources; using FinalSuspect.Helpers; using FinalSuspect.Modules.Features; using FinalSuspect.Modules.Features.CheckingandBlocking; @@ -13,70 +13,62 @@ namespace FinalSuspect.Modules.Resources; public static class VersionChecker { - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start)), HarmonyPriority(Priority.LowerThanNormal)] - public class Start - { - public static void Postfix() - { - CustomPopup.Init(); - if (firstStart) - { - _ = CheckForUpdate(); - _ = SpamManager.Init(); - _ = ModNewsHistory.LoadModAnnouncements(); - CustomPopup.Show(GetString("updateCheckPopupTitle"), GetString("LoadingWithDot"), null); - } - - ModUpdater.SetUpdateButtonStatus(); - firstStart = false; - } - } - - public static bool firstStart = true; + public static bool FirstStart = true; - public static bool hasUpdate; - public static bool forceUpdate; - public static bool isBroken; - public static bool isChecked; - public static bool DebugUnused; - public static string versionInfoRaw = ""; + public static bool HasUpdate; + public static bool ForceUpdate; + public static bool IsBroken; + public static bool IsChecked; - public static Version latestVersion; - public static string showVer = ""; - public static Version DebugVer; + private static Version _latestVersion; + public static string ShowVer = ""; public static bool CanUpdate; - public static string verHead = ""; - public static string verDate = ""; - public static Version minimumVersion; - public static int creation; - public static string md5 = ""; + private static string _verHead = ""; + private static string _verDate = ""; + private static Version _minimumVersion; + private static int _creation; + public static string MD5 = ""; + + private static int _retried; + private static bool _firstLaunch = true; public static bool IsSupported { get; private set; } = true; - private static int retried; - private static bool firstLaunch = true; + private static async void StartTasks() + { + try + { + _ = ModNewsHistory.LoadModAnnouncements(); + await Task.Delay(100); + await SpamManager.Init(); + await Task.Delay(100); + await ResourcesManager.CheckForResources(); + await Task.Delay(100); + await CheckForUpdate(); + } + catch + { + /* ignored */ + } + } public static void Check() { var amongUsVersion = Version.Parse(Application.version); var lowestSupportedVersion = Version.Parse(Main.LowestSupportedVersion); IsSupported = amongUsVersion >= lowestSupportedVersion; - if (!IsSupported) - { - ErrorText.Instance.AddError(ErrorCode.UnsupportedVersion); - } + if (!IsSupported) ErrorText.Instance.AddError(ErrorCode.UnsupportedVersion); } private static void Retry() { - retried++; - CustomPopup.Show(GetString("updateCheckPopupTitle"), GetString("PleaseWait"), null); + _retried++; + CustomPopup.Show(GetString("UpdateCheck.Popup_Title"), GetString("Tip.PleaseWait"), null); _ = new LateTask(() => _ = CheckForUpdate(), 0.3f, "Retry Check Update"); } private static async Task CheckForUpdate() { - ResolutionManager.SetResolution(1920, 1080, Screen.fullScreen); - isChecked = false; + IsChecked = false; ModUpdater.DeleteOldFiles(); foreach (var url in GetInfoFileUrlList(true)) @@ -84,32 +76,32 @@ private static async Task CheckForUpdate() var task = GetVersionInfo(url + "fs_info.json"); await task; if (!task.Result) continue; - isChecked = true; + IsChecked = true; break; } _ = new MainThreadTask(() => { - Msg("Check For Update: " + isChecked, "CheckRelease"); - isBroken = !isChecked; - if (isChecked) + Msg("Check For Update: " + IsChecked, "CheckRelease"); + IsBroken = !IsChecked; + if (IsChecked) { - Info("Has Update: " + hasUpdate, "CheckRelease"); - Info("Latest Version: " + latestVersion, "CheckRelease"); - Info("Minimum Version: " + minimumVersion, "CheckRelease"); - Info("Creation: " + creation, "CheckRelease"); - Info("Force Update: " + forceUpdate, "CheckRelease"); - Info("File MD5: " + md5, "CheckRelease"); + Info("Has Update: " + HasUpdate, "CheckRelease"); + Info("Latest Version: " + _latestVersion, "CheckRelease"); + Info("Minimum Version: " + _minimumVersion, "CheckRelease"); + Info("Creation: " + _creation, "CheckRelease"); + Info("Force Update: " + ForceUpdate, "CheckRelease"); + Info("File MD5: " + MD5, "CheckRelease"); Info("Github Url: " + downloadUrl_github, "CheckRelease"); Info("Gitee Url: " + downloadUrl_gitee, "CheckRelease"); - Info("Api Url: " + downloadUrl_xtremeapi, "CheckRelease"); - if (firstLaunch || isBroken) + if (_firstLaunch || IsBroken) { - firstLaunch = false; + _firstLaunch = false; var annos = ModUpdater.announcement[TranslationController.Instance.currentLanguage.languageID]; - if (isBroken) CustomPopup.Show(GetString(StringNames.AnnouncementLabel), annos, - [(GetString(StringNames.ExitGame), Application.Quit)]); + if (IsBroken) + CustomPopup.Show(GetString(StringNames.AnnouncementLabel), annos, + [(GetString(StringNames.ExitGame), Application.Quit)]); else CustomPopup.Show(GetString(StringNames.AnnouncementLabel), annos, [(GetString(StringNames.Okay), null)]); @@ -117,14 +109,18 @@ private static async Task CheckForUpdate() } else { - if (retried >= 2) - CustomPopup.Show(GetString("updateCheckPopupTitle"), GetString("updateCheckFailedExit"), + if (_retried >= 2) + CustomPopup.Show(GetString("UpdateCheck.Popup_Title"), GetString("UpdateCheck.Failed_Exit"), [(GetString(StringNames.Okay), null)]); else - CustomPopup.Show(GetString("updateCheckPopupTitle"), GetString("updateCheckFailedRetry"), + CustomPopup.Show(GetString("UpdateCheck.Popup_Title"), GetString("UpdateCheck.Failed_Retry"), [(GetString("Retry"), Retry)]); } + ModUpdater.SetUpdateButtonStatus(); + VersionShowerStartPatch.VisitText.text = IsChecked + ? string.Format(GetString("FinalSuspectWelcomeText"), ColorHelper.FSColorHex) + : GetString("RetrieveVersionInfoFailed"); }, "Check For Update"); } @@ -133,56 +129,35 @@ private static async Task GetVersionInfo(string url) Msg(url, "CheckRelease"); try { - string result; - if (url.StartsWith("file:///")) - { - result = await File.ReadAllTextAsync(url[8..]); - } - else - { - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("User-Agent", "FinalSuspect Updater"); - client.DefaultRequestHeaders.Add("Referer", "gitee.com"); - using var response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseContentRead); - if (!response.IsSuccessStatusCode) - { - Error($"Failed: {response.StatusCode}", "CheckRelease"); - return false; - } - - result = await response.Content.ReadAsStringAsync(); - result = result.Replace("\r", string.Empty).Replace("\n", string.Empty).Trim(); - } + var task = JsonHelper.GetJsonStringAsync(url); + await task; + var (result, succeed) = task.Result; + if (!succeed) return false; var data = JObject.Parse(result); - verHead = new string(data["verHead"]?.ToString()); - - DebugVer = new Version(data["DebugVer"]?.ToString() ?? string.Empty); + _verHead = new string(data["verHead"]?.ToString()); CanUpdate = bool.Parse(new string(data["CanUpdate"]?.ToString())); - verDate = new string(data["verDate"]?.ToString()); - md5 = data["md5"]?.ToString(); - latestVersion = new Version(data["version"]?.ToString() ?? string.Empty); + _verDate = new string(data["verDate"]?.ToString()); + MD5 = data["md5"]?.ToString(); + _latestVersion = new Version(data["version"]?.ToString() ?? string.Empty); - showVer = $"{verHead}_{verDate}"; + ShowVer = $"{_verHead}_{_verDate}"; var minVer = data["minVer"]?.ToString(); - if (minVer != null) minimumVersion = minVer.ToLower() == "latest" ? latestVersion : new Version(minVer); - creation = int.Parse(data["creation"]?.ToString() ?? string.Empty); - isBroken = data["allowStart"]?.ToString().ToLower() != "true"; + if (minVer != null) _minimumVersion = minVer.ToLower() == "latest" ? _latestVersion : new Version(minVer); + _creation = int.Parse(data["creation"]?.ToString() ?? string.Empty); + IsBroken = data["allowStart"]?.ToString().ToLower() != "true"; var announcement = data["announcement"].Cast(); foreach (var langid in EnumHelper.GetAllValues()) ModUpdater.announcement[langid] = announcement[langid.ToString()]?.ToString(); - downloadUrl_gitee = downloadUrl_gitee.Replace("{showVer}", showVer); - hasUpdate = Main.version < latestVersion && creation > Main.PluginCreation; - forceUpdate = Main.version < minimumVersion || creation > Main.PluginCreation; -#if DEBUG - DebugUnused = Main.version < DebugVer; - hasUpdate = forceUpdate = DebugUnused; -#endif + downloadUrl_gitee = downloadUrl_gitee.Replace("{showVer}", ShowVer); + HasUpdate = Main.version < _latestVersion && _creation > Main.PluginCreation; + ForceUpdate = Main.version < _minimumVersion || _creation > Main.PluginCreation; + return true; } catch @@ -190,4 +165,23 @@ private static async Task GetVersionInfo(string url) return false; } } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPriority(Priority.LowerThanNormal)] + public class Start + { + public static void Postfix() + { + CustomPopup.Init(); + if (FirstStart && !Main.OfflineMode.Value) + { + StartTasks(); + CustomPopup.Show(GetString("UpdateCheck.Popup_Title"), GetString("Tip.LoadingWithDot"), null); + } + + NameTagManager.ReloadTag(null); + ModUpdater.SetUpdateButtonStatus(); + FirstStart = false; + } + } } \ No newline at end of file diff --git a/FinalSuspect/Modules/Scrapped/Cloud.cs b/FinalSuspect/Modules/Scrapped/Cloud.cs index 5f50da36..2eb0911c 100644 --- a/FinalSuspect/Modules/Scrapped/Cloud.cs +++ b/FinalSuspect/Modules/Scrapped/Cloud.cs @@ -32,7 +32,7 @@ public static void Init() } catch (Exception e) { - XtremeLogger.Exception(e, "Cloud Init"); + FinalLogger.Exception(e, "Cloud Init"); } }* / private static string GetResourcesTxt(string path) @@ -47,11 +47,11 @@ public static bool ShareLobby(bool command = false) { try { - if (!Main.NewLobby || !XtremeGameData.GameStates.IsLobby) return false; + if (!Main.NewLobby || !FinalGameData.GameStates.IsLobby) return false; if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return false; if (IP == null || LOBBY_PORT == 0) throw new("Has no ip or port"); - + var msg = $"{GameStartManager.Instance.GameRoomNameCode.text}|{Main.DisplayedVersion_Head}|{GameData.Instance.PlayerCount}|{TranslationController.Instance.currentLanguage.languageID}|{ServerName}|{DataManager.player.customization.name}"; if (msg.Length <= 60) { @@ -61,12 +61,12 @@ public static bool ShareLobby(bool command = false) ClientSocket.Send(buffer); ClientSocket.Close(); } - Main.NewLobby = false; + Main.NewLobby = false; } catch (Exception e) { - XtremeLogger.Exception(e, "SentLobbyToQQ"); + FinalLogger.Exception(e, "SentLobbyToQQ"); throw; } return true; @@ -90,12 +90,12 @@ public static void StartConnect() LastRepotTimeStamp = Utils.GetTimeStamp(); EacClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); EacClientSocket.Connect(IP, FAC_PORT); - XtremeLogger.Warn("已连接至FinalSuspect服务器", "FAC Cloud"); + FinalLogger.Warn("已连接至FinalSuspect服务器", "FAC Cloud"); } catch (Exception ex) { connecting = false; - XtremeLogger.Error($"Connect To FAC Failed:\n{ex.Message}", "FAC Cloud", false); + FinalLogger.Error($"Connect To FAC Failed:\n{ex.Message}", "FAC Cloud", false); } connecting = false; }, 3.5f, "FAC Cloud Connect"); @@ -110,7 +110,7 @@ public static void SendData(string msg) StartConnect(); if (EacClientSocket == null || !EacClientSocket.Connected) { - XtremeLogger.Warn("未连接至FinalSuspect服务器,报告被取消", "FAC Cloud"); + FinalLogger.Warn("未连接至FinalSuspect服务器,报告被取消", "FAC Cloud"); return; } EacClientSocket.Send(Encoding.Default.GetBytes(msg)); @@ -124,9 +124,10 @@ public static void Postfix() { LastRepotTimeStamp = 0; StopConnect(); - XtremeLogger.Warn("超时自动断开与FinalSuspect服务器的连接", "FAC Cloud"); + FinalLogger.Warn("超时自动断开与FinalSuspect服务器的连接", "FAC Cloud"); } } } } - */ \ No newline at end of file + */ + diff --git a/FinalSuspect/Modules/SoundInterface/AudioLoader.cs b/FinalSuspect/Modules/SoundInterface/AudioLoader.cs deleted file mode 100644 index 41b4947a..00000000 --- a/FinalSuspect/Modules/SoundInterface/AudioLoader.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace FinalSuspect.Modules.SoundInterface; - -public class AudioLoader -{ - // 静态构造函数用于预热 - static AudioLoader() - { - Warmup(); - } - - private static void Warmup() - { - var dummyBytes = new byte[2]; - ConvertBytesToFloats(dummyBytes); - - var warmupClip = AudioClip.Create("Warmup", 1, 1, 44100, false); - warmupClip.SetData(new float[] { 0 }, 0); - Object.Destroy(warmupClip); - } - - public static async Task LoadAudioClipAsync(string filePath) - { - if (!File.Exists(filePath)) - { - Debug.LogError("File does not exist: " + filePath); - return null; - } - - byte[] audioData; - - try - { - audioData = await ReadAllBytesAsync(filePath); - } - catch (Exception e) - { - Debug.LogError("Failed to read file: " + filePath + "\n" + e.Message); - return null; - } - - var floatData = await Task.Run(() => ConvertBytesToFloats(audioData)); - audioData = null; // 及时释放内存 - - var audioClip = AudioClip.Create("LoadedAudioClip", floatData.Length, 2, 44100, false); - audioClip.SetData(floatData, 0); - - return audioClip; - } - - private static async Task ReadAllBytesAsync(string filePath) - { - using var sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); - var buffer = new byte[sourceStream.Length]; - // ReSharper disable once MustUseReturnValue - await sourceStream.ReadAsync(buffer.AsMemory(0, (int)sourceStream.Length)).ConfigureAwait(false); - return buffer; - } - - private static float[] ConvertBytesToFloats(byte[] audioBytes) - { - var floatCount = audioBytes.Length / 2; - var floatData = new float[floatCount]; - - unsafe - { - fixed (byte* bytePtr = audioBytes) - fixed (float* floatPtr = floatData) - { - var src = (short*)bytePtr; - var dst = floatPtr; - for (var i = 0; i < floatCount; i++) - { - dst[i] = src[i] / 32768.0f; - } - } - } - return floatData; - } -} \ No newline at end of file diff --git a/FinalSuspect/Modules/SoundInterface/CustomSoundsManager.cs b/FinalSuspect/Modules/SoundInterface/CustomSoundsManager.cs deleted file mode 100644 index 72cd3dc9..00000000 --- a/FinalSuspect/Modules/SoundInterface/CustomSoundsManager.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FinalSuspect.Modules.Panels; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace FinalSuspect.Modules.SoundInterface; - -public static class CustomSoundsManager -{ - public static async void Play(XtremeMusic audio) - { - try - { - if (audio.CurrectAudioStates is AudiosStates.NotExist or AudiosStates.IsPlaying) return; - if (!Constants.ShouldPlaySfx()) return; - - _ = new MainThreadTask(() => - { - StopPlayMod(); - StopPlayVanilla(); - }, "Playing Sfx"); - - await XtremeMusic.LoadClip(audio.CurrectAudio); - - _ = new MainThreadTask(() => - { - foreach (var file in XtremeMusic.musics.Where(file => file.FileName == audio.FileName)) - { - file.CurrectAudioStates = AudiosStates.IsPlaying; - } - - SoundManager.ReloadTag(); - MyMusicPanel.RefreshTagList(); - SoundManagementPanel.RefreshTagList(); - global::SoundManager.Instance.CrossFadeSound(audio.FileName, audio.Clip, 1f); - Msg($"播放声音:{audio.Name}", "CustomSounds"); - }, "Playing Sfx"); - } - catch - { - /* ignored */ - } - } - - public static void StopPlayMod() - { - XtremeMusic.musics.Do(x => - { - x.Clip = null; - x.CurrectAudioStates = x.LastAudioStates; - global::SoundManager.Instance.StopNamedSound(x.FileName); - }); - _ = new MainThreadTask(() => - { - MyMusicPanel.RefreshTagList(); - SoundManagementPanel.RefreshTagList(); - }, "Refresh Tag List"); - if (Main.DisableVanillaSound.Value) - { - StopPlayVanilla(); - } - else - { - StartPlayVanilla(); - } - } - - public static void StopPlayVanilla() - { - global::SoundManager.Instance.StopNamedSound("MapTheme"); - global::SoundManager.Instance.StopNamedSound("MainBG"); - } - - public static void StartPlayVanilla() - { - var isPlaying = XtremeMusic.musics.Any(x => x.CurrectAudioStates == AudiosStates.IsPlaying); - if (isPlaying) return; - if (IsLobby) - global::SoundManager.Instance.CrossFadeSound("MapTheme", LobbyBehaviour.Instance.MapTheme, 0.07f); - else if (IsNotJoined) - global::SoundManager.Instance.CrossFadeSound("MainBG", DestroyableSingleton.Instance.IntroMusic, 1f); - } - /* - public static void AutoPlay(string sound, string name) - { - Play(sound); - MusicNow = name; - MusicPlaybackCompletedHandler(); - } - - public static string MusicNow = ""; - private static void MusicPlaybackCompletedHandler() - { - var rd = IRandom.Instance; - List mus = new(); - foreach (var audio in XtremeMusic.musics) - { - var music = audio.FileName; - mus.Add(music); - } - if (MyMusicPanel.PlayMode == 2) - { - for (int i = 0; i < 10; i++) - { - var select = mus[rd.Next(0, mus.Count)]; - var path = @$"Final Suspect_Data/Resources/Audios/{select}.wav"; - if (ConvertExtension(ref path)) - StartPlayWait(path); - else - i--; - } - - } - else if (MyMusicPanel.PlayMode == 3) - { - var musicn = mus.IndexOf(MusicNow); - for (int i = 0; i < 10; i++) - { - int index = musicn; - if (index > mus.Count - 2) - index = -1; - var select = mus[index + 1]; - var path = @$"Final Suspect_Data/Resources/Audios/{select}.wav"; - if (ConvertExtension(ref path)) - { - StartPlayWait(path); - musicn++; - - } - else - i--; - } - - } - new LateTask(() => - { - MusicPlaybackCompletedHandler(); - }, 40f, "AddMusic"); - } - public static void StartPlayOnce(string path) => PlaySound(@$"{path}", 0, 1); 第3个形参,换为9,连续播放 - - public static void StartPlayInAmongUs(XtremeMusic audio) - { - if (audio.Clip != null) - { - StopPlay(); - SoundManager.Instance.CrossFadeSound(audio.Name, audio.Clip, 0.5f); - } - else - { - AudioManagementPanel.Delete(audio); - } - }*/ -} -[HarmonyPatch(typeof(global::SoundManager), nameof(global::SoundManager.PlaySoundImmediate))] -[HarmonyPatch(typeof(global::SoundManager), nameof(global::SoundManager.PlaySound))] -public class AudioManagementPlaySoundPatch -{ - public static bool Prefix(global::SoundManager __instance, [HarmonyArgument(0)] AudioClip clip, [HarmonyArgument(1)] bool loop) - { - var isPlaying = XtremeMusic.musics.Any(x => x.CurrectAudioStates == AudiosStates.IsPlaying); - var disableVanilla = Main.DisableVanillaSound.Value; - return !(isPlaying || disableVanilla) || !loop; - } -} - -[HarmonyPatch(typeof(global::SoundManager), nameof(global::SoundManager.PlayDynamicSound))] -[HarmonyPatch(typeof(global::SoundManager), nameof(global::SoundManager.PlayNamedSound))] -public class AudioManagementPlayDynamicAndNamedSoundPatch -{ - public static bool Prefix([HarmonyArgument(0)] string name, [HarmonyArgument(1)] AudioClip clip, [HarmonyArgument(2)] bool loop) - { - var isPlaying = XtremeMusic.musics.Any(x => x.CurrectAudioStates == AudiosStates.IsPlaying); - var isModMusic = XtremeMusic.musics.Any(x => x.FileName == name); - var disableVanilla = Main.DisableVanillaSound.Value; - return !(isPlaying || disableVanilla) || !loop || isModMusic; - } -} - -[HarmonyPatch(typeof(global::SoundManager), nameof(global::SoundManager.CrossFadeSound))] -public class AudioManagementCrossFadeSoundPatch -{ - public static bool Prefix([HarmonyArgument(0)] string name) - { - var isPlaying = XtremeMusic.musics.Any(x => x.CurrectAudioStates == AudiosStates.IsPlaying); - var isModMusic = XtremeMusic.musics.Any(x => x.FileName == name); - var disableVanilla = Main.DisableVanillaSound.Value; - return !(isPlaying || disableVanilla) || isModMusic; - } -} - -[HarmonyPatch(typeof(global::SoundManager), nameof(global::SoundManager.StopAllSound))] -public class AudioManagementStopAllSoundPatch -{ - public static bool Prefix(global::SoundManager __instance) - { - for (var i = __instance.soundPlayers.Count - 1; i >= 0; i--) - { - if (XtremeMusic.musics.Any(x => x.Clip == __instance.soundPlayers[i].Player.clip)) - continue; - - Object.Destroy(__instance.soundPlayers[i].Player); - __instance.soundPlayers.RemoveAt(i); - } - - var keysToRemove = new List(); - foreach (var (key, value) in __instance.allSources) - { - if (XtremeMusic.musics.Any(x => x.Clip == key)) - { - continue; - } - - value.volume = 0f; - value.Stop(); - Object.Destroy(value); - keysToRemove.Add(key); - } - - foreach (var key in keysToRemove) - { - __instance.allSources.Remove(key); - } - - return false; - } -} \ No newline at end of file diff --git a/FinalSuspect/Modules/SoundInterface/SoundManagementNewWindow.cs b/FinalSuspect/Modules/SoundInterface/SoundManagementNewWindow.cs deleted file mode 100644 index 4eac8f86..00000000 --- a/FinalSuspect/Modules/SoundInterface/SoundManagementNewWindow.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using FinalSuspect.Modules.Panels; -using TMPro; -using UnityEngine; -using UnityEngine.UI; -using Object = UnityEngine.Object; - -namespace FinalSuspect.Modules.SoundInterface; - -public static class SoundManagementNewWindow -{ - public static GameObject Window { get; private set; } - public static GameObject Info { get; private set; } - public static GameObject EnterBox { get; private set; } - public static GameObject ConfirmButton { get; private set; } - public static void Open() - { - if (Window == null) Init(); - if (Window == null) return; - Window.SetActive(true); - EnterBox.GetComponent().Clear(); - } - public static void Init() - { - Window = Object.Instantiate(AccountManager.Instance.transform.FindChild("InfoTextBox").gameObject, SoundManagementPanel.CustomBackground.transform.parent); - Window.name = "New Music Window"; - Window.transform.FindChild("Background").localScale *= 0.7f; - Window.transform.localPosition += Vector3.back * 21; - Object.Destroy(Window.transform.FindChild("Button2").gameObject); - - var closeButton = Object.Instantiate(Window.transform.parent.FindChild("CloseButton"), Window.transform); - closeButton.transform.localPosition = new Vector3(2.4f, 1.2f, -21f); - closeButton.transform.localScale = new Vector3(1f, 1f, 1f); - closeButton.GetComponent().OnClick = new Button.ButtonClickedEvent(); - closeButton.GetComponent().OnClick.AddListener((Action)(() => - { - Window.SetActive(false); - })); - - var titlePrefab = Window.transform.FindChild("TitleText_TMP").gameObject; - titlePrefab.name = "Title Prefab"; - titlePrefab.transform.localPosition += Vector3.back * 10; - var infoPrefab = Window.transform.FindChild("InfoText_TMP").gameObject; - infoPrefab.name = "Info Prefab"; - infoPrefab.transform.localPosition += Vector3.back * 10; - var buttonPrefab = Window.transform.FindChild("Button1").gameObject; - buttonPrefab.name = "Button Prefab"; - buttonPrefab.GetComponent().OnClick = new Button.ButtonClickedEvent(); - buttonPrefab.transform.localPosition += Vector3.back * 10; - var enterPrefab = Object.Instantiate(AccountManager.Instance.transform.FindChild("PremissionRequestWindow/GuardianEmailConfirm").gameObject, Window.transform); - enterPrefab.name = "Enter Box Prefab"; - enterPrefab.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f); - enterPrefab.transform.localPosition += Vector3.back * 10; - Object.Destroy(enterPrefab.GetComponent()); - - Info = Object.Instantiate(infoPrefab, Window.transform); - Info.name = "Enter Friend Code Description"; - Info.transform.localPosition = new Vector3(0f, 0.1f, -20f); - var colorInfoTmp = Info.GetComponent(); - colorInfoTmp.text = GetString("PleaseEnterMusic"); - - EnterBox = Object.Instantiate(enterPrefab, Window.transform); - EnterBox.name = "Enter Friend Code Box"; - EnterBox.transform.localPosition = new Vector3(0f, -0.04f, -20f); - var enterBoxTBT = EnterBox.GetComponent(); - enterBoxTBT.AllowEmail = false; - enterBoxTBT.AllowSymbols = true; - enterBoxTBT.AllowPaste = true; - - ConfirmButton = Object.Instantiate(buttonPrefab, Window.transform); - ConfirmButton.name = "Confirm Button"; - ConfirmButton.transform.localPosition = new Vector3(0, -0.8f, -20f); - ConfirmButton.GetComponent().OnClick.AddListener((Action)(() => - { - var code = EnterBox.GetComponent().text; - var reg = new Regex(@"^(\s{1}|)$"); - - if (XtremeMusic.musics.Any(x => x.Name == code)) - { - ConfirmButton.SetActive(false); - colorInfoTmp.text = GetString("AudioManagementAlreadyExist"); - colorInfoTmp.color = Color.blue; - } - else if (reg.IsMatch(code)) - { - ConfirmButton.SetActive(false); - colorInfoTmp.text = GetString("NotAllowedMusic"); - colorInfoTmp.color = Color.red; - } - else - { - Window.SetActive(false); - SaveToFile(code); - SoundManager.ReloadTag(false); - SoundManagementPanel.RefreshTagList(); - MyMusicPanel.RefreshTagList(); - return; - } - - _ = new LateTask(() => - { - colorInfoTmp.text = GetString("PleaseEnterMusic"); - colorInfoTmp.color = Color.white; - ConfirmButton.SetActive(true); - }, 1.2f, "Reactivate Enter Box"); - })); - var upperButtonTmp = ConfirmButton.transform.FindChild("Text_TMP").GetComponent(); - upperButtonTmp.text = GetString(StringNames.Confirm); - - titlePrefab.SetActive(false); - infoPrefab.SetActive(false); - buttonPrefab.SetActive(false); - enterPrefab.SetActive(false); - } - private static void SaveToFile(string name) - { - using StreamWriter sr = new(SoundManager.TAGS_PATH, true); - sr.WriteLine(name); - } -} \ No newline at end of file diff --git a/FinalSuspect/Modules/SoundInterface/SoundManager.cs b/FinalSuspect/Modules/SoundInterface/SoundManager.cs deleted file mode 100644 index d66abc10..00000000 --- a/FinalSuspect/Modules/SoundInterface/SoundManager.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using FinalSuspect.Helpers; -using FinalSuspect.Modules.Features.CheckingandBlocking; -using FinalSuspect.Modules.Panels; -using FinalSuspect.Modules.Resources; -using UnityEngine; - -namespace FinalSuspect.Modules.SoundInterface; - -#nullable enable -public static class SoundManager -{ - public static readonly string TAGS_PATH = GetResourceFilesPath(FileType.Sounds, "SoundsName.txt"); - - public static List CustomAudios = []; - - public static void ReloadTag(bool official = true) - { - CustomAudios = []; -#nullable disable - if (official) - { - Init(); - return; - } - try - { - using StreamReader sr = new(TAGS_PATH); - while (sr.ReadLine() is { } line) - { - if (string.IsNullOrWhiteSpace(line)) continue; - XtremeMusic.CreateMusic(line); - Info($"Audio Loaded: {line}", "AudioManager"); - } - } - catch (Exception ex) - { - Error("Load Audios Failed\n" + ex, "AudioManager", false); - } - } - - public static void Init() - { - if (!File.Exists(TAGS_PATH)) File.Create(TAGS_PATH).Close(); - var attributes = File.GetAttributes(TAGS_PATH); - File.SetAttributes(TAGS_PATH, attributes | FileAttributes.Hidden); - XtremeMusic.InitializeAll(); - } - - public static bool ConvertExtension(ref string path) - { - if (path == null) return false; - List extensions = [".wav", ".flac", ".aiff", ".mp3", ".aac", ".ogg", ".m4a"]; - - while (!File.Exists(path)) - { - var currectpath = path; - var extensionsArray = extensions.ToArray(); - if (extensionsArray.Length == 0) return false; - var matchingKey = extensions.FirstOrDefault(currectpath.Contains); - if (matchingKey is null) return false; - var currentIndex = Array.IndexOf(extensionsArray, matchingKey); - if (currentIndex == -1) - { - return false; - } - var nextIndex = (currentIndex + 1) % extensionsArray.Length; - path = path.Replace(matchingKey, extensionsArray[nextIndex]); - extensions.Remove(matchingKey); - } - return true; - } - - public static void PlaySound(byte playerID, Sounds sound) - { - if (PlayerControl.LocalPlayer.PlayerId == playerID) - { - switch (sound) - { - case Sounds.KillSound: - global::SoundManager.Instance.PlaySound(PlayerControl.LocalPlayer.KillSfx, false); - break; - case Sounds.TaskComplete: - global::SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskCompleteSound, false); - break; - case Sounds.TaskUpdateSound: - global::SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskUpdateSound, false); - break; - case Sounds.ImpTransform: - global::SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx, false, 0.8f); - break; - case Sounds.Yeehawfrom: - global::SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.HnSLocalYeehawSfx, false, 0.8f); - break; - } - } - } -} - -public enum SupportedMusics -{ - UnOfficial, - - // ## World Music - GongXiFaCai__Andy_Lau, - NeverGonnaGiveYouUp__Rick_Astley, - CountingStars__One_Republic, - - //20250214 - - // ## Mod Music - TidalSurge__Slok, - TrailOfTruth__Slok, - Interlude__Slok, - Fractured__Slok, - ElegyOfFracturedVow__Slok, - VestigiumSplendoris__Slok, - ReturnToSimplicity__Slok, - - //20250214 - Affinity__Slok, - Inceps_Plus_InProgress__Slok - -} -public enum AudiosStates -{ - NotExist, - IsDownLoading, - Exist, - IsPlaying, - DownLoadSucceedNotice, - DownLoadFailureNotice, - IsLoading, -} - -public class XtremeMusic -{ - public static List musics = []; - - public string Name; - public string FileName; - public string Author; - public string Path; - public AudioClip Clip; - - public SupportedMusics CurrectAudio; - public AudiosStates CurrectAudioStates; - public AudiosStates LastAudioStates; - - public bool UnOfficial; - public bool unpublished; - - - public static void InitializeAll() - { - foreach (var file in EnumHelper.GetAllValues().ToList()) - { - CreateMusic(music: file); - } - - var soundnum = 0; - try - { - using StreamReader sr = new(SoundManager.TAGS_PATH); - string line; - while ((line = sr.ReadLine()) != null) - { - if (string.IsNullOrWhiteSpace(line)) continue; - CreateMusic(line); - Info($"Sound Loaded: {line}", "AudioManager"); - soundnum++; - } - } - catch (Exception ex) - { - Error("Load Audio Failed\n" + ex, "AudioManager", false); - } - Msg($"{soundnum} Custom Sounds Loaded", "AudioManager"); - } - - private static readonly object finalMusicsLock = new(); - - public static void CreateMusic(string name = "", SupportedMusics music = SupportedMusics.UnOfficial) - { - var mus = new XtremeMusic(); - mus.Create(name, music); - } - - public static async Task LoadClip(SupportedMusics music = SupportedMusics.UnOfficial) - { - var mus = musics.FirstOrDefault(x => x.CurrectAudio == music); - if (mus != null) - await mus.Load(); - } - - private async Task Load() - { - if (CurrectAudioStates != AudiosStates.Exist) return; - var task = AudioLoader.LoadAudioClipAsync(Path); - _ = new MainThreadTask(() => - { - LastAudioStates = CurrectAudioStates = AudiosStates.IsLoading; - MyMusicPanel.RefreshTagList(); - }, "Update Audio States"); - await task; - _ = new MainThreadTask(() => - { - if (task.Result != null) - Clip = task.Result; - LastAudioStates = CurrectAudioStates = Clip != null ? AudiosStates.Exist : AudiosStates.NotExist; - MyMusicPanel.RefreshTagList(); - }, "Update Audio States"); - } - - private void Create(string name, SupportedMusics music) - { - if (music != SupportedMusics.UnOfficial) - { - var Part = music.ToString().Split("__"); - FileName = Part[0]; - Name = GetString($"Mus.{Part[0]}"); - Author = Part[1].Replace("_", " "); - } - else - { - SoundManager.CustomAudios.Remove(name); - SoundManager.CustomAudios.Add(name); - FileName = Name = name; - Author = ""; - } - - UnOfficial = music == SupportedMusics.UnOfficial; - CurrectAudio = music; - Path = GetResourceFilesPath(FileType.Sounds, FileName + ".wav"); - CurrectAudioStates = LastAudioStates = SoundManager.ConvertExtension(ref Path) ? AudiosStates.Exist : AudiosStates.NotExist; - - lock (finalMusicsLock) - { - var file = musics.Find(x => x.FileName == FileName); - if (file != null) - { - file.Path = Path; - if (file.CurrectAudioStates is AudiosStates.DownLoadFailureNotice or AudiosStates.DownLoadSucceedNotice - || CurrectAudioStates is AudiosStates.NotExist) - { - file.CurrectAudioStates = file.LastAudioStates = CurrectAudioStates; - } - } - else if (Name != string.Empty) - musics.Add(this); - } - } -} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/ChatBubblePatch.cs b/FinalSuspect/Patches/Game_Vanilla/ChatBubblePatch.cs similarity index 70% rename from FinalSuspect/Patches/System/ChatBubblePatch.cs rename to FinalSuspect/Patches/Game_Vanilla/ChatBubblePatch.cs index 99fe55af..cf9e146f 100644 --- a/FinalSuspect/Patches/System/ChatBubblePatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/ChatBubblePatch.cs @@ -1,20 +1,24 @@ using FinalSuspect.Helpers; using UnityEngine; -namespace FinalSuspect.Patches.System; +namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch(typeof(ChatBubble))] public static class ChatBubblePatch { - private static bool IsModdedMsg(string name) => name.EndsWith('\0'); + private static bool IsModdedMsg(string name) + { + return name.EndsWith('\0'); + } - [HarmonyPatch(nameof(ChatBubble.SetText)), HarmonyPrefix] + [HarmonyPatch(nameof(ChatBubble.SetText))] + [HarmonyPrefix] public static void SetText_Prefix(ChatBubble __instance, ref string chatText) { if (__instance.TextArea.color == Color.red) return; - var bgcolor = ColorHelper.HalfModColor32; + var bgcolor = ColorHelper.HalfFSColor; var sr = __instance.Background; - Color namecolor= ColorHelper.FaultColor; + Color namecolor = ColorHelper.FaultColor; string name = null; var modded = IsModdedMsg(__instance.playerInfo.PlayerName); if (__instance.playerInfo?.PlayerId == null) @@ -24,24 +28,25 @@ public static void SetText_Prefix(ChatBubble __instance, ref string chatText) else if (modded) { bgcolor = Color.black; - namecolor = ColorHelper.TeamColor32; + namecolor = ColorHelper.AuthorColor; chatText = StringHelper.ColorString(Color.white, chatText.TrimEnd('\0')); __instance.SetLeft(); } else if (__instance.NameText.color == Color.green) { bgcolor = ColorHelper.HalfYellow; - namecolor = ColorHelper.TeamColor32; + namecolor = ColorHelper.AuthorColor; } else { - XtremeLocalHandling.GetChatBubbleText( + FinalLocalHandling.GetChatBubbleText( __instance.playerInfo.PlayerId, ref name, - ref bgcolor, + ref bgcolor, ref namecolor ); } + __instance.NameText.color = namecolor; __instance.NameText.text = name ?? __instance.NameText.text; sr.color = bgcolor; diff --git a/FinalSuspect/Patches/Game_Vanilla/ChatControlPatch.cs b/FinalSuspect/Patches/Game_Vanilla/ChatControlPatch.cs index 6610dfbb..7fada4da 100644 --- a/FinalSuspect/Patches/Game_Vanilla/ChatControlPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/ChatControlPatch.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using AmongUs.Data; using FinalSuspect.Modules.Features.CheckingandBlocking; using InnerNet; @@ -11,32 +10,35 @@ namespace FinalSuspect.Patches.Game_Vanilla; public static class ChatControllerUpdatePatch { public static int CurrentHistorySelection = -1; + public static void Prefix() { if (AmongUsClient.Instance.AmHost && DataManager.Settings.Multiplayer.ChatMode == QuickChatModes.QuickChatOnly) - DataManager.Settings.Multiplayer.ChatMode = QuickChatModes.FreeChatOrQuickChat; + DataManager.Settings.Multiplayer.ChatMode = QuickChatModes.FreeChatOrQuickChat; } + public static void Postfix(ChatController __instance) { - if (OtherModHost || IsFreePlay || IsLocalGame) - { - __instance.timeSinceLastMessage = 3f; - } + if (OtherModHost || IsFreePlay || IsLocalGame) __instance.timeSinceLastMessage = 3f; + if (!__instance.freeChatField.textArea.hasFocus) return; if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.V)) - __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + GUIUtility.systemCopyBuffer); + __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + + GUIUtility.systemCopyBuffer); if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X)) { ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); __instance.freeChatField.textArea.SetText(""); } + if (Input.GetKeyDown(KeyCode.UpArrow) && ChatCommands.SentHistory.Count > 0) { CurrentHistorySelection = Mathf.Clamp(--CurrentHistorySelection, 0, ChatCommands.SentHistory.Count - 1); __instance.freeChatField.textArea.SetText(ChatCommands.SentHistory[CurrentHistorySelection]); } + if (Input.GetKeyDown(KeyCode.DownArrow) && ChatCommands.SentHistory.Count > 0) { CurrentHistorySelection++; @@ -46,20 +48,17 @@ public static void Postfix(ChatController __instance) } } } + [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] internal class ChatCommands { public static readonly List SentHistory = []; + public static bool Prefix(ChatController __instance) { - if (__instance.quickChatField.Visible) - { - return true; - } - if (string.IsNullOrWhiteSpace(__instance.freeChatField.textArea.text)) - { - return false; - } + if (__instance.quickChatField.Visible) return true; + + if (string.IsNullOrWhiteSpace(__instance.freeChatField.textArea.text)) return false; var text = __instance.freeChatField.textArea.text; if (SentHistory.Count == 0 || SentHistory[^1] != text) SentHistory.Add(text); @@ -67,6 +66,7 @@ public static bool Prefix(ChatController __instance) return true; } } + [HarmonyPatch(typeof(ChatController), nameof(ChatController.AddChat))] internal class ChatAdd { diff --git a/FinalSuspect/Patches/Game_Vanilla/DisconnectPatch.cs b/FinalSuspect/Patches/Game_Vanilla/DisconnectPatch.cs index a2366bd0..ed323892 100644 --- a/FinalSuspect/Patches/Game_Vanilla/DisconnectPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/DisconnectPatch.cs @@ -5,11 +5,12 @@ internal class ShowDisconnectPopupPatch { public static DisconnectReasons Reason; public static string StringReason; + public static void Postfix(DisconnectPopup __instance) { _ = new MainThreadTask(() => { - if (__instance == null) return; + if (!__instance) return; try { void SetText(string text) @@ -17,7 +18,7 @@ void SetText(string text) if (__instance._textArea?.text != null) __instance._textArea.text = text; } - + switch (Reason) { case DisconnectReasons.Hacking: @@ -42,11 +43,13 @@ void SetText(string text) SetText(GetString("DCNotify.IncorrectVersion")); break; case DisconnectReasons.Error: - if (StringReason.Contains("Failed to send message")) SetText(GetString("DCNotify.DCFromServer")); + if (StringReason.Contains("Failed to send message")) + SetText(GetString("DCNotify.DCFromServer")); break; case DisconnectReasons.Custom: - if (StringReason.Contains("Reliable packet")) SetText(GetString("DCNotify.DCFromServer")); - else if (StringReason.Contains("remote has not responded to")) SetText(GetString("DCNotify.DCFromServer")); + if (StringReason.Contains("Reliable packet") || + StringReason.Contains("remote has not responded to")) + SetText(GetString("DCNotify.DCFromServer")); break; } } diff --git a/FinalSuspect/Patches/Game_Vanilla/EndGameManagerPatch.cs b/FinalSuspect/Patches/Game_Vanilla/EndGameManagerPatch.cs index 9b248d68..dc96f510 100644 --- a/FinalSuspect/Patches/Game_Vanilla/EndGameManagerPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/EndGameManagerPatch.cs @@ -3,28 +3,24 @@ [HarmonyPatch] public class EndGameManagerPatch { - [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.ShowButtons)), HarmonyPostfix] + [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.ShowButtons))] + [HarmonyPostfix] public static void ShowButtons_Postfix(EndGameManager __instance) { if (!Main.AutoEndGame.Value) return; + DestroyableSingleton.Instance.ContinueButton.gameObject.SetActive(false); _ = new LateTask(__instance.Navigation.NextGame, 2f, "Auto End Game"); } } + [HarmonyPatch] -public class ControllerNavMenuPatch -{ - [HarmonyPatch(typeof(ControllerNavMenu), nameof(ControllerNavMenu.Start)), HarmonyPostfix] - public static void Start_Postfix(ControllerNavMenu __instance) - { - if (!Main.AutoEndGame.Value) return; - __instance.gameObject.SetActive(false); - } -} -[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] -class GameEndChecker +public class GameEndChecker { - public static bool Prefix() + [HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] + [HarmonyPatch(typeof(LogicGameFlowHnS), nameof(LogicGameFlowHnS.CheckEndCriteria))] + [HarmonyPrefix] + public static bool CheckEndCriteria() { - return !(Main.NoGameEnd.Value && DebugModeManager.AmDebugger); + return !(Main.NoGameEnd.Value && DebugModeManager.IsDebugMode); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/Guardian.cs b/FinalSuspect/Patches/Game_Vanilla/Guardian.cs new file mode 100644 index 00000000..85f922f9 --- /dev/null +++ b/FinalSuspect/Patches/Game_Vanilla/Guardian.cs @@ -0,0 +1,220 @@ +using System; +using AmongUs.InnerNet.GameDataMessages; +using FinalSuspect.DataHandling.FinalGameData; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using Hazel; +using InnerNet; + +namespace FinalSuspect.Patches.Game_Vanilla; + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleGameData))] +public static class HandleGameDataPatch +{ + public static bool Prefix(InnerNetClient __instance, [HarmonyArgument(0)] MessageReader parentReader) + { + if (!IsLobby || IsNotJoined || !FinalGameData.JoinedCompleted || !Main.EnableGuardian.Value) return true; + + try + { + if (parentReader.BytesRemaining < 1) return false; + var messageReader = MessageReader.Get(parentReader); + var reader = messageReader.ReadMessageAsNewBuffer(); + return reader.BytesRemaining >= 1; + } + catch + { + return PlayerControl.LocalPlayer?.GetClient() == null; + } + } +} + +[HarmonyPatch(typeof(InnerNetClient._HandleGameDataInner_d__165), + nameof(InnerNetClient._HandleGameDataInner_d__165.MoveNext))] +public static class HandleGameDataInnerPatch +{ + private static readonly Dictionary playerMsgCounters = new(); + + public static bool Prefix(InnerNetClient._HandleGameDataInner_d__165 __instance) + { + if (!IsLobby || IsNotJoined || !FinalGameData.JoinedCompleted || !Main.EnableGuardian.Value) return true; + var reader = __instance.reader; + if (reader.BytesRemaining < 1) + { + reader.Recycle(); + return false; + } + + var innerNetClient = __instance.__4__this; + var tag = (GameDataTypes)reader.Tag; + var sr = MessageReader.Get(reader); + InnerNetObject targetObject = null; + ClientData clientData = null; + try + { + switch (tag) + { + case GameDataTypes.DataFlag: + case GameDataTypes.RpcFlag: + var netId = sr.ReadPackedUInt32(); + lock (innerNetClient.allObjects) + { + innerNetClient.allObjects.AllObjectsFast.TryGetValue(netId, out targetObject); + } + + if (tag == GameDataTypes.RpcFlag) + sr.ReadByte(); + break; + case GameDataTypes.ReadyFlag: + clientData = innerNetClient.FindClientById(sr.ReadPackedInt32()); + break; + case GameDataTypes.SceneChangeFlag: + var clientId = sr.ReadPackedInt32(); + clientData = innerNetClient.FindClientById(clientId); + break; + case GameDataTypes.SpawnFlag: + case GameDataTypes.DespawnFlag: + case GameDataTypes.XboxDeclareXuid: + sr.Recycle(); + return true; + default: + sr.Recycle(); + reader.Recycle(); + return false; + } + } + catch + { + /* ignored */ + } + + if (clientData == null && targetObject == null || sr.BytesRemaining < 0) + { + sr.Recycle(); + reader.Recycle(); + return false; + } + + var ownerId = targetObject?.OwnerId; + if (!FinalPlayerData.AllPlayerData.Any(x => x.Player.OwnerId == ownerId)) + { + sr.Recycle(); + return true; + } + + var client = innerNetClient.FindClientById(ownerId ?? -1232242121); // 瞎写八写保证为null + clientData ??= client; + if (clientData == null) + { + sr.Recycle(); + reader.Recycle(); + return false; + } + + if (!playerMsgCounters.TryGetValue(clientData.Id, out var counter)) + { + counter = new MsgCounter(); + playerMsgCounters[clientData.Id] = counter; + } + + if (counter.IncomingOverload) return false; + + counter.Update(reader.Tag); + + if (counter.TotalMsgLastSecond <= 100 && counter.GetRpcCount(reader.Tag) <= 60) + { + sr.Recycle(); + return true; + } + + counter.IncomingOverload = true; + var _player = FinalPlayerData.AllPlayerData.FirstOrDefault(x => x.CheatData.ClientData.Id == clientData.Id) + ?.Player; + Warn($"Incoming Msg Overloaded: {_player?.GetDataName() ?? ""}", "FAC"); + _player?.MarkAsHacker(); + sr.Recycle(); + reader.Recycle(); + return false; + } + + private class MsgCounter + { + private readonly Dictionary MsgTypeCounts = new(); + public bool IncomingOverload; + private DateTime lastReset = DateTime.UtcNow; + public int TotalMsgLastSecond; + + public void Update(byte rpcType) + { + if ((DateTime.UtcNow - lastReset).TotalSeconds >= 1) + { + TotalMsgLastSecond = 0; + MsgTypeCounts.Clear(); + lastReset = DateTime.UtcNow; + } + + TotalMsgLastSecond++; + MsgTypeCounts[rpcType] = MsgTypeCounts.TryGetValue(rpcType, out var count) ? count + 1 : 1; + } + + public int GetRpcCount(byte rpcType) + { + return MsgTypeCounts.GetValueOrDefault(rpcType, 0); + } + } +} + +[HarmonyPatch(typeof(InnerNetServer), nameof(InnerNetServer.HandleMessage))] +internal class HandleMessagePatch +{ + private static readonly Dictionary playerMsgCounters = new(); + + public static bool Prefix(InnerNetServer.Player client, MessageReader reader) + { + if (!IsLobby || IsNotJoined || !FinalGameData.JoinedCompleted || !Main.EnableGuardian.Value) return true; + + if (!playerMsgCounters.TryGetValue(client.Id, out var counter)) + { + counter = new MsgCounter(); + playerMsgCounters[client.Id] = counter; + } + + if (counter.IncomingOverload) return false; + counter.Update(reader.Tag); + + if (counter.TotalMsgLastSecond <= 100 && counter.GetRpcCount(reader.Tag) <= 60) return true; + + counter.IncomingOverload = true; + var _player = FinalPlayerData.AllPlayerData.FirstOrDefault(x => x.CheatData.ClientData.Id == client.Id) + ?.Player; + Warn($"Incoming Msg Overloaded: {_player?.GetDataName() ?? ""}", "FAC"); + _player?.MarkAsHacker(); + return false; + } + + private class MsgCounter + { + private readonly Dictionary MsgTypeCounts = new(); + public bool IncomingOverload; + private DateTime lastReset = DateTime.UtcNow; + public int TotalMsgLastSecond; + + public void Update(byte rpcType) + { + if ((DateTime.UtcNow - lastReset).TotalSeconds >= 1) + { + TotalMsgLastSecond = 0; + MsgTypeCounts.Clear(); + lastReset = DateTime.UtcNow; + } + + TotalMsgLastSecond++; + + MsgTypeCounts[rpcType] = MsgTypeCounts.TryGetValue(rpcType, out var count) ? count + 1 : 1; + } + + public int GetRpcCount(byte rpcType) + { + return MsgTypeCounts.GetValueOrDefault(rpcType, 0); + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/HauntMenuMinigamePatch.cs b/FinalSuspect/Patches/Game_Vanilla/HauntMenuMinigamePatch.cs index 8f31e888..095a1058 100644 --- a/FinalSuspect/Patches/Game_Vanilla/HauntMenuMinigamePatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/HauntMenuMinigamePatch.cs @@ -5,6 +5,6 @@ public static class HauntMenuMinigameSetFilterTextPatch { public static bool Prefix(HauntMenuMinigame __instance) { - return XtremeLocalHandling.GetHauntFilterText(__instance); + return FinalLocalHandling.GetHauntFilterText(__instance); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/HudPatch.cs b/FinalSuspect/Patches/Game_Vanilla/HudPatch.cs index 371b1efe..6b37f03c 100644 --- a/FinalSuspect/Patches/Game_Vanilla/HudPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/HudPatch.cs @@ -1,13 +1,14 @@ using System; using System.Collections; -using System.Linq; using System.Text; using AmongUs.Data; using AmongUs.GameOptions; using BepInEx.Unity.IL2CPP.Utils; using FinalSuspect.Attributes; +using FinalSuspect.DataHandling.FinalGameData; using FinalSuspect.Helpers; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using FinalSuspect.Modules.Features.DisplayedRoleTag; using FinalSuspect.Patches.System; using FinalSuspect.Templates; using InnerNet; @@ -18,17 +19,19 @@ namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))] -class SetVentOutlinePatch +internal class SetVentOutlinePatch { public static void Postfix(Vent __instance, [HarmonyArgument(1)] ref bool mainTarget) { - XtremeLocalHandling.SetVentOutlineColor(__instance, ref mainTarget); + FinalLocalHandling.SetVentOutlineColor(__instance, ref mainTarget); } } [HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.SetTaskText))] -class TaskPanelBehaviourPatch +internal class TaskPanelBehaviourPatch { + private static bool even; + public static void Postfix(TaskPanelBehaviour __instance) { if (!IsInGame) return; @@ -43,19 +46,35 @@ public static void Postfix(TaskPanelBehaviour __instance) var AllText = StringHelper.ColorString(player.GetRoleColor(), RoleWithInfo); - var lines = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); - StringBuilder sb = new(); - foreach (var eachLine in lines) + if (!taskText.Contains(GetString(StringNames.FixComms)) || PlayerControl.LocalPlayer.IsImpostor()) { - var line = eachLine.Trim(); - if ((line.StartsWith("") || line.StartsWith("")) && sb.Length < 1 && !line.Contains('(')) continue; - sb.Append(line + "\r\n"); + var lines = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); + StringBuilder sb = new(); + foreach (var eachLine in lines) + { + var line = eachLine.Trim(); + if (((line.StartsWith("") || line.StartsWith("")) && sb.Length < 1 && + !line.Contains('(')) || line.Contains(GetString(StringNames.FixComms))) continue; + sb.Append(line + "\r\n"); + } + + if (sb.Length > 1) + { + var text = sb.ToString().TrimEnd('\n').TrimEnd('\r'); + if (player.IsImpostor() && sb.ToString().Count(s => s == '\n') >= 2) + text = + $"{StringHelper.ColorString(new Color32(255, 20, 147, byte.MaxValue), GetString("FakeTask"))}\r\n{text}"; + AllText += $"\r\n\r\n{text}"; + } } - if (sb.Length > 1) + + if (taskText.Contains(GetString(StringNames.FixComms))) { - var text = sb.ToString().TrimEnd('\n').TrimEnd('\r'); - if (player.IsImpostor() && sb.ToString().Count(s => s == '\n') >= 2) - text = $"{StringHelper.ColorString(new Color32(255, 20, 147, byte.MaxValue), GetString("FakeTask"))}\r\n{text}"; + even = !even; + var color = even ? Color.yellow : Color.red; + var text = color.ToTextColor(); + text += GetString(StringNames.FixComms); + text += ""; AllText += $"\r\n\r\n{text}"; } @@ -67,97 +86,17 @@ public static void Postfix(TaskPanelBehaviour __instance) public static class HudManagerPatch { - static GameObject ModLoading; + private static GameObject ModLoading; private static int currentIndex; - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] - public static class Update - { - public static void Prefix(HudManager __instance) - { - if (ModLoading == null && !IsFreePlay) - { - ModLoading = new GameObject("ModLoading") { layer = 5 }; - ModLoading.transform.SetParent(__instance.GameLoadAnimation.transform.parent); - - ModLoading.transform.localScale = new Vector3(0.4f, 0.4f, 1f); - ModLoading.transform.localPosition = new Vector3(4.5833f, -2.25f, -600); - - var Sprite = ModLoading.AddComponent(); - Sprite.color = Color.white; - Sprite.flipX = false; - ModLoading.SetActive(false); - __instance.StartCoroutine(SwitchRoleIllustration(Sprite)); - } - /*Scrapped - if (WarningText == null) - { - WarningText = Object.Instantiate(__instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject, __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField")); - var tmp = WarningText.GetComponent(); - tmp.text = GetString("BrowsingMode"); - tmp.color = Color.blue; - WarningText.SetActive(false); - } - if (IsInGame) - { - if (IsInTask) - { - if (PlayerControl.LocalPlayer.IsAlive()) - { - __instance.Chat.gameObject.SetActive(true); - WarningText.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("Background").gameObject.SetActive(false); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("CharCounter (TMP)").gameObject.SetActive(false); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("ChatSendButton").gameObject.SetActive(false); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject.SetActive(false); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("QuickChatPreview").gameObject.SetActive(false); - } - else - { - WarningText.SetActive(false); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("Background").gameObject.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("CharCounter (TMP)").gameObject.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("ChatSendButton").gameObject.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject.SetActive(true); + private static TextMeshPro roleSummary; + public static SimpleButton showHideButton; + private static SpriteRenderer backgroundRenderer; - } - } - else if (!__instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").gameObject.active && !__instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("QuickChatPreview").gameObject.active) - { - WarningText.SetActive(false); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("Background").gameObject.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("CharCounter (TMP)").gameObject.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("ChatSendButton").gameObject.SetActive(true); - __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject.SetActive(true); - } - }*/ - } + private static bool Refresh; - public static void Postfix(HudManager __instance) - { - try - { - UpdateResult(__instance); - SetChatBG(__instance); - SetAbilityButtonColor(__instance); - } - catch - { - /* ignored */ - } - } - } - [HarmonyPatch(typeof(HudManager), nameof(HudManager.CoFadeFullScreen))] - public static class CoFadeFullScreen - { - public static void Prefix([HarmonyArgument(3)] ref bool showLoader) - { - ModLoading.SetActive(showLoader); - showLoader = false; - } - } - public static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) + private static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) { while (true) { @@ -172,6 +111,7 @@ public static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) spriter.color = Color.white.AlphaMultiplied(alpha); yield return null; } + currentIndex = (currentIndex + 1) % AwakeAccountManager.AllRoleRoleIllustration.Length; yield return new WaitForSeconds(1f); @@ -185,19 +125,6 @@ public static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) } } - [HarmonyPatch(typeof(HudManager), nameof(HudManager.HideGameLoader))] - public static class HideGameLoader - { - public static void Prefix() - { - ModLoading.SetActive(false); - } - } - - private static TextMeshPro roleSummary; - public static SimpleButton showHideButton; - private static SpriteRenderer backgroundRenderer; - [GameModuleInitializer] public static void Init() { @@ -205,7 +132,7 @@ public static void Init() { Object.Destroy(showHideButton.Button.gameObject); Object.Destroy(roleSummary.gameObject); - Object.Destroy(backgroundRenderer.gameObject); //销毁背景 + Object.Destroy(backgroundRenderer.gameObject); //销毁背景 } catch { @@ -217,65 +144,68 @@ public static void Init() backgroundRenderer = null; } - public static void SetChatBG(HudManager __instance) + private static void SetChatBG(HudManager __instance) { Color color; if (IsInGame) { if (PlayerControl.LocalPlayer.IsImpostor()) - { color = ColorHelper.ImpostorRedPale; - } else - { color = GetRoleColor(RoleTypes.Crewmate); - } } else { - color = ColorHelper.TeamColor32; + color = ColorHelper.AuthorColor; } __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("Background").gameObject .GetComponent().color = color; } - public static void SetAbilityButtonColor(HudManager __instance) + + [GameModuleInitializer] + public static void InitForRefresh() { - if (!IsInGame) return; + Refresh = false; + } + + private static void SetAbilityButtonColor(HudManager __instance) + { + if (!IsInGame) + return; var color = GetRoleColor(PlayerControl.LocalPlayer.GetRoleType()); __instance.AbilityButton.buttonLabelText.SetOutlineColor(color); - __instance.AbilityButton.cooldownTimerText.color = Color.green; - __instance.KillButton.cooldownTimerText.color = ColorHelper. ImpostorRedPale; + __instance.AbilityButton.cooldownTimerText.color = color; + __instance.KillButton.cooldownTimerText.color = ColorHelper.ImpostorRedPale; + + // 刷新按钮状态 + if (!__instance.AbilityButton.gameObject.active || Refresh) return; + Refresh = true; + __instance.AbilityButton.gameObject.SetActive(false); + __instance.AbilityButton.gameObject.SetActive(true); } - public static int GetLineCount(string text) + + private static int GetLineCount(string text) { if (string.IsNullOrEmpty(text)) return 0; var lines = text.Split(["\r\n", "\n"], StringSplitOptions.None); return lines.Length; } - public static string LastResultText; - public static string LastGameData; - public static string LastGameResult; - public static string LastRoomCode; - public static string LastServer; - [GameModuleInitializer] - public static void InitForLastResult() + private static void UpdateResult(HudManager __instance) { - LastResultText = LastGameData = LastGameResult = LastRoomCode = LastServer = ""; - } - public static void UpdateResult(HudManager __instance) - { - if (IsFreePlay || !IsInGame && GetLineCount(LastResultText) < 6 ) + if (IsFreePlay || (!IsInGame && GetLineCount(FinalGameData.LastResultText) < 6)) return; var showInitially = Main.ShowResults.Value; - + showHideButton ??= new SimpleButton( __instance.transform, "ShowHideResultsButton", - IsInGame? new Vector3(0.2f, 2.685f, -14f) : new Vector3(-4.5f, 2.6f, -14f), // 比 BackgroundLayer(z = -13) 更靠前 + IsInGame + ? new Vector3(0.2f * GetResolutionOffset(), 2.685f, -14f) + : new Vector3(-4.5f * GetResolutionOffset(), 2.6f, -14f), // 比 BackgroundLayer(z = -13) 更靠前 new Color32(209, 190, 255, byte.MaxValue), new Color32(208, 222, 255, byte.MaxValue), () => @@ -283,89 +213,196 @@ public static void UpdateResult(HudManager __instance) var setToActive = !roleSummary.gameObject.activeSelf; roleSummary.gameObject.SetActive(setToActive); Main.ShowResults.Value = setToActive; - showHideButton.Label.text = GetString(setToActive ? "HideResults" : "ShowResults"); + showHideButton.Label.text = GetString(setToActive ? "Summary.HideResults" : "Summary.ShowResults"); }, - GetString(showInitially ? "HideResults" : "ShowResults")) + GetString(showInitially ? "Summary.HideResults" : "Summary.ShowResults")) { Scale = new Vector2(1.5f, 0.5f), - FontSize = 2f, + FontSize = 2f }; - StringBuilder sb = new($"{GetString("RoleSummaryText")}{LastGameResult}"); + StringBuilder sb = new($"{GetString("Summary.Text")}{FinalGameData.LastGameResult}"); if (IsInGame) { - LastRoomCode = GameCode.IntToGameName(AmongUsClient.Instance.GameId); - LastServer = IsOnlineGame + FinalGameData.LastRoomCode = GameCode.IntToGameName(AmongUsClient.Instance.GameId); + FinalGameData.LastServer = IsOnlineGame ? PingTrackerUpdatePatch.ServerName : GetString("Local"); } - - var gamecode = StringHelper.ColorString( - ColorHelper.ModColor32, - DataManager.Settings.Gameplay.StreamerMode? new string('*', LastRoomCode.Length): LastRoomCode); - sb.Append("\n"+ LastServer +" "+gamecode); + + var gamecode = StringHelper.ColorString( + ColorHelper.FSColor, + DataManager.Settings.Gameplay.StreamerMode + ? new string('*', FinalGameData.LastRoomCode.Length) + : FinalGameData.LastRoomCode); + sb.Append("\n" + FinalGameData.LastServer + " " + gamecode); if (IsInGame) { StringBuilder sb2 = new(); - foreach (var data in XtremePlayerData.AllPlayerData) - { + foreach (var data in FinalPlayerData.AllPlayerData) sb2.Append("\n\u3000 ").Append(SummaryTexts(data.PlayerId)); - } - LastGameData = sb2.ToString(); + + FinalGameData.LastGameData = sb2.ToString(); } - sb.Append(LastGameData); - LastResultText = sb.ToString(); - if (roleSummary == null) + sb.Append(FinalGameData.LastGameData); + FinalGameData.LastResultText = sb.ToString(); + if (!roleSummary) { roleSummary = TMPTemplate.Create( - "RoleSummaryText", - LastResultText, + "RoleSummaryText", FinalGameData.LastResultText, Color.white, 1.25f, TextAlignmentOptions.TopLeft, - setActive: showInitially, - parent: showHideButton.Button.transform); - roleSummary.transform.localPosition = new Vector3(1.7f, -0.4f, -1f); + showInitially, + showHideButton.Button.transform); + roleSummary.transform.localPosition = + new Vector3(IsInGame ? 0f : 1.7f, -0.4f, -1f); roleSummary.transform.localScale = new Vector3(1.2f, 1.2f, 1f); roleSummary.fontStyle = FontStyles.Bold; roleSummary.SetOutlineColor(Color.black); roleSummary.SetOutlineThickness(0.15f); - + var backgroundObject = new GameObject("RoleSummaryBackground"); - backgroundObject.transform.SetParent(roleSummary.transform); + backgroundObject.transform.SetParent(roleSummary.transform); backgroundRenderer = backgroundObject.AddComponent(); - backgroundRenderer.sprite = LoadSprite("LastResult-BG.png",200f); - backgroundRenderer.color = new Color(0.5f,0.5f,0.5f,1f); + backgroundRenderer.sprite = LoadSprite("LastResult-BG.png", 200f); + backgroundRenderer.color = new Color(0.5f, 0.5f, 0.5f, 1f); } - + showHideButton.Button.transform.localPosition = IsInGame ? new Vector3(0.2f, 2.685f, -14f) : new Vector3(-4.5f, 2.6f, -1f); if (IsInGame) + { showHideButton.Button.gameObject.SetActive (PlayerControl.LocalPlayer.GetRoleType() is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost && - !IsMeeting); + !IsInMeeting); + } else showHideButton.Button.gameObject.SetActive(true); - roleSummary.text = LastResultText; + roleSummary.text = FinalGameData.LastResultText; AdjustBackgroundSize(); } private static void AdjustBackgroundSize() { - if (roleSummary != null && backgroundRenderer != null) + if (!roleSummary || !backgroundRenderer) return; + var textBounds = roleSummary.textBounds; + + var backgroundSprite = backgroundRenderer.sprite; + if (!backgroundSprite) return; + var scaleX = (textBounds.size.x + 0.4f) / backgroundSprite.bounds.size.x; + var scaleY = (textBounds.size.y + 0.5f) / backgroundSprite.bounds.size.y; + + backgroundRenderer.transform.localScale = new Vector3(scaleX, scaleY, 1f); + backgroundRenderer.transform.localPosition = new Vector3(textBounds.center.x, textBounds.center.y, 2f); + } + + [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] + public static class Update + { + public static void Prefix(HudManager __instance) { - var textBounds = roleSummary.textBounds; + if (!ModLoading) + { + ModLoading = new GameObject("ModLoading") { layer = 5 }; + ModLoading.transform.SetParent(__instance.GameLoadAnimation.transform.parent); + + var Sprite = ModLoading.AddComponent(); + Sprite.color = Color.white; + Sprite.flipX = false; + ModLoading.SetActive(false); + __instance.StartCoroutine(SwitchRoleIllustration(Sprite)); + + var ap = ModLoading.AddComponent(); + ap.Alignment = AspectPosition.EdgeAlignments.RightBottom; + ap.DistanceFromEdge = new Vector3(0.6f, 0.5f, -1000); + ap.updateAlways = true; + + ModLoading.transform.localScale = new Vector3(0.4f, 0.4f, 1f); + } - var backgroundSprite = backgroundRenderer.sprite; - if (backgroundSprite != null) + ModLoading.SetActive(!IsInGame && !IsLobby); + + var ap_n = __instance.Notifier.GetComponent(); + ap_n.DistanceFromEdge = new Vector3(ap_n.DistanceFromEdge.x, ap_n.DistanceFromEdge.y, -900); + + //ModLogo.SetActive(!IsInGame && !IsLobby); + /*Scrapped + if (WarningText == null) + { + WarningText = Object.Instantiate(__instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject, __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField")); + var tmp = WarningText.GetComponent(); + tmp.text = GetString("BrowsingMode"); + tmp.color = Color.blue; + WarningText.SetActive(false); + + } + if (IsInGame) { - var scaleX = (textBounds.size.x + 0.4f) / backgroundSprite.bounds.size.x; - var scaleY = (textBounds.size.y + 0.5f) / backgroundSprite.bounds.size.y; - - backgroundRenderer.transform.localScale = new Vector3(scaleX, scaleY, 1f); - backgroundRenderer.transform.localPosition = new Vector3(textBounds.center.x, textBounds.center.y,2f); + if (IsInTask) + { + if (PlayerControl.LocalPlayer.IsAlive()) + { + __instance.Chat.gameObject.SetActive(true); + WarningText.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("Background").gameObject.SetActive(false); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("CharCounter (TMP)").gameObject.SetActive(false); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("ChatSendButton").gameObject.SetActive(false); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject.SetActive(false); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("QuickChatPreview").gameObject.SetActive(false); + } + else + { + WarningText.SetActive(false); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("Background").gameObject.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("CharCounter (TMP)").gameObject.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("ChatSendButton").gameObject.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject.SetActive(true); + + } + } + else if (!__instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").gameObject.active && !__instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("QuickChatPreview").gameObject.active) + { + WarningText.SetActive(false); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("Background").gameObject.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("CharCounter (TMP)").gameObject.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("ChatSendButton").gameObject.SetActive(true); + __instance.Chat.chatScreen.transform.FindChild("ChatScreenContainer").FindChild("FreeChatInputField").FindChild("TextArea").gameObject.SetActive(true); + } + }*/ + } + + public static void Postfix(HudManager __instance) + { + try + { + UpdateResult(__instance); + SetChatBG(__instance); + SetAbilityButtonColor(__instance); + + if (!IsFreePlay && IsInGame) + { + var notShowPane = DestroyableSingleton.Instance.Chat.IsOpenOrOpening || + DisplayerRoleTagHelper.selectionUI != null && + DisplayerRoleTagHelper.selectionUI.activeSelf || + MapBehaviour.Instance.gameObject.activeSelf || + !ControllerManagerUpdatePatch.ShowSettingsPanel || + !FinalGameData.IntroDestroyed || + !ControllerManagerUpdatePatch.ShowHudUI; + + if (GameStartManagerPatch.Instance.LobbyInfoPane.gameObject.activeSelf && notShowPane) + { + GameStartManagerPatch.Instance.LobbyInfoPane.DeactivatePane(); + } + + GameStartManagerPatch.Instance.LobbyInfoPane.gameObject.SetActive(!notShowPane); + } + } + catch + { + /* ignored */ } } } diff --git a/FinalSuspect/Patches/Game_Vanilla/IntroPatch.cs b/FinalSuspect/Patches/Game_Vanilla/IntroPatch.cs index f62c1561..a29c8952 100644 --- a/FinalSuspect/Patches/Game_Vanilla/IntroPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/IntroPatch.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; using FinalSuspect.Helpers; -using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using TMPro; using UnityEngine; @@ -9,98 +9,111 @@ namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch(typeof(IntroCutscene))] internal class IntroCutscenePatch { - [HarmonyPatch(nameof(IntroCutscene.ShowRole)), HarmonyPostfix] - public static void ShowRole_Postfix(IntroCutscene __instance) + [HarmonyPatch(nameof(IntroCutscene.CoBegin))] + [HarmonyPrefix] + public static void CoBegin_Prefix() + { + UpdateGameState_IsInGame(true); + Info("Game Start", "IntroCutscene"); + } + + [HarmonyPatch(typeof(IntroCutscene._ShowRole_d__41), nameof(IntroCutscene._ShowRole_d__41.MoveNext))] + [HarmonyPostfix] + public static void Postfix(IntroCutscene._ShowRole_d__41 __instance, ref bool __result) { - if (OtherModHost) return; + if (!Main.EnableFinalSuspect.Value) return; + var intro = __instance.__4__this; _ = new MainThreadTask(() => { var roleType = PlayerControl.LocalPlayer.Data.Role.Role; - __instance.YouAreText.color = __instance.RoleText.color = __instance.RoleBlurbText.color = GetRoleColor(roleType); - __instance.RoleText.text = GetRoleName(roleType); - __instance.RoleText.fontWeight = FontWeight.Thin; - __instance.RoleText.SetOutlineColor(GetRoleColor(roleType).ShadeColor(0.1f).SetAlpha(0.38f)); - __instance.RoleText.SetOutlineThickness(0.17f); - __instance.RoleBlurbText.text = roleType.GetRoleInfoForVanilla(); + intro.YouAreText.color = + intro.RoleText.color = + intro.RoleBlurbText.color = GetRoleColor(roleType); + intro.RoleText.text = GetRoleName(roleType); + intro.RoleText.fontWeight = FontWeight.Thin; + intro.RoleText.SetOutlineColor(GetRoleColor(roleType).ShadeColor(0.1f).SetAlpha(0.38f)); + intro.RoleText.SetOutlineThickness(0.17f); + intro.RoleBlurbText.text = roleType.GetRoleInfoForVanilla(); }, "Override Role Text"); - - } - [HarmonyPatch(nameof(IntroCutscene.CoBegin)), HarmonyPrefix] - public static void CoBegin_Prefix() - { - InGame = true; - Info("Game Start", "IntroCutscene"); } - [HarmonyPatch(nameof(IntroCutscene.BeginImpostor)), HarmonyPostfix] + + [HarmonyPatch(nameof(IntroCutscene.BeginImpostor))] + [HarmonyPostfix] public static void BeginImpostor_Postfix(IntroCutscene __instance) { - if (OtherModHost) return; + if (!Main.EnableFinalSuspect.Value) return; __instance.ImpostorText.gameObject.SetActive(true); - var onlyimp = GameManager.Instance.LogicOptions.GetAdjustedNumImpostors(GameData.Instance.PlayerCount) == 1; - var color = Palette.ImpostorRed; - var colorcode = onlyimp ? ColorHelper.ColorToHex(Palette.DisabledGrey) : ColorHelper.ColorToHex(Palette.ImpostorRed); - __instance.TeamTitle.text = onlyimp - ? GetString("TeamImpostorOnly") - : GetString("TeamImpostor"); + var playerCount = GameData.Instance.PlayerCount; + var impostorCount = GameManager.Instance.LogicOptions.GetAdjustedNumImpostors(playerCount); + var onlyImp = impostorCount == 1; - __instance.TeamTitle.color = color; - __instance.ImpostorText.text = $""; - __instance.ImpostorText.text += onlyimp - ? GetString("ImpostorNumImpOnly") - : $"{string.Format(GetString("ImpostorNumImp"), GameManager.Instance.LogicOptions.GetAdjustedNumImpostors(GameData.Instance.PlayerCount))}"; + var impColor = Palette.ImpostorRed; + var impColorCode = ColorHelper.ColorToHex(onlyImp ? Palette.DisabledGrey : impColor); + var teamTitleText = GetString(onlyImp ? "Team.Imp_Only" : "Team.Imp"); + var introText = GetString(onlyImp ? "IntroText.Imp_Only" : "IntroText.Imp"); - __instance.ImpostorText.text += "\n" + (onlyimp - ? GetString("ImpostorIntroTextOnly") - : GetString("ImpostorIntroText")); + __instance.TeamTitle.text = teamTitleText; + __instance.TeamTitle.color = impColor; - __instance.BackgroundBar.material.color = Palette.DisabledGrey; + __instance.ImpostorText.text = onlyImp + ? $"{GetString("ImpostorNum.Imp_Only")}\n{introText}" + : $" 500) + await Task.Delay(1000); + var milliseconds = 0; + while (true) { - Info("break", "StartFadeIntro"); - break; + await Task.Delay(20); + milliseconds += 20; + var time = milliseconds / (float)500; + var LerpingColor = Color.Lerp(start, end, time); + if (!__instance || milliseconds > 500) + { + Info("break", "StartFadeIntro"); + break; + } + + __instance.BackgroundBar.material.color = LerpingColor; } - __instance.BackgroundBar.material.color = LerpingColor; + } + catch + { + /* ignored */ } } } \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/LobbyPatch.cs b/FinalSuspect/Patches/Game_Vanilla/LobbyPatch.cs index 5e9ef21e..ad50205c 100644 --- a/FinalSuspect/Patches/Game_Vanilla/LobbyPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/LobbyPatch.cs @@ -8,14 +8,15 @@ namespace FinalSuspect.Patches.Game_Vanilla; public class LobbyStartPatch { private static GameObject Paint; + public static void Postfix(LobbyBehaviour __instance) { - if (Paint != null) return; + if (Paint) return; Paint = Object.Instantiate(__instance.transform.FindChild("Leftbox").gameObject, __instance.transform); Paint.name = "FinalSuspect Lobby Paint"; Paint.transform.localPosition = new Vector3(0.042f, -2.59f, -10.5f); var renderer = Paint.GetComponent(); - renderer.sprite = LoadSprite("TeamLogo.png", 290f); + renderer.sprite = LoadSprite("AuthorLogo2.png", 440f); } } @@ -23,18 +24,18 @@ public static void Postfix(LobbyBehaviour __instance) public static class HostInfoPanelUpdatePatch { private static TextMeshPro HostText; + public static void Postfix(HostInfoPanel __instance) { - if (AmongUsClient.Instance.AmHost) - { - if (HostText == null) - HostText = __instance.content.transform.FindChild("Name").GetComponent(); + if (!AmongUsClient.Instance.AmHost) return; + if (!HostText) + HostText = __instance.content.transform.FindChild("Name").GetComponent(); - var htmlStringRgb = ColorUtility.ToHtmlStringRGB(Palette.PlayerColors[__instance.player.ColorId]); - var hostName = Main.HostNickName; - var youLabel = DestroyableSingleton.Instance.GetString(StringNames.HostYouLabel); + var htmlStringRgb = ColorUtility.ToHtmlStringRGB(Palette.PlayerColors[__instance.player.ColorId]); + var hostName = Main.HostNickName; + var youLabel = DestroyableSingleton.Instance.GetString(StringNames.HostYouLabel); - HostText.text = $"{hostName} {youLabel}"; - } + HostText.text = + $"{hostName} {youLabel}"; } } \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/MapRealTimeLocationPatch.cs b/FinalSuspect/Patches/Game_Vanilla/MapRealTimeLocationPatch.cs index ad540e72..ccc7fcd6 100644 --- a/FinalSuspect/Patches/Game_Vanilla/MapRealTimeLocationPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/MapRealTimeLocationPatch.cs @@ -1,6 +1,6 @@ using System.Collections; -using System.Linq; using BepInEx.Unity.IL2CPP.Utils; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using UnityEngine; namespace FinalSuspect.Patches.Game_Vanilla; @@ -8,13 +8,15 @@ namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch] public class MapRealTimeLocationPatch { - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show)), HarmonyPostfix] + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show))] + [HarmonyPostfix] public static void ShowMapAfter(MapBehaviour __instance, [HarmonyArgument(0)] MapOptions opts) { - XtremeLocalHandling.ShowMap(__instance, opts); + FinalLocalHandling.ShowMap(__instance, opts); } - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Awake)), HarmonyPostfix] + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Awake))] + [HarmonyPostfix] public static void AwakeAfter(MapBehaviour __instance) { AmongUsClient.Instance.StartCoroutine(CreateTargetRends(__instance)); @@ -22,48 +24,50 @@ public static void AwakeAfter(MapBehaviour __instance) private static IEnumerator CreateTargetRends(MapBehaviour mapBehaviour) { - while (XtremePlayerData.AllPlayerData.Count < Main.AllPlayerControls.Count()) yield return null; - foreach (var data in XtremePlayerData.AllPlayerData) + while (FinalPlayerData.AllPlayerData.Count < Main.AllPlayerControls.Count()) yield return null; + foreach (var data in FinalPlayerData.AllPlayerData) { var rend = Object.Instantiate(mapBehaviour.HerePoint, mapBehaviour.HerePoint.transform.parent, true); rend.gameObject.SetActive(false); data.Rend = rend; - data.Deadbodyrend = Object.Instantiate(rend, rend.transform.parent); - data.Deadbodyrend.flipY = true; + data.Rend_DeadBody = Object.Instantiate(rend, rend.transform.parent); + data.Rend_DeadBody.flipY = true; } } - - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.FixedUpdate)), HarmonyPostfix] + + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.FixedUpdate))] + [HarmonyPostfix] public static void FixedUpdateAfter(MapBehaviour __instance) { - XtremeLocalHandling.UpdateMap(); + FinalLocalHandling.UpdateMap(); } - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.SetPreMeetingPosition)), HarmonyPostfix] - public static void SetPreMeetingPositionAfter(MapBehaviour __instance, [HarmonyArgument(0)] Vector3 preMeetingPosition) + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.SetPreMeetingPosition))] + [HarmonyPostfix] + public static void SetPreMeetingPositionAfter(MapBehaviour __instance, + [HarmonyArgument(0)] Vector3 preMeetingPosition) { - foreach (var data in XtremePlayerData.AllPlayerData) + foreach (var data in FinalPlayerData.AllPlayerData.Where(data => !data.IsDisconnected)) { - if (data.IsDisconnected) continue; data.PreMeetingPosition = data.Player.GetTruePosition(); + data.PreMeetingRoomName = data.Player.GetPlainShipRoomName(); } } - - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.GenericShow)), HarmonyPostfix] + + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.GenericShow))] + [HarmonyPostfix] public static void GenericShowAfter(MapBehaviour __instance) { - foreach (var data in XtremePlayerData.AllPlayerData) + foreach (var data in FinalPlayerData.AllPlayerData.Where(data => !data.IsDisconnected)) { - if (data.IsDisconnected)continue; data.Rend.material.SetInt(PlayerMaterial.MaskLayer, 255); } } - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Close)), HarmonyPostfix] + + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Close))] + [HarmonyPostfix] public static void CloseAfter(MapBehaviour __instance) { - foreach (var data in XtremePlayerData.AllPlayerData) - { - data.Rend.enabled = true; - } + foreach (var data in FinalPlayerData.AllPlayerData) data.Rend.enabled = true; } } \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/MeetingHudPatch.cs b/FinalSuspect/Patches/Game_Vanilla/MeetingHudPatch.cs index c3706c92..836fa9df 100644 --- a/FinalSuspect/Patches/Game_Vanilla/MeetingHudPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/MeetingHudPatch.cs @@ -1,5 +1,6 @@ -using System.Linq; using AmongUs.GameOptions; +using FinalSuspect.DataHandling.FinalGameData; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using Object = UnityEngine.Object; namespace FinalSuspect.Patches.Game_Vanilla; @@ -8,22 +9,22 @@ namespace FinalSuspect.Patches.Game_Vanilla; public static class MeetingHudPatch { [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.UpdateButtons))] - class UpdatePatch + public class UpdatePatch { public static void Postfix(MeetingHud __instance) { try { - if (__instance == null) return; - if (AmongUsClient.Instance?.AmHost == true) return; + if (!__instance) return; + if (AmongUsClient.Instance?.AmHost == true) return; - for (var i = 0; i < __instance.playerStates?.Length; i++) + for (var i = 0; i < __instance.playerStates?.Length; i++) { var playerVoteArea = __instance.playerStates[i]; - if (playerVoteArea == null) continue; + if (!playerVoteArea) continue; var playerById = GameData.Instance?.GetPlayerById(playerVoteArea.TargetPlayerId); - if (playerById == null) + if (!playerById) { playerVoteArea.SetDisabled(); } @@ -31,41 +32,43 @@ public static void Postfix(MeetingHud __instance) { var flag = playerById.Disconnected || playerById.IsDead; if (flag == playerVoteArea.AmDead) continue; - var isReporter = __instance.reporterId == playerById.PlayerId; - playerVoteArea.SetDead(isReporter, flag, + var isReporter = __instance.reporterId == playerById.PlayerId; + playerVoteArea.SetDead(isReporter, flag, playerById.Role?.Role == RoleTypes.GuardianAngel); __instance.SetDirtyBit(1U); } } } - catch + catch { /* ignored */ } } } - + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.VotingComplete))] [HarmonyPriority(Priority.First)] - class VotingCompletePatch + public class VotingCompletePatch { - public static void Postfix([HarmonyArgument(1)]NetworkedPlayerInfo exiled, [HarmonyArgument(2)]bool tie ) + public static void Postfix([HarmonyArgument(1)] NetworkedPlayerInfo exiled, [HarmonyArgument(2)] bool tie) { - foreach (var data in XtremePlayerData.AllPlayerData.Where(data => data?.Deadbodyrend != null)) + foreach (var data in FinalPlayerData.AllPlayerData.Where(data => data?.Rend_DeadBody)) { - Object.Destroy(data.Deadbodyrend); - data.Deadbodyrend = null; + if (data == null) continue; + Object.Destroy(data.Rend_DeadBody); + data.Rend_DeadBody = null; } - if (tie || exiled == null) return; + if (tie || !exiled) return; var player = GetPlayerById(exiled.PlayerId); player.SetDead(); player.SetDeathReason(VanillaDeathReason.Exile, true); } } } + [HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.SetHighlighted))] -class SetHighlightedPatch +internal class SetHighlightedPatch { public static bool Prefix(PlayerVoteArea __instance, bool value) { diff --git a/FinalSuspect/Patches/Game_Vanilla/NotificationPopperPatch.cs b/FinalSuspect/Patches/Game_Vanilla/NotificationPopperPatch.cs index b8967865..a1901708 100644 --- a/FinalSuspect/Patches/Game_Vanilla/NotificationPopperPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/NotificationPopperPatch.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using FinalSuspect.Modules.Features.CheckingandBlocking; +using FinalSuspect.Modules.Features.CheckingandBlocking; namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.AddDisconnectMessage))] public class NotificationPopperPatch { - private static List WaitToSend = []; + private static readonly List WaitToSend = []; + public static bool Prefix(string item) { if (!WaitToSend.Contains(item)) return false; @@ -14,12 +14,15 @@ public static bool Prefix(string item) WaitToSend.Remove(item); return true; } - public static void AddItem(string text) + + private static void AddItem(string text) { WaitToSend.Add(text); - if (DestroyableSingleton._instance) DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(text); + if (DestroyableSingleton._instance) + DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(text); else WaitToSend.Remove(text); } + public static void NotificationPop(string text) { AddItem(text); diff --git a/FinalSuspect/Patches/Game_Vanilla/OutroPatch.cs b/FinalSuspect/Patches/Game_Vanilla/OutroPatch.cs index 5f42bf4c..25139c91 100644 --- a/FinalSuspect/Patches/Game_Vanilla/OutroPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/OutroPatch.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; -using System.Linq; using System.Text; using AmongUs.Data; +using FinalSuspect.DataHandling.FinalGameData; using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Templates; using TMPro; using UnityEngine; @@ -10,22 +10,25 @@ namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] -class AmongUsClientEndGamePatch +internal class AmongUsClientEndGamePatch { public static Dictionary SummaryText = new(); - public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref EndGameResult endGameResult) + + public static void Postfix() { + FinalGameData.LastLocalPlayerRoleColor = PlayerControl.LocalPlayer.GetRoleColor(); SummaryText = new Dictionary(); - foreach (var data in XtremePlayerData.AllPlayerData) + foreach (var data in FinalPlayerData.AllPlayerData) SummaryText[data.PlayerId] = SummaryTexts(data.PlayerId); } } + [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))] -class SetEverythingUpPatch +internal class SetEverythingUpPatch { private static TextMeshPro roleSummary; private static SimpleButton showHideButton; - static bool DidHumansWin; + private static bool DidHumansWin; public static void Prefix() { @@ -36,31 +39,29 @@ public static void Postfix(EndGameManager __instance) { var showInitially = Main.ShowResults.Value; - //####################################### - // ==勝利陣営表示== - //####################################### var WinnerTextObject = Object.Instantiate(__instance.WinText.gameObject); - WinnerTextObject.transform.position = new Vector3(__instance.WinText.transform.position.x, __instance.WinText.transform.position.y - 0.5f, __instance.WinText.transform.position.z); + WinnerTextObject.transform.position = new Vector3(__instance.WinText.transform.position.x, + __instance.WinText.transform.position.y - 0.5f, __instance.WinText.transform.position.z); WinnerTextObject.transform.localScale = new Vector3(0.6f, 0.6f, 0.6f); - var WinnerText = WinnerTextObject.GetComponent(); // WinTextと同じ型のコンポーネントを取得 - WinnerText.fontSizeMin = 3f; + var winnerText = WinnerTextObject.GetComponent(); + winnerText.fontSizeMin = 3f; - var CustomWinnerColor = DidHumansWin ? "#8CFFFF" : "#FF1919"; - __instance.BackgroundBar.material.color = __instance.WinText.color = WinnerText.color = DidHumansWin ? Palette.CrewmateBlue : Palette.ImpostorRed; - __instance.WinText.text = DidHumansWin ? GetString("CrewmatesWin") : GetString("ImpostorsWin"); - WinnerText.text = DidHumansWin ? GetString("CrewmatesWinBlurb") : GetString("ImpostorsWinBlurb"); + var winnerColor = DidHumansWin ? "#8CFFFF" : "#FF1919"; + __instance.BackgroundBar.material.color = __instance.WinText.color = + winnerText.color = DidHumansWin ? Palette.CrewmateBlue : Palette.ImpostorRed; + __instance.WinText.text = DidHumansWin ? GetString("Outro.Crews_Win") : GetString("Outro.Imps_Win"); + winnerText.text = DidHumansWin ? GetString("Outro.Crews_WinBlurb") : GetString("Outro.Imps_WinBlurb"); __instance.WinText.gameObject.SetActive(!showInitially); WinnerTextObject.SetActive(!showInitially); - //ShowResult: - showHideButton = + showHideButton = new SimpleButton( __instance.transform, "ShowHideResultsButton", - new Vector3(-4.5f, 2.6f, -14f), // 比 BackgroundLayer(z = -13) 更靠前 - new Color32(209, 190, 0, byte.MaxValue), - new Color32(byte.MaxValue, byte.MaxValue, 0, byte.MaxValue), + new Vector3(-4.5f * GetResolutionOffset(), 2.6f, -14f), // 比 BackgroundLayer(z = -13) 更靠前 + FinalGameData.LastLocalPlayerRoleColor, + FinalGameData.LastLocalPlayerRoleColor.ShadeColor(0.1f), () => { var setToActive = !roleSummary.gameObject.activeSelf; @@ -68,33 +69,33 @@ public static void Postfix(EndGameManager __instance) Main.ShowResults.Value = setToActive; __instance.WinText.gameObject.SetActive(!setToActive); WinnerTextObject.SetActive(!setToActive); - showHideButton.Label.text = GetString(setToActive ? "HideResults" : "ShowResults"); + showHideButton.Label.text = GetString(setToActive ? "Summary.HideResults" : "Summary.ShowResults"); }, - GetString(showInitially ? "HideResults" : "ShowResults")) + GetString(showInitially ? "Summary.HideResults" : "Summary.ShowResults")) { Scale = new Vector2(1.5f, 0.5f), - FontSize = 2f, + FontSize = 2f }; - var lastgameresult = DidHumansWin ? GetString("CrewsWin") : GetString("ImpsWin"); - HudManagerPatch.LastGameResult = lastgameresult; - StringBuilder sb = new($"{GetString("RoleSummaryText")}{lastgameresult}"); - var gamecode = StringHelper.ColorString( - ColorHelper.ModColor32, - DataManager.Settings.Gameplay.StreamerMode? new string('*', HudManagerPatch.LastRoomCode.Length): HudManagerPatch.LastRoomCode); - sb.Append("\n"+ HudManagerPatch.LastServer +" "+gamecode); - sb.Append("\n" + GetString("HideSummaryTextToShowWinText")); + var lastGameResult = DidHumansWin ? GetString("Summary.CrewsWin") : GetString("Summary.ImpsWin"); + FinalGameData.LastGameResult = lastGameResult; + StringBuilder sb = new($"{GetString("Summary.Text")}{lastGameResult}"); + var gameCode = StringHelper.ColorString( + ColorHelper.FSColor, + DataManager.Settings.Gameplay.StreamerMode + ? new string('*', FinalGameData.LastRoomCode.Length) + : FinalGameData.LastRoomCode); + sb.Append("\n" + FinalGameData.LastServer + " " + gameCode); + sb.Append("\n" + GetString("Tip.HideSummaryTextToShowWinText")); StringBuilder sb2 = new(); - foreach (var data in XtremePlayerData.AllPlayerData.Where(x => x.IsImpostor != DidHumansWin)) - { - sb2.Append($"\n★ ").Append(AmongUsClientEndGamePatch.SummaryText[data.PlayerId]); - } - foreach (var data in XtremePlayerData.AllPlayerData.Where(x => x.IsImpostor == DidHumansWin)) - { + foreach (var data in FinalPlayerData.AllPlayerData.Where(x => x.IsImpostor != DidHumansWin)) + sb2.Append($"\n★ ") + .Append(AmongUsClientEndGamePatch.SummaryText[data.PlayerId]); + + foreach (var data in FinalPlayerData.AllPlayerData.Where(x => x.IsImpostor == DidHumansWin)) sb2.Append("\n\u3000 ").Append(AmongUsClientEndGamePatch.SummaryText[data.PlayerId]); - } - HudManagerPatch.LastGameData = sb2.ToString(); + FinalGameData.LastGameData = sb2.ToString(); sb.Append(sb2); HudManagerPatch.Init(); roleSummary = TMPTemplate.Create( @@ -103,14 +104,14 @@ public static void Postfix(EndGameManager __instance) Color.white, 1.25f, TextAlignmentOptions.TopLeft, - setActive: showInitially, - parent: showHideButton.Button.transform); + showInitially, + showHideButton.Button.transform); roleSummary.transform.localPosition = new Vector3(1.7f, -0.4f, -1f); roleSummary.transform.localScale = new Vector3(1.2f, 1.2f, 1f); roleSummary.fontStyle = FontStyles.Bold; roleSummary.SetOutlineColor(Color.black); roleSummary.SetOutlineThickness(0.15f); - - XtremePlayerData.DisposeAll(); + + FinalPlayerData.DisposeAll(); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/PlayerContorolPatch.cs b/FinalSuspect/Patches/Game_Vanilla/PlayerContorolPatch.cs index b587b70a..07aa737d 100644 --- a/FinalSuspect/Patches/Game_Vanilla/PlayerContorolPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/PlayerContorolPatch.cs @@ -1,20 +1,21 @@ using AmongUs.GameOptions; -using FinalSuspect.Modules.Core.Game; -using Il2CppSystem.Collections.Generic; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using TMPro; using UnityEngine; namespace FinalSuspect.Patches.Game_Vanilla; [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))] -class MurderPlayerPatch +internal class MurderPlayerPatch { public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) { target.SetRealKiller(__instance); } } + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoSetRole))] -class CoSetRolePatch +internal class CoSetRolePatch { public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes roleTypes) { @@ -30,18 +31,22 @@ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTy } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))] -class PlayerStartPatch +internal class PlayerStartPatch { public static void Postfix(PlayerControl __instance) { var topText = Object.Instantiate(__instance.cosmetics.nameText, __instance.cosmetics.nameText.transform, true); + topText.text = topText.gameObject.name = "TopText"; topText.transform.localPosition = new Vector3(0f, 0.2f, 0f); topText.transform.localScale = new Vector3(1f, 1f, 1f); topText.fontSize = Main.RoleTextSize; topText.text = "TopText"; topText.gameObject.name = "TopText"; topText.enabled = false; - var bottomText = Object.Instantiate(__instance.cosmetics.nameText, __instance.cosmetics.nameText.transform, true); + topText.alignment = TextAlignmentOptions.Bottom; + + var bottomText = + Object.Instantiate(__instance.cosmetics.nameText, __instance.cosmetics.nameText.transform, true); bottomText.transform.localPosition = new Vector3(0f, 0.2f, 0f); bottomText.transform.localScale = new Vector3(1f, 1f, 1f); bottomText.fontSize = Main.RoleTextSize; @@ -52,27 +57,28 @@ public static void Postfix(PlayerControl __instance) } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetTasks))] -class PlayerControlSetTasksPatch +internal class PlayerControlSetTasksPatch { - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] List tasks) + public static void Postfix(PlayerControl __instance, + [HarmonyArgument(0)] Il2CppSystem.Collections.Generic.List tasks) { // 自由模式假人处理 - if (__instance.GetXtremeData() == null) - XtremePlayerData.CreateDataFor(__instance); + if (__instance.GetFinalData() == null) + FinalPlayerData.CreateDataFor(__instance); __instance.SetTaskTotalCount(tasks.Count); } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] -class PlayerControlCompleteTaskPatch +internal class PlayerControlCompleteTaskPatch { public static void Postfix(PlayerControl __instance) { - var pc = __instance; - Info($"TaskComplete:{pc.GetNameWithRole()}", "CompleteTask"); - pc.OnCompleteTask(); + Info($"TaskComplete:{__instance.GetNameWithRole()}", "CompleteTask"); + __instance.OnCompleteTask(); GameData.Instance.RecomputeTaskCounts(); - Info($"TotalTaskCounts = {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", "TaskState.Update"); + Info($"TotalTaskCounts: {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", + "TaskState.Update"); } -} +} \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/PlayerJoinAndLeftPatch.cs b/FinalSuspect/Patches/Game_Vanilla/PlayerJoinAndLeftPatch.cs new file mode 100644 index 00000000..5c83b5a6 --- /dev/null +++ b/FinalSuspect/Patches/Game_Vanilla/PlayerJoinAndLeftPatch.cs @@ -0,0 +1,163 @@ +using AmongUs.Data; +using FinalSuspect.DataHandling.FinalGameData; +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using FinalSuspect.Modules.Features.CheckingandBlocking; +using FinalSuspect.Patches.System; +using InnerNet; + +namespace FinalSuspect.Patches.Game_Vanilla; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] +public class OnGameJoinedPatch +{ + public static void Postfix(AmongUsClient __instance) + { + HudManagerPatch.Init(); + Info($"{__instance.GameId} 加入房间", "OnGameJoined"); + FinalGameData.PlayerVersion.playerVersion = new Dictionary(); + FinalPlayerData.InitializeAll(); + UpdateGameState_IsInGame(false); + UpdateGameState_IsInMeeting(false); + ErrorText.Instance.Clear(); + ServerAddManager.SetServerName(); + FinalGameData.JoinedCompleted = false; + Init_FAC(); + _ = new LateTask(() => { _ = RPC.RpcVersionCheck(); }, 0.5f, "SyncJoined"); + _ = new LateTask(() => { FinalGameData.JoinedCompleted = true; }, 4f, "SyncJoined"); + + if (AmongUsClient.Instance.AmHost) GameStartManagerPatch.GameStartManagerUpdatePatch.exitTimer = -1; + SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); + //Main.NewLobby = true; + } +} + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))] +internal class DisconnectInternalPatch +{ + public static void Prefix(InnerNetClient __instance, DisconnectReasons reason, string stringReason) + { + try + { + ShowDisconnectPopupPatch.Reason = reason; + ShowDisconnectPopupPatch.StringReason = stringReason; + + Info($"断开连接(理由:{reason}:{stringReason},Ping:{__instance.Ping})", "Session"); + HudManagerPatch.Init(); + FinalPlayerData.DisposeAll(); + + ErrorText.Instance.CheatDetected = false; + ErrorText.Instance.SBDetected = false; + ErrorText.Instance.Clear(); + //Cloud.StopConnect(); + } + catch + { + /* ignored */ + } + } +} + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] +public class OnPlayerJoinedPatch +{ + public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData client) + { + Info($"{client.PlayerName}(ClientID:{client.Id}/FriendCode:{client.FriendCode}) 加入房间", "Session"); + + BanManager.CheckFriendCode(client); + BanManager.CheckBanPlayer(client); + BanManager.CheckDenyNamePlayer(client); + KickUnspawnedPlayers(client); + } + + private static void KickUnspawnedPlayers(ClientData client) + { + _ = new LateTask(() => + { + try + { + if (!AmongUsClient.Instance.AmHost || AmongUsClient.Instance.allClients.Contains(client) || + !client.Character.Data.IsIncomplete) return; + SendInGame(GetString("Warning.InvalidColor") + + $" {client.PlayerName}(ClientID:{client.Id}/FriendCode:{client.FriendCode})"); + AmongUsClient.Instance.KickPlayer(client.Id, false); + Info( + $"Kicked {client.PlayerName}(ClientID:{client.Id}/FriendCode:{client.FriendCode}) due to it was unspawned", + "OnPlayerJoinedPatchPostfix"); + } + catch + { + /* ignored */ + } + }, 4.5f, "Kick Unspawned Players"); + } +} + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] +internal class OnPlayerLeftPatch +{ + public static readonly List ClientsProcessed = []; + + public static void Add(int id) + { + ClientsProcessed.Remove(id); + ClientsProcessed.Add(id); + } + + public static void Postfix([HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) + { + try + { + if (data == null) + { + Error("错误的客户端数据:数据为空", "Session"); + return; + } + + data.Character?.SetDisconnected(); + + Info( + $"{data.PlayerName}(ClientID:{data.Id}/FriendCode:{data.FriendCode})断开连接(理由:{reason},Ping:{AmongUsClient.Instance.Ping})", + "Session"); + var id = data.ColorId; + var color = Palette.PlayerColors[id]; + var name = StringHelper.ColorString(color, data.PlayerName); + + // 附加描述掉线原因 + switch (reason) + { + case DisconnectReasons.Hacking: + NotificationPopperPatch.NotificationPop( + string.Format(GetString("Notification.PlayerLeftByAU-Anticheat"), name)); + break; + case DisconnectReasons.Error: + NotificationPopperPatch.NotificationPop(string.Format(GetString("Notification.PlayerLeftCuzError"), + name)); + break; + case DisconnectReasons.Kicked: + case DisconnectReasons.Banned: + break; + case DisconnectReasons.ClientTimeout: + NotificationPopperPatch.NotificationPop( + string.Format(GetString("Notification.PlayerLeftCuzTimeout"), name)); + break; + default: + if (!ClientsProcessed.Contains(data.Id)) + NotificationPopperPatch.NotificationPop(string.Format(GetString("Notification.PlayerLeft"), + name)); + break; + } + + Dispose(data.Character?.PlayerId ?? 255); + + FinalGameData.PlayerVersion.playerVersion.Remove(data.Character?.GetClientId() ?? 0); + ClientsProcessed.Remove(data.Id); + } + catch + { + /* ignored */ + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/ServerDropDownPatch.cs b/FinalSuspect/Patches/Game_Vanilla/ServerDropDownPatch.cs index db3cddbb..f8d85c39 100644 --- a/FinalSuspect/Patches/Game_Vanilla/ServerDropDownPatch.cs +++ b/FinalSuspect/Patches/Game_Vanilla/ServerDropDownPatch.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Il2CppInterop.Runtime.InteropTypes.Arrays; using UnityEngine; using UnityEngine.SceneManagement; @@ -16,27 +14,30 @@ public static class ServerDropDownPatch internal static bool FillServerOptions_Prefix(ServerDropdown __instance) { if (SceneManager.GetActiveScene().name == "FindAGame") return true; + const int maxPerColumn = 6; // 每列最大按钮数 + const float columnWidth = 4.15f; // 列宽度 + const float buttonSpacing = 0.5f; // 按钮间距 + - // 调整背景大小 __instance.background.size = new Vector2(5, 1); var num = 0; var column = 0; - const int maxPerColumn = 6; // 每列最大按钮数 - const float columnWidth = 4.15f; // 列宽度 - const float buttonSpacing = 0.5f; // 按钮间距 - var regions = DestroyableSingleton.Instance.AvailableRegions.OrderBy(ServerManager.DefaultRegions.Contains).ToList(); + var regions = DestroyableSingleton.Instance.AvailableRegions + .OrderBy(ServerManager.DefaultRegions.Contains).ToList(); var totalColumns = Mathf.Max(1, Mathf.CeilToInt(regions.Count / (float)maxPerColumn)); - //int rowsInLastColumn = regions.Count % maxPerColumn; - var maxRows = (regions.Count > maxPerColumn) ? maxPerColumn : regions.Count; + + var maxRows = regions.Count > maxPerColumn ? maxPerColumn : regions.Count; foreach (var regionInfo in regions) { if (DestroyableSingleton.Instance.CurrentRegion.Name == regionInfo.Name) { __instance.defaultButtonSelected = __instance.firstOption; - __instance.firstOption.ChangeButtonText(DestroyableSingleton.Instance.GetStringWithDefault(regionInfo.TranslateName, regionInfo.Name, new Il2CppReferenceArray(0))); + __instance.firstOption.ChangeButtonText( + DestroyableSingleton.Instance.GetStringWithDefault(regionInfo.TranslateName, + regionInfo.Name, new Il2CppReferenceArray(0))); continue; } @@ -51,7 +52,7 @@ internal static bool FillServerOptions_Prefix(ServerDropdown __instance) // 按钮位置和缩放 serverListButton.transform.localPosition = new Vector3(xPos, yPos, -1f); serverListButton.transform.localScale = Vector3.one; - + // 设置按钮 serverListButton.Text.text = DestroyableSingleton.Instance.GetStringWithDefault( regionInfo.TranslateName, @@ -63,17 +64,14 @@ internal static bool FillServerOptions_Prefix(ServerDropdown __instance) __instance.controllerSelectable.Add(serverListButton.Button); num++; - if (num % maxPerColumn == 0) - { - column++; - } + if (num % maxPerColumn == 0) column++; } // 调整背景大小和位置 var backgroundHeight = 1.2f + buttonSpacing * (maxRows - 1); - var backgroundWidth = (totalColumns > 1) ? - (columnWidth * (totalColumns - 1) + __instance.background.size.x) : - __instance.background.size.x; + var backgroundWidth = totalColumns > 1 + ? columnWidth * (totalColumns - 1) + __instance.background.size.x + : __instance.background.size.x; __instance.background.transform.localPosition = new Vector3( 0f, @@ -91,11 +89,15 @@ internal static void FillServerOptions_Postfix(ServerDropdown __instance) // 仅在搜索界面生效 if (SceneManager.GetActiveScene().name != "FindAGame") return; - var buttonSpacing = 0.6f; - var columnSpacing = 6f; + const float buttonSpacing = 0.6f; + const float columnSpacing = 7.2f; // 按钮按Y轴排序 - List allButtons = [.. __instance.GetComponentsInChildren().OrderByDescending(b => b.transform.localPosition.y)]; + List allButtons = + [ + .. __instance.GetComponentsInChildren() + .OrderByDescending(b => b.transform.localPosition.y) + ]; if (allButtons.Count == 0) return; @@ -107,14 +109,14 @@ internal static void FillServerOptions_Postfix(ServerDropdown __instance) { var col = i / buttonsPerColumn; var row = i % buttonsPerColumn; - allButtons[i].transform.localPosition = startPosition + new Vector3(col * columnSpacing, -row * buttonSpacing, 0f); + allButtons[i].transform.localPosition = + startPosition + new Vector3(col * columnSpacing, -row * buttonSpacing, 0f); } // 计算背景大小和位置 - var maxRows = Math.Min(buttonsPerColumn, allButtons.Count); + var maxRows = Math.Min(buttonsPerColumn, allButtons.Count); var backgroundHeight = 1.2f + buttonSpacing * (maxRows - 1); - var backgroundWidth = (columnCount > 1) ? - (columnSpacing * (columnCount - 1) + 5) : 5; + var backgroundWidth = columnCount > 1 ? columnSpacing * (columnCount - 1) + 5 : 5; __instance.background.transform.localPosition = new Vector3( 0f, diff --git a/FinalSuspect/Patches/Game_Vanilla/ShipStatusPatch.cs b/FinalSuspect/Patches/Game_Vanilla/ShipStatusPatch.cs new file mode 100644 index 00000000..58a0a016 --- /dev/null +++ b/FinalSuspect/Patches/Game_Vanilla/ShipStatusPatch.cs @@ -0,0 +1,117 @@ +using FinalSuspect.Helpers; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace FinalSuspect.Patches.Game_Vanilla; + +[HarmonyPatch] +public class ShipStatusPatch +{ + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] + [HarmonyPostfix] + public static void ShipStatus_Start(ShipStatus __instance) + { + if (__instance.name.Contains("Skeld")) + { + var speed = HashRandom.Next(-20, -1); + var starGen = DestroyableSingleton.Instance; + if (Main.IsAprilFools) + { + speed = -50; + var posi = starGen.gameObject.transform.localPosition; + starGen.gameObject.transform.localPosition = + new Vector3(posi.x, posi.y, -posi.z); + } + + starGen.SetDirection(new Vector2(speed, 0)); + } + else if (__instance.name.Contains("Mira")) + { + Retry: + var speed_x = HashRandom.Next(-10, 10); + var speed_y = HashRandom.Next(-10, 10); + if (speed_x is 0 || speed_y is 0) goto Retry; + var cloudGen = DestroyableSingleton.Instance; + if (Main.IsAprilFools) + { + var posi = cloudGen.gameObject.transform.localPosition; + cloudGen.gameObject.transform.localPosition = + new Vector3(posi.x, posi.y, -posi.z); + } + + cloudGen.SetDirection(new Vector2(speed_x, speed_y)); + } + else if (__instance.name.Contains("Polus")) + { + var speed = HashRandom.Next(1, 20); + var size = HashRandom.Next(1, 100); + size /= 1000; + var snow = DestroyableSingleton.Instance.particles; + if (Main.IsAprilFools) + { + size = 10; + speed = 15; + snow.startColor = new Color(1f, 1f, 1f, 0.15f); + } + else if (Main.IsValentines) + { + snow.startColor = new Color(0.85f, 0.5f, 0.6f, 1f); + } + else if (Main.IsInitialRelease) + { + snow.startColor = ColorHelper.FSColor; + } + + snow.startSpeed = speed; + snow.startSize = size; + } + else if (__instance.name.Contains("Airship")) + { + var speed = HashRandom.Next(1, 10); + var cloudGenerators = Object.FindObjectsOfType(); + + foreach (var generator in cloudGenerators) + { + if (Main.IsAprilFools) + { + var posi = generator.gameObject.transform.localPosition; + generator.gameObject.transform.localPosition = + new Vector3(posi.x, posi.y, -posi.z); + } + + generator.SetDirection(new Vector2(speed, 0)); + } + } + else if (__instance.name.Contains("Fungle")) + { + var _base = __instance.transform.FindChild("Backgrounds").FindChild("Base"); + var leftColor = _base.FindChild("WaterLeft").gameObject.GetComponent(); + var rightColor = _base.FindChild("WaterRight").gameObject.GetComponent(); + var b = HashRandom.Next(0, 5); + b /= 10; + var color = leftColor.color; + color.b = b; + leftColor.color = color; + rightColor.color = color; + + if (Main.IsAprilFools) + { + var blood = new Color(0.8f, 0f, 0f, 1f); + + _base.FindChild("OverlayTint").gameObject.GetComponent().color = + new Color(0.6f, 0f, 0f, 0.2f); + leftColor.color = blood; + rightColor.color = blood; + + var _bgSunSet = __instance.transform.FindChild("FungleSunsetParallax").FindChild("Contents"); + _bgSunSet.FindChild("Stars").gameObject.GetComponent().color = blood; + _bgSunSet.FindChild("Sunset").gameObject.GetComponent().color = blood; + + __instance.transform.FindChild("FungleWavesAnimated(Clone)").FindChild("Waves").gameObject + .GetComponent().material.color = blood; + DestroyableSingleton.Instance.ShadowQuad.GetComponent().material.color = + new Color(0.25f, 0.05f, 0.1f, 1f); + } + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/Game_Vanilla/TaskProgressTrackerPatch.cs b/FinalSuspect/Patches/Game_Vanilla/TaskProgressTrackerPatch.cs new file mode 100644 index 00000000..bec9b33d --- /dev/null +++ b/FinalSuspect/Patches/Game_Vanilla/TaskProgressTrackerPatch.cs @@ -0,0 +1,69 @@ +using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; +using TMPro; +using UnityEngine; + +namespace FinalSuspect.Patches.Game_Vanilla; + +[HarmonyPatch] +public class TaskProgressTrackerPatch +{ + private static string TitleText; + private static readonly int FullColor = Shader.PropertyToID("_FullColor"); + private static float lastPercentage; + + + [HarmonyPatch(typeof(ProgressTracker), nameof(ProgressTracker.Start))] + [HarmonyPostfix] + public static void ProgressTracker_Start(ProgressTracker __instance) + { + TitleText = __instance.gameObject.transform.FindChild("TitleText_TMP").GetComponent().text; + } + + [HarmonyPatch(typeof(ProgressTracker), nameof(ProgressTracker.FixedUpdate))] + [HarmonyPostfix] + public static void ProgressTracker_FixedUpdate(ProgressTracker __instance) + { + __instance.TileParent.material.SetColor(FullColor, ColorHelper.CompleteGreen); + if (!IsInGame) + { + lastPercentage = 0; + return; + } + + var instance = GameData.Instance; + var percentage = instance.CompletedTasks / (float)instance.TotalTasks * 100f; + var data = PlayerControl.LocalPlayer.GetFinalData(); + switch (GameManager.Instance.LogicOptions.GetTaskBarMode()) + { + case TaskBarMode.Normal: + break; + case TaskBarMode.MeetingOnly: + if (!MeetingHud.Instance) + goto End; + break; + case TaskBarMode.Invisible: + default: + goto End; + } + + lastPercentage = percentage; + End: + + __instance.TileParent.material.SetColor(FullColor, + data.IsImpostor + ? ColorHelper.GetColorByPercentage(lastPercentage) + : PlayerControl.LocalPlayer.GetRoleColor()); + + var tmp = __instance.gameObject.transform.FindChild("TitleText_TMP").GetComponent(); + tmp.text = $"{TitleText}({lastPercentage:F1}%)"; + tmp.fontStyle = FontStyles.Bold; + if (data.IsImpostor) return; + var comms = IsActive(SystemTypes.Comms); + var NormalColor = data.TaskCompleted ? Color.green : Color.yellow; + var TextColor = comms ? Color.gray : NormalColor; + tmp.color = TextColor; + if (comms) + tmp.text = $"{TitleText}(??.?%)"; + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/Scrapped/Dleks/DleksPatch.cs b/FinalSuspect/Patches/Scrapped/Dleks/DleksPatch.cs index 8657e6ee..65448d8b 100644 --- a/FinalSuspect/Patches/Scrapped/Dleks/DleksPatch.cs +++ b/FinalSuspect/Patches/Scrapped/Dleks/DleksPatch.cs @@ -44,13 +44,13 @@ public static void Postfix_AllMapIcons(GameStartManager __instance) { if (__instance == null) return; - if (XtremeGameData.GameStates.IsNormalGame && Main.NormalOptions.MapId == 3) + if (FinalGameData.GameStates.IsNormalGame && Main.NormalOptions.MapId == 3) { Main.NormalOptions.MapId = 0; __instance.UpdateMapImage(MapNames.Skeld); } - else if (XtremeGameData.GameStates.IsHideNSeek && Main.HideNSeekOptions.MapId == 3) + else if (FinalGameData.GameStates.IsHideNSeek && Main.HideNSeekOptions.MapId == 3) { Main.HideNSeekOptions.MapId = 0; __instance.UpdateMapImage(MapNames.Skeld); @@ -82,19 +82,19 @@ public static class VentSetButtonsPatch public static bool ShowButtons = false; private static bool Prefix(Vent __instance, [HarmonyArgument(0)] ref bool enabled) { - if (XtremeGameData.GameStates.MapIsActive(MapNames.Dleks) && IntroCutsceneOnDestroyPatch.introDestroyed) + if (FinalGameData.GameStates.MapIsActive(MapNames.Dleks) && IntroCutsceneOnDestroyPatch.introDestroyed) { enabled = false; - ShowButtons = !XtremeGameData.GameStates.IsMeeting; + ShowButtons = !FinalGameData.GameStates.IsMeeting; } return true; } public static void Postfix(Vent __instance, [HarmonyArgument(0)] bool enabled) { - if (!XtremeGameData.GameStates.MapIsActive(MapNames.Dleks)) return; + if (!FinalGameData.GameStates.MapIsActive(MapNames.Dleks)) return; if (enabled || !IntroCutsceneOnDestroyPatch.introDestroyed) return; - var setActive = ShowButtons || !PlayerControl.LocalPlayer.inVent && !XtremeGameData.GameStates.IsMeeting; + var setActive = ShowButtons || !PlayerControl.LocalPlayer.inVent && !FinalGameData.GameStates.IsMeeting; switch (__instance.Id) { case 0: @@ -126,7 +126,7 @@ class VentTryMoveToVentPatch { private static void Postfix(Vent __instance, [HarmonyArgument(0)] Vent otherVent) { - if (__instance == null || otherVent == null || !XtremeGameData.GameStates.MapIsActive(MapNames.Dleks)) return; + if (__instance == null || otherVent == null || !FinalGameData.GameStates.MapIsActive(MapNames.Dleks)) return; VentSetButtonsPatch.ShowButtons = true; VentSetButtonsPatch.Postfix(otherVent, false); @@ -138,6 +138,7 @@ class VentUpdateArrowsPatch { private static bool Prefix() { - return !XtremeGameData.GameStates.MapIsActive(MapNames.Dleks); + return !FinalGameData.GameStates.MapIsActive(MapNames.Dleks); } -}*/ \ No newline at end of file +}*/ + diff --git a/FinalSuspect/Patches/Scrapped/Dleks/MapPickerMenuPatch.cs b/FinalSuspect/Patches/Scrapped/Dleks/MapPickerMenuPatch.cs index 6dcd0799..aa9d5297 100644 --- a/FinalSuspect/Patches/Scrapped/Dleks/MapPickerMenuPatch.cs +++ b/FinalSuspect/Patches/Scrapped/Dleks/MapPickerMenuPatch.cs @@ -42,9 +42,9 @@ // __instance.selectedButton.Button.SelectButton(true); // __instance.selectedMapId = 3; -// if (XtremeGameData.GameStates.IsNormalGame) +// if (FinalGameData.GameStates.IsNormalGame) // Main.NormalOptions.MapId = 0; -// else if (XtremeGameData.GameStates.IsHideNSeek) +// else if (FinalGameData.GameStates.IsHideNSeek) // Main.HideNSeekOptions.MapId = 0; // //__instance.MapImage.transform.localScale = new Vector3(-1f, 1f, 1f); @@ -177,4 +177,5 @@ // transform2.position = vector3; // } // } -//} \ No newline at end of file +//} + diff --git a/FinalSuspect/Patches/Scrapped/Prank/VentKiller/AnimationPatch.cs b/FinalSuspect/Patches/Scrapped/Prank/VentKiller/AnimationPatch.cs index 666b6c05..9e8c18a2 100644 --- a/FinalSuspect/Patches/Scrapped/Prank/VentKiller/AnimationPatch.cs +++ b/FinalSuspect/Patches/Scrapped/Prank/VentKiller/AnimationPatch.cs @@ -23,7 +23,7 @@ private static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)]int id) return false; } - + } [HarmonyPatch(typeof(PlayerPhysics._CoEnterVent_d__55), nameof(PlayerPhysics._CoEnterVent_d__55.MoveNext))] internal static class EnterVentAnimationPatch @@ -88,3 +88,4 @@ private static void Postfix(PlayerPhysics._CoExitVent_d__56 __instance, bool __r } } }*/ + diff --git a/FinalSuspect/Patches/Scrapped/Prank/VentKiller/GameManagerPatch.cs b/FinalSuspect/Patches/Scrapped/Prank/VentKiller/GameManagerPatch.cs index 134d670e..af063a1a 100644 --- a/FinalSuspect/Patches/Scrapped/Prank/VentKiller/GameManagerPatch.cs +++ b/FinalSuspect/Patches/Scrapped/Prank/VentKiller/GameManagerPatch.cs @@ -25,4 +25,5 @@ public static bool IsPlayer(this Vent vent) { return vent.Id >= 10000; } -}*/ \ No newline at end of file +}*/ + diff --git a/FinalSuspect/Patches/Scrapped/Prank/VentKiller/VentPatch.cs b/FinalSuspect/Patches/Scrapped/Prank/VentKiller/VentPatch.cs index 0637034f..ccbc7857 100644 --- a/FinalSuspect/Patches/Scrapped/Prank/VentKiller/VentPatch.cs +++ b/FinalSuspect/Patches/Scrapped/Prank/VentKiller/VentPatch.cs @@ -12,4 +12,5 @@ private static bool OutlinePatch(Vent __instance) { return !__instance.IsPlayer(); } -}*/ \ No newline at end of file +}*/ + diff --git a/FinalSuspect/Patches/System/AccountManagerPatch.cs b/FinalSuspect/Patches/System/AccountManagerPatch.cs index fc60dc27..b30296f3 100644 --- a/FinalSuspect/Patches/System/AccountManagerPatch.cs +++ b/FinalSuspect/Patches/System/AccountManagerPatch.cs @@ -1,52 +1,33 @@ using System.Collections; using BepInEx.Unity.IL2CPP.Utils; -using Il2CppSystem; using UnityEngine; +using static FinalSuspect.Modules.Core.Plugin.ModMainMenuManager; namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(AccountTab), nameof(AccountTab.Awake))] public static class AwakeFriendCodeUIPatch { - public static GameObject FriendsButton; - public static void Prefix() { var BarSprit = GameObject.Find("BarSprite"); if (BarSprit) { - GameObject CustomBarSprit = new(); - CustomBarSprit.transform.SetParent(BarSprit.transform.parent); - CustomBarSprit.transform.localScale = BarSprit.transform.localScale; - CustomBarSprit.transform.localPosition = BarSprit.transform.localPosition; - - void ResetParent(GameObject obj) - { - obj.transform.SetParent(CustomBarSprit.transform); - } - BarSprit.ForEachChild((Action)ResetParent); - BarSprit.SetActive(false); - } - - var newRequest = GameObject.Find("NewRequest"); - if (newRequest != null) - { - newRequest.transform.localPosition -= new Vector3(0f, 0f, 10f); - newRequest.transform.localScale = new Vector3(0.8f, 1f, 1f); + BarSprit.GetComponent().color = Color.clear; } FriendsButton = GameObject.Find("FriendsButton"); FriendsButton.transform.FindChild("Highlight").FindChild("NewRequestActive").FindChild("Background").gameObject - .GetComponent().color = Color.white.AlphaMultiplied(0.5f); + .GetComponent().color = Color.white.AlphaMultiplied(0.3f); FriendsButton.transform.FindChild("Inactive").FindChild("NewRequestInactive").FindChild("Background").gameObject - .GetComponent().color = Color.white.AlphaMultiplied(0.5f); + .GetComponent().color = Color.white.AlphaMultiplied(0.3f); } } [HarmonyPatch(typeof(AccountManager), nameof(AccountManager.Awake))] public static class AwakeAccountManager { - public static Sprite[] AllRoleRoleIllustration = + public static readonly Sprite[] AllRoleRoleIllustration = [ LoadSprite("CI_Crewmate.png", 450f), LoadSprite("CI_HnSEngineer.png", 450f), @@ -61,11 +42,13 @@ public static class AwakeAccountManager LoadSprite("CI_Shapeshifter.png", 450f), LoadSprite("CI_Phantom.png", 450f), LoadSprite("CI_ImpostorGhost.png", 450f) - ]; + ]; + private static int currentIndex; - static GameObject crewpet_walk0001; - static GameObject ModLoading; + private static GameObject crewpet_walk0001; + private static GameObject ModLoading; + public static void Prefix(AccountManager __instance) { try @@ -80,19 +63,25 @@ public static void Prefix(AccountManager __instance) ModLoading = new GameObject("ModLoading"); ModLoading.transform.SetParent(crewpet_walk0001.transform.parent); ModLoading.transform.localScale = new Vector3(0.4f, 0.4f, 1f); - ModLoading.transform.localPosition = new Vector3(4.5f, - 2.4f, - 1f); + ModLoading.transform.localPosition = new Vector3(4.5f, -2.4f, -1f); var Sprite = ModLoading.AddComponent(); Sprite.color = Color.white; Sprite.flipX = false; __instance.StartCoroutine(SwitchRoleIllustration(Sprite)); crewpet_walk0001.SetActive(false); + + var ap = ModLoading.AddComponent(); + ap.Alignment = AspectPosition.EdgeAlignments.RightBottom; + ap.DistanceFromEdge = new Vector3(0.6f, 0.5f, -1000); + ap.updateAlways = true; } catch { /* ignored */ } } - public static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) + + private static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) { while (true) { @@ -107,6 +96,7 @@ public static IEnumerator SwitchRoleIllustration(SpriteRenderer spriter) spriter.color = Color.white.AlphaMultiplied(alpha); yield return null; } + currentIndex = (currentIndex + 1) % AllRoleRoleIllustration.Length; yield return new WaitForSeconds(1f); diff --git a/FinalSuspect/Patches/System/AnnouncementPatch.cs b/FinalSuspect/Patches/System/AnnouncementPatch.cs index dd548666..ecb8af6d 100644 --- a/FinalSuspect/Patches/System/AnnouncementPatch.cs +++ b/FinalSuspect/Patches/System/AnnouncementPatch.cs @@ -1,265 +1,270 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using AmongUs.Data; using AmongUs.Data.Player; using Assets.InnerNet; using FinalSuspect.Helpers; using Il2CppInterop.Runtime.InteropTypes.Arrays; using UnityEngine; +using static FinalSuspect.Modules.Core.Plugin.ModMainMenuManager; namespace FinalSuspect.Patches.System; // 参考:https://github.com/Yumenopai/TownOfHost_Y public class ModNews { - public int Number; - public uint Lang; - public string Title; - public string SubTitle; - public string ShortTitle; - public string Text; - public string Date; + public string Date; + public uint Lang; + public int Number; + public string ShortTitle; + public string SubTitle; + public string Text; + public string Title; - public Announcement ToAnnouncement() - { - var result = new Announcement - { - Number = Number, - Language = Lang, - Title = Title, - SubTitle = SubTitle, - ShortTitle = ShortTitle, - Text = Text, - Date = Date, - Id = "ModNews" - }; - return result; - } + public Announcement ToAnnouncement() + { + var result = new Announcement + { + Number = Number, + Language = Lang, + Title = Title, + SubTitle = SubTitle, + ShortTitle = ShortTitle, + Text = Text, + Date = Date, + Id = "ModNews" + }; + return result; + } } [HarmonyPatch] public class ModNewsHistory { - private static readonly List AllModNews = []; - - [HarmonyPatch(typeof(PlayerAnnouncementData), nameof(PlayerAnnouncementData.SetAnnouncements)), HarmonyPrefix] - public static bool SetModAnnouncements([HarmonyArgument(0)] ref Il2CppReferenceArray aRange) - { - try - { - var FinalAllNews = new List(); - AllModNews.ForEach(n => - { - if (n.Lang == (uint)TranslationController.Instance.currentLanguage.languageID) - FinalAllNews.Add(n.ToAnnouncement()); - }); + private static readonly List AllModNews = []; - foreach (var news in aRange) - { - if (!AllModNews.Any(x => x.Number == news.Number)) - { - FinalAllNews.Add(news); - } - } + public static bool AnnouncementLoadComplete; - FinalAllNews.Sort((a1, a2) => - { - if (string.IsNullOrEmpty(a1.Date) || string.IsNullOrEmpty(a2.Date)) - { - return string.IsNullOrEmpty(a1.Date) ? 1 : -1; - } + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Show))] + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Init))] + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.ShowIfNew))] + [HarmonyPrefix] + public static bool AnnouncementPopupPrefix() + { + return AnnouncementLoadComplete; + } - return DateTime.Parse(a2.Date).CompareTo(DateTime.Parse(a1.Date)); - }); + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Show))] + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Init))] + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.ShowIfNew))] + [HarmonyPostfix] + public static void AnnouncementPopupPostfix() + { + if (!AnnouncementLoadComplete) Instance.announcementPopUp.Close(); + } - if (FinalAllNews.Count == 0) - { - aRange = new Il2CppReferenceArray(0); - } - else - { - aRange = new Il2CppReferenceArray(FinalAllNews.Count); - for (var i = 0; i < FinalAllNews.Count; i++) - { - aRange[i] = FinalAllNews[i]; - } - } - } - catch (Exception ex) - { - Error($"Exception in SetModAnnouncements: {ex}", "SetModAnnouncements"); - } - return true; - } - //Reference: https://github.com/Team-YuTeam/YuEzTools - [HarmonyPatch(typeof(AnnouncementPanel), nameof(AnnouncementPanel.SetUp)), HarmonyPostfix] - public static void SetUpPanel(AnnouncementPanel __instance, [HarmonyArgument(0)] Announcement announcement) - { - if (announcement.Number < 100000) return; - var teamLogo = new GameObject("TeamLogo") { layer = 5 }; - teamLogo.transform.SetParent(__instance.transform); - teamLogo.transform.localPosition = new Vector3(-0.81f, 0.16f, 0.5f); - teamLogo.transform.localScale = new Vector3(0.9f, 0.9f, 0.9f); - var sr = teamLogo.AddComponent(); - sr.sprite = LoadSprite("TeamLogo.png", 1000f); - sr.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - } - - public static bool AnnouncementLoadComplete; + [HarmonyPatch(typeof(PlayerAnnouncementData), nameof(PlayerAnnouncementData.SetAnnouncements))] + [HarmonyPrefix] + public static bool SetModAnnouncements([HarmonyArgument(0)] ref Il2CppReferenceArray aRange) + { + try + { + var FinalAllNews = new List(); + AllModNews.ForEach(n => + { + if (n.Lang == (uint)TranslationController.Instance.currentLanguage.languageID) + FinalAllNews.Add(n.ToAnnouncement()); + }); + FinalAllNews.AddRange(aRange.Where(news => !AllModNews.Any(x => x.Number == news.Number))); + FinalAllNews.Sort((a1, a2) => + { + if (string.IsNullOrEmpty(a1.Date) || string.IsNullOrEmpty(a2.Date)) + return string.IsNullOrEmpty(a1.Date) ? 1 : -1; - public static async Task LoadModAnnouncements() - { - try - { - // 如果 AllModNews 为空,加载所有语言的 ModNews - if (AllModNews.Count >= 1) return; - foreach (var lang in EnumHelper.GetAllValues()) - { - foreach (var target in ResourcesHelper.RemoteModNewsList) - { - foreach (var url in GetInfoFileUrlList()) - { - var task = GetAnnouncements(url + $"Assets/ModNews/{lang}/{target}", target); - await task; - var result = task.Result; - if (!result.Item1) - continue; - try - { - var content = GetContentFromRes(result.Item2, lang); - if (content != null && !string.IsNullOrEmpty(content.Date)) - { - AllModNews.Add(content); - } - } - catch - { - /* ignored */ - } - break; - } - } - } + return DateTime.Parse(a2.Date).CompareTo(DateTime.Parse(a1.Date)); + }); - // 对 AllModNews 进行排序,处理可能的空值 - AllModNews.Sort((a1, a2) => - { - if (string.IsNullOrEmpty(a1.Date) || string.IsNullOrEmpty(a2.Date)) - { - return string.IsNullOrEmpty(a1.Date) ? 1 : -1; - } - return DateTime.Parse(a2.Date).CompareTo(DateTime.Parse(a1.Date)); - }); - } - catch - { - /* ignored */ - } - - AnnouncementLoadComplete = true; - Info("Loading mod announcements complete.", "SetModAnnouncements"); - } + if (FinalAllNews.Count == 0) + { + aRange = new Il2CppReferenceArray(0); + } + else + { + aRange = new Il2CppReferenceArray(FinalAllNews.Count); + for (var i = 0; i < FinalAllNews.Count; i++) aRange[i] = FinalAllNews[i]; + } + } + catch (Exception ex) + { + Error($"Exception in SetModAnnouncements: {ex}", "SetModAnnouncements"); + } - private static async Task<(bool, string)> GetAnnouncements(string url, string name) - { - try - { - string result; - if (url.StartsWith("file:///")) - { - try - { - // Windows 格式 - var filePath = url[8..].Replace('/', '\\'); - result = await File.ReadAllTextAsync(filePath); - } - catch (FileNotFoundException) - { - Warn($"服务器文件缺失: {url[8..]}", "GetAnnouncements"); - return (false, ""); - } - catch (Exception ex) - { - Error($"读取本地文件失败: {ex.Message}", "GetAnnouncements"); - return (false, ""); - } - } - else - { - using HttpClient client = new(); - client.DefaultRequestHeaders.Add("User-Agent", "FinalSuspect" + name); - client.DefaultRequestHeaders.Add("Referer", "gitee.com"); + return true; + } - using var response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseContentRead); - if (!response.IsSuccessStatusCode) - { - Error($"服务器请求失败 [{url}]: {response.StatusCode}", "GetAnnouncements"); - return (false, ""); - } + //Reference: https://github.com/Team-YuTeam/YuEzTools + [HarmonyPatch(typeof(AnnouncementPanel), nameof(AnnouncementPanel.SetUp))] + [HarmonyPostfix] + public static void SetUpPanel(AnnouncementPanel __instance, [HarmonyArgument(0)] Announcement announcement) + { + if (announcement.Number < 100000) return; + var authorLogo = new GameObject("AuthorLogo") { layer = 5 }; + authorLogo.transform.SetParent(__instance.transform); + authorLogo.transform.localPosition = new Vector3(-0.75f, 0.2f, 0.5f); + authorLogo.transform.localScale = new Vector3(0.9f, 0.9f, 0.9f); + var sr = authorLogo.AddComponent(); + sr.sprite = LoadSprite("AuthorLogo2.png", 1700f); + sr.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + } - result = await response.Content.ReadAsStringAsync(); - } + public static async Task LoadModAnnouncements() + { + try + { + // 如果 AllModNews 为空,加载所有语言的 ModNews + if (AllModNews.Count >= 1) return; + foreach (var lang in EnumHelper.GetAllValues()) + foreach (var target in ResourcesHelper.RemoteModNewsList) + foreach (var url in GetInfoFileUrlList()) + { + var task = GetAnnouncements(url + $"Assets/ModNews/{lang}/{target}"); + await task; + var result = task.Result; + if (!result.Item1) + continue; + try + { + var content = GetContentFromRes(result.Item2, lang); + if (content != null && !string.IsNullOrEmpty(content.Date)) AllModNews.Add(content); + } + catch + { + /* ignored */ + } - await Task.Delay(100); - return (true, result); - } - catch - { - return (false, ""); - } - } + break; + } - private static ModNews GetContentFromRes(string content, SupportedLangs lang) - { - ModNews mn = new(); + // 对 AllModNews 进行排序,处理可能的空值 + AllModNews.Sort((a1, a2) => + { + if (string.IsNullOrEmpty(a1.Date) || string.IsNullOrEmpty(a2.Date)) + return string.IsNullOrEmpty(a1.Date) ? 1 : -1; - var byteArray = Encoding.UTF8.GetBytes(content); - using MemoryStream stream = new(byteArray); - using StreamReader reader = new(stream, Encoding.UTF8); - var text = ""; - var langId = (uint)lang; - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - if (line!.StartsWith("#Number:")) mn.Number = int.Parse(line.Replace("#Number:", string.Empty)); - else if (line.StartsWith("#LangId:")) langId = uint.Parse(line.Replace("#LangId:", string.Empty)); - else if (line.StartsWith("#Title:")) mn.Title = line.Replace("#Title:", string.Empty); - else if (line.StartsWith("#SubTitle:")) mn.SubTitle = line.Replace("#SubTitle:", string.Empty); - else if (line.StartsWith("#ShortTitle:")) mn.ShortTitle = line.Replace("#ShortTitle:", string.Empty); - else if (line.StartsWith("#Date:")) mn.Date = line.Replace("#Date:", string.Empty); - else if (line.StartsWith("#---")) continue; - else if (line.StartsWith("# ")) continue; - else - { - var pattern = @"\[(.*?)\]\((.*?)\)"; - var regex = new Regex(pattern); - line = regex.Replace(line, match => - { - var content1 = match.Groups[1].Value; - var content2 = match.Groups[2].Value; - return $"{content1} "; - }); + return DateTime.Parse(a2.Date).CompareTo(DateTime.Parse(a1.Date)); + }); + } + catch + { + /* ignored */ + } - if (line.StartsWith("## ")) line = line.Replace("## ", "") + ""; - else if (line.StartsWith("- ") && !line.StartsWith(" - ")) line = line.Replace("- ", "・"); + _ = new MainThreadTask(() => + { + AnnouncementLoadComplete = true; + DataManager.Player.Announcements.AllAnnouncements.Clear(); + if (!Instance) return; + try + { + Instance.announcementPopUp.Show(); + Info("Loading mod announcements complete.", "SetModAnnouncements"); + } + catch + { + /* ignored */ + } + }, "ReShow mod announcements"); + } - text += $"{line}\n"; - } - } + private static async Task<(bool, string)> GetAnnouncements(string url) + { + try + { + var task = JsonHelper.GetJsonStringAsync(url); + await task; + var (result, succeed) = task.Result; + if (!succeed) return (false, ""); - mn.Lang = langId; - mn.Text = text; - Info($"Number:{mn.Number}", "ModNews"); - Info($"Title:{mn.Title}", "ModNews"); - Info($"SubTitle:{mn.SubTitle}", "ModNews"); - Info($"ShortTitle:{mn.ShortTitle}", "ModNews"); - Info($"Date:{mn.Date}", "ModNews"); - return mn; - } + await Task.Delay(100); + return (true, result); + } + catch + { + return (false, ""); + } + } + + private static ModNews GetContentFromRes(string content, SupportedLangs lang) + { + ModNews mn = new(); + + var byteArray = Encoding.UTF8.GetBytes(content); + using MemoryStream stream = new(byteArray); + using StreamReader reader = new(stream, Encoding.UTF8); + var text = ""; + var langId = (uint)lang; + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + if (line!.StartsWith("#Number:")) + { + mn.Number = int.Parse(line.Replace("#Number:", string.Empty)); + } + else if (line.StartsWith("#LangId:")) + { + langId = uint.Parse(line.Replace("#LangId:", string.Empty)); + } + else if (line.StartsWith("#Title:")) + { + mn.Title = line.Replace("#Title:", string.Empty); + } + else if (line.StartsWith("#SubTitle:")) + { + mn.SubTitle = line.Replace("#SubTitle:", string.Empty); + } + else if (line.StartsWith("#ShortTitle:")) + { + mn.ShortTitle = line.Replace("#ShortTitle:", string.Empty); + } + else if (line.StartsWith("#Date:")) + { + mn.Date = line.Replace("#Date:", string.Empty); + } + else if (line.StartsWith("#---")) + { + } + else if (line.StartsWith("# ")) + { + } + else + { + const string pattern = @"\[(.*?)\]\((.*?)\)"; + var regex = new Regex(pattern); + line = regex.Replace(line, match => + { + var content1 = match.Groups[1].Value; + var content2 = match.Groups[2].Value; + return $"{content1} "; + }); + + if (line.StartsWith("## ")) line = line.Replace("## ", "") + ""; + else if (line.StartsWith("- ") && !line.StartsWith(" - ")) line = line.Replace("- ", "・"); + + text += $"{line}\n"; + } + } + + mn.Lang = langId; + mn.Text = text; + Info($"Number:{mn.Number}", "ModNews"); + Info($"Title:{mn.Title}", "ModNews"); + Info($"SubTitle:{mn.SubTitle}", "ModNews"); + Info($"ShortTitle:{mn.ShortTitle}", "ModNews"); + Info($"Date:{mn.Date}", "ModNews"); + return mn; + } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/AprilFoolsModePatch.cs b/FinalSuspect/Patches/System/AprilFoolsModePatch.cs index c6fc854d..0a75d0f0 100644 --- a/FinalSuspect/Patches/System/AprilFoolsModePatch.cs +++ b/FinalSuspect/Patches/System/AprilFoolsModePatch.cs @@ -1,37 +1,30 @@ -using FinalSuspect.Modules.Core.Game; -using Il2CppSystem; +using Il2CppSystem; namespace FinalSuspect.Patches.System; // 参考:https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/HorseModePatch.cs // 来源:Town Of Host : Enhanced -[HarmonyPatch(typeof(AprilFoolsMode), nameof(AprilFoolsMode.ShouldLongAround))] -public static class AprilFoolsModePatch -{ - public static bool Prefix(ref bool __result) - { - __result = Main.ChangeOutfit.Value == Main.OutfitType[2]; - return false; - } -} #region GameManager Patches + [HarmonyPatch(typeof(NormalGameManager), nameof(NormalGameManager.GetBodyType))] public static class GetNormalBodyType_Patch { public static void Postfix(ref PlayerBodyTypes __result) { - //if (Main.ChangeOutfit.Value == Main.changeOutfit[1]) - //{ - // __result = PlayerBodyTypes.Horse; - // return; - //} - if (AprilFoolsMode.ShouldLongAround()) + switch (Main.SwitchOutfitType.Value) { - __result = PlayerBodyTypes.Long; - return; + case OutfitType.HorseMode: + __result = PlayerBodyTypes.Horse; + return; + case OutfitType.LongMode: + __result = PlayerBodyTypes.Long; + return; + case OutfitType.BeanMode: + default: + __result = PlayerBodyTypes.Normal; + break; } - __result = PlayerBodyTypes.Normal; } } @@ -41,100 +34,106 @@ public static class GetHnsBodyType_Patch public static void Postfix(ref PlayerBodyTypes __result, [HarmonyArgument(0)] PlayerControl player) { if (player == null || player.Data == null || player.Data.Role == null) - { - //if (Main.ChangeOutfit.Value == Main.changeOutfit[1]) - //{ - // __result = PlayerBodyTypes.Horse; - // return; - //} - if (AprilFoolsMode.ShouldLongAround()) + switch (Main.SwitchOutfitType.Value) { - __result = PlayerBodyTypes.Long; - return; + case OutfitType.HorseMode: + __result = PlayerBodyTypes.Horse; + return; + case OutfitType.LongMode: + __result = PlayerBodyTypes.Long; + return; + case OutfitType.BeanMode: + default: + __result = PlayerBodyTypes.Normal; + return; } - __result = PlayerBodyTypes.Normal; - return; - } - //else if (Main.ChangeOutfit.Value == Main.changeOutfit[1]) - //{ - // if (player.IsImpostor()) - // { - // __result = PlayerBodyTypes.Normal; - // return; - // } - // __result = PlayerBodyTypes.Horse; - // return; - //} - - if (AprilFoolsMode.ShouldLongAround()) + + switch (Main.SwitchOutfitType.Value) { - if (player.IsImpostor()) - { + case OutfitType.HorseMode when player.Data.Role.IsImpostor: + __result = PlayerBodyTypes.Normal; + return; + case OutfitType.HorseMode: + __result = PlayerBodyTypes.Horse; + return; + case OutfitType.LongMode when player.Data.Role.IsImpostor: __result = PlayerBodyTypes.LongSeeker; return; - } - __result = PlayerBodyTypes.Long; - return; - } + case OutfitType.LongMode: + __result = PlayerBodyTypes.Long; + return; + case OutfitType.BeanMode: + default: + { + if (player.Data.Role.IsImpostor) + { + __result = PlayerBodyTypes.Seeker; + return; + } - if (player.IsImpostor()) - { - __result = PlayerBodyTypes.Seeker; - return; + __result = PlayerBodyTypes.Normal; + return; + } } - __result = PlayerBodyTypes.Normal; - return; } } + #endregion #region LongBoi Patches + [HarmonyPatch(typeof(LongBoiPlayerBody))] public static class LongBoiPatches { [HarmonyPatch(nameof(LongBoiPlayerBody.Awake))] [HarmonyPrefix] - public static bool LongBoyAwake_Patch(LongBoiPlayerBody __instance) + public static bool LongBoyAwake_Prefix(LongBoiPlayerBody __instance) { //Fixes base-game layer issues __instance.cosmeticLayer.OnSetBodyAsGhost += (Action)__instance.SetPoolableGhost; __instance.cosmeticLayer.OnColorChange += (Action)__instance.SetHeightFromColor; - __instance.cosmeticLayer.OnCosmeticSet += (Action)__instance.OnCosmeticSet; + __instance.cosmeticLayer.OnCosmeticSet += + (Action)__instance.OnCosmeticSet; __instance.gameObject.layer = 8; return false; } [HarmonyPatch(nameof(LongBoiPlayerBody.Start))] [HarmonyPrefix] - public static bool LongBoyStart_Patch(LongBoiPlayerBody __instance) + public static bool LongBoyStart_Prefix(LongBoiPlayerBody __instance) { //Fixes more runtime issues __instance.ShouldLongAround = true; - if (__instance.hideCosmeticsQC) - { - __instance.cosmeticLayer.SetHatVisorVisible(false); - } + if (__instance.hideCosmeticsQC) __instance.cosmeticLayer.SetHatVisorVisible(false); + __instance.SetupNeckGrowth(); if (__instance.isExiledPlayer) { var instance = ShipStatus.Instance; if (instance == null || instance.Type != ShipStatus.MapType.Fungle) - { __instance.cosmeticLayer.AdjustCosmeticRotations(-17.75f); - } - } - if (!__instance.isPoolablePlayer) - { - __instance.cosmeticLayer.ValidateCosmetics(); } + + if (!__instance.isPoolablePlayer) __instance.cosmeticLayer.ValidateCosmetics(); + return false; } + // Fix System.IndexOutOfRangeException: Index was outside the bounds of the array + // When colorIndex is 255 them heightsPerColor[255] gets exception + [HarmonyPatch(nameof(LongBoiPlayerBody.SetHeightFromColor))] + [HarmonyPrefix] + public static bool SetHeightFromColor_Prefix(int colorIndex) + { + return colorIndex != byte.MaxValue; + } + [HarmonyPatch(nameof(LongBoiPlayerBody.SetHeighFromDistanceHnS))] [HarmonyPrefix] - public static bool LongBoyNeckSize_Patch(LongBoiPlayerBody __instance, ref float distance) + public static bool LongBoyNeckSize_Prefix(LongBoiPlayerBody __instance, ref float distance) { - //Remove the limit of neck size to prevent issues in FinalSuspectE HnS + //Remove the limit of neck size to prevent issues in TOHE HnS + __instance.targetHeight = distance / 10f + 0.5f; __instance.SetupNeckGrowth(true); //se quiser sim mano return false; @@ -142,13 +141,13 @@ public static bool LongBoyNeckSize_Patch(LongBoiPlayerBody __instance, ref float [HarmonyPatch(typeof(HatManager), nameof(HatManager.CheckLongModeValidCosmetic))] [HarmonyPrefix] - public static bool CheckLongMode_Patch(out bool __result, ref string cosmeticID) + public static bool CheckLongMode_Prefix(out bool __result, ref string cosmeticID) { - //if (Main.ChangeOutfit.Value == Main.changeOutfit[1]) - //{ - // __result = false; - // return false; - //} + if (AprilFoolsMode.ShouldHorseAround()) + { + __result = true; + return false; + } var flag = AprilFoolsMode.ShouldLongAround(); @@ -157,8 +156,10 @@ public static bool CheckLongMode_Patch(out bool __result, ref string cosmeticID) __result = false; return false; } + __result = true; return false; } } + #endregion \ No newline at end of file diff --git a/FinalSuspect/Patches/System/ClientOptionsPatch.cs b/FinalSuspect/Patches/System/ClientOptionsPatch.cs deleted file mode 100644 index f9a3688f..00000000 --- a/FinalSuspect/Patches/System/ClientOptionsPatch.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System; -using System.IO; -using BepInEx.Configuration; -using FinalSuspect.Helpers; -using FinalSuspect.Modules.ClientOptions; -using FinalSuspect.Modules.Panels; -using FinalSuspect.Modules.SoundInterface; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace FinalSuspect.Patches.System; - -[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))] -public static class OptionsMenuBehaviourStartPatch -{ - private static ClientOptionItem_Boolean UnlockFPS; - private static ClientOptionItem_String ChangeOutfit; - private static ClientOptionItem_Boolean KickPlayerFriendCodeNotExist; - private static ClientOptionItem_Boolean KickPlayerWithDenyName; - private static ClientOptionItem_Boolean KickPlayerInBanList; - private static ClientOptionItem_Boolean SpamDenyWord; - private static ClientOptionItem_Boolean AutoStartGame; - private static ClientOptionItem_Boolean AutoEndGame; - //private static ClientOptionItem_Boolean PrunkMode; - private static ClientOptionItem_Boolean DisableVanillaSound; - private static ClientOptionItem_Boolean DisableFAC; - private static ClientOptionItem_Boolean ShowPlayerInfo; - private static ClientOptionItem_Boolean UseModCursor; - private static ClientOptionItem_Boolean FastBoot; - private static ClientFeatureItem ClearAutoLogs; - private static ClientFeatureItem DumpLog; - private static ClientFeatureItem UnloadMod; - private static ClientOptionItem_Boolean VersionCheat; - private static ClientOptionItem_Boolean GodMode; - private static ClientOptionItem_Boolean NoGameEnd; - - public static ClientFeatureItem SoundBtn; - public static ClientFeatureItem AudioManagementBtn; - public static OptionsMenuBehaviour Instance { get; private set; } - private static bool reseted; - public static bool recreate; - - public static void Postfix(OptionsMenuBehaviour __instance) - { - if (__instance.DisableMouseMovement == null) return; - Instance = __instance; - - if (!reseted || !DebugModeManager.AmDebugger) - { - reseted = true; - Main.VersionCheat.Value = false; - Main.GodMode.Value = false; - Main.NoGameEnd.Value = false; - } - - if (recreate) - { - ClientActionItem.ModOptionsButton.gameObject.SetActive(false); - Object.Destroy(ClientActionItem.ModOptionsButton); - Object.Destroy(ClientActionItem.CustomBackground); - ClientFeatureItem.ModOptionsButton.gameObject.SetActive(false); - Object.Destroy(ClientFeatureItem.ModOptionsButton); - Object.Destroy(ClientFeatureItem.CustomBackground); - - Object.Destroy(ModUnloaderScreen.Popup); - Object.Destroy(MyMusicPanel.CustomBackground); - Object.Destroy(SoundManagementPanel.CustomBackground); - ClientActionItem.ModOptionsButton = null; - ClientActionItem.CustomBackground = null; - - ClientFeatureItem.ModOptionsButton = null; - ClientFeatureItem.CustomBackground = null; - - ModUnloaderScreen.Popup = null; - MyMusicPanel.CustomBackground = null; - SoundManagementPanel.CustomBackground = null; - } - CreateOptionItem(ref UnlockFPS, "UnlockFPS", Main.UnlockFPS, __instance, UnlockFPSButtonToggle); - CreateOptionItem(ref ChangeOutfit, "ChangeOutfit", Main.ChangeOutfit, __instance, Main.OutfitType, SwitchHorseMode); - CreateOptionItem(ref KickPlayerFriendCodeNotExist, "KickPlayerFriendCodeNotExist", Main.KickPlayerWhoFriendCodeNotExist, __instance); - CreateOptionItem(ref KickPlayerInBanList, "KickPlayerInBanList", Main.KickPlayerInBanList, __instance); - CreateOptionItem(ref KickPlayerWithDenyName, "KickPlayerWithDenyName", Main.KickPlayerWithDenyName, __instance); - CreateOptionItem(ref SpamDenyWord, "SpamDenyWord", Main.SpamDenyWord, __instance); - CreateOptionItem(ref AutoStartGame, "AutoStartGame", Main.AutoStartGame, __instance, AutoStartButtonToggle); - CreateOptionItem(ref AutoEndGame, "AutoEndGame", Main.AutoEndGame, __instance); - //CreateOptionItem(ref PrunkMode, "PrunkMode", Main.PrunkMode, __instance); - CreateOptionItem(ref DisableVanillaSound, "DisableVanillaSound", Main.DisableVanillaSound, __instance, () => { - if (Main.DisableVanillaSound.Value) - CustomSoundsManager.StopPlayVanilla(); - else - { - CustomSoundsManager.StartPlayVanilla(); - } - }); - CreateOptionItem(ref DisableFAC, "DisableFAC", Main.DisableFAC, __instance); - CreateOptionItem(ref ShowPlayerInfo, "ShowPlayerInfo", Main.ShowPlayerInfo, __instance); - CreateOptionItem(ref UseModCursor, "UseModCursor", Main.UseModCursor, __instance, SetCursor); - CreateOptionItem(ref FastBoot, "FastBoot", Main.FastBoot, __instance); - if (DebugModeManager.AmDebugger) - { - CreateOptionItem(ref VersionCheat, "VersionCheat", Main.VersionCheat, __instance); - CreateOptionItem(ref GodMode, "GodMode", Main.GodMode, __instance); - CreateOptionItem(ref NoGameEnd, "NoGameEnd", Main.NoGameEnd, __instance); - } - - CreateFeatureItem(ref DumpLog, "DumpLog", () => - { - DumpLog(); - }, __instance); - CreateFeatureItem(ref ClearAutoLogs, "ClearAutoLogs", () => - { - ClearAutoLogs(); - SetFeatureItemDisabled(ClearAutoLogs); - }, __instance); - CreateFeatureItem(ref UnloadMod, "UnloadMod", ModUnloaderScreen.Show, __instance); - - CreateFeatureItem(ref SoundBtn, "SoundOption", () => - { - MyMusicPanel.CustomBackground?.gameObject.SetActive(true); - }, __instance); - CreateFeatureItem(ref AudioManagementBtn, "SoundManager", () => - { - SoundManagementPanel.CustomBackground?.gameObject.SetActive(true); - }, __instance); - - SetFeatureItemTextAndColor(SoundBtn, "SoundOptions"); - SetFeatureItemTextAndColor(AudioManagementBtn, "AudioManagementOptions"); - - if (!IsNotJoined) - { - SetOptionItemDisabled_Menu(ChangeOutfit); - SetFeatureItemDisabled_Menu(AudioManagementBtn); - } - - if (Directory.GetFiles(GetLogFolder(true).FullName + "/Final Suspect-logs").Length <= 0) - { - SetFeatureItemDisabled(ClearAutoLogs); - } - - Modules.SoundInterface.SoundManager.ReloadTag(); - MyMusicPanel.Init(__instance); - SoundManagementPanel.Init(__instance); - - if (ModUnloaderScreen.Popup == null) - ModUnloaderScreen.Init(__instance); - recreate = false; - } - - private static void CreateOptionItem(ref ClientOptionItem_Boolean item, string name, ConfigEntry value, OptionsMenuBehaviour instance, Action toggleAction = null) - { - if (recreate) - { - Object.Destroy(item.ToggleButton.gameObject); - item = null; - } - if (item == null || item.ToggleButton == null) - { - item = ClientOptionItem_Boolean.Create(name, value, instance, toggleAction); - } - } - - private static void CreateOptionItem(ref ClientOptionItem_String item, string name, ConfigEntry value, OptionsMenuBehaviour instance, string[] options, Action toggleAction = null) - { - if (recreate) - { - Object.Destroy(item.ToggleButton.gameObject); - item = null; - } - if (item == null || item.ToggleButton == null) - { - item = ClientOptionItem_String.Create(name,value.Value ?? options[0], value, instance, options, toggleAction); - } - } - - private static void CreateActionItem(ref ClientActionItem item, string name, Action action, OptionsMenuBehaviour instance) - { - if (recreate) - { - Object.Destroy(item.ToggleButton.gameObject); - item = null; - } - - if (item == null || item.ToggleButton == null) - { - item = ClientActionItem.Create(name, action, instance); - } - } - - private static void CreateFeatureItem(ref ClientFeatureItem item, string name, Action action, OptionsMenuBehaviour instance) - { - if (recreate) - { - Object.Destroy(item.ToggleButton.gameObject); - item = null; - } - if (item == null || item.ToggleButton == null) - { - item = ClientFeatureItem.Create(name, action, instance); - } - } - - private static void SetFeatureItemTextAndColor(ClientFeatureItem item, string text) - { - item.ToggleButton.Text.text = GetString(text); - item.ToggleButton.GetComponent().enabled = true; - item.ToggleButton.Background.color = ColorHelper.ClientFeatureColor; - } - - private static void SetOptionItemDisabled(ClientOptionItem_Boolean item) - { - item.ToggleButton.Text.text += $"\n|{GetString("OnlyAvailableInMainMenu")}|"; - item.ToggleButton.GetComponent().enabled = false; - item.ToggleButton.Background.color = ColorHelper.ClientOptionColor_CanNotUse; - } - private static void SetOptionItemDisabled_Menu(ClientOptionItem_String item) - { - item.ToggleButton.Text.text = GetString(item.Name) + $"\n|{GetString("OnlyAvailableInMainMenu")}|"; - item.ToggleButton.GetComponent().enabled = false; - item.ToggleButton.Background.color = ColorHelper.ClientOptionColor_CanNotUse; - } - private static void SetFeatureItemDisabled_Menu(ClientFeatureItem item) - { - item.ToggleButton.Text.text += $"\n|{GetString("OnlyAvailableInMainMenu")}|"; - SetFeatureItemDisabled(item); - } - - private static void SetFeatureItemDisabled(ClientFeatureItem item) - { - item.ToggleButton.GetComponent().enabled = false; - item.ToggleButton.Background.color = ColorHelper.ClientFeatureColor_CanNotUse; - } - - private static void SetFeatureItemEnable(ClientFeatureItem item) - { - item.ToggleButton.GetComponent().enabled = true; - item.ToggleButton.Background.color = ColorHelper.ClientFeatureColor; - } - private static void UnlockFPSButtonToggle() - { - Application.targetFrameRate = Main.UnlockFPS.Value ? 165 : 60; - SendInGame(string.Format(GetString("FPSSetTo"), Application.targetFrameRate)); - } - - private static void SwitchHorseMode() - { - ChangeOutfit.UpdateToggle(Main.OutfitType); - //if (Main.ChangeOutfit.Value == Main.changeOutfit[1]) - //foreach (var pc in PlayerControl.AllPlayerControls) - //{ - // pc.MyPhysics.SetBodyType(pc.BodyType); - // if (pc.BodyType == PlayerBodyTypes.Normal) - // { - // pc.cosmetics.currentBodySprite.BodySprite.transform.localScale = new(0.5f, 0.5f, 1f); - // } - //} - } - - private static void AutoStartButtonToggle() - { - if (Main.AutoStartGame.Value == false && IsCountDown) - { - GameStartManager.Instance.ResetStartState(); - } - } - - public static void SetCursor() - { - try - { - var sprite = LoadSprite("Cursor.png"); - Cursor.SetCursor(Main.UseModCursor.Value ? sprite.texture: null, Vector2.zero, CursorMode.Auto); - } - catch - { - Main.UseModCursor.Value = false; - } - } -} - -[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] -public static class OptionsMenuBehaviourClosePatch -{ - public static void Postfix() - { - ClientActionItem.CustomBackground?.gameObject.SetActive(false); - ClientFeatureItem.CustomBackground?.gameObject.SetActive(false); - ModUnloaderScreen.Hide(); - MyMusicPanel.Hide(); - SoundManagementPanel.Hide(); - } -} - -[HarmonyPatch(typeof(LanguageSetter), nameof(LanguageSetter.SetLanguage))] -public static class LanguageSetterSetLanguagePatch -{ - public static void Postfix() - { - OptionsMenuBehaviourStartPatch.recreate = true; - try - { - Object.Destroy(VersionShowerStartPatch.VisitText); - } - catch - { - /* ignored */ - } - - VersionShowerStartPatch.VisitText = null; - VersionShowerStartPatch.CreateVisitText(null); - OptionsMenuBehaviourStartPatch.Postfix(OptionsMenuBehaviourStartPatch.Instance); - } -} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/ClientPatch.cs b/FinalSuspect/Patches/System/ClientPatch.cs index bc1dc4ef..5ef3d83e 100644 --- a/FinalSuspect/Patches/System/ClientPatch.cs +++ b/FinalSuspect/Patches/System/ClientPatch.cs @@ -1,6 +1,6 @@ using System; -using System.Linq; using FinalSuspect.Helpers; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Modules.Features.CheckingandBlocking; using FinalSuspect.Modules.Resources; using FinalSuspect.Patches.Game_Vanilla; @@ -14,50 +14,42 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.MakePublic))] internal class MakePublicPatch { - public static bool Prefix(GameStartManager __instance) + public static bool Prefix() { - if (VersionChecker.isBroken || (VersionChecker.hasUpdate && VersionChecker.forceUpdate) || !VersionChecker.IsSupported ) - { - var message = GetString("PublicNotAvailableOnThisVersion"); - if (VersionChecker.isBroken) message = GetString("ModBrokenMessage"); - if (VersionChecker.hasUpdate) message = GetString("CanNotJoinPublicRoomNoLatest"); - Info(message, "MakePublicPatch"); - SendInGame(message); - return false; - } - return true; + if (Main.OfflineMode.Value) return true; + if (!VersionChecker.IsBroken && (!VersionChecker.HasUpdate || !VersionChecker.ForceUpdate) && + VersionChecker.IsSupported) return true; + var message = ""; + if (VersionChecker.IsBroken) message = GetString("ModBrokenMessage"); + if (VersionChecker.HasUpdate) message = GetString("CanNotJoinPublicRoomNoLatest"); + Info(message, "MakePublicPatch"); + SendInGame(message); + return false; } } + [HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Start))] -class MMOnlineManagerStartPatch +internal class MMOnlineManagerStartPatch { - public static void Postfix(MMOnlineManager __instance) + public static void Postfix() { - if (!(VersionChecker.hasUpdate || VersionChecker.isBroken || !VersionChecker.IsSupported)) return; + if (!(VersionChecker.HasUpdate || VersionChecker.IsBroken || !VersionChecker.IsSupported)) return; var obj = GameObject.Find("FindGameButton"); - if (obj) - { - obj.SetActive(false); - _ = obj.transform.parent.gameObject; - var textObj = Object.Instantiate(obj.transform.FindChild("Text_TMP").GetComponent()); - textObj.transform.position = new Vector3(0.5f, -0.4f, 0f); - textObj.name = "CanNotJoinPublic"; - textObj.DestroyTranslator(); - var message = ""; - if (VersionChecker.hasUpdate) - { - message = GetString("CanNotJoinPublicRoomNoLatest"); - } - else if (VersionChecker.isBroken) - { - message = GetString("ModBrokenMessage"); - } - else if (!VersionChecker.IsSupported) - { - message = GetString("UnsupportedVersion"); - } - textObj.text = $"{StringHelper.ColorString(Color.red, message)}"; - } + if (!obj) return; + obj.SetActive(false); + _ = obj.transform.parent.gameObject; + var textObj = Object.Instantiate(obj.transform.FindChild("Text_TMP").GetComponent()); + textObj.transform.position = new Vector3(0.5f, -0.4f, 0f); + textObj.name = "CanNotJoinPublic"; + textObj.DestroyTranslator(); + var message = ""; + if (VersionChecker.HasUpdate) + message = GetString("CanNotJoinPublicRoomNoLatest"); + else if (VersionChecker.IsBroken) + message = GetString("ModBrokenMessage"); + else if (!VersionChecker.IsSupported) message = GetString("UnsupportedVersion"); + + textObj.text = $"{StringHelper.ColorString(Color.red, message)}"; } } @@ -75,7 +67,7 @@ public static void Prefix(ref bool canOnline) // 如果您修改了代码,请在房间公告内表明这是修改版本,并给出修改作者 // If you wish to make your lobby public in a debug build, please use it only for testing purposes // If you modify the code, please indicate in the lobby announcement that this is a modified version and provide the author of the modification - canOnline = Environment.UserName is "Slok7" or "Administrator"; + canOnline = Environment.UserName is "Slok7" or "LezaiYa"; #endif } } @@ -86,7 +78,7 @@ internal class BanMenuSetVisiblePatch public static bool Prefix(BanMenu __instance, bool show) { if (!AmongUsClient.Instance.AmHost) return true; - show &= PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data != null; + show &= PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data; __instance.BanButton.gameObject.SetActive(AmongUsClient.Instance.CanBan()); __instance.KickButton.gameObject.SetActive(AmongUsClient.Instance.CanKick()); __instance.MenuButton.gameObject.SetActive(show); @@ -107,28 +99,32 @@ public static bool Prefix(InnerNetClient __instance, ref bool __result) [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.KickPlayer))] internal class KickPlayerPatch { - public static bool Prefix(InnerNetClient __instance, int clientId, bool ban) + public static bool Prefix(int clientId, bool ban) { try { - if (Main.AllPlayerControls.Where(p => p.IsDev()).Any(p => AmongUsClient.Instance.GetRecentClient(clientId).FriendCode == p.FriendCode)) + if (clientId == AmongUsClient.Instance.HostId) return false; + if (Main.AllPlayerControls.Where(p => p.IsDev()).Any(p => + AmongUsClient.Instance.GetRecentClient(clientId).FriendCode == p.FriendCode)) { SendInGame(GetString("Warning.CantKickDev")); return false; } - if (!OnPlayerLeftPatch.ClientsProcessed.Contains(clientId)) + if (OnPlayerLeftPatch.ClientsProcessed.Contains(clientId)) return true; + OnPlayerLeftPatch.Add(clientId); + var color = Palette.PlayerColors[AmongUsClient.Instance.GetRecentClient(clientId).ColorId]; + var name = AmongUsClient.Instance.GetRecentClient(clientId).PlayerName; + if (ban) + { + BanManager.AddBanPlayer(AmongUsClient.Instance.GetRecentClient(clientId)); + NotificationPopperPatch.NotificationPop(string.Format(GetString("Notification.PlayerBanByHost"), + StringHelper.ColorString(color, name))); + } + else { - OnPlayerLeftPatch.Add(clientId); - if (ban) - { - BanManager.AddBanPlayer(AmongUsClient.Instance.GetRecentClient(clientId)); - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerBanByHost"), AmongUsClient.Instance.GetRecentClient(clientId).PlayerName)); - } - else - { - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerKickByHost"), AmongUsClient.Instance.GetRecentClient(clientId).PlayerName)); - } + NotificationPopperPatch.NotificationPop(string.Format(GetString("Notification.PlayerKickByHost"), + StringHelper.ColorString(color, name))); } } catch diff --git a/FinalSuspect/Patches/System/ColorPatch.cs b/FinalSuspect/Patches/System/ColorPatch.cs index 91179ebd..91cb2d0f 100644 --- a/FinalSuspect/Patches/System/ColorPatch.cs +++ b/FinalSuspect/Patches/System/ColorPatch.cs @@ -4,26 +4,31 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(ButtonRolloverHandler))] -class ButtonRolloverHandlerPatch +internal class ButtonRolloverHandlerPatch { - [HarmonyPatch(nameof(ButtonRolloverHandler.DoMouseOver)), HarmonyPrefix] + [HarmonyPatch(nameof(ButtonRolloverHandler.DoMouseOver))] + [HarmonyPrefix] public static void DoMouseOver_Prefix(ButtonRolloverHandler __instance) { if (__instance.OverColor == new Color(0, 1, 0, 1) || __instance.OverColor == Palette.AcceptedGreen) - __instance.OverColor = ColorHelper.ModColor32; + __instance.OverColor = ColorHelper.FSColor; } - [HarmonyPatch(nameof(ButtonRolloverHandler.ChangeOutColor)), HarmonyPrefix] + + [HarmonyPatch(nameof(ButtonRolloverHandler.ChangeOutColor))] + [HarmonyPrefix] public static void ChangeOutColor_Prefix(ButtonRolloverHandler __instance, ref Color color) { - if (color.r == 0 && Mathf.Approximately(color.g, 1) && color.b is > 0.163f and < 0.165f && Mathf.Approximately(color.a, 1)) - color = ColorHelper.ClientOptionColor; + if (color.r == 0 && Mathf.Approximately(color.g, 1) && color.b is > 0.163f and < 0.165f && + Mathf.Approximately(color.a, 1)) + color = ColorHelper.FSClientOptionColor; } } [HarmonyPatch(typeof(Palette))] -class PalettePath +internal class PalettePath { - [HarmonyPatch(nameof(Palette.AcceptedGreen), MethodType.Getter), HarmonyPrefix] + [HarmonyPatch(nameof(Palette.AcceptedGreen), MethodType.Getter)] + [HarmonyPrefix] public static bool Get_AcceptedGreen_Prefix(ref Color __result) { __result = new Color32(180, 179, 231, (byte)(__result.a * 255)); diff --git a/FinalSuspect/Patches/System/ConstantsPatch.cs b/FinalSuspect/Patches/System/ConstantsPatch.cs index c0fbdb8e..444acfc3 100644 --- a/FinalSuspect/Patches/System/ConstantsPatch.cs +++ b/FinalSuspect/Patches/System/ConstantsPatch.cs @@ -5,13 +5,7 @@ public class ConstantsPatch { public static void Postfix(ref int __result) { - if (IsLocalGame) - { - Info($"IsLocalGame: {__result}", "VersionServer"); - } - if (IsOnlineGame) - { - Info($"IsOnlineGame: {__result}", "VersionServer"); - } + if (IsLocalGame) Info($"IsLocalGame: {__result}", "VersionServer"); + if (IsOnlineGame) Info($"IsOnlineGame: {__result}", "VersionServer"); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/ControlPatch.cs b/FinalSuspect/Patches/System/ControlPatch.cs index 0473267f..9d259fb2 100644 --- a/FinalSuspect/Patches/System/ControlPatch.cs +++ b/FinalSuspect/Patches/System/ControlPatch.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using FinalSuspect.Modules.Features; using UnityEngine; @@ -8,29 +7,43 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(ControllerManager), nameof(ControllerManager.Update))] internal class ControllerManagerUpdatePatch { - private static readonly (int, int)[] resolutions = [(480, 270), (640, 360), (800, 450), (1280, 720), (1600, 900), (1920, 1080)]; + private static readonly (int, int)[] resolutions = + [(480, 270), (640, 360), (800, 450), (1280, 720), (1600, 900), (1920, 1080)]; + private static int resolutionIndex; - public static void Postfix(ControllerManager __instance) + public static bool ShowSettingsPanel = true; + public static bool ShowHudUI = true; + + public static void Postfix() { //职业介绍 - if (IsInGame && (IsCanMove || IsMeeting)) + if (IsInGame && (IsCanMove || IsInMeeting) && Input.GetKey(KeyCode.F1)) { - if (Input.GetKey(KeyCode.F1)) - { - if (!InGameRoleInfoMenu.Showing) - InGameRoleInfoMenu.SetRoleInfoRef(PlayerControl.LocalPlayer); - InGameRoleInfoMenu.Show(); - } - else InGameRoleInfoMenu.Hide(); + if (!InGameRoleInfoMenu.Showing) + InGameRoleInfoMenu.SetRoleInfoRef(PlayerControl.LocalPlayer); + InGameRoleInfoMenu.Show(); + } + else + { + InGameRoleInfoMenu.Hide(); } - else InGameRoleInfoMenu.Hide(); + + if (Input.GetKeyDown(KeyCode.F2) && IsInGame) + ShowSettingsPanel = !ShowSettingsPanel; //更改分辨率 if (Input.GetKeyDown(KeyCode.F11)) { resolutionIndex++; if (resolutionIndex >= resolutions.Length) resolutionIndex = 0; - ResolutionManager.SetResolution(resolutions[resolutionIndex].Item1, resolutions[resolutionIndex].Item2, false); + ResolutionManager.SetResolution(resolutions[resolutionIndex].Item1, resolutions[resolutionIndex].Item2, + Screen.fullScreen); + } + + if (Input.GetKeyDown(KeyCode.F4)) + { + ResolutionManager.SetResolution(resolutions[resolutionIndex].Item1, resolutions[resolutionIndex].Item2, + !Screen.fullScreen); } //重新加载自定义翻译 @@ -40,27 +53,43 @@ public static void Postfix(ControllerManager __instance) LoadLangs(); SendInGame("Reloaded Custom Translation File"); } + if (GetKeysDown(KeyCode.F5, KeyCode.X)) { Info("导出自定义翻译文件", "KeyCommand"); ExportCustomTranslation(); SendInGame("Exported Custom Translation File"); } + //日志文件转储 if (GetKeysDown(KeyCode.F1, KeyCode.LeftControl)) { Info("输出日志", "KeyCommand"); DumpLog(); } + if (GetKeysDown(KeyCode.F1, KeyCode.RightControl)) { Info("输出日志", "KeyCommand"); DumpLog(); } + //打开游戏目录 - if (GetKeysDown(KeyCode.F10)) + if (GetKeysDown(KeyCode.F10)) OpenDirectory(Environment.CurrentDirectory); + + if (Input.GetKeyDown(KeyCode.Tab)) { - OpenDirectory(Environment.CurrentDirectory); + if (IsNotJoined) + { + VersionShowerStartPatch.ModLogo.SetActive(ModMainMenuManager.Active); + VersionShowerStartPatch.AuthorLogo.SetActive(ModMainMenuManager.Active); + ModMainMenuManager.Active = !ModMainMenuManager.Active; + ModMainMenuManager.Instance.mainMenuUI.SetActive(ModMainMenuManager.Active); + VersionShowerStartPatch.CreditTextCredential.gameObject.SetActive(ModMainMenuManager.Active); + VersionShowerStartPatch.VisitText.gameObject.SetActive(ModMainMenuManager.Active); + DestroyableSingleton.Instance.gameObject.SetActive(ModMainMenuManager.Active); + ModMainMenuManager.ModStamp.SetActive(ModMainMenuManager.Active); + } } //-- 下面是主机专用的命令--// @@ -70,6 +99,7 @@ public static void Postfix(ControllerManager __instance) { Info("倒计时修改为0", "KeyCommand"); GameStartManager.Instance.countDownTimer = 0; + SoundManager.Instance.StopSound(GameStartManager.Instance.gameStartSound); } //倒计时取消 @@ -85,11 +115,42 @@ public static void Postfix(ControllerManager __instance) isAlsoInGame = !isAlsoInGame; SendInGame($"游戏中输出日志:{isAlsoInGame}"); } + if (GetKeysDown(KeyCode.F2, KeyCode.RightControl)) { isAlsoInGame = !isAlsoInGame; SendInGame($"游戏中输出日志:{isAlsoInGame}"); } + + if (!DebugModeManager.IsDebugMode) return; + + //实名投票 + if (GetKeysDown(KeyCode.Return, KeyCode.V, KeyCode.LeftShift) && IsInMeeting && !IsOnlineGame) + MeetingHud.Instance.RpcClearVote(AmongUsClient.Instance.ClientId); + + //打开飞艇所有的门 + if (GetKeysDown(KeyCode.Return, KeyCode.D, KeyCode.LeftShift) && IsInGame) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 79); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 80); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 81); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 82); + } + + //将击杀冷却设定为0秒 + if (GetKeysDown(KeyCode.Return, KeyCode.K, KeyCode.LeftShift) && IsInGame) + PlayerControl.LocalPlayer.Data.Object.SetKillTimer(0f); + + //开场动画测试 + if (Input.GetKeyDown(KeyCode.G)) + { + HudManager.Instance.StartCoroutine(HudManager.Instance.CoFadeFullScreen(Color.clear, Color.black)); + HudManager.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); + } + + //获取现在的坐标 + if (Input.GetKeyDown(KeyCode.I)) + Info(PlayerControl.LocalPlayer.GetTruePosition().ToString(), "GetLocalPlayerPos"); } private static bool GetKeysDown(params KeyCode[] keys) @@ -99,8 +160,9 @@ private static bool GetKeysDown(params KeyCode[] keys) Info($"快捷键:{keys.Where(Input.GetKeyDown).First()} in [{string.Join(",", keys)}]", "GetKeysDown"); return true; } + return false; } - private static bool ORGetKeysDown(params KeyCode[] keys) => keys.Any(Input.GetKeyDown); + // private static bool ORGetKeysDown(params KeyCode[] keys) => keys.Any(Input.GetKeyDown); } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/CredentialsPatch.cs b/FinalSuspect/Patches/System/CredentialsPatch.cs index 97539d60..c8eed24e 100644 --- a/FinalSuspect/Patches/System/CredentialsPatch.cs +++ b/FinalSuspect/Patches/System/CredentialsPatch.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; using System.Text; +using FinalSuspect.ClientActions; +using FinalSuspect.ClientActions.FeatureItems.MainMenuStyle; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; using FinalSuspect.Helpers; using FinalSuspect.Modules.Resources; using FinalSuspect.Patches.Game_Vanilla; @@ -8,7 +10,9 @@ using TMPro; using UnityEngine; using UnityEngine.Events; +using UnityEngine.SceneManagement; using UnityEngine.UI; +using static FinalSuspect.Modules.Core.Plugin.ModMainMenuManager; using ColorHelper = FinalSuspect.Helpers.ColorHelper; using Object = UnityEngine.Object; @@ -24,36 +28,35 @@ internal class PingTrackerUpdatePatch public static void Postfix(PingTracker __instance) { - if (CreditTextCredential == null) + if (!CreditTextCredential) { var uselessPingTracker = Object.Instantiate(__instance, __instance.transform.parent); CreditTextCredential = uselessPingTracker.GetComponent(); Object.Destroy(uselessPingTracker); CreditTextCredential.alignment = TextAlignmentOptions.TopRight; CreditTextCredential.color = new Color(1f, 1f, 1f, 0.7f); - CreditTextCredential.rectTransform.pivot = new Vector2(1f, 1f); // 将中心点设定在右上角 + CreditTextCredential.rectTransform.pivot = new Vector2(1f, 1f); // 将中心点设定在右上角 CreditTextCredentialAspectPos = CreditTextCredential.GetComponent(); CreditTextCredentialAspectPos.Alignment = AspectPosition.EdgeAlignments.RightTop; } - + if (CreditTextCredentialAspectPos) - { - CreditTextCredentialAspectPos.DistanceFromEdge = - DestroyableSingleton.InstanceExists && DestroyableSingleton.Instance.Chat.chatButton.gameObject.active - ? new Vector3(2.5f, 0f, -800f) + CreditTextCredentialAspectPos.DistanceFromEdge = + DestroyableSingleton.InstanceExists && + DestroyableSingleton.Instance.Chat.chatButton.gameObject.active + ? new Vector3(2.5f, 0f, -800f) : new Vector3(1.8f, 0f, -800f); - } StringBuilder sb = new(); - + sb.Append(Main.CredentialsText); CreditTextCredential.text = sb.ToString(); if ( - (GameSettingMenu.Instance?.gameObject.active ?? false) - || IsMeeting + (GameSettingMenu.Instance?.gameObject.active ?? false) + || IsInMeeting || (FriendsListUI.Instance?.gameObject.active ?? false) - || (HudManagerPatch.showHideButton?.Button?.gameObject.active ?? false) && Main.ShowResults.Value) + || ((HudManagerPatch.showHideButton?.Button?.gameObject.active ?? false) && Main.ShowResults.Value)) CreditTextCredential.text = ""; var ping = AmongUsClient.Instance.Ping; @@ -70,8 +73,8 @@ public static void Postfix(PingTracker __instance) var fps = Mathf.Ceil(1.0f / deltaTime); __instance.text.alignment = TextAlignmentOptions.TopGeoAligned; - __instance.text.text = - $"{GetString("Ping")}:{ping} ms" + " " + __instance.text.text = + $"{GetString("Ping")}:{ping} ms" + " " + $"{GetString("FrameRate")}:{fps} FPS" + $"{" ◈" + (IsOnlineGame ? ServerName : GetString("Local"))}"; } @@ -84,76 +87,106 @@ public class VersionShowerStartPatch public static TextMeshPro VisitText; public static TextMeshPro CreditTextCredential; public static GameObject ModLogo; - public static GameObject TeamLogo; + public static GameObject AuthorLogo; + + private static VersionShower Instance; public static void Postfix(VersionShower __instance) { TMPTemplate.SetBase(__instance.text); - Main.CredentialsText = $"\r\n" + - $"== {Main.ModName} ==" - + ""; - Main.CredentialsText += "\r\n By XtremeWave"; + Main.CredentialsText = + $"\r\n" + + $"== " + + $"{Main.ModName} " + + $"==" + + ""; + Main.CredentialsText += "\r\n By Slok"; Main.CredentialsText += $"\r\nv{Main.DisplayedVersion}"; - -#if RELEASE +#if !DEBUG var additionalCredentials = GetString("TextBelowVersionText"); if (additionalCredentials != null && additionalCredentials != "*" && additionalCredentials != "") { Main.CredentialsText += $"\r\n{additionalCredentials}"; } -#else - Main.CredentialsText += $"\r\n{Main.GitBranch} - {Main.GitCommit}"; #endif +#if !RELEASE + Main.CredentialsText += $"\r\n{Main.GitBranch} - {Main.GitCommit}"; +#endif + + if (Main.IsAprilFools) + { + Main.CredentialsText = + $"\r\n" + + $"== " + + $"Feline Susspekt " + + $"==" + + ""; + Main.CredentialsText += "\r\n By XtremeWives"; + Main.CredentialsText += "\r\n 4.1.Never Gonna Give You Up"; + } ErrorText.Create(__instance.text); - if (Main.hasArgumentException && ErrorText.Instance != null) + if (Main.hasArgumentException && ErrorText.Instance) ErrorText.Instance.AddError(ErrorCode.Main_DictionaryError); - if ((OVersionShower = GameObject.Find("VersionShower")) != null && VisitText == null) - { - CreateVisitText(__instance); - } + if ((OVersionShower = GameObject.Find("VersionShower")) && !VisitText) CreateVisitText(__instance); - if ((OVersionShower = GameObject.Find("VersionShower")) != null && CreditTextCredential == null) + if ((OVersionShower = GameObject.Find("VersionShower")) && !CreditTextCredential) { - var credentialsText = string.Format(GetString("MainMenuCredential"), $"XtremeWave"); + var credentialsText = string.Format(GetString("MainMenuCredential"), + $"Slok"); credentialsText += "\n"; - var versionText = $"FS - v{Main.DisplayedVersion}"; - -#if !RELEASE - versionText = $"{Main.GitBranch} - {Main.GitCommit}"; +#if DEBUG + var versionText = $"{Main.GitBranch} - {Main.GitCommit}"; +#elif RELEASE + var versionText = + $"FS - v{Main.DisplayedVersion}"; +#elif OPENBETA + var versionText = + $"{Main.GitBranch} - {Main.GitCommit}\n" + + $"FS - v{Main.DisplayedVersion}"; #endif + credentialsText += versionText; + if (Main.IsAprilFools) + { + credentialsText = + "XtremeWives © 1987\n4.1.Never Gonna Give You Up"; + } + CreditTextCredential = Object.Instantiate(__instance.text); CreditTextCredential.name = "FinalSuspect CreditText"; CreditTextCredential.alignment = TextAlignmentOptions.Right; CreditTextCredential.text = credentialsText; CreditTextCredential.transform.localScale = new Vector3(2.5f, 2.5f, 2.5f); - CreditTextCredential.transform.localPosition = new Vector3(0.3f, -2.6f, 0f); - CreditTextCredential.enabled = GameObject.Find("FinalSuspect Background") != null; - CreditTextCredential.SetOutlineColor(ColorHelper.ShadeColor(ColorHelper.ModColor32, 0.75f)); + CreditTextCredential.enabled = GameObject.Find("FinalSuspect Background"); + CreditTextCredential.SetOutlineColor(ColorHelper.ShadeColor(ColorHelper.FSColor, 0.75f)); CreditTextCredential.SetOutlineThickness(0.20f); CreditTextCredential.fontStyle = FontStyles.Bold; - var ap1 = OVersionShower.GetComponent(); - if (ap1 != null) Object.Destroy(ap1); - var ap2 = CreditTextCredential.GetComponent(); - if (ap2 != null) Object.Destroy(ap2); + var ap_credit = CreditTextCredential.gameObject.AddComponent(); + ap_credit.Alignment = AspectPosition.EdgeAlignments.RightBottom; + ap_credit.DistanceFromEdge = new Vector3(5f, 0.4f); + ap_credit.updateAlways = true; } - - TeamLogo = new GameObject + + AuthorLogo = new GameObject { layer = 5, - name = "Team Logo" + name = "Author Logo" }; - TeamLogo.AddComponent().sprite = LoadSprite("TeamLogo.png", 400f); - TeamLogo.GetComponent().color = new Color32(255, 255, 255, 120); - TeamLogo.transform.SetParent(VisitText.transform.parent); - TeamLogo.transform.localPosition = new Vector3(-4.72f, -2.5f, 0f); - TeamLogo.SetActive(false); + AuthorLogo.AddComponent().sprite = LoadSprite("AuthorLogo2.png", 840f); + AuthorLogo.GetComponent().color = new Color32(255, 255, 255, 120); + AuthorLogo.transform.SetParent(VisitText.transform.parent); + var ap_authorLogo = AuthorLogo.gameObject.AddComponent(); + ap_authorLogo.Alignment = AspectPosition.EdgeAlignments.LeftBottom; + ap_authorLogo.DistanceFromEdge = new Vector3(0.6f, 0.5f); + ap_authorLogo.updateAlways = true; + + AuthorLogo.SetActive(false); ModLogo = new GameObject { layer = 5, @@ -161,101 +194,86 @@ public static void Postfix(VersionShower __instance) }; ModLogo.AddComponent().sprite = LoadSprite("FinalSuspect-Logo.png", 250f); ModLogo.GetComponent().color = new Color32(255, 255, 255, 120); - ModLogo.transform.localPosition = new Vector3(3.7f, -2.6f, 0f); + var ap_modLogo = ModLogo.gameObject.AddComponent(); + ap_modLogo.Alignment = AspectPosition.EdgeAlignments.RightBottom; + ap_modLogo.DistanceFromEdge = new Vector3(1.6f, 0.4f); + ap_modLogo.updateAlways = true; ModLogo.SetActive(false); } - private static VersionShower Instance; - public static void CreateVisitText(VersionShower __instance) { - if (__instance == null) + if (!__instance) __instance = Instance; else - { Instance = __instance; - } - + VisitText = Object.Instantiate(__instance.text); VisitText.name = "FinalSuspect VisitText"; VisitText.alignment = TextAlignmentOptions.Left; - VisitText.text = VersionChecker.isChecked - ? string.Format(GetString("FinalSuspectWelcomeText"), ColorHelper.ModColor) - : GetString("ConnectToFinalSuspectServerFailed"); + VisitText.text = VersionChecker.IsChecked + ? string.Format(GetString("FinalSuspectWelcomeText"), ColorHelper.FSColorHex) + : GetString("RetrieveVersionInfoFailed"); VisitText.transform.localScale = new Vector3(0.7f, 0.7f, 0.7f); - VisitText.transform.localPosition = new Vector3(-3.92f, -2.9f, 0f); - VisitText.enabled = GameObject.Find("FinalSuspect Background") != null; + VisitText.enabled = GameObject.Find("FinalSuspect Background"); __instance.text.alignment = TextAlignmentOptions.Left; - OVersionShower.transform.localPosition = new Vector3(-4.92f, -3.3f, 0f); - var ap1 = OVersionShower.GetComponent(); - if (ap1 != null) Object.Destroy(ap1); - var ap2 = VisitText.GetComponent(); - if (ap2 != null) Object.Destroy(ap2); + ap1.Alignment = AspectPosition.EdgeAlignments.LeftBottom; + ap1.DistanceFromEdge = new Vector3(0.4f, -0.3f); + ap1.updateAlways = true; + + var ap2 = VisitText.gameObject.AddComponent(); + ap2.Alignment = AspectPosition.EdgeAlignments.LeftBottom; + ap2.DistanceFromEdge = new Vector3(1.4f, 0.1f); + ap2.updateAlways = true; } } -[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start)), HarmonyPriority(Priority.First)] +[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] +[HarmonyPriority(Priority.First)] internal class TitleLogoPatch { - public static GameObject ModStamp; - public static GameObject FinalSuspect_Background; - public static GameObject Ambience; - public static GameObject Starfield; - public static GameObject LeftPanel; - public static GameObject RightPanel; - public static GameObject CloseRightButton; - public static GameObject Tint; - public static GameObject Sizer; - public static GameObject AULogo; - public static GameObject BottomButtonBounds; - - public static Vector3 RightPanelOp; - public static void Postfix(MainMenuManager __instance) { - GameObject.Find("BackgroundTexture")?.SetActive(!MainMenuManagerPatch.ShowedBak); + GameObject.Find("BackgroundTexture")?.SetActive(!ShowedBak); Color shade = new(0f, 0f, 0f, 0f); var standardActiveSprite = __instance.newsButton.activeSprites.GetComponent().sprite; var minorActiveSprite = __instance.quitButton.activeSprites.GetComponent().sprite; + var style = MainMenuStyleManager.MainMenuStyles[Main.CurrentStyleId.Value]; - var friendsButton = AwakeFriendCodeUIPatch.FriendsButton.GetComponent(); + var friendsButton = FriendsButton.GetComponent(); Dictionary, (Sprite, Color, Color, Color, Color)> mainButtons = new() { { [__instance.playButton, __instance.inventoryButton, __instance.shopButton], - (standardActiveSprite, new Color(0.5216f, 1f, 0.9490f, 0.8f), shade, Color.white, Color.white) + (standardActiveSprite, style.MainUIColors[0], shade, Color.white, Color.white) }, { [__instance.newsButton, __instance.myAccountButton, __instance.settingsButton], - (minorActiveSprite, new Color( 0.5216f, 0.7765f, 1f, 0.8f), shade, Color.white, Color.white) + (minorActiveSprite, style.MainUIColors[1], shade, Color.white, Color.white) }, { [__instance.creditsButton, __instance.quitButton], - (minorActiveSprite, new Color(0.7294f, 0.6353f, 1.0f, 0.8f), shade, Color.white, Color.white) + (minorActiveSprite, style.MainUIColors[2], shade, Color.white, Color.white) }, { [friendsButton], - (minorActiveSprite, new Color(0.0235f, 0f, 0.8f, 0.8f), shade, Color.white, Color.white) - }, + (minorActiveSprite, style.MainUIColors[3], shade, Color.white, Color.white) + } }; - // ReSharper disable once UnusedParameter.Local - - foreach (var kvp in mainButtons) - { kvp.Key.Do(button => { - FormatButtonColor(__instance, button, kvp.Value.Item2, kvp.Value.Item3, kvp.Value.Item4, kvp.Value.Item5); + FormatButtonColor(__instance, button, kvp.Value.Item2, kvp.Value.Item3, kvp.Value.Item4, + kvp.Value.Item5); }); - } - + try { - mainButtons.Keys.Flatten()?.DoIf(x => x != null, x => x.buttonText.color = Color.white); + mainButtons.Keys.Flatten()?.DoIf(x => x, x => x.buttonText.color = Color.white); } catch { @@ -274,12 +292,13 @@ public static void Postfix(MainMenuManager __instance) } }; var bgRenderer = FinalSuspect_Background.AddComponent(); - bgRenderer.sprite = LoadSprite("FinalSuspect-BG-MiraHQ.jpg", 179f); + bgRenderer.sprite = style.Sprite; if (!(Ambience = GameObject.Find("Ambience"))) return; if (!(Starfield = Ambience.transform.FindChild("starfield").gameObject)) return; + Starfield.SetActive(style.StarFieldActive); var starGen = Starfield.GetComponent(); - starGen.SetDirection(new Vector2(0, -2)); + starGen.SetDirection(new Vector2(0, style.StarGenDire)); Starfield.transform.SetParent(FinalSuspect_Background.transform); Object.Destroy(Ambience); @@ -291,15 +310,14 @@ public static void Postfix(MainMenuManager __instance) GameObject.Find("Divider")?.SetActive(false); if (!(RightPanel = GameObject.Find("RightPanel"))) return; - var rpap = RightPanel.GetComponent(); - if (rpap) Object.Destroy(rpap); - RightPanelOp = RightPanel.transform.localPosition; + var rightPanelAP = RightPanel.GetComponent(); + if (rightPanelAP) Object.Destroy(rightPanelAP); RightPanel.transform.localPosition = RightPanelOp + new Vector3(10f, 0f, 0f); RightPanel.GetComponent().color = new Color(1f, 0.78f, 0.9f, 1f); CloseRightButton = new GameObject("CloseRightPanelButton"); CloseRightButton.transform.SetParent(RightPanel.transform); - CloseRightButton.transform.localPosition = new Vector3(-4.78f, 1.3f, 1f); + CloseRightButton.transform.localPosition = new Vector3(-4.78f * GetResolutionOffset(), 1.3f, 1f); CloseRightButton.transform.localScale = new Vector3(1f, 1f, 1f); CloseRightButton.AddComponent().size = new Vector2(0.6f, 1.5f); var closeRightSpriteRenderer = CloseRightButton.AddComponent(); @@ -309,15 +327,18 @@ public static void Postfix(MainMenuManager __instance) closeRightPassiveButton.OnClick = new Button.ButtonClickedEvent(); closeRightPassiveButton.OnClick.AddListener((global::System.Action)MainMenuManagerPatch.HideRightPanel); closeRightPassiveButton.OnMouseOut = new UnityEvent(); - closeRightPassiveButton.OnMouseOut.AddListener((global::System.Action)(() => closeRightSpriteRenderer.color = new Color(1f, 0.78f, 0.9f, 1f))); + closeRightPassiveButton.OnMouseOut.AddListener((global::System.Action)(() => + closeRightSpriteRenderer.color = new Color(1f, 0.78f, 0.9f, 1f))); closeRightPassiveButton.OnMouseOver = new UnityEvent(); - closeRightPassiveButton.OnMouseOver.AddListener((global::System.Action)(() => closeRightSpriteRenderer.color = new Color(1f, 0.68f, 0.99f, 1f))); + closeRightPassiveButton.OnMouseOver.AddListener((global::System.Action)(() => + closeRightSpriteRenderer.color = new Color(1f, 0.68f, 0.99f, 1f))); Tint = __instance.screenTint.gameObject; var ttap = Tint.GetComponent(); if (ttap) Object.Destroy(ttap); Tint.transform.SetParent(RightPanel.transform); - Tint.transform.localPosition = new Vector3(-0.0824f, 0.0513f, Tint.transform.localPosition.z); + Tint.transform.localPosition = + new Vector3(-0.0824f * GetResolutionOffset(), 0.0513f, Tint.transform.localPosition.z); Tint.transform.localScale = new Vector3(1f, 1f, 1f); var creditsScreen = __instance.creditsScreen; @@ -331,7 +352,8 @@ public static void Postfix(MainMenuManager __instance) if (!(Sizer = GameObject.Find("Sizer"))) return; if (!(AULogo = GameObject.Find("LOGO-AU"))) return; - Sizer.transform.localPosition += new Vector3(0f, 0.12f, 0f); + Sizer.transform.localPosition = + new Vector3(-4.0f * GetResolutionOffset(), 1.4f, -1.0f); AULogo.transform.localScale = new Vector3(0.66f, 0.67f, 1f); AULogo.transform.position += new Vector3(0f, 0.1f, 0f); var logoRenderer = AULogo.GetComponent(); @@ -339,8 +361,16 @@ public static void Postfix(MainMenuManager __instance) if (!(BottomButtonBounds = GameObject.Find("BottomButtonBounds"))) return; BottomButtonBounds.transform.localPosition -= new Vector3(0f, 0.1f, 0f); + + var mainButtonsobj = GameObject.Find("Main Buttons"); + mainButtonsobj.transform.position = new Vector3(-3.4f * GetResolutionOffset(), + mainButtonsobj.transform.position.y, mainButtonsobj.transform.position.z); return; - static void ResetParent(GameObject obj) => obj.transform.SetParent(LeftPanel.transform.parent); + + static void ResetParent(GameObject obj) + { + obj.transform.SetParent(LeftPanel.transform.parent); + } } } @@ -348,17 +378,27 @@ public static void Postfix(MainMenuManager __instance) internal class ModManagerLateUpdatePatch { private static bool firstRun; - + private static string LastScene = ""; + + public static void Prefix(ModManager __instance) { __instance.ShowModStamp(); - if (!firstRun) + if (firstRun) + { + if (LastScene != SceneManager.GetActiveScene().name) + { + LastScene = SceneManager.GetActiveScene().name; + OnSceneChange(LastScene); + } + } + else { OptionsMenuBehaviourStartPatch.SetCursor(); __instance.ModStamp.sprite = LoadSprite("ModStamp.png", 100f); firstRun = true; } - + LateTask.Update(Time.deltaTime); MainThreadTask.Update(); } @@ -370,6 +410,19 @@ public static void Postfix(ModManager __instance) __instance.localCamera, AspectPosition.EdgeAlignments.RightTop, new Vector3(0.4f, offset_y, __instance.localCamera.nearClipPlane + 0.1f)); } + + private static void OnSceneChange(string name) + { + if (name is "MainMenu" or "MatchMaking") + { + var style = MainMenuStyleManager.MainMenuStyles[Main.CurrentStyleId.Value]; + var audio = FinalMusic.musics.FirstOrDefault(x => x.CurrentAudio == style.MainMenuMusic); + if (audio != null) + { + _ = new LateTask(() => { AudioPlayer.Play(audio, true); }, 0.01f, "Play Custom MainBG"); + } + } + } } [HarmonyPatch(typeof(CreditsScreenPopUp))] @@ -380,4 +433,45 @@ public static void Postfix(CreditsScreenPopUp __instance) { __instance.BackButton.transform.parent.FindChild("Background").gameObject.SetActive(false); } +} + +[HarmonyPatch(typeof(ResolutionManager))] +internal class ResolutionManagerPatch +{ + [HarmonyPatch(nameof(ResolutionManager.SetResolution))] + public static void Postfix(int width, int height) + { + _ = new LateTask(() => + { + if (!GameObject.Find("MainUI")) return; + var offset = GetResolutionOffset(); + CloseRightButton.transform.localPosition = new Vector3(-4.78f * offset, 1.3f, 1.0f); + Tint.transform.localPosition = + new Vector3(-0.0824f * offset, 0.0513f, Tint.transform.localPosition.z); + Sizer.transform.localPosition = new Vector3(-4.0f * offset, 1.4f, -1.0f); + var mainButtons = GameObject.Find("Main Buttons"); + mainButtons.transform.position = new Vector3(-3.4f * offset, mainButtons.transform.position.y, + mainButtons.transform.position.z); + MainMenuButtonHoverAnimation.RefreshButtons(mainButtons); + + List nullObj = []; + foreach (var button in MainMenuCustomButtons) + { + if (!button) + { + nullObj.Add(button); + continue; + } + + var scale = Instance.quitButton.transform.localScale; + button.transform.localScale = + new Vector3(scale.x * GetResolutionOffset(), button.transform.localScale.y); + } + + foreach (var obj in nullObj) MainMenuCustomButtons.Remove(obj); + + CloseRightButton.transform.localPosition = + new Vector3(-4.78f * GetResolutionOffset(), 1.3f, 1f); + }, 0.01f, "RefreshMenu"); + } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/CreditsControllerPatch.cs b/FinalSuspect/Patches/System/CreditsControllerPatch.cs index 6d2c1cd7..29c5a5ac 100644 --- a/FinalSuspect/Patches/System/CreditsControllerPatch.cs +++ b/FinalSuspect/Patches/System/CreditsControllerPatch.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using FinalSuspect.Helpers; +using FinalSuspect.Helpers; namespace FinalSuspect.Patches.System; @@ -11,16 +9,24 @@ public class CreditsControllerPatch { var devList = new List { - $"{Main.ModName}", - $"By XtremeWave", + $"{Main.ModName}", + $"By By Slok", //Others - $"{GetString("ModInfos.Contributors")}", + $"{GetString("Id.Contributor")}", + "- Nonalus", + "- KpCam", + "- 小黄117", "- LezaiYa", + "- 白糖咖啡", + "- Elinmei", + "- QingFeng", + "- Yu(Night_瓜)", + "- FangKuai", + "- KARPED1EM", "- Niko233", "- Amongus(水木年华)", - "- Yu(Night_瓜)", - "- 天寸梦初", + "- 天寸梦初" }; var credits = new List(); @@ -41,31 +47,34 @@ void AddSpcaeToCredits() { AddTitleToCredits(string.Empty); } + void AddTitleToCredits(string title) { credits.Add(new CreditsController.CreditStruct { format = "title", - columns = new[] { title }, + columns = new[] { title } }); } + void AddPersonToCredits(List list) { - foreach (var line in list) + foreach (var cols in list.Select(line => line.Split(" - ").ToList())) { - var cols = line.Split(" - ").ToList(); if (cols.Count < 2) cols.Add(string.Empty); credits.Add(new CreditsController.CreditStruct { format = "person", - columns = cols.ToArray(), + columns = cols.ToArray() }); } } } - [HarmonyPatch(nameof(CreditsController.AddCredit)), HarmonyPrefix] - public static void AddCreditPrefix(CreditsController __instance, [HarmonyArgument(0)] CreditsController.CreditStruct originalCredit) + [HarmonyPatch(nameof(CreditsController.AddCredit))] + [HarmonyPrefix] + public static void AddCreditPrefix(CreditsController __instance, + [HarmonyArgument(0)] CreditsController.CreditStruct originalCredit) { if (originalCredit.columns[0] != "logoImage") return; diff --git a/FinalSuspect/Patches/System/FindGameButtonPatch.cs b/FinalSuspect/Patches/System/FindGameButtonPatch.cs new file mode 100644 index 00000000..b700208d --- /dev/null +++ b/FinalSuspect/Patches/System/FindGameButtonPatch.cs @@ -0,0 +1,16 @@ +using AmongUs.Data; + +namespace FinalSuspect.Patches.System; + +[HarmonyPatch(typeof(FindGameButton), nameof(FindGameButton.OnClick))] +public class FindGameButtonPatch +{ + public static bool Prefix(FindGameButton __instance) + { + DataManager.Settings.Save(); + AmongUsClient.Instance.NetworkMode = NetworkModes.OnlineGame; + AmongUsClient.Instance.MainMenuScene = "MainMenu"; + __instance.StartCoroutine(AmongUsClient.Instance.CoFindGame()); + return false; + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/GameOptionPatch.cs b/FinalSuspect/Patches/System/GameOptionPatch.cs index ce574b83..3d999e91 100644 --- a/FinalSuspect/Patches/System/GameOptionPatch.cs +++ b/FinalSuspect/Patches/System/GameOptionPatch.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using AmongUs.GameOptions; using FinalSuspect.Helpers; using TMPro; @@ -7,7 +6,7 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.UpdateValuesAndText))] -class RoleOptionSettingPatch +internal class RoleOptionSettingPatch { public static void Postfix(RoleOptionSetting __instance) { @@ -16,8 +15,9 @@ public static void Postfix(RoleOptionSetting __instance) __instance.titleText.color = Color.white; } } + [HarmonyPatch(typeof(RolesSettingsMenu), nameof(RolesSettingsMenu.Update))] -class RolesSettingsMenuPatch +internal class RolesSettingsMenuPatch { private static readonly List rolecolors = [ @@ -49,10 +49,7 @@ private static void ConfigureHeaderButtons() var header = GameObject.Find("HeaderButtons"); var headerbuttons = new List(); - for (var i = 4; i <= 10; i++) - { - headerbuttons.Add(header.transform.GetChild(i).gameObject); - } + for (var i = 4; i <= 10; i++) headerbuttons.Add(header.transform.GetChild(i).gameObject); var index = 0; foreach (var button in headerbuttons) @@ -61,6 +58,7 @@ private static void ConfigureHeaderButtons() SetColor(button, rolecolors[index], roleColor); index++; } + ConfigureAllButtonColors(); } @@ -70,24 +68,22 @@ private static void ConfigureAllButtonColors() AllButton.transform.FindChild("Highlight").gameObject.GetComponent().color = AllButton.transform.FindChild("Inactive").gameObject.GetComponent().color = AllButton.transform.FindChild("Selected").gameObject.GetComponent().color = - ColorHelper.ModColor32; + ColorHelper.FSColor; var text = AllButton.transform.FindChild("Text").gameObject.GetComponent(); - if (text.color == Color.white || text.color == ColorHelper.ModColor32) - { - text.color = ColorHelper.ModColor32; - } + if (text.color == Color.white || text.color == ColorHelper.FSColor) + text.color = ColorHelper.FSColor; else - { text.color = new Color(0.45f, 0.45f, 0.65f); - } } private static void SetRoleAreaColors() { var RoleArea = GameObject.Find("ROLES TAB").transform.FindChild("Scroller").FindChild("SliderInner"); - GameOptionsMenuPatch.SetColorForCat(RoleArea.FindChild("ChancesTab").FindChild("CategoryHeaderMasked").gameObject, Color.green); - GameOptionsMenuPatch.SetColorForCat(RoleArea.FindChild("AdvancedTab").FindChild("CategoryHeaderMasked").gameObject, Color.blue); + GameOptionsMenuPatch.SetColorForCat( + RoleArea.FindChild("ChancesTab").FindChild("CategoryHeaderMasked").gameObject, Color.green); + GameOptionsMenuPatch.SetColorForCat( + RoleArea.FindChild("AdvancedTab").FindChild("CategoryHeaderMasked").gameObject, Color.blue); } private static void SetColor(GameObject obj, Color iconcolor, Color bgcolor) @@ -98,6 +94,7 @@ private static void SetColor(GameObject obj, Color iconcolor, Color bgcolor) obj.transform.FindChild("RoleIcon").gameObject.GetComponent().color = iconcolor; } } + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))] internal class GameOptionsMenuPatch { @@ -108,6 +105,7 @@ internal class GameOptionsMenuPatch Color.yellow, Color.green ]; + private static readonly List HnSbannercolors = [ GetRoleColor(RoleTypes.Crewmate), @@ -115,6 +113,7 @@ internal class GameOptionsMenuPatch Palette.Purple, Color.green ]; + public static void Postfix() { var setArea = GameObject.Find("GAME SETTINGS TAB").transform.FindChild("Scroller").FindChild("SliderInner"); @@ -125,7 +124,6 @@ public static void Postfix() var numindex = 0; var boxindex = 0; foreach (var banner in banners) - { if (banner.name == "CategoryHeaderMasked(Clone)") { SetColorForCat(banner.gameObject, Normalbannercolors[headerindex]); @@ -133,29 +131,22 @@ public static void Postfix() } else if (banner.name.Contains("Num") || banner.name.Contains("Str")) { - Color color; - if (numindex <= 3) - color = Normalbannercolors[0]; - else if (numindex <= 5) - color = Normalbannercolors[1]; - else if (numindex <= 9) - color = Normalbannercolors[2]; - else - color = Normalbannercolors[3]; + Color color = numindex switch + { + <= 3 => Normalbannercolors[0], + <= 5 => Normalbannercolors[1], + <= 9 => Normalbannercolors[2], + _ => Normalbannercolors[3] + }; SetColorForSettingsOpt_StringAndNumber(banner.gameObject, color); numindex++; } else if (banner.name.Contains("Checkbox")) { - Color color; - if (boxindex <= 1) - color = Normalbannercolors[2]; - else - color = Normalbannercolors[3]; + Color color = boxindex <= 1 ? Normalbannercolors[2] : Normalbannercolors[3]; SetColorForSettingsOpt_Checkbox(banner.gameObject, color); boxindex++; } - } } else { @@ -163,81 +154,76 @@ public static void Postfix() var numindex = 0; var boxindex = 0; foreach (var banner in banners) - { if (banner.name == "CategoryHeaderMasked(Clone)") { SetColorForCat(banner.gameObject, HnSbannercolors[headerindex]); headerindex++; } - else if (banner.name.Contains("Num") || banner.name.Contains("Str")|| banner.name.Contains("Play")) + else if (banner.name.Contains("Num") || banner.name.Contains("Str") || banner.name.Contains("Play")) { - Color color; - if (numindex <= 5) - color = HnSbannercolors[0]; - else if (numindex <= 8) - color = HnSbannercolors[1]; - else if (numindex <= 11) - color = HnSbannercolors[2]; - else - color = HnSbannercolors[3]; - SetColorForSettingsOpt_StringAndNumber(banner.gameObject, color); + Color color = numindex switch + { + <= 5 => HnSbannercolors[0], + <= 8 => HnSbannercolors[1], + <= 11 => HnSbannercolors[2], + _ => HnSbannercolors[3] + }; + SetColorForSettingsOpt_StringAndNumber(banner.gameObject, color); numindex++; } else if (banner.name.Contains("Checkbox")) { - Color color; - if (boxindex <= 1) - color = HnSbannercolors[0]; - else - color = HnSbannercolors[2]; - SetColorForSettingsOpt_Checkbox(banner.gameObject, color); + Color color = boxindex <= 1 ? HnSbannercolors[0] : HnSbannercolors[2]; + SetColorForSettingsOpt_Checkbox(banner.gameObject, color); boxindex++; } - } } } + internal static void SetColorForCat(GameObject obj, Color color) { - if (obj == null) return; - obj.transform.FindChild("LabelSprite").gameObject.GetComponent().color = color.ShadeColor(0.18f); - obj.transform.FindChild("DividerImage").gameObject.GetComponent().color = color.ShadeColor(0.18f); + if (!obj) return; + obj.transform.FindChild("LabelSprite").gameObject.GetComponent().color = + color.ShadeColor(0.18f); + obj.transform.FindChild("DividerImage").gameObject.GetComponent().color = + color.ShadeColor(0.18f); } - static void SetColorForSettingsOpt_StringAndNumber(GameObject obj, Color color) + + private static void SetColorForSettingsOpt_StringAndNumber(GameObject obj, Color color) { - obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = color.ShadeColor(0.38f); + obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = + color.ShadeColor(0.38f); obj.transform.FindChild("ValueBox").gameObject.GetComponent().color = color; } - static void SetColorForSettingsOpt_Checkbox(GameObject obj, Color color) + + private static void SetColorForSettingsOpt_Checkbox(GameObject obj, Color color) { - obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = color.ShadeColor(0.38f); - obj.transform.FindChild("Toggle").FindChild("InactiveSprite").gameObject.GetComponent().color = color; + obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = + color.ShadeColor(0.38f); + obj.transform.FindChild("Toggle").FindChild("InactiveSprite").gameObject.GetComponent().color = + color; } } [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.Update))] -class GameSettingMenuPatch +internal class GameSettingMenuPatch { - static GameObject GamePresetButton; - static GameObject GameSettingsButton; - static GameObject RoleSettingsButton; + private static GameObject GamePresetButton; + private static GameObject GameSettingsButton; + private static GameObject RoleSettingsButton; + public static void Postfix() { try { var Panel = GameObject.Find("LeftPanel"); - if (GamePresetButton == null) - { - GamePresetButton = Panel.transform.FindChild("GamePresetButton").gameObject; - } - if (GameSettingsButton == null) - { - GameSettingsButton = Panel.transform.FindChild("GameSettingsButton").gameObject; - } - if (RoleSettingsButton == null && IsNormalGame) - { + if (!GamePresetButton) GamePresetButton = Panel.transform.FindChild("GamePresetButton").gameObject; + + if (!GameSettingsButton) GameSettingsButton = Panel.transform.FindChild("GameSettingsButton").gameObject; + + if (!RoleSettingsButton && IsNormalGame) RoleSettingsButton = Panel.transform.FindChild("RoleSettingsButton").gameObject; - } SetColor(GamePresetButton, new Color32(205, 255, 253, 255)); SetColor(GameSettingsButton, new Color32(206, 205, 253, 255)); @@ -245,16 +231,18 @@ public static void Postfix() var ps = GameObject.Find("PanelSprite"); ps.GetComponent().color = new Color(1, 1, 1, 0.4f); - ps.transform.FindChild("LeftSideTint").gameObject.GetComponent().color = new Color(0.1176f, 0.1176f, 0.1176f, 0.8f); + ps.transform.FindChild("LeftSideTint").gameObject.GetComponent().color = + new Color(0.1176f, 0.1176f, 0.1176f, 0.8f); } catch { /* ignored */ } } - static void SetColor(GameObject obj, Color bgcolor) + + private static void SetColor(GameObject obj, Color bgcolor) { - if (obj == null) return; + if (!obj) return; obj.transform.FindChild("Highlight").gameObject.GetComponent().color = bgcolor; obj.transform.FindChild("Selected").gameObject.GetComponent().color = bgcolor; obj.transform.FindChild("Inactive").gameObject.GetComponent().color = bgcolor; diff --git a/FinalSuspect/Patches/System/GameStartManagerPatch.cs b/FinalSuspect/Patches/System/GameStartManagerPatch.cs index 40e50025..3e706be7 100644 --- a/FinalSuspect/Patches/System/GameStartManagerPatch.cs +++ b/FinalSuspect/Patches/System/GameStartManagerPatch.cs @@ -1,5 +1,6 @@ using System; using AmongUs.Data; +using FinalSuspect.Attributes; using FinalSuspect.Helpers; using FinalSuspect.Modules.Resources; using InnerNet; @@ -17,31 +18,46 @@ public static void Prefix(GameStartManager __instance) { __instance.MinPlayers = 1; - //Scrapped - //if (CreateOptionsPickerPatch.SetDleks && AmongUsClient.Instance.AmHost) - //{ - // if (IsNormalGame) - // Main.NormalOptions.MapId = 3; + /*Scrapped + if (CreateOptionsPickerPatch.SetDleks && AmongUsClient.Instance.AmHost) + { + if (IsNormalGame) + Main.NormalOptions.MapId = 3; - // else if (IsHideNSeek) - // Main.HideNSeekOptions.MapId = 3; - //} + else if (IsHideNSeek) + Main.HideNSeekOptions.MapId = 3; + }*/ } } -public class GameStartManagerPatch + +public static class GameStartManagerPatch { private static float timer = 600f; private static Vector3 GameStartTextlocalPosition; private static TextMeshPro timerText; private static PassiveButton cancelButton; private static TextMeshPro warningText; - public static TextMeshPro HideName; + private static TextMeshPro HideName; + public static GameStartManager Instance; + + [GameModuleInitializer] + public static void Init() + { + DestroyableSingleton.Instance.transform.gameObject.ForEachChild( + (Action)HideAllBtns); + } + + private static void HideAllBtns(GameObject obj) + { + obj.SetActive(false); + } [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))] public class GameStartManagerStartPatch { public static void Postfix(GameStartManager __instance) { + Instance = __instance; __instance.MinPlayers = 1; __instance.GameRoomNameCode.text = GameCode.IntToGameName(AmongUsClient.Instance.GameId); @@ -52,7 +68,7 @@ public static void Postfix(GameStartManager __instance) HideName.name = "HideName"; HideName.color = ColorUtility.TryParseHtmlString(Main.HideColor.Value, out var color) ? color : - ColorUtility.TryParseHtmlString(ColorHelper.ModColor, out var modColor) ? modColor : HideName.color; + ColorUtility.TryParseHtmlString(ColorHelper.FSColorHex, out var modColor) ? modColor : HideName.color; HideName.text = Main.HideName.Value; warningText = Object.Instantiate(__instance.GameStartText, __instance.transform); @@ -60,14 +76,10 @@ public static void Postfix(GameStartManager __instance) warningText.transform.localPosition = new Vector3(0f, 0f - __instance.transform.localPosition.y, -1f); warningText.gameObject.SetActive(false); - if (AmongUsClient.Instance.AmHost) - { - timerText = Object.Instantiate(__instance.PlayerCounter, __instance.StartButton.transform.parent); - } - else - { - timerText = Object.Instantiate(__instance.PlayerCounter, __instance.StartButtonClient.transform.parent); - } + timerText = Object.Instantiate(__instance.PlayerCounter, + AmongUsClient.Instance.AmHost + ? __instance.StartButton.transform.parent + : __instance.StartButtonClient.transform.parent); timerText.fontSize = 6.2f; timerText.autoSizeTextContainer = true; timerText.name = "Timer"; @@ -77,9 +89,10 @@ public static void Postfix(GameStartManager __instance) timerText.outlineColor = Color.black; timerText.outlineWidth = 0.40f; timerText.hideFlags = HideFlags.None; - timerText.transform.localPosition += new Vector3(-0.55f, -0.4f, 0f); + timerText.transform.localPosition += new Vector3(-0.55f, -0.4f, 0f); timerText.transform.localScale = new Vector3(0.7f, 0.7f, 1f); - timerText.gameObject.SetActive(AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame && IsVanillaServer); + timerText.gameObject.SetActive(AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame && + IsVanillaServer); cancelButton = Object.Instantiate(__instance.StartButton, __instance.transform); var cancelLabel = cancelButton.GetComponentInChildren(); @@ -90,24 +103,19 @@ public static void Postfix(GameStartManager __instance) var cancelButtonActiveRenderer = cancelButton.activeSprites.GetComponent(); cancelButtonActiveRenderer.color = Color.red; var cancelButtonInactiveShine = cancelButton.inactiveSprites.transform.Find("Shine"); - if (cancelButtonInactiveShine) - { - cancelButtonInactiveShine.gameObject.SetActive(false); - } + if (cancelButtonInactiveShine) cancelButtonInactiveShine.gameObject.SetActive(false); + cancelButton.activeTextColor = cancelButton.inactiveTextColor = Color.white; GameStartTextlocalPosition = __instance.GameStartText.transform.localPosition; cancelButton.OnClick = new Button.ButtonClickedEvent(); - cancelButton.OnClick.AddListener((Action)(() => - { - __instance.ResetStartState(); - })); + cancelButton.OnClick.AddListener((Action)(__instance.ResetStartState)); cancelButton.gameObject.SetActive(false); - if (AmongUsClient.Instance.AmHost && (VersionChecker.isBroken || (VersionChecker.hasUpdate && VersionChecker.forceUpdate) || !VersionChecker.IsSupported)) - { - __instance.HostPrivateButton.inactiveTextColor = Palette.DisabledClear; - __instance.HostPrivateButton.activeTextColor = Palette.DisabledClear; - } + if (!AmongUsClient.Instance.AmHost || (!VersionChecker.IsBroken && + (!VersionChecker.HasUpdate || !VersionChecker.ForceUpdate) && + VersionChecker.IsSupported)) return; + __instance.HostPrivateButton.inactiveTextColor = Palette.DisabledClear; + __instance.HostPrivateButton.activeTextColor = Palette.DisabledClear; } } @@ -116,38 +124,38 @@ public class GameStartManagerUpdatePatch { private static int updateTimer; public static float exitTimer = -1f; - public static void Prefix(GameStartManager __instance) + + public static bool Prefix(GameStartManager __instance) { + if (IsInGame) return false; // Lobby code if (DataManager.Settings.Gameplay.StreamerMode) { - __instance.GameRoomNameCode.color = new Color(__instance.GameRoomNameCode.color.r, __instance.GameRoomNameCode.color.g, __instance.GameRoomNameCode.color.b, 0); + __instance.GameRoomNameCode.color = new Color(__instance.GameRoomNameCode.color.r, + __instance.GameRoomNameCode.color.g, __instance.GameRoomNameCode.color.b, 0); HideName.enabled = !IsLocalGame; } else { - __instance.GameRoomNameCode.color = new Color(__instance.GameRoomNameCode.color.r, __instance.GameRoomNameCode.color.g, __instance.GameRoomNameCode.color.b, 255); + __instance.GameRoomNameCode.color = new Color(__instance.GameRoomNameCode.color.r, + __instance.GameRoomNameCode.color.g, __instance.GameRoomNameCode.color.b, 255); HideName.enabled = false; } - if (Main.AutoStartGame.Value && AmongUsClient.Instance.AmHost) - { - updateTimer++; - if (updateTimer >= 50) - { - updateTimer = 0; - var maxPlayers = GameManager.Instance.LogicOptions.MaxPlayers; - if (GameData.Instance.PlayerCount >= maxPlayers - 1 && !IsCountDown) - { - GameStartManager.Instance.startState = GameStartManager.StartingStates.Countdown; - GameStartManager.Instance.countDownTimer = 10; - } - } - } + if (!Main.AutoStartGame.Value || !AmongUsClient.Instance.AmHost) return true; + updateTimer++; + if (updateTimer < 50) return true; + updateTimer = 0; + var maxPlayers = GameManager.Instance.LogicOptions.MaxPlayers; + if (GameData.Instance.PlayerCount < maxPlayers - 1 || IsCountDown) return true; + GameStartManager.Instance.startState = GameStartManager.StartingStates.Countdown; + GameStartManager.Instance.countDownTimer = 10; + return true; } + public static void Postfix(GameStartManager __instance) { - if (!AmongUsClient.Instance) return; + if (!AmongUsClient.Instance) return; if (AmongUsClient.Instance.AmHost) { /*bool canStartGame = true; @@ -172,20 +180,21 @@ public static void Postfix(GameStartManager __instance) cancelButton.gameObject.SetActive(__instance.startState == GameStartManager.StartingStates.Countdown); __instance.StartButton.gameObject.SetActive(!cancelButton.gameObject.active); } - /*if (MatchVersions(0, true) || Main.VersionCheat.Value) - exitTimer = 0; - else + + /*if (MatchVersions(0, true) || Main.VersionCheat.Value) + exitTimer = 0; + else + { + exitTimer += Time.deltaTime; + if (exitTimer >= 5) { - exitTimer += Time.deltaTime; - if (exitTimer >= 5) - { - exitTimer = 0; - AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); - SceneChanger.ChangeScene("MainMenu"); - } - if (exitTimer != 0) - warningMessage = StringHelper.ColorString(Color.red, string.Format(GetString("Warning.AutoExitAtMismatchedVersion"), $"{Main.ModName}", Math.Round(5 - exitTimer).ToString())); - }*/ + exitTimer = 0; + AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); + SceneChanger.ChangeScene("MainMenu"); + } + if (exitTimer != 0) + warningMessage = StringHelper.ColorString(Color.red, string.Format(GetString("Warning.AutoExitAtMismatchedVersion"), $"{Main.ModName}", Math.Round(5 - exitTimer).ToString())); + }*/ var warningMessage = ""; if (warningMessage == "") { @@ -196,17 +205,18 @@ public static void Postfix(GameStartManager __instance) warningText.text = warningMessage; warningText.gameObject.SetActive(true); } + if (AmongUsClient.Instance.AmHost) - { - __instance.GameStartText.transform.localPosition = new Vector3(__instance.GameStartText.transform.localPosition.x, 2f, __instance.GameStartText.transform.localPosition.z); - } + __instance.GameStartText.transform.localPosition = new Vector3( + __instance.GameStartText.transform.localPosition.x, 2f, + __instance.GameStartText.transform.localPosition.z); else - { __instance.GameStartText.transform.localPosition = GameStartTextlocalPosition; - } + timerText.text = ""; // Lobby timer - if (!GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame || !IsVanillaServer || !AmongUsClient.Instance.AmHost) return; + if (!GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame || + !IsVanillaServer || !AmongUsClient.Instance.AmHost) return; timer = Mathf.Max(0f, timer -= Time.deltaTime); var minutes = (int)timer / 60; @@ -215,13 +225,14 @@ public static void Postfix(GameStartManager __instance) if (timer <= 60) countDown = StringHelper.ColorString(Color.red, countDown); timerText.text = countDown; } - private static bool MatchVersions(byte playerId, bool acceptVanilla = false) + + /*private static bool MatchVersions(byte playerId, bool acceptVanilla = false) { - if (!XtremeGameData.PlayerVersion.playerVersion.TryGetValue(playerId, out var version)) return acceptVanilla; + if (!FinalGameData.PlayerVersion.playerVersion.TryGetValue(playerId, out var version)) return acceptVanilla; return Main.ForkId == version.forkId && Main.version.CompareTo(version.version) == 0 && version.tag == $"{Main.GitCommit}({Main.GitBranch})"; - } + }*/ } } @@ -233,22 +244,12 @@ public static void Postfix(TextBoxTMP __instance) if (__instance.name == "GameIdText") __instance.outputText.text = new string('*', __instance.text.Length); } } + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ResetStartState))] -class ResetStartStatePatch -{ - public static void Prefix(GameStartManager __instance) - { - if (IsCountDown) - { - SoundManager.Instance.StopSound(__instance.gameStartSound); - } - } -} -[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.FinallyBegin))] -class FinallyBeginPatch +internal class ResetStartStatePatch { public static void Prefix(GameStartManager __instance) { - SoundManager.Instance.StopSound(__instance.gameStartSound); + if (IsCountDown) SoundManager.Instance.StopSound(__instance.gameStartSound); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/HashRandomPatch.cs b/FinalSuspect/Patches/System/HashRandomPatch.cs index 4e0e334e..a7f884ec 100644 --- a/FinalSuspect/Patches/System/HashRandomPatch.cs +++ b/FinalSuspect/Patches/System/HashRandomPatch.cs @@ -3,10 +3,11 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(HashRandom))] -class HashRandomPatch +internal class HashRandomPatch { - [HarmonyPatch(nameof(HashRandom.FastNext)), HarmonyPrefix] - static bool FastNext([HarmonyArgument(0)] int maxInt, ref int __result) + [HarmonyPatch(nameof(HashRandom.FastNext))] + [HarmonyPrefix] + private static bool FastNext([HarmonyArgument(0)] int maxInt, ref int __result) { if (IRandom.Instance is HashRandomWrapper) return true; @@ -14,8 +15,10 @@ static bool FastNext([HarmonyArgument(0)] int maxInt, ref int __result) return false; } - [HarmonyPatch(nameof(HashRandom.Next), typeof(int)), HarmonyPrefix] - static bool MaxNext([HarmonyArgument(0)] int maxInt, ref int __result) + + [HarmonyPatch(nameof(HashRandom.Next), typeof(int))] + [HarmonyPrefix] + private static bool MaxNext([HarmonyArgument(0)] int maxInt, ref int __result) { if (IRandom.Instance is HashRandomWrapper) return true; @@ -23,8 +26,10 @@ static bool MaxNext([HarmonyArgument(0)] int maxInt, ref int __result) return false; } - [HarmonyPatch(nameof(HashRandom.Next), typeof(int), typeof(int)), HarmonyPrefix] - static bool MinMaxNext([HarmonyArgument(0)] int minInt, [HarmonyArgument(1)] int maxInt, ref int __result) + + [HarmonyPatch(nameof(HashRandom.Next), typeof(int), typeof(int))] + [HarmonyPrefix] + private static bool MinMaxNext([HarmonyArgument(0)] int minInt, [HarmonyArgument(1)] int maxInt, ref int __result) { if (IRandom.Instance is HashRandomWrapper) return true; diff --git a/FinalSuspect/Patches/System/HideBanButtonPatch.cs b/FinalSuspect/Patches/System/HideBanButtonPatch.cs index efaba0eb..9f601257 100644 --- a/FinalSuspect/Patches/System/HideBanButtonPatch.cs +++ b/FinalSuspect/Patches/System/HideBanButtonPatch.cs @@ -1,13 +1,10 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(ChatController), nameof(ChatController.Toggle))] -class CancelBanMenuStuckPatch +internal class CancelBanMenuStuckPatch { public static void Prefix(ChatController __instance) { - if (__instance.IsOpenOrOpening && !__instance.IsAnimating) - { - __instance.banButton.SetVisible(false); - } + if (__instance.IsOpenOrOpening && !__instance.IsAnimating) __instance.banButton.SetVisible(false); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/JoinGameButtonPatch.cs b/FinalSuspect/Patches/System/JoinGameButtonPatch.cs index 74c1840f..d71c1182 100644 --- a/FinalSuspect/Patches/System/JoinGameButtonPatch.cs +++ b/FinalSuspect/Patches/System/JoinGameButtonPatch.cs @@ -4,15 +4,14 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(JoinGameButton), nameof(JoinGameButton.OnClick))] -class JoinGameButtonPatch +internal class JoinGameButtonPatch { public static void Prefix(JoinGameButton __instance) { - if (__instance.GameIdText == null) return; - if (__instance.GameIdText.text == "" && Regex.IsMatch(GUIUtility.systemCopyBuffer.Trim('\r', '\n'), @"^[A-Z]{6}$")) - { - Info($"{GUIUtility.systemCopyBuffer}", "ClipBoard"); - __instance.GameIdText.SetText(GUIUtility.systemCopyBuffer.Trim('\r', '\n')); - } + if (!__instance.GameIdText) return; + if (__instance.GameIdText.text != "" || + !Regex.IsMatch(GUIUtility.systemCopyBuffer.Trim('\r', '\n'), "^[A-Z]{6}$")) return; + Info($"{GUIUtility.systemCopyBuffer}", "ClipBoard"); + __instance.GameIdText.SetText(GUIUtility.systemCopyBuffer.Trim('\r', '\n')); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/LoadManagerPatch.cs b/FinalSuspect/Patches/System/LoadManagerPatch.cs new file mode 100644 index 00000000..5b04f6c8 --- /dev/null +++ b/FinalSuspect/Patches/System/LoadManagerPatch.cs @@ -0,0 +1,59 @@ +using FinalSuspect.Helpers; +using UnityEngine; +using Image = UnityEngine.UI.Image; + +namespace FinalSuspect.Patches.System; + +[HarmonyPatch(typeof(LoadingBarManager))] +public class LoadingBarManagerPatch +{ + private static GameObject AmongUsLogo; + private static GameObject ModLogo; + private static Vector3 LogoVector; + private static bool hasSetVec; + + [HarmonyPatch(nameof(LoadingBarManager.ToggleLoadingBar))] + public static void Prefix(LoadingBarManager __instance, ref bool on) + { + AmongUsLogo = __instance.loadingBar.transform.FindChild("Canvas").FindChild("Logo").gameObject; + if (AmongUsLogo) + { + var trans = AmongUsLogo.GetComponent(); + var cr = AmongUsLogo.GetComponent(); + if (!hasSetVec) + { + hasSetVec = true; + LogoVector = trans.localPosition; + } + + if (!ModLogo) + { + ModLogo = Object.Instantiate(AmongUsLogo, trans.parent); + ModLogo.GetComponent().sprite = LoadSprite("FinalSuspect-Logo.png", 150f); + } + + trans.localScale = new Vector3(0.3f, 0.3f, 1); + cr.transform.localPosition = new Vector3(LogoVector.x, LogoVector.y + 60, LogoVector.z); + trans.localPosition = new Vector3(LogoVector.x, LogoVector.y + 60, LogoVector.z); + cr.transform.localScale = new Vector3(0.3f, 0.3f, 1); + ModLogo.GetComponent().localPosition = + new Vector3(LogoVector.x, LogoVector.y - 80, LogoVector.z); + ModLogo.GetComponent().transform.localPosition = + new Vector3(LogoVector.x, LogoVector.y - 80, LogoVector.z); + ModLogo.GetComponent().localScale = new Vector3(1.4f, 1.4f, 1); + ModLogo.GetComponent().transform.localScale = new Vector3(1.4f, 1.4f, 1); + } + + __instance.loadingBar.barFill.color = ColorHelper.FSColor; + __instance.loadingBar.crewmate.gameObject.SetActive(false); + try + { + if (!IsNotJoined) return; + on = false; + } + catch + { + on = false; + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/LoadPatch.cs b/FinalSuspect/Patches/System/LoadPatch.cs index 2bdb19fc..add5d30f 100644 --- a/FinalSuspect/Patches/System/LoadPatch.cs +++ b/FinalSuspect/Patches/System/LoadPatch.cs @@ -1,9 +1,9 @@ using System; using System.Collections; -using System.Collections.Generic; using System.IO; -using System.Linq; using BepInEx.Unity.IL2CPP.Utils; +using FinalSuspect.ClientActions.FeatureItems.MainMenuStyle; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; using FinalSuspect.Helpers; using FinalSuspect.Modules.Resources; using TMPro; @@ -12,454 +12,476 @@ namespace FinalSuspect.Patches.System; +[HarmonyPatch(typeof(SplashManager))] public static class LoadPatch { - #region UI Components - - private static TextMeshPro _loadText = null!; - private static TextMeshPro _processText = null!; - private static SpriteRenderer _teamLogo = null!; - private static SpriteRenderer _modLogo = null!; - private static SpriteRenderer _modLogoBlurred = null!; - private static SpriteRenderer _glow = null!; - - #endregion - private static bool _reloadLanguage; private static bool _skipLoadAnimation; private static bool _firstLaunch; - [HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Start))] - public class Start + [HarmonyPatch(nameof(SplashManager.Start)), HarmonyPrefix] + public static bool Start(SplashManager __instance) + { + __instance.startTime = Time.time; + __instance.StartCoroutine(InitializeRefData(__instance)); + return false; + } + + private static IEnumerator InitializeRefData(SplashManager instance) + { + CreateTextComponents(instance); + yield return HandleFirstLaunch(); + CreateLogoComponents(); + yield return HandleCoreLoadingProcess(); + instance.sceneChanger.BeginLoadingScene(); + instance.doneLoadingRefdata = true; + } + + private static IEnumerator LoadAmongUsTranslation() { - public static bool Prefix(SplashManager __instance) + yield return DestroyableSingleton.Instance.Initialize(); + try { - ResolutionManager.SetResolution(1920, 1080, Screen.fullScreen); - __instance.startTime = Time.time; - __instance.StartCoroutine(InitializeRefData(__instance)); - return false; + DestroyableSingleton.Instance.Initialize(); } - - private static IEnumerator InitializeRefData(SplashManager instance) + catch { - CreateTextComponents(instance); - yield return HandleFirstLaunch(); - CreateLogoComponents(); - yield return HandleCoreLoadingProcess(); - instance.sceneChanger.BeginLoadingScene(); - instance.doneLoadingRefdata = true; + /* Ignored */ } + } - #region Initialization Helpers - private static void CreateTextComponents(SplashManager instance) - { - _loadText = CreateTextComponent(instance, new Vector3(0f, -0.28f, -10f)); - _processText = CreateTextComponent(instance, new Vector3(0f, -0.7f, -10f)); - } + #region Harmony Patches - private static TextMeshPro CreateTextComponent(SplashManager instance, Vector3 position) - { - var text = Object.Instantiate(instance.errorPopup.InfoText, null); - text.transform.localPosition = position; - text.fontStyle = FontStyles.Bold; - text.text = string.Empty; - return text; - } + [HarmonyPatch(nameof(SplashManager.Update)), HarmonyPrefix] + public static void Update(SplashManager __instance) + { + if (!_skipLoadAnimation) return; + __instance.sceneChanger.AllowFinishLoadingScene(); + __instance.startedSceneLoad = true; + } - private static void CreateLogoComponents() - { - _teamLogo = CreateSpriteRenderer("Team_Logo", "TeamLogo.png", 120f, new Vector3(0, 0f, -5f)); - _modLogo = CreateSpriteRenderer("Mod_Logo", "FinalSuspect-Logo.png", 150f, new Vector3(0, 0.3f, -5f)); - _modLogoBlurred = CreateSpriteRenderer("Mod_Logo_Blurred", "FinalSuspect-Logo-Blurred.png", 150f, new Vector3(0, 0.3f, -5f)); - _glow = CreateSpriteRenderer("Glow", "FinalSuspect-Logo.png", 1f, new Vector3(0, 0.3f, -5f)); - } + #endregion - private static SpriteRenderer CreateSpriteRenderer(string name, string spriteName, float pixelsPerUnit, Vector3 position) - { - var renderer = ObjectHelper.CreateObject(name, null, position); - renderer.sprite = LoadSprite(spriteName, pixelsPerUnit); - renderer.color = Color.clear; - return renderer; - } + #region Initialization Helpers - #endregion + private static void CreateTextComponents(SplashManager instance) + { + _loadText = CreateTextComponent(instance, new Vector3(0f, -0.28f, -10f)); + _processText = CreateTextComponent(instance, new Vector3(0f, -0.7f, -10f)); + } - #region Loading Process - - private static IEnumerator HandleFirstLaunch() - { - var logoAnimator = GameObject.Find("LogoAnimator"); - logoAnimator.SetActive(false); - - CheckForListResources(ref ResourcesHelper.PreReadyRemoteImageList, FileType.Images); - yield return DownloadResources(ResourcesHelper.PreReadyRemoteImageList, FileType.Images, HandleFirstLaunchText, true); - if (string.IsNullOrEmpty(_loadText.text)) yield break; - _loadText.text = string.Empty; - _firstLaunch = true; - yield return new WaitForSeconds(2f); - } + private static TextMeshPro CreateTextComponent(SplashManager instance, Vector3 position) + { + var text = Object.Instantiate(instance.errorPopup.InfoText, null); + text.transform.localPosition = position; + text.fontStyle = FontStyles.Bold; + text.text = string.Empty; + return text; + } - private static void HandleFirstLaunchText() - { - _loadText.text = $"Welcome to FinalSuspect."; - } + private static void CreateLogoComponents() + { + _authorLogo = + ObjectHelper.CreateSpriteRenderer("Author_Logo", "AuthorLogo1.png", 120f, new Vector3(0, 0f, -5f)); + _modLogo = ObjectHelper.CreateSpriteRenderer("Mod_Logo", "FinalSuspect-Logo.png", 150f, + new Vector3(0, 0.3f, -5f)); + _modLogoBlurred = ObjectHelper.CreateSpriteRenderer("Mod_Logo_Blurred", "FinalSuspect-Logo-Blurred.png", + 150f, + new Vector3(0, 0.3f, -5f)); + _glow = ObjectHelper.CreateSpriteRenderer("Glow", "FinalSuspect-Logo.png", 1f, new Vector3(0, 0.3f, -5f)); + } - private static IEnumerator HandleCoreLoadingProcess() - { - var fastBoot = CheckFastBootCondition() && !_firstLaunch; + #endregion - yield return fastBoot ? HandleFastBoot() : HandleNormalBoot(); - yield return LoadEssentialResources(); - yield return HandlePostDownloadProcess(fastBoot); - } + #region Loading Process + + private static IEnumerator HandleFirstLaunch() + { + var logoAnimator = GameObject.Find("LogoAnimator"); + logoAnimator.SetActive(false); + + + CheckForListResources(ref ResourcesHelper.PreReadyRemoteImageList, FileType.Images); + CheckForListResources(ref ResourcesHelper.PreReadyRemoteMusicList, FileType.Musics); + yield return DownloadResources(ResourcesHelper.PreReadyRemoteImageList, FileType.Images, + HandleFirstLaunchText, true); + yield return DownloadResources(ResourcesHelper.PreReadyRemoteMusicList, FileType.Musics, + HandleFirstLaunchText, true); + if (string.IsNullOrEmpty(_loadText.text)) yield break; + _loadText.text = string.Empty; + _firstLaunch = true; + yield return new WaitForSeconds(2f); + } + + private static void HandleFirstLaunchText() + { + _loadText.text = $"Welcome to FinalSuspect."; + } + + private static IEnumerator HandleCoreLoadingProcess() + { + var fastLaunchMode = (CheckFastLaunchModeCondition() || Main.OfflineMode.Value) && !_firstLaunch; + Main.OfflineMode.Value = Main.FastLaunchMode.Value = fastLaunchMode; - private static bool CheckFastBootCondition() + yield return fastLaunchMode ? HandleFastLaunchMode() : HandleNormalBoot(); + + yield return LoadEssentialResources(); + yield return HandlePostDownloadProcess(fastLaunchMode); + } + + private static bool CheckFastLaunchModeCondition() + { + var currentVersion = $"{Main.PluginVersion}|{Main.DisplayedVersion}|{Main.GitCommit}-{Main.GitBranch}"; + var bypassType = Main.LanguageUpdateBypass.Value; + + _reloadLanguage = currentVersion != Main.LastStartVersion.Value + && bypassType == BypassType.Dont; + + switch (bypassType) { - var currentVersion = $"{Main.PluginVersion}|{Main.DisplayedVersion}|{Main.GitCommit}-{Main.GitBranch}"; - var bypassPathOnce = GetBypassFileType(FileType.Languages, BypassType.Once); - var bypassPathLongTerm = GetBypassFileType(FileType.Languages, BypassType.Longterm); - - _reloadLanguage = currentVersion != Main.LastStartVersion.Value && !(File.Exists(bypassPathOnce) || File.Exists(bypassPathLongTerm)); - - if (File.Exists(bypassPathOnce) || File.Exists(bypassPathLongTerm)) - { - if (File.Exists(bypassPathOnce)) - { - File.Delete(bypassPathOnce); - } - } - else - { + case BypassType.Dont: Main.LastStartVersion.Value = currentVersion; - } - return Main.FastBoot.Value && !_reloadLanguage; + break; + case BypassType.Once: + Main.LanguageUpdateBypass.Value = BypassType.Dont; + break; + case BypassType.LongTerm: + default: + break; } - #endregion + return Main.FastLaunchMode.Value && !_reloadLanguage; + } - #region Boot Handlers + #endregion - private static IEnumerator HandleFastBoot() - { - SetFastBootVisuals(); - TranslatorInit(); - UpdateProcessText(GetString("FastBoot"), Color.green); - yield return new WaitForSeconds(1f); - _skipLoadAnimation = true; - } + #region Boot Handlers - private static void SetFastBootVisuals() - { - _teamLogo.color = Color.white; - _teamLogo.transform.localPosition = new Vector3(0, 1.7f, -5f); - _teamLogo.transform.localScale = new Vector3(0.7f, 0.7f, 1f); + private static IEnumerator HandleFastLaunchMode() + { + SetFastLaunchModeVisuals(); + TranslatorInit(); + AudioManager.ReloadTag(); + var style = MainMenuStyleManager.MainMenuStyles[Main.CurrentStyleId.Value]; + var audio = FinalMusic.musics.FirstOrDefault(x => x.CurrentAudio == style.MainMenuMusic); + if (audio != null) + AudioPlayer.Play(audio, true); + + + UpdateProcessText( + GetString(Main.OfflineMode.Value ? "ClientOption.OfflineMode" : "ClientOption.FastLaunchMode"), + Main.OfflineMode.Value ? Color.gray : Color.green); + yield return new WaitForSeconds(1f); + _skipLoadAnimation = true; + } - _modLogo.color = Color.white; - _modLogo.transform.localPosition = new Vector3(0, 0, -5f); - _modLogo.transform.localScale = new Vector3(1.5f, 1.5f, 1f); + private static void SetFastLaunchModeVisuals() + { + _authorLogo.color = Color.white; + _authorLogo.transform.localPosition = new Vector3(0, 2.4f, -5f); + _authorLogo.transform.localScale = new Vector3(0.55f, 0.55f, 1f); - _glow.color = Color.green; - } + _modLogo.color = Color.white; + _modLogo.transform.localPosition = new Vector3(0, 0, -5f); + _modLogo.transform.localScale = new Vector3(1.5f, 1.5f, 1f); - private static IEnumerator HandleNormalBoot() - { - yield return AnimateTeamLogo(); - yield return AnimateModLogo(); - yield return ShowLoadingProgress(); - } + _glow.color = Main.OfflineMode.Value ? Color.gray : Color.green; + } - #endregion + private static IEnumerator HandleNormalBoot() + { + yield return AnimateAuthorLogo(); + yield return AnimateModLogo(); + yield return ShowLoadingProgress(); + } - #region Animation Coroutines + #endregion - private static IEnumerator AnimateTeamLogo() - { - yield return FadeSprite(_teamLogo, 2.8f, false); - yield return new WaitForSeconds(1.5f); - yield return FadeSprite(_teamLogo, 2.8f, true); - yield return new WaitForSeconds(2f); - } + #region Animation Coroutines - private static IEnumerator AnimateModLogo() - { - var progress = 1f; - while (progress > 0f) - { - progress -= Time.deltaTime * 2.8f; - var alpha = 1 - progress; - - _modLogo.color = Color.white.AlphaMultiplied(alpha); - _modLogoBlurred.color = Color.white.AlphaMultiplied(Mathf.Min(1f, alpha * (progress * 2))); - - var scale = Vector3.one * (progress * progress * 0.012f + 1f); - _modLogo.transform.localScale = scale; - _modLogoBlurred.transform.localScale = scale; - - yield return null; - } - - _modLogo.color = Color.white; - _modLogoBlurred.gameObject.SetActive(false); - _modLogo.transform.localScale = Vector3.one; - yield return new WaitForSeconds(0.75f); - } + private static IEnumerator AnimateAuthorLogo() + { + _authorLogo.transform.localScale = new Vector3(0.5f, 0.5f, 1f); + yield return FadeSprite(_authorLogo, 2.8f, false); + yield return new WaitForSeconds(1.5f); + yield return FadeSprite(_authorLogo, 2.8f, true); + yield return new WaitForSeconds(2f); + } - private static IEnumerator ShowLoadingProgress() + private static IEnumerator AnimateModLogo() + { + var progress = 1f; + while (progress > 0f) { - _loadText.color = Color.white.AlphaMultiplied(0.75f); - _loadText.text = "Loading..."; + progress -= Time.deltaTime * 2.8f; + var alpha = 1 - progress; - var progress = 1f; - while (progress > 0) - { - progress -= Time.deltaTime * 2.8f; - var alpha = 1 - progress; - _glow.color = Color.white.AlphaMultiplied(alpha); + _modLogo.color = Color.white.AlphaMultiplied(alpha); + _modLogoBlurred.color = Color.white.AlphaMultiplied(Mathf.Min(1f, alpha * (progress * 2))); - if (alpha < 0.75f) - _loadText.color = Color.white.AlphaMultiplied(alpha); + var scale = Vector3.one * (progress * progress * 0.012f + 1f); + _modLogo.transform.localScale = scale; + _modLogoBlurred.transform.localScale = scale; - yield return null; - } + yield return null; } - #endregion + _modLogo.color = Color.white; + _modLogoBlurred.gameObject.SetActive(false); + _modLogo.transform.localScale = Vector3.one; + yield return new WaitForSeconds(0.75f); + } - #region Resource Management + private static IEnumerator ShowLoadingProgress() + { + _loadText.color = Color.white.AlphaMultiplied(0.75f); + _loadText.text = "Loading..."; - private static IEnumerator LoadEssentialResources() + var progress = 1f; + while (progress > 0) { - yield return LoadAmongUsTranslation(); - CheckForListResources(ref ResourcesHelper.RemoteDependList, FileType.Depends); - yield return DownloadResources(ResourcesHelper.RemoteDependList, FileType.Depends, null, true); + progress -= Time.deltaTime * 2.8f; + var alpha = 1 - progress; + _glow.color = Color.white.AlphaMultiplied(alpha); + + if (alpha < 0.75f) + _loadText.color = Color.white.AlphaMultiplied(alpha); + + yield return null; + } + } + + #endregion + + #region Resource Management - List RemoteLanguageList = []; - RemoteLanguageList.AddRange(EnumHelper.GetAllNames().Select(lang => lang + ".yaml")); + private static IEnumerator LoadEssentialResources() + { + yield return LoadAmongUsTranslation(); + + if (Main.OfflineMode.Value) yield break; + CheckForListResources(ref ResourcesHelper.RemoteDependList, FileType.Depends); + yield return DownloadResources(ResourcesHelper.RemoteDependList, FileType.Depends, null, true); + + List remoteLanguageList = []; + remoteLanguageList.AddRange(EnumHelper.GetAllNames().Select(lang => lang + ".yaml")); - if (!_reloadLanguage) - CheckForListResources(ref RemoteLanguageList, FileType.Languages); + if (!_reloadLanguage) + CheckForListResources(ref remoteLanguageList, FileType.Languages); - if (RemoteLanguageList.Count > 0) - yield return DownloadResources(RemoteLanguageList, FileType.Languages, null, true); + if (remoteLanguageList.Count > 0) + yield return DownloadResources(remoteLanguageList, FileType.Languages, null, true); + + if (!_skipLoadAnimation) + { TranslatorInit(); + AudioManager.ReloadTag(); + var style = MainMenuStyleManager.MainMenuStyles[Main.CurrentStyleId.Value]; + var audio = FinalMusic.musics.FirstOrDefault(x => x.CurrentAudio == style.MainMenuMusic); + if (audio != null) + AudioPlayer.Play(audio, true); } + } - private static IEnumerator HandlePostDownloadProcess(bool fastBoot) + private static IEnumerator HandlePostDownloadProcess(bool fastLaunchMode) + { + if (fastLaunchMode) yield break; + if (TranslationController.Instance.currentLanguage.languageID != SupportedLangs.English) { - if (fastBoot) yield break; - - if (TranslationController.Instance.currentLanguage.languageID != SupportedLangs.English) - { - yield return FadeText(_loadText, false); - _loadText.text = GetString("Loading"); - _loadText.color = Color.white; - yield return FadeText(_loadText, true); - } - - yield return new WaitForSeconds(1f); - yield return VerifyAdditionalResources(); - yield return ShowLoadCompleteAnimation(); + yield return FadeText(_loadText, false); + _loadText.text = GetString("Tip.Loading"); + _loadText.color = Color.white; + yield return FadeText(_loadText, true); } - private static IEnumerator VerifyAdditionalResources() - { - UpdateProcessText(GetString("CheckingForFiles"), Color.blue.AlphaMultiplied(0.75f)); - yield return FadeText(_processText, true); + yield return new WaitForSeconds(1f); + yield return VerifyAdditionalResources(); + yield return ShowLoadCompleteAnimation(); + } - CheckForListResources(ref ResourcesHelper.RemoteImageList, FileType.Images); + private static IEnumerator VerifyAdditionalResources() + { + UpdateProcessText(GetString("Tip.CheckingForFiles"), Color.blue.AlphaMultiplied(0.75f)); + yield return FadeText(_processText, true); - if (ResourcesHelper.RemoteImageList.Count > 0) - yield return HandleResourceDownloads(); - else - yield return FadeText(_processText, false); + CheckForListResources(ref ResourcesHelper.RemoteImageList, FileType.Images); - } + if (ResourcesHelper.RemoteImageList.Count > 0) + yield return HandleResourceDownloads(); + else + yield return FadeText(_processText, false); + } + + private static IEnumerator HandleResourceDownloads() + { + var downloadCount = ResourcesHelper.RemoteImageList.Count; + var progress = 0; - private static IEnumerator HandleResourceDownloads() + UpdateProcessText($"{GetString("Tip.Downloading")}({progress}/{downloadCount})", + ColorHelper.DownloadYellow); + yield return FadeText(_processText, true); + + var updateProgress = new Action(() => { - var downloadCount = ResourcesHelper.RemoteImageList.Count; - var progress = 0; + progress++; + _processText.text = $"{GetString("Tip.Downloading")}({progress}/{downloadCount})"; + }); - UpdateProcessText($"{GetString("DownloadingResources")}({progress}/{downloadCount})", - ColorHelper.DownloadYellow); - yield return FadeText(_processText, true); + yield return DownloadResources(ResourcesHelper.RemoteImageList, FileType.Images, updateProgress); + yield return ShowDownloadCompletion(); + } - var updateProgress = new Action(() => - { - progress++; - _processText.text = $"{GetString("DownloadingResources")}({progress}/{downloadCount})"; - }); + private static IEnumerator ShowDownloadCompletion() + { + yield return FadeText(_processText, false); + UpdateProcessText(GetString("Tip.DownLoadFinished"), ColorHelper.DownloadYellow); + yield return FadeText(_processText, true); + yield return new WaitForSeconds(0.5f); + yield return FadeText(_processText, false); + } - yield return DownloadResources(ResourcesHelper.RemoteImageList, FileType.Images, updateProgress); - yield return ShowDownloadCompletion(); - } + #region Load Complete Animation - private static IEnumerator ShowDownloadCompletion() - { - yield return FadeText(_processText, false); - UpdateProcessText(GetString("DownLoadSucceedNotice"), ColorHelper.DownloadYellow); - yield return FadeText(_processText, true); - yield return new WaitForSeconds(0.5f); - yield return FadeText(_processText, false); - } + private static IEnumerator ShowLoadCompleteAnimation() + { + yield return new WaitForSeconds(1f); - #region Load Complete Animation + Color green = ColorHelper.CompleteGreen; + _loadText.color = green.AlphaMultiplied(0.75f); + _loadText.text = GetString("Tip.LoadingComplete"); - private static IEnumerator ShowLoadCompleteAnimation() - { - yield return new WaitForSeconds(1f); - - Color green = ColorHelper.LoadCompleteGreen; - _loadText.color = green.AlphaMultiplied(0.75f); - _loadText.text = GetString("LoadingComplete"); - - for (var i = 0; i < 3; i++) - { - _loadText.gameObject.SetActive(false); - yield return new WaitForSeconds(0.03f); - _loadText.gameObject.SetActive(true); - yield return new WaitForSeconds(0.03f); - } - yield return new WaitForSeconds(0.5f); - - var progress = 1f; - while (progress > 0f) - { - progress -= Time.deltaTime * 1.2f; - _glow.color = Color.white.AlphaMultiplied(progress); - _modLogo.color = Color.white.AlphaMultiplied(progress); - - if (progress >= 0.75f) - _loadText.color = green.AlphaMultiplied(progress - 0.75f); - - yield return null; - } - Object.Destroy(_loadText.gameObject); - Object.Destroy(_processText.gameObject); - Object.Destroy(_modLogo.gameObject); - Object.Destroy(_modLogoBlurred.gameObject); - Object.Destroy(_teamLogo.gameObject); - Object.Destroy(_glow.gameObject); - } - - #endregion - - #endregion - - #region Utility Methods - - private static void CheckForListResources(ref List targetList, FileType fileType) + for (var i = 0; i < 3; i++) { - for (var i = targetList.Count - 1; i >= 0; i--) - { - var resource = targetList[i]; - if (File.Exists(GetLocalFilePath(fileType, resource))) - targetList.Remove(resource); - else - Warn($"File does not exist: {GetLocalFilePath(fileType, resource)}", "Check"); - } + _loadText.gameObject.SetActive(false); + yield return new WaitForSeconds(0.03f); + _loadText.gameObject.SetActive(true); + yield return new WaitForSeconds(0.03f); } - private static IEnumerator DownloadResources(List resources, FileType fileType, Action progressCallback = null, bool essential = false) - { - foreach (var resource in resources) - { - progressCallback?.Invoke(); - var task = ResourcesDownloader.StartDownload(fileType, resource); - while (!task.IsCompleted) yield return null; - - if (!task.IsFaulted && task.Result) continue; - - Error($"Download failed: {resource} - {task.Exception}", "Download Resource"); - if (!essential) continue; - yield return HandleDownloadError(); - Fatal("DOWNLOAD ESSENTIAL RESOURCES FAILED", "Download Resource"); - } - } + yield return new WaitForSeconds(0.5f); - private static IEnumerator HandleDownloadError() + var progress = 1f; + while (progress > 0f) { - yield return FadeText(_loadText, false); - _loadText.text = "Downloading essential resources failed, please restart the game\nRestart countdown: "; - _loadText.color = Color.red; - yield return FadeText(_loadText, true); + progress -= Time.deltaTime * 1.2f; + _glow.color = Color.white.AlphaMultiplied(progress); + _modLogo.color = Color.white.AlphaMultiplied(progress); + + if (progress >= 0.75f) + _loadText.color = green.AlphaMultiplied(progress - 0.75f); - var countdown = 10f; - while (countdown > 0) - { - _loadText.text = $"Downloading essential resources failed, please restart the game\nRestart countdown: {countdown:F0}"; - countdown -= Time.deltaTime; - yield return null; - } - Application.Quit(); + yield return null; } - - private static IEnumerator FadeText(TextMeshPro text, bool show, float duration = 2.8f) + + Object.Destroy(_loadText.gameObject); + Object.Destroy(_processText.gameObject); + Object.Destroy(_modLogo.gameObject); + Object.Destroy(_modLogoBlurred.gameObject); + Object.Destroy(_authorLogo.gameObject); + Object.Destroy(_glow.gameObject); + } + + #endregion + + #endregion + + #region Utility Methods + + private static void CheckForListResources(ref List targetList, FileType fileType) + { + for (var i = targetList.Count - 1; i >= 0; i--) { - var progress = 0.75f; - var originalColor = text.color; - - while (progress > 0) - { - progress -= Time.deltaTime * duration; - var alpha = show ? 0.75f - progress : progress; - text.color = originalColor.AlphaMultiplied(alpha); - yield return null; - } + var resource = targetList[i]; + var path = GetLocalFilePath(fileType, resource); + if (fileType is FileType.Musics) + AudioManager.ConvertExtension(ref path); + if (File.Exists(path)) + targetList.Remove(resource); + else + Warn($"File does not exist: {GetLocalFilePath(fileType, resource)}", "Check"); } + } - private static IEnumerator FadeSprite(SpriteRenderer sprite, float speed, bool fadeOut) + private static IEnumerator DownloadResources(List resources, FileType fileType, + Action progressCallback = null, bool essential = false) + { + foreach (var resource in resources) { - var progress = 1f; - while (progress > 0f) - { - progress -= Time.deltaTime * speed; - var alpha = fadeOut ? progress : 1 - progress; - sprite.color = Color.white.AlphaMultiplied(alpha); - yield return null; - } + progressCallback?.Invoke(); + var task = ResourcesDownloader.StartDownload(fileType, resource); + while (!task.IsCompleted) yield return null; + + if (!task.IsFaulted && task.Result) continue; + + Error($"Download failed: {resource} - {task.Exception}", "Download Resource"); + if (!essential) continue; + yield return HandleDownloadError(); + Fatal("DOWNLOAD ESSENTIAL RESOURCES FAILED", "Download Resource"); } + } - private static void UpdateProcessText(string text, Color color) + private static IEnumerator HandleDownloadError() + { + yield return FadeText(_loadText, false); + _loadText.text = "Downloading essential resources failed, please restart the game\nRestart countdown: "; + _loadText.color = Color.red; + yield return FadeText(_loadText, true); + + var countdown = 10f; + while (countdown > 0) { - _processText.text = text; - _processText.color = color; + _loadText.text = + $"Downloading essential resources failed, please restart the game\nRestart countdown: {countdown:F0}"; + countdown -= Time.deltaTime; + yield return null; } - - #endregion - private static IEnumerator LoadAmongUsTranslation() + Application.Quit(); + } + + private static IEnumerator FadeText(TextMeshPro text, bool show, float duration = 2.8f) + { + var progress = 0.75f; + var originalColor = text.color; + + while (progress > 0) { - yield return DestroyableSingleton.Instance.Initialize(); - try - { - DestroyableSingleton.Instance.Initialize(); - } - catch - { - /* Ignored */ - } + progress -= Time.deltaTime * duration; + var alpha = show ? 0.75f - progress : progress; + text.color = originalColor.AlphaMultiplied(alpha); + yield return null; } } - #region Harmony Patches - - [HarmonyPatch(typeof(SplashManager), nameof(SplashManager.Update))] - public class SplashLogoAnimatorPatch + private static IEnumerator FadeSprite(SpriteRenderer sprite, float speed, bool fadeOut) { - public static void Prefix(SplashManager __instance) + var progress = 1f; + while (progress > 0f) { - if (!_skipLoadAnimation) return; - __instance.sceneChanger.AllowFinishLoadingScene(); - __instance.startedSceneLoad = true; + progress -= Time.deltaTime * speed; + var alpha = fadeOut ? progress : 1 - progress; + sprite.color = Color.white.AlphaMultiplied(alpha); + yield return null; } } - [HarmonyPatch(typeof(LoadingBarManager), nameof(LoadingBarManager.ToggleLoadingBar))] - public class LoadingBarManagerPatch + private static void UpdateProcessText(string text, Color color) { - public static void Prefix(ref bool on) => on = false; + _processText.text = text; + _processText.color = color; } + + #endregion + + #region UI Components + + private static TextMeshPro _loadText = null!; + private static TextMeshPro _processText = null!; + private static SpriteRenderer _authorLogo = null!; + private static SpriteRenderer _modLogo = null!; + private static SpriteRenderer _modLogoBlurred = null!; + private static SpriteRenderer _glow = null!; + #endregion } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/LobbyListPatch.cs b/FinalSuspect/Patches/System/LobbyListPatch.cs index 3926d4c6..5e571784 100644 --- a/FinalSuspect/Patches/System/LobbyListPatch.cs +++ b/FinalSuspect/Patches/System/LobbyListPatch.cs @@ -2,88 +2,96 @@ using FinalSuspect.Helpers; using InnerNet; using TMPro; +using UnityEngine; +using Object = UnityEngine.Object; namespace FinalSuspect.Patches.System; -[HarmonyPatch(typeof(MatchMakerGameButton), nameof(MatchMakerGameButton.SetGame))] -public static class MatchMakerGameButtonSetGamePatch +[HarmonyPatch(typeof(GameContainer), nameof(GameContainer.SetupGameInfo))] +public class SetupGameInfoPatch { - public static bool Prefix([HarmonyArgument(0)] GameListing game) + public static void Postfix(GameContainer __instance) { - var nameList = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? Main.TName_Snacks_CN : Main.TName_Snacks_EN; + var mapTrans = __instance.mapLogo.transform; + var old = mapTrans.parent.FindChild("NameText")?.gameObject; + if (old) + Object.Destroy(old); - if (game.Language.ToString().Length > 9) return true; - var str = Math.Abs(game.GameId).ToString(); - var id = Math.Min(Math.Max(int.Parse(str.Substring(str.Length - 2, 2)), 1) * nameList.Count / 100, nameList.Count); + var nameText = new GameObject("NameText") + { + transform = + { + parent = __instance.mapLogo.transform.parent, + localPosition = new Vector3(-0.6f, mapTrans.localPosition.y, mapTrans.localPosition.z), + localScale = new Vector3(0.14f, 0.14f, 1f) + } + }; + var tmp = nameText.AddComponent(); + var game = __instance.gameListing; var color = "#ffffff"; - string RoomName = null; - var name = "?"; - var LobbyTime = Math.Max(0, game.Age); - var LobbyTimeDisplayText = GetString("LobbyTimeDisplay"); - var lobbyTimeDisplay = $"{LobbyTimeDisplayText}:{LobbyTime / 60}:{(LobbyTime % 60 < 10 ? "0" : "")}{LobbyTime % 60}"; + string ShowHostName = null; + var trueHostName = __instance.gameListing.TrueHostName; + var platform = "???"; switch (game.Platform) { case Platforms.StandaloneEpicPC: color = "#905CDA"; - name = "Itch"; + platform = "Epic"; break; case Platforms.StandaloneSteamPC: color = "#4391CD"; - name = "Steam"; + platform = "Steam"; break; case Platforms.StandaloneMac: color = "#e3e3e3"; - name = "Mac."; + platform = "Mac."; break; case Platforms.StandaloneWin10: color = "#0078d4"; - name = GetString("MicrosoftStore"); + platform = GetString("Platform.MicrosoftStore"); break; case Platforms.StandaloneItch: color = "#E35F5F"; - name = "Itch"; + platform = "Itch"; break; case Platforms.IPhone: color = "#e3e3e3"; - name = GetString("IPhone"); + platform = GetString("Platform.IPhone"); break; case Platforms.Android: color = "#1EA21A"; - name = GetString("Android"); + platform = GetString("Platform.Android"); break; case Platforms.Switch: - var totalname = nameList[id]; - var halfLength = totalname.Length / 2; - var firstHalf = totalname.AsSpan(0, halfLength).ToString(); - var secondHalf = totalname.AsSpan(halfLength).ToString(); - RoomName = $"{firstHalf}{secondHalf}"; - name = "NintendoSwitch"; + var halfLength = trueHostName.Length / 2; + var firstHalf = trueHostName.AsSpan(0, halfLength).ToString(); + var secondHalf = trueHostName.AsSpan(halfLength).ToString(); + ShowHostName = $"{firstHalf}{secondHalf}"; + platform = "NintendoSwitch"; break; case Platforms.Xbox: color = "#07ff00"; - name = "Xbox"; + platform = "Xbox"; break; case Platforms.Playstation: color = "#0014b4"; - name = "PlayStation"; + platform = "PlayStation"; + break; + case Platforms.Unknown: + default: + color = "#E57373"; break; } - RoomName ??= $"{nameList[id]}"; - var platforms = $"{name}"; + ShowHostName ??= $"{trueHostName}"; + var platforms = $"{platform}"; - game.HostName = $"{RoomName}" + - $" ({Math.Max(0, 100 - game.Age / 100)}%)" + - $"\n{GameCode.IntToGameName(game.GameId)}" + - $"----{platforms}" + - $"----{lobbyTimeDisplay}"; - return true; - } - - public static void Postfix(MatchMakerGameButton __instance) - { - __instance.NameText.fontStyle = FontStyles.Bold; + tmp.text = $"{ShowHostName}" + + $"\n{GameCode.IntToGameName(game.GameId)}" + + $" ----{platforms}----"; + tmp.fontStyle = FontStyles.Bold; + tmp.alignment = TextAlignmentOptions.Left; } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/LobbyPanelPatch.cs b/FinalSuspect/Patches/System/LobbyPanelPatch.cs index 76209fe2..e35b4f5b 100644 --- a/FinalSuspect/Patches/System/LobbyPanelPatch.cs +++ b/FinalSuspect/Patches/System/LobbyPanelPatch.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; -using System.Linq; using AmongUs.GameOptions; +using FinalSuspect.Attributes; using FinalSuspect.Helpers; using Il2CppSystem; using TMPro; @@ -9,25 +8,35 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(LobbyInfoPane), nameof(LobbyInfoPane.Update))] -class LobbyInfoPanePatch +internal class LobbyInfoPaneUpdatePatch { + [GameModuleInitializer] + public static void Init() + { + var trans = DestroyableSingleton.Instance.transform.FindChild("AspectSize") + .FindChild("GameSettingsButtons"); + trans.FindChild("Host Buttons").gameObject.SetActive(false); + trans.FindChild("Client Buttons").gameObject.SetActive(true); + trans.FindChild("ButtonSettingsHeader").gameObject.GetComponent().text += + $" - {GetString("PressF2ToHidePane")}"; + DestroyableSingleton.Instance.gameObject.GetComponent().DistanceFromEdge += + Vector3.forward * -30; + } + public static void Postfix() { var AspectSize = GameObject.Find("AspectSize"); - AspectSize.transform.FindChild("Background").gameObject.GetComponent().color = new Color(1, 1, 1, 0.4f); + AspectSize.transform.FindChild("Background").gameObject.GetComponent().color = + new Color(1, 1, 1, 0.4f); if (MapIsActive(MapNames.Dleks)) - AspectSize.transform.FindChild("MapImage").gameObject.GetComponent().sprite = LoadSprite("DleksBanner-Wordart.png", 160f); + AspectSize.transform.FindChild("MapImage").gameObject.GetComponent().sprite = + LoadSprite("DleksBanner-Wordart.png", 160f); } } [HarmonyPatch] -class LobbyViewSettingsPanePatch +internal class LobbyViewSettingsPanePatch { - [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.Awake)), HarmonyPostfix] - static void Awake() - { - GameObject.Find("RulesPopOutWindow").transform.localPosition += Vector3.left * 0.4f; - } private static readonly List Normalbannercolors = [ GetRoleColor(RoleTypes.Impostor), @@ -35,6 +44,7 @@ static void Awake() Color.yellow, Color.green ]; + private static readonly List HnSbannercolors = [ GetRoleColor(RoleTypes.Crewmate), @@ -42,6 +52,7 @@ static void Awake() Palette.Purple, Color.green ]; + private static readonly List rolecolors = [ GetRoleColor(RoleTypes.Engineer), @@ -52,53 +63,67 @@ static void Awake() GetRoleColor(RoleTypes.Shapeshifter), GetRoleColor(RoleTypes.Phantom) ]; + private static readonly List rolecatcolors = [ Color.green, Color.blue ]; - [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.Update)), HarmonyPostfix] - static void Update() + [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.Awake))] + [HarmonyPostfix] + private static void Awake() + { + GameObject.Find("RulesPopOutWindow").transform.localPosition += Vector3.left * 0.4f; + } + + [HarmonyPatch(typeof(LobbyViewSettingsPane), nameof(LobbyViewSettingsPane.Update))] + [HarmonyPostfix] + private static void Update() { try { - var Area = GameObject.Find("MainArea").transform.FindChild("Scaler").FindChild("Scroller").FindChild("SliderInner"); + var Area = GameObject.Find("MainArea").transform.FindChild("Scaler").FindChild("Scroller") + .FindChild("SliderInner"); Transform[] banners = Area.GetComponentsInChildren(true); - + if (IsNormalGame) { #region 游戏设置 + if (Area.childCount == 21) { var catindex = 0; var bannerindex = 0; foreach (var banner in banners) { - if (banner.name == "CategoryHeaderMasked LongDivider(Clone)") + switch (banner.name) { - SetColorForCat(banner.gameObject, Normalbannercolors[catindex]); - catindex++; - } - - if (banner.name == "ViewSettingsInfoPanel(Clone)") - { - Color color; - if (bannerindex <= 3) - color = Normalbannercolors[0]; - else if (bannerindex <= 5) - color = Normalbannercolors[1]; - else if (bannerindex <= 11) - color = Normalbannercolors[2]; - else - color = Normalbannercolors[3]; - SetColorForSettingsBanner(banner.gameObject, color); - bannerindex++; + case "CategoryHeaderMasked LongDivider(Clone)": + SetColorForCat(banner.gameObject, Normalbannercolors[catindex]); + catindex++; + break; + case "ViewSettingsInfoPanel(Clone)": + { + Color color; + if (bannerindex <= 3) + color = Normalbannercolors[0]; + else if (bannerindex <= 5) + color = Normalbannercolors[1]; + else if (bannerindex <= 11) + color = Normalbannercolors[2]; + else + color = Normalbannercolors[3]; + SetColorForSettingsBanner(banner.gameObject, color); + bannerindex++; + break; + } } } } #endregion + #region 职业详细设定 else @@ -108,28 +133,29 @@ static void Update() var enableroleindex = new List(); foreach (var banner in banners) { - if (banner.name == "CategoryHeaderMasked LongDivider(Clone)") + switch (banner.name) { - SetColorForCat(banner.gameObject, rolecatcolors[catindex]); - catindex++; - } - - if (banner.name == "ViewSettingsInfoPanel_Role Variant(Clone)") - { - var roleColor = bannerindex <= 4 - ? GetRoleColor(RoleTypes.Crewmate) - : GetRoleColor(RoleTypes.Impostor); - SetColorForRolesBanner(banner.gameObject, rolecolors[bannerindex], roleColor); - if (banner.gameObject.transform.FindChild("LabelBackground").gameObject - .GetComponent().color != new Color(0.3f, 0.3f, 0.3f, 1)) + case "CategoryHeaderMasked LongDivider(Clone)": + SetColorForCat(banner.gameObject, rolecatcolors[catindex]); + catindex++; + break; + case "ViewSettingsInfoPanel_Role Variant(Clone)": { - enableroleindex.Add(bannerindex); + var roleColor = bannerindex <= 4 + ? GetRoleColor(RoleTypes.Crewmate) + : GetRoleColor(RoleTypes.Impostor); + SetColorForRolesBanner(banner.gameObject, rolecolors[bannerindex], roleColor); + if (banner.gameObject.transform.FindChild("LabelBackground").gameObject + .GetComponent().color != new Color(0.3f, 0.3f, 0.3f, 1)) + enableroleindex.Add(bannerindex); + + bannerindex++; + break; } - bannerindex++; } } + foreach (var banner in banners) - { if (banner.name == "AdvancedRoleViewPanel(Clone)") { var iconindex = enableroleindex.First(); @@ -139,37 +165,40 @@ static void Update() SetColorForIcon(banner.gameObject, rolecolors[iconindex], roleColor); enableroleindex.RemoveAt(0); } - } } + #endregion } else { #region 游戏设置 + var catindex = 0; var bannerindex = 0; foreach (var banner in banners) { - if (banner.name == "CategoryHeaderMasked LongDivider(Clone)") - { - SetColorForCat(banner.gameObject, HnSbannercolors[catindex]); - catindex++; - } - if (banner.name == "ViewSettingsInfoPanel(Clone)") + switch (banner.name) { - Color color; - if (bannerindex <= 7) - color = HnSbannercolors[0]; - else if (bannerindex <= 10) - color = HnSbannercolors[1]; - else if (bannerindex <= 15) - color = HnSbannercolors[2]; - else - color = HnSbannercolors[3]; - SetColorForSettingsBanner(banner.gameObject, color); - bannerindex++; + case "CategoryHeaderMasked LongDivider(Clone)": + SetColorForCat(banner.gameObject, HnSbannercolors[catindex]); + catindex++; + break; + case "ViewSettingsInfoPanel(Clone)": + { + Color color = bannerindex switch + { + <= 7 => HnSbannercolors[0], + <= 10 => HnSbannercolors[1], + <= 15 => HnSbannercolors[2], + _ => HnSbannercolors[3] + }; + SetColorForSettingsBanner(banner.gameObject, color); + bannerindex++; + break; + } } } + #endregion } } @@ -178,43 +207,52 @@ static void Update() /* ignored */ } } - static void SetColorForRolesBanner(GameObject obj, Color iconcolor, Color bgcolor) + + private static void SetColorForRolesBanner(GameObject obj, Color iconcolor, Color bgcolor) { - if (obj == null) return; - if (obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color == new Color(0.3f, 0.3f, 0.3f, 1)) return; - obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = bgcolor.ShadeColor(0.32f); + if (!obj) return; + if (obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color == + new Color(0.3f, 0.3f, 0.3f, 1)) return; + obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = + bgcolor.ShadeColor(0.32f); obj.transform.FindChild("RoleIcon").gameObject.GetComponent().color = iconcolor; } - static void SetColorForIcon(GameObject obj, Color iconcolor, Color bgcolor) + private static void SetColorForIcon(GameObject obj, Color iconcolor, Color bgcolor) { - if (obj == null) return; + if (!obj) return; var cat = obj.transform.FindChild("CategoryHeaderRoleVariant"); cat.FindChild("LabelSprite").gameObject.GetComponent().color = bgcolor.ShadeColor(0.32f); cat.FindChild("Divider").gameObject.GetComponent().color = bgcolor.ShadeColor(0.32f); cat.FindChild("HeaderText").gameObject.GetComponent().color = Color.white; cat.FindChild("Icon").gameObject.GetComponent().color = iconcolor; obj.ForEachChild((Action)SetColor); + return; void SetColor(GameObject _obj) { - if (_obj.name == "ViewSettingsInfoPanel(Clone)") - { - _obj.transform.FindChild("Value").FindChild("Sprite").gameObject.GetComponent().color = iconcolor; - _obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = bgcolor.ShadeColor(0.38f); - } + if (_obj.name != "ViewSettingsInfoPanel(Clone)") return; + _obj.transform.FindChild("Value").FindChild("Sprite").gameObject.GetComponent().color = + iconcolor; + _obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = + bgcolor.ShadeColor(0.38f); } } - static void SetColorForSettingsBanner(GameObject obj, Color color) + + private static void SetColorForSettingsBanner(GameObject obj, Color color) { - if (obj == null) return; - obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = color.ShadeColor(0.38f); + if (!obj) return; + obj.transform.FindChild("LabelBackground").gameObject.GetComponent().color = + color.ShadeColor(0.38f); obj.transform.FindChild("Value").FindChild("Sprite").gameObject.GetComponent().color = color; } - static void SetColorForCat(GameObject obj, Color color) + + private static void SetColorForCat(GameObject obj, Color color) { - if (obj == null) return; - obj.transform.FindChild("LabelSprite").gameObject.GetComponent().color = color.ShadeColor(0.18f); - obj.transform.FindChild("DividerImage").gameObject.GetComponent().color = color.ShadeColor(0.18f); + if (!obj) return; + obj.transform.FindChild("LabelSprite").gameObject.GetComponent().color = + color.ShadeColor(0.18f); + obj.transform.FindChild("DividerImage").gameObject.GetComponent().color = + color.ShadeColor(0.18f); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/LobbyScreenPatch.cs b/FinalSuspect/Patches/System/LobbyScreenPatch.cs index e7ff3dea..981b953c 100644 --- a/FinalSuspect/Patches/System/LobbyScreenPatch.cs +++ b/FinalSuspect/Patches/System/LobbyScreenPatch.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using AmongUs.Data; using FinalSuspect.Helpers; using InnerNet; @@ -11,15 +11,12 @@ namespace FinalSuspect.Patches.System; public sealed class LobbyJoinBind { private static int GameId; - private static GameObject LastRoomText; - private static GameObject CopiedRoomText; - private static TextMeshPro lastRoomTextComponent; - private static TextMeshPro copiedRoomTextComponent; - - private const float TEXT_SIZE = 1.5f; - private const string LAST_ROOM_TEXT_NAME = "LastLobbyCode"; - private const string COPIED_ROOM_TEXT_NAME = "CopiedLobbyCode"; - private const string MOD_COLOR = ColorHelper.ModColor; + private static Color Color = ColorHelper.CompleteGreen; + private static GameObject LobbyText; + private static GameObject LeftShiftSprite; + private static GameObject RightShiftSprite; + private static GameObject KeyBindBackground; + private static GameObject KeyBindBackground_Clone; [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.JoinGame))] [HarmonyPostfix] @@ -28,73 +25,121 @@ public static void Postfix(InnerNetClient __instance) GameId = __instance.GameId; } - [HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Start))] + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] [HarmonyPostfix] public static void Postfix() { - InitializeTextObject(ref LastRoomText, ref lastRoomTextComponent, LAST_ROOM_TEXT_NAME, new Vector3(9.8f, -3.6f, 0)); - InitializeTextObject(ref CopiedRoomText, ref copiedRoomTextComponent, COPIED_ROOM_TEXT_NAME, new Vector3(9.8f, -3.8f, 0)); - } + var code2 = GUIUtility.systemCopyBuffer; - private static void InitializeTextObject(ref GameObject gameObject, ref TextMeshPro textComponent, string name, Vector3 position) - { - if (gameObject) return; - gameObject = new GameObject(name); - textComponent = gameObject.AddComponent(); - textComponent.fontSize = TEXT_SIZE; - gameObject.transform.localPosition = position; - gameObject.SetActive(true); + if (code2.Length != 6 || !Regex.IsMatch(code2, "^[a-zA-Z]+$")) + code2 = ""; + + if (LobbyText) return; + LobbyText = new GameObject("lobbycode"); + LobbyText.transform.SetParent(GameObject.Find("RightPanel").transform, false); + var comp = LobbyText.AddComponent(); + comp.fontSize = 2.5f; + comp.outlineWidth = -2f; + var lastY = code2 == "" ? -0.15f : 0.1f; + LobbyText.transform.localPosition = new Vector3(8.3f, lastY, 0); + LobbyText.SetActive(true); + //LeftShift Sprite + LeftShiftSprite = new GameObject("LeftShiftSprite"); + LeftShiftSprite.transform.SetParent(GameObject.Find("RightPanel").transform, false); + var LSsp = LeftShiftSprite.AddComponent(); + LSsp.sprite = LoadSprite("KeyLeftShift.png", 115f); + if (LobbyText != null) LeftShiftSprite.SetActive(true); + //RightShift Sprite + RightShiftSprite = new GameObject("RightShiftSprite"); + RightShiftSprite.transform.SetParent(GameObject.Find("RightPanel").transform, false); + var RSsp = RightShiftSprite.AddComponent(); + RSsp.sprite = LoadSprite("KeyRightShift.png", 115f); + if (LobbyText != null) RightShiftSprite.SetActive(true); + //KeyBindBackGround Belong to Left Shift + KeyBindBackground = new GameObject("KeyBindBackground"); + KeyBindBackground.transform.SetParent(GameObject.Find("RightPanel").transform, false); + var KeyBindSp = KeyBindBackground.AddComponent(); + KeyBindSp.GetComponent().sprite = LoadSprite("KeyBackground.png", 100f); + if (LeftShiftSprite != null) KeyBindBackground.SetActive(true); + //KeyBindBackGround Belong to Right Shift + KeyBindBackground_Clone = new GameObject("KeyBindBackground_Clone"); + KeyBindBackground_Clone.transform.SetParent(GameObject.Find("RightPanel").transform, false); + var KeyBindSp_Clone = KeyBindBackground_Clone.AddComponent(); + KeyBindSp_Clone.GetComponent().sprite = LoadSprite("KeyBackground.png", 100f); + if (RightShiftSprite != null) KeyBindBackground_Clone.SetActive(true); } - [HarmonyPatch(typeof(MMOnlineManager), nameof(MMOnlineManager.Update))] + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate))] [HarmonyPostfix] - public static void Postfix(MMOnlineManager __instance) - { - UpdateGameJoinLogic(__instance); - UpdateTextDisplay(); - } - private static void UpdateGameJoinLogic(MMOnlineManager manager) + public static void Postfix(MainMenuManager __instance) { + var code2 = GUIUtility.systemCopyBuffer; + + if (code2.Length != 6 || !Regex.IsMatch(code2, "^[a-zA-Z]+$")) + code2 = ""; + var code2Disp = DataManager.Settings.Gameplay.StreamerMode ? new string('*', code2.Length) : code2.ToUpper(); if (GameId != 0 && Input.GetKeyDown(KeyCode.LeftShift)) { - manager.StartCoroutine(AmongUsClient.Instance.CoJoinOnlineGameFromCode(GameId)); - } - else if (Input.GetKeyDown(KeyCode.RightShift)) - { - var copyBuffer = GUIUtility.systemCopyBuffer; - if (Regex.IsMatch(copyBuffer, @"^[a-zA-Z]+$")) - { - manager.StartCoroutine(AmongUsClient.Instance.CoJoinOnlineGameFromCode(GameCode.GameNameToInt(copyBuffer))); - } + __instance.StartCoroutine(AmongUsClient.Instance.CoJoinOnlineGameFromCode(GameId)); + LobbyText.GetComponent().color = Color.AlphaMultiplied(0.75f); } - } - private static void UpdateTextDisplay() - { - if (lastRoomTextComponent == null || copiedRoomTextComponent == null) - { - return; - } - - var lastCode = GameId != 0 && GameId != 32 ? GameCode.IntToGameName(GameId) : ""; - var copiedCode = GUIUtility.systemCopyBuffer; - - if (!Regex.IsMatch(copiedCode, @"^[a-zA-Z]+$") || copiedCode.Length > 6) + else if (Input.GetKeyDown(KeyCode.RightShift) && code2 != "") { - copiedCode = ""; + __instance.StartCoroutine(AmongUsClient.Instance.CoJoinOnlineGameFromCode(GameCode.GameNameToInt(code2))); + LobbyText.GetComponent().color = Color.AlphaMultiplied(0.75f); } - if (DataManager.Settings.Gameplay.StreamerMode) + if (LobbyText) { - lastCode = new string('*', lastCode.Length); - copiedCode = new string('*', copiedCode.Length); - } - var lastY = copiedCode == "" ? -3.8f : -3.6f; - LastRoomText.transform.localPosition = new Vector3(9.8f, lastY, 0); - lastCode = string.IsNullOrEmpty(lastCode) ? "" : lastCode.ToUpper(); - copiedCode = string.IsNullOrEmpty(copiedCode) ? "" : copiedCode.ToUpper(); + LobbyText.GetComponent().text = ""; + LeftShiftSprite.SetActive(false); + RightShiftSprite.SetActive(false); + KeyBindBackground.SetActive(false); + KeyBindBackground_Clone.SetActive(false); + if (GameId != 0 && GameId != 32) + { + var code = GameCode.IntToGameName(GameId); + + if (code != "") + { + code = DataManager.Settings.Gameplay.StreamerMode ? new string('*', code.Length) : code; + LeftShiftSprite.transform.localPosition = new Vector3(-1.9f, 2.1f, -1); + KeyBindBackground.transform.localPosition = new Vector3(LeftShiftSprite.transform.localPosition.x, + LeftShiftSprite.transform.localPosition.y, -0.5f); + KeyBindBackground.SetActive(true); + LeftShiftSprite.SetActive(true); + if (code != "" && code2 != "") + { + LeftShiftSprite.transform.localPosition = new Vector3(-1.9f, 2.4f, -1); + RightShiftSprite.transform.localPosition = new Vector3(-1.9f, 2.15f, -1); + KeyBindBackground.transform.localPosition = new Vector3( + LeftShiftSprite.transform.localPosition.x, LeftShiftSprite.transform.localPosition.y, + -0.5f); + KeyBindBackground_Clone.transform.localPosition = new Vector3( + RightShiftSprite.transform.localPosition.x, RightShiftSprite.transform.localPosition.y, + -0.5f); + LeftShiftSprite.SetActive(true); + RightShiftSprite.SetActive(true); + KeyBindBackground.SetActive(true); + KeyBindBackground_Clone.SetActive(true); + } - lastRoomTextComponent.text = lastCode != "" ? $" {GetString("LShift")}: {lastCode} " : ""; - copiedRoomTextComponent.text = copiedCode != "" ? $" {GetString("RShift")}: {copiedCode} " : ""; + LobbyText.GetComponent().text = + string.Format($"{GetString("LShift")}:{code}"); + } + } + + if (code2 != "") + { + RightShiftSprite.transform.localPosition = new Vector3(-1.9f, 2.1f, -1); + KeyBindBackground_Clone.transform.localPosition = new Vector3( + RightShiftSprite.transform.localPosition.x, RightShiftSprite.transform.localPosition.y, -0.5f); + RightShiftSprite.SetActive(true); + KeyBindBackground_Clone.SetActive(true); + LobbyText.GetComponent().text += + string.Format($"\n{GetString("RShift")}:{code2Disp}"); + } + } } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/MainMenuButtonHoverAnimation.cs b/FinalSuspect/Patches/System/MainMenuButtonHoverAnimation.cs index 22601c6f..82bef85d 100644 --- a/FinalSuspect/Patches/System/MainMenuButtonHoverAnimation.cs +++ b/FinalSuspect/Patches/System/MainMenuButtonHoverAnimation.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.Linq; +using FinalSuspect.ClientActions.FeatureItems.MainMenuStyle; +using FinalSuspect.Helpers; using Il2CppSystem; using UnityEngine; @@ -8,63 +8,59 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch] public class MainMenuButtonHoverAnimation { - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start)), HarmonyPostfix] + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPostfix] [HarmonyPriority(Priority.Last)] private static void Start_Postfix(MainMenuManager __instance) { var mainButtons = GameObject.Find("Main Buttons"); mainButtons.ForEachChild((Action)Init); - static void Init(GameObject obj) - { - if (obj.name is "BottomButtonBounds" or "Divider") return; - if (AllButtons.ContainsKey(obj)) return; - SetButtonStatus(obj, false); - var pb = obj.GetComponent(); - pb.OnMouseOver.AddListener((global::System.Action)(() => SetButtonStatus(obj, true))); - pb.OnMouseOut.AddListener((global::System.Action)(() => SetButtonStatus(obj, false))); - } } - private static Dictionary AllButtons = new(); private static void SetButtonStatus(GameObject obj, bool active) { - AllButtons.TryAdd(obj, (obj.transform.position, active)); - AllButtons[obj] = (AllButtons[obj].Item1, active); + ModMainMenuManager.AllButtons.TryAdd(obj, (obj.transform.position, active)); + ModMainMenuManager.AllButtons[obj] = (ModMainMenuManager.AllButtons[obj].Item1, active); } - public static bool Active = true; - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate)), HarmonyPostfix] + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate))] + [HarmonyPostfix] private static void Update_Postfix(MainMenuManager __instance) { - if (Input.GetKeyDown(KeyCode.Tab)) - { - VersionShowerStartPatch.ModLogo.SetActive(Active); - VersionShowerStartPatch.TeamLogo.SetActive(Active); - Active = !Active; - __instance.mainMenuUI.SetActive(Active); - VersionShowerStartPatch.CreditTextCredential.gameObject.SetActive(Active); - VersionShowerStartPatch.VisitText.gameObject.SetActive(Active); - DestroyableSingleton.Instance.gameObject.SetActive(Active); - TitleLogoPatch.ModStamp.SetActive(Active); - } - - if (GameObject.Find("MainUI") == null) return; + if (!GameObject.Find("MainUI")) return; - if (!ModNewsHistory.AnnouncementLoadComplete) - FormatButtonColor(__instance, __instance.newsButton, new Color(0.9f, 0.9f, 1f), new Color(0f, 0f, 0f, 0f), Color.white, Color.white); - else - FormatButtonColor(__instance, __instance.newsButton, new Color( 0.5216f, 0.7765f, 1f, 0.8f), new Color(0f, 0f, 0f, 0f), Color.white, Color.white); + var style = MainMenuStyleManager.MainMenuStyles[Main.CurrentStyleId.Value]; + FormatButtonColor(__instance, __instance.newsButton, + !ModNewsHistory.AnnouncementLoadComplete + ? ColorHelper.ConvertToLightGray(style.MainUIColors[1]) + : style.MainUIColors[1], new Color(0f, 0f, 0f, 0f), Color.white, Color.white); __instance.newsButton.enabled = ModNewsHistory.AnnouncementLoadComplete; - foreach (var kvp in AllButtons.Where(x => x.Key != null && x.Key.active)) + foreach (var (button, value) in ModMainMenuManager.AllButtons.Where(x => x.Key != null && x.Key.active)) { - var button = kvp.Key; var pos = button.transform.position; - var targetPos = kvp.Value.Item1 + new Vector3(kvp.Value.Item2 ? 0.35f : 0f, 0f, 0f); - if (kvp.Value.Item2 && pos.x > kvp.Value.Item1.x + 0.2f) continue; - button.transform.position = kvp.Value.Item2 + var targetPos = value.Item1 + new Vector3(value.Item2 ? 0.35f : 0f, 0f, 0f); + if (value.Item2 && pos.x > value.Item1.x + 0.2f) continue; + button.transform.position = value.Item2 ? Vector3.Lerp(pos, targetPos, Time.deltaTime * 2f) : Vector3.MoveTowards(pos, targetPos, Time.deltaTime * 2f); } } + + public static void RefreshButtons(GameObject obj) + { + ModMainMenuManager.AllButtons = new Dictionary(); + obj.ForEachChild((Action)Init); + } + + private static void Init(GameObject obj) + { + if (obj.name is "BottomButtonBounds" or "Divider") return; + if (ModMainMenuManager.AllButtons.ContainsKey(obj)) return; + SetButtonStatus(obj, false); + var pb = obj.GetComponent(); + pb.OnMouseOver.AddListener((global::System.Action)(() => SetButtonStatus(obj, true))); + pb.OnMouseOut.AddListener((global::System.Action)(() => SetButtonStatus(obj, false))); + } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/MainMenuManagerPatch.cs b/FinalSuspect/Patches/System/MainMenuManagerPatch.cs index 4cff6111..494d653c 100644 --- a/FinalSuspect/Patches/System/MainMenuManagerPatch.cs +++ b/FinalSuspect/Patches/System/MainMenuManagerPatch.cs @@ -6,6 +6,7 @@ using TMPro; using UnityEngine; using UnityEngine.UI; +using static FinalSuspect.Modules.Core.Plugin.ModMainMenuManager; using Object = UnityEngine.Object; namespace FinalSuspect.Patches.System; @@ -13,24 +14,23 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch] public class MainMenuManagerPatch { - public static MainMenuManager Instance { get; private set; } - - public static GameObject InviteButton; - public static GameObject GithubButton; //public static GameObject WebsiteButton; - public static GameObject UpdateButton; - public static GameObject PlayButton; [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenGameModeMenu))] [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenAccountMenu))] [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenCredits))] - [HarmonyPrefix, HarmonyPriority(Priority.Last)] - public static void ShowRightPanel() => ShowingPanel = true; + [HarmonyPrefix] + [HarmonyPriority(Priority.Last)] + public static void ShowRightPanel() + { + ShowingPanel = true; + } [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Show))] - [HarmonyPrefix, HarmonyPriority(Priority.Last)] + [HarmonyPrefix] + [HarmonyPriority(Priority.Last)] public static void HideRightPanel() { try @@ -47,54 +47,65 @@ public static void HideRightPanel() public static void ShowRightPanelImmediately() { ShowingPanel = true; - TitleLogoPatch.RightPanel.transform.localPosition = TitleLogoPatch.RightPanelOp; + RightPanel.transform.localPosition = RightPanelOp; Instance.OpenGameModeMenu(); } - private static bool isOnline; - public static bool ShowedBak; - public static bool ShowingPanel; - [HarmonyPatch(typeof(SignInStatusComponent), nameof(SignInStatusComponent.SetOnline)), HarmonyPostfix] - public static void SetOnline_Postfix() { _ = new LateTask(() => { isOnline = true; }, 0.1f, "Set Online Status"); } - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate)), HarmonyPostfix] - public static void MainMenuManager_LateUpdate() + [HarmonyPatch(typeof(SignInStatusComponent), nameof(SignInStatusComponent.SetOnline))] + [HarmonyPostfix] + public static void SetOnline_Postfix() + { + _ = new LateTask(() => { isOnline = true; }, 0.1f, "Set Online Status"); + } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate))] + [HarmonyPostfix] + public static void MainMenuManager_LateUpdate(MainMenuManager __instance) { CustomPopup.Update(); - if (GameObject.Find("MainUI") == null) ShowingPanel = false; - VersionShowerStartPatch.CreditTextCredential.gameObject.SetActive(!ShowingPanel && MainMenuButtonHoverAnimation.Active); + if (!GameObject.Find("MainUI")) ShowingPanel = false; + VersionShowerStartPatch.CreditTextCredential.gameObject.SetActive(!ShowingPanel && + Active); - if (TitleLogoPatch.RightPanel != null) + if (RightPanel) { - var pos1 = TitleLogoPatch.RightPanel.transform.localPosition; - var lerp1 = Vector3.Lerp(pos1, TitleLogoPatch.RightPanelOp + new Vector3(ShowingPanel ? 0f : 10f, 0f, 0f), Time.deltaTime * (ShowingPanel ? 3f : 2f)); + var pos1 = RightPanel.transform.localPosition; + var pos3 = new Vector3( + RightPanelOp.x * GetResolutionOffset(), + RightPanelOp.y, RightPanelOp.z); + var lerp1 = Vector3.Lerp(pos1, ShowingPanel ? pos3 : RightPanelOp + new Vector3(10f, 0f, 0f), + Time.deltaTime * (ShowingPanel ? 3f : 2f)); if (ShowingPanel - ? TitleLogoPatch.RightPanel.transform.localPosition.x > TitleLogoPatch.RightPanelOp.x + 0.03f - : TitleLogoPatch.RightPanel.transform.localPosition.x < TitleLogoPatch.RightPanelOp.x + 9f - ) TitleLogoPatch.RightPanel.transform.localPosition = lerp1; + ? RightPanel.transform.localPosition.x > pos3.x + 0.03f + : RightPanel.transform.localPosition.x < RightPanelOp.x + 9f + ) RightPanel.transform.localPosition = lerp1; } + if (ShowedBak || !isOnline) return; var bak = GameObject.Find("BackgroundTexture"); - if (bak == null || !bak.active) return; + if (!bak || !bak.active) return; var pos2 = bak.transform.position; var lerp2 = Vector3.Lerp(pos2, new Vector3(pos2.x, 7.1f, pos2.z), Time.deltaTime * 1.4f); bak.transform.position = lerp2; if (pos2.y > 7f) ShowedBak = true; } - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start)), HarmonyPostfix] + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPostfix] public static void Start_Postfix(MainMenuManager __instance) { Instance = __instance; SimpleButton.SetBase(__instance.quitButton); - var row = 1; var col = 0; + var row = 1; + var col = 0; var extraLinkName = IsChineseUser ? "QQ群" : "Discord"; var extraLinkUrl = IsChineseUser ? Main.QQInviteUrl : Main.DiscordInviteUrl; - if (InviteButton == null) InviteButton = CreatButton(extraLinkName, () => { Application.OpenURL(extraLinkUrl); }); + if (!InviteButton) InviteButton = CreatButton(extraLinkName, () => { Application.OpenURL(extraLinkUrl); }); InviteButton.gameObject.SetActive(true); InviteButton.name = "FinalSuspect Extra Link Button"; @@ -102,12 +113,12 @@ public static void Start_Postfix(MainMenuManager __instance) //WebsiteButton.gameObject.SetActive(true); //WebsiteButton.name = "FinalSuspect Website Button"; - if (GithubButton == null) GithubButton = CreatButton("Github", () => Application.OpenURL(Main.GithubRepoUrl)); + if (!GithubButton) GithubButton = CreatButton("Github", () => Application.OpenURL(Main.GithubRepoUrl)); GithubButton.gameObject.SetActive(true); GithubButton.name = "FinalSuspect Github Button"; PlayButton = __instance.playButton.gameObject; - if (UpdateButton == null) + if (!UpdateButton) { UpdateButton = Object.Instantiate(PlayButton, PlayButton.transform.parent); UpdateButton.name = "FinalSuspect Update Button"; @@ -120,27 +131,28 @@ public static void Start_Postfix(MainMenuManager __instance) { PlayButton.SetActive(true); UpdateButton.SetActive(false); - if (!DebugModeManager.AmDebugger || !Input.GetKey(KeyCode.LeftShift)) - { - if (VersionChecker.CanUpdate) - { - ModUpdater.StartUpdate(); - } - else - { - CustomPopup.Show(GetString("UpdateBySelfTitle"), GetString("UpdateBySelfText"), - [(GetString(StringNames.Okay), null)]); - } - } + if (DebugModeManager.IsDebugMode && Input.GetKey(KeyCode.LeftShift)) return; + if (VersionChecker.CanUpdate) + ModUpdater.StartUpdate(); + else + CustomPopup.Show(GetString("UpdateRemind.BySelf_Title"), GetString("UpdateRemind.BySelf_Text"), + [(GetString(StringNames.Okay), null)]); })); UpdateButton.transform.transform.FindChild("FontPlacer").GetChild(0).gameObject.DestroyTranslator(); } + Application.targetFrameRate = Main.UnlockFPS.Value ? 165 : 60; return; GameObject CreatButton(string text, Action action) { - col++; if (col > 2) { col = 1; row++; } + col++; + if (col > 2) + { + col = 1; + row++; + } + var template = col == 1 ? __instance.creditsButton.gameObject : __instance.quitButton.gameObject; var button = Object.Instantiate(template, template.transform.parent); button.transform.transform.FindChild("FontPlacer").GetChild(0).gameObject.DestroyTranslator(); @@ -151,6 +163,9 @@ GameObject CreatButton(string text, Action action) passiveButton.OnClick.AddListener(action); var aspectPosition = button.GetComponent(); aspectPosition.anchorPoint = new Vector2(col == 1 ? 0.415f : 0.583f, 0.5f - 0.08f * row); + var scale = button.transform.localScale; + button.transform.localScale = new Vector3(scale.x * GetResolutionOffset(), button.transform.localScale.y); + MainMenuCustomButtons.Add(button); return button; } } diff --git a/FinalSuspect/Patches/System/MeetingButtonPatch.cs b/FinalSuspect/Patches/System/MeetingButtonPatch.cs new file mode 100644 index 00000000..ac7fb5b3 --- /dev/null +++ b/FinalSuspect/Patches/System/MeetingButtonPatch.cs @@ -0,0 +1,86 @@ +using System; +using FinalSuspect.Helpers; +using UnityEngine; +using UnityEngine.UI; +using static FinalSuspect.Modules.Features.DisplayedRoleTag.DisplayerRoleTagHelper; +using Object = UnityEngine.Object; + +namespace FinalSuspect.Patches.System; + +[HarmonyPatch(typeof(MeetingHud))] +public class MeetingButtonManager +{ + private static int Count; + private static bool ButtonCreated; + + private static void ClearMeetingButton(MeetingHud __instance) + => __instance.playerStates.ToList().ForEach(x => + { + if (x.transform.FindChild("Custom Meeting Button") != null) + Object.Destroy(x.transform.FindChild("Custom Meeting Button").gameObject); + }); + + [HarmonyPatch(nameof(MeetingHud.Start)), HarmonyPrefix] + [HarmonyPriority(Priority.First)] + public static void Start(MeetingHud __instance) + { + textTemplate = Object.Instantiate(__instance.playerStates[0]!.NameText); + textTemplate.enabled = false; + + ButtonCreated = false; + CreateMeetingButton(__instance); + } + + [HarmonyPatch(nameof(MeetingHud.Update)), HarmonyPostfix, HarmonyPriority(Priority.LowerThanNormal)] + public static void Update(MeetingHud __instance) + { + if (__instance == null || !IsInGame || __instance.IsDestroyedOrNull()) return; + + Count = Count > 20 ? 0 : ++Count; + if (Count != 0) return; + + if (!IsInMeeting && __instance.lastSecond < 1) + { + if (GameObject.Find("Custom Meeting Button") != null) ClearMeetingButton(__instance); + return; + } + + if (!ButtonCreated) + { + CreateMeetingButton(__instance); + } + } + + private static void CreateMeetingButton(MeetingHud __instance) + { + foreach (var pva in __instance.playerStates) + { + var pc = GetPlayerById(pva.TargetPlayerId); + if (pc == null) continue; + var template = pva.Buttons.transform.Find("CancelButton").gameObject; + var targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "Custom Meeting Button"; + targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.31f); + + var highlight = targetBox.transform.FindChild("ControllerHighlight").gameObject; + highlight.GetComponent().color = + ColorHelper.FSColor; + highlight.transform.localPosition = new Vector3(0f, 0f, 1.4f); + + var renderer = targetBox.GetComponent(); + renderer.sprite = LoadSprite("EditTag.png", 115f); + var button = targetBox.GetComponent(); + button.OnClick = new Button.ButtonClickedEvent(); + button.OnClick.AddListener((Action)(() => { ShowSelectionPanel(__instance, pc); })); + } + + ButtonCreated = true; + } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy)), HarmonyPostfix] + public static void OnDestroy() + { + if (textTemplate != null && textTemplate.gameObject != null) + Object.Destroy(textTemplate.gameObject); + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/FilePatch.cs b/FinalSuspect/Patches/System/Others/FilePatch.cs similarity index 71% rename from FinalSuspect/Patches/System/FilePatch.cs rename to FinalSuspect/Patches/System/Others/FilePatch.cs index 016bed54..efcc6c9b 100644 --- a/FinalSuspect/Patches/System/FilePatch.cs +++ b/FinalSuspect/Patches/System/Others/FilePatch.cs @@ -1,8 +1,8 @@ using System.IO; -namespace FinalSuspect.Patches.System; +namespace FinalSuspect.Patches.System.Others; -public class FilePatch +public static class FilePatch { [HarmonyPatch(typeof(File), nameof(File.Delete))] public static bool Prefix([HarmonyArgument(0)] string path) diff --git a/FinalSuspect/Patches/System/Others/HazelPatch.cs b/FinalSuspect/Patches/System/Others/HazelPatch.cs new file mode 100644 index 00000000..bc44ed50 --- /dev/null +++ b/FinalSuspect/Patches/System/Others/HazelPatch.cs @@ -0,0 +1,15 @@ +using Hazel; + +namespace FinalSuspect.Patches.System.Others; + +[HarmonyPatch] +internal class HazelPatch +{ + [HarmonyPatch(typeof(MessageReader), nameof(MessageReader.ReadPackedUInt32))] + [HarmonyPriority(Priority.First)] + [HarmonyPrefix] + public static bool Read(MessageReader __instance) + { + return __instance.BytesRemaining >= 1; + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/Others/UnityEnginePatch.cs b/FinalSuspect/Patches/System/Others/UnityEnginePatch.cs new file mode 100644 index 00000000..3c46fe64 --- /dev/null +++ b/FinalSuspect/Patches/System/Others/UnityEnginePatch.cs @@ -0,0 +1,19 @@ +using Object = UnityEngine.Object; + +namespace FinalSuspect.Patches.System.Others; + +[HarmonyPatch(typeof(Object), nameof(Object.Destroy), typeof(Object))] +public class UnityEnginePatch +{ + public static bool Prefix([HarmonyArgument(0)] Object obj) + { + try + { + return obj.name is not "LobbyInfoPane" and not "GameStartManager" || IsFreePlay || IsNotJoined; + } + catch + { + return true; + } + } +} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/PhasePatch.cs b/FinalSuspect/Patches/System/PhasePatch.cs index e5b036d4..46953885 100644 --- a/FinalSuspect/Patches/System/PhasePatch.cs +++ b/FinalSuspect/Patches/System/PhasePatch.cs @@ -1,58 +1,84 @@ using FinalSuspect.Attributes; +using FinalSuspect.DataHandling.FinalGameData; namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] -class ShipStatusStartPatch +public class ShipStatusStartPatch { public static void Postfix() { Info("-----------游戏开始-----------", "Phase"); } } + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] -class AmongUsClientOnGameEndPatch +public class AmongUsClientOnGameEndPatch { public static void Postfix() { - InGame = false; + UpdateGameState_IsInGame(false); Info("-----------游戏结束-----------", "Phase"); } } + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] [HarmonyPriority(Priority.First)] -class MeetingHudStartPatch +public class MeetingHudStartPatch { public static void Prefix() { + _ = new LateTask(() => UpdateGameState_IsInMeeting(true), 1f, "UpdateGameState_IsInMeeting"); Info("------------会议开始------------", "Phase"); } } + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))] -class MeetingHudOnDestroyPatch +internal class MeetingHudOnDestroyPatch { public static void Postfix() { + UpdateGameState_IsInMeeting(false); Info("------------会议结束------------", "Phase"); } } + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGame))] internal class CoStartGamePatch { public static void Postfix() { - IntroCutsceneOnDestroyPatch.IntroDestroyed = false; GameModuleInitializerAttribute.InitializeAll(); } - } + +/*[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGameHost))] +internal class CoStartGameHPatch +{ + public static void Prefix() + { + foreach (var client in AmongUsClient.Instance.allClients) + { + client.IsReady = true; + } + } + + public static void Postfix() + { + var clientData = GetPlayerById(1).GetFinalData().CheatData.ClientData; + + AmongUsClient.Instance.SendLateRejection(clientData.Id, DisconnectReasons.ClientTimeout); + clientData.IsReady = true; + AmongUsClient.Instance.OnPlayerLeft(clientData, DisconnectReasons.ClientTimeout); + } +}*/ + [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] public static class IntroCutsceneOnDestroyPatch { - public static bool IntroDestroyed; public static void Postfix() { - IntroDestroyed = true; + FinalGameData.IntroDestroyed = true; Info("OnDestroy", "IntroCutscene"); } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/PlayerJoinAndLeftPatch.cs b/FinalSuspect/Patches/System/PlayerJoinAndLeftPatch.cs deleted file mode 100644 index 062d360d..00000000 --- a/FinalSuspect/Patches/System/PlayerJoinAndLeftPatch.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using AmongUs.Data; -using FinalSuspect.DataHandling.FinalAntiCheat.Core; -using FinalSuspect.Helpers; -using FinalSuspect.Modules.Core.Game; -using FinalSuspect.Modules.Features.CheckingandBlocking; -using FinalSuspect.Patches.Game_Vanilla; -using InnerNet; - -namespace FinalSuspect.Patches.System; - -[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] -class OnGameJoinedPatch -{ - public static void Postfix(AmongUsClient __instance) - { - HudManagerPatch.Init(); - - Info($"{__instance.GameId} 加入房间", "OnGameJoined"); - XtremeGameData.PlayerVersion.playerVersion = new Dictionary(); - SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); - XtremePlayerData.InitializeAll(); - RPC.RpcVersionCheck(); - InGame = false; - ErrorText.Instance.Clear(); - ServerAddManager.SetServerName(); - - Init(); - if (AmongUsClient.Instance.AmHost) - { - GameStartManagerPatch.GameStartManagerUpdatePatch.exitTimer = -1; - //Main.NewLobby = true; - } - } -} -[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))] -class DisconnectInternalPatch -{ - public static void Prefix(InnerNetClient __instance, DisconnectReasons reason, string stringReason) - { - try - { - ShowDisconnectPopupPatch.Reason = reason; - ShowDisconnectPopupPatch.StringReason = stringReason; - - Info($"断开连接(理由:{reason}:{stringReason},Ping:{__instance.Ping})", "Session"); - HudManagerPatch.Init(); - XtremePlayerData.DisposeAll(); - - ErrorText.Instance.CheatDetected = false; - ErrorText.Instance.SBDetected = false; - ErrorText.Instance.Clear(); - //Cloud.StopConnect(); - } - catch - { - /* ignored */ - } - } -} -[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] -public class OnPlayerJoinedPatch -{ - private static readonly Regex ValidFormatRegex = new( - @"^[A-Za-z]+#\d{4}$", - RegexOptions.Compiled - ); - public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData client) - { - Info($"{client.PlayerName}(ClientID:{client.Id}/FriendCode:{client.FriendCode}) 加入房间", "Session"); - if (AmongUsClient.Instance.AmHost && client.FriendCode == "" && Main.KickPlayerWhoFriendCodeNotExist.Value) - { - KickPlayer(client.Id, false, "NotLogin"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("Message.KickedByNoFriendCode"), client.PlayerName)); - Info($"没有好友代码的玩家 {client.PlayerName} 已被踢出。", "Kick"); - } - if (DestroyableSingleton.Instance.IsPlayerBlockedUsername(client.FriendCode) && AmongUsClient.Instance.AmHost && Main.KickPlayerInBanList.Value) - { - KickPlayer(client.Id, true, "BanList"); - Info($"已封锁的玩家 {client.PlayerName} ({client.FriendCode}) 已被封禁。", "BAN"); - } - if (AmongUsClient.Instance.AmHost && !ValidFormatRegex.IsMatch(client.FriendCode)) - { - KickPlayer(client.Id, false, "NotLogin"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("Warning.Cheater"), client.PlayerName)); - Info($"没有好友代码的玩家 {client.PlayerName} 已被踢出。", "Kick"); - } - if (client.PlayerName.Contains("1337")) - { - KickPlayer(client.Id, false, "NotLogin"); - NotificationPopperPatch.NotificationPop(string.Format(GetString("Warning.Cheater"), client.PlayerName)); - Info($"Kami玩家 {client.PlayerName} 已被踢出。", "Kick"); - } - BanManager.CheckBanPlayer(client); - BanManager.CheckDenyNamePlayer(client); - - RPC.RpcVersionCheck(); - } -} - -[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] -class OnPlayerLeftPatch -{ - public static readonly List ClientsProcessed = []; - public static void Add(int id) - { - ClientsProcessed.Remove(id); - ClientsProcessed.Add(id); - } - public static void Postfix([HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) - { - try - { - if (data == null) - { - Error("错误的客户端数据:数据为空", "Session"); - return; - } - - data.Character?.SetDisconnected(); - - Info($"{data.PlayerName}(ClientID:{data.Id}/FriendCode:{data.FriendCode})断开连接(理由:{reason},Ping:{AmongUsClient.Instance.Ping})", "Session"); - var id = data.Character?.Data?.DefaultOutfit?.ColorId ?? XtremePlayerData.AllPlayerData - .Where(playerData => playerData.CheatData.ClientData.Id == data.Id).FirstOrDefault()!.ColorId; - var color = Palette.PlayerColors[id]; - var name = StringHelper.ColorString(color, data.PlayerName); - // 附加描述掉线原因 - switch (reason) - { - case DisconnectReasons.Hacking: - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerLeftByAU-Anticheat"), name)); - break; - case DisconnectReasons.Error: - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerLeftCuzError"), name)); - break; - case DisconnectReasons.Kicked: - case DisconnectReasons.Banned: - break; - case DisconnectReasons.ExitGame: - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerLeft"), name)); - break; - case DisconnectReasons.ClientTimeout: - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerLeftCuzTimeout"), name)); - break; - default: - if (!ClientsProcessed.Contains(data.Id)) - NotificationPopperPatch.NotificationPop(string.Format(GetString("PlayerLeft"), name)); - break; - } - - Dispose(data.Character?.PlayerId ?? 255); - - XtremeGameData.PlayerVersion.playerVersion.Remove(data.Character?.PlayerId ?? 255); - ClientsProcessed.Remove(data.Id); - XtremePlayerData.AllPlayerData.Do(_data => _data.AdjustPlayerId()); - } - catch - { - /* ignored */ - } - } -} \ No newline at end of file diff --git a/FinalSuspect/Patches/System/RegionMenuPatch.cs b/FinalSuspect/Patches/System/RegionMenuPatch.cs index 2a68ab7a..c0f4176d 100644 --- a/FinalSuspect/Patches/System/RegionMenuPatch.cs +++ b/FinalSuspect/Patches/System/RegionMenuPatch.cs @@ -6,12 +6,13 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch(typeof(RegionMenu))] public static class RegionMenuPatch { - public static Scroller Scroller; + private static Scroller Scroller; - [HarmonyPatch(nameof(RegionMenu.Awake)), HarmonyPostfix] + [HarmonyPatch(nameof(RegionMenu.Awake))] + [HarmonyPostfix] public static void Awake_Postfix(RegionMenu __instance) { - if (Scroller != null) return; + if (Scroller) return; var back = __instance.ButtonPool.transform.FindChild("Backdrop"); back.transform.localScale *= 10f; @@ -25,7 +26,11 @@ public static void Awake_Postfix(RegionMenu __instance) Scroller.SetYBoundsMax(4f); Scroller.allowY = true; } - [HarmonyPatch(nameof(RegionMenu.ChooseOption)), HarmonyPostfix] + + [HarmonyPatch(nameof(RegionMenu.ChooseOption))] + [HarmonyPostfix] public static void ChooseOption_Postfix() - => ServerAddManager.SetServerName(); + { + ServerAddManager.SetServerName(); + } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/SwitchShipCostumeButtonPatch.cs b/FinalSuspect/Patches/System/SwitchShipCostumeButtonPatch.cs index d0678953..931bf209 100644 --- a/FinalSuspect/Patches/System/SwitchShipCostumeButtonPatch.cs +++ b/FinalSuspect/Patches/System/SwitchShipCostumeButtonPatch.cs @@ -1,4 +1,5 @@ -using FinalSuspect.Modules.Features.CheckingandBlocking; +using FinalSuspect.ClientActions.FeatureItems.MyMusic; +using FinalSuspect.Modules.Features.CheckingandBlocking; using UnityEngine; namespace FinalSuspect.Patches.System; @@ -6,46 +7,52 @@ namespace FinalSuspect.Patches.System; [HarmonyPatch] public class SwitchShipCostumeButtonPatch { - public static int Costume; - public static GameObject SwitchShipCostumeButton; - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Awake)), HarmonyPostfix] + private static int Costume; + private static GameObject SwitchShipCostumeButton; + + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Awake))] + [HarmonyPostfix] public static void ShipStatusFixedUpdate(ShipStatus __instance) { var mapId = GameOptionsManager.Instance.CurrentGameOptions.MapId; if (mapId != 0) { - if (SwitchShipCostumeButton != null) + if (SwitchShipCostumeButton) Object.Destroy(SwitchShipCostumeButton); SwitchShipCostumeButton = null; return; } - if (SwitchShipCostumeButton == null) - { - var template = __instance.EmergencyButton.gameObject; - SwitchShipCostumeButton = Object.Instantiate(template, template.transform.parent); - SwitchShipCostumeButton.name = "Switch Ship Costume Button"; - SwitchShipCostumeButton.transform.localScale = new Vector3(0.65f, 0.65f, 1f); - SwitchShipCostumeButton.transform.localPosition = new Vector3(-9.57f, -5.36f, -10f); - var console = SwitchShipCostumeButton.GetComponent(); - console.Image.color = new Color32(80, 255, 255, byte.MaxValue); - console.usableDistance /= 2; - console.name = "Switch Ship Costume Console"; - } + + if (SwitchShipCostumeButton) return; + var template = __instance.EmergencyButton.gameObject; + SwitchShipCostumeButton = Object.Instantiate(template, template.transform.parent); + SwitchShipCostumeButton.name = "Switch Ship Costume Button"; + SwitchShipCostumeButton.transform.localScale = new Vector3(0.65f, 0.65f, 1f); + SwitchShipCostumeButton.transform.localPosition = new Vector3(-9.57f, -5.36f, -10f); + var console = SwitchShipCostumeButton.GetComponent(); + console.Image.color = new Color32(80, 255, 255, byte.MaxValue); + console.usableDistance /= 2; + console.name = "Switch Ship Costume Console"; } - [HarmonyPatch(typeof(SystemConsole), nameof(SystemConsole.Use)), HarmonyPrefix] + + [HarmonyPatch(typeof(SystemConsole), nameof(SystemConsole.Use))] + [HarmonyPrefix] public static bool UseConsole(SystemConsole __instance) { if (__instance.name != "Switch Ship Costume Console") return true; Costume++; if (Costume > 2) Costume = 0; - ShipStatus.Instance.gameObject.transform.FindChild("HelloweenDecorSkeld")?.gameObject.SetActive(Costume == 1); + ShipStatus.Instance.gameObject.transform.FindChild("HalloweenDecorSkeld")?.gameObject.SetActive(Costume == 1); ShipStatus.Instance.gameObject.transform.FindChild("BirthdayDecorSkeld")?.gameObject.SetActive(Costume == 2); - var sounds = Sounds.TaskComplete; - if (Costume == 0) sounds = Sounds.KillSound; - if (Costume == 1) sounds = Sounds.ImpTransform; - if (Costume == 2) sounds = Sounds.TaskUpdateSound; - Modules.SoundInterface.SoundManager.PlaySound(PlayerControl.LocalPlayer.PlayerId, sounds); + var sounds = Costume switch + { + 0 => Sounds.KillSound, + 1 => Sounds.ImpTransform, + 2 => Sounds.TaskUpdateSound, + _ => Sounds.TaskComplete + }; + AudioManager.PlaySound(PlayerControl.LocalPlayer.PlayerId, sounds); return false; } } \ No newline at end of file diff --git a/FinalSuspect/Patches/System/VoteBanSystemPatch.cs b/FinalSuspect/Patches/System/VoteBanSystemPatch.cs deleted file mode 100644 index e182ca93..00000000 --- a/FinalSuspect/Patches/System/VoteBanSystemPatch.cs +++ /dev/null @@ -1,14 +0,0 @@ -using QRCoder; - -namespace FinalSuspect.Patches.System; - -[HarmonyPatch] -public class VoteBanSystemPatch -{ - // thanks: NikoCat233 - [HarmonyPatch(typeof(VoteBanSystem), nameof(VoteBanSystem.AddVote)), HarmonyPrefix] - public static bool AddVote(VoteBanSystem __instance) - { - return false; - } -} \ No newline at end of file diff --git a/FinalSuspect/Templates/SimpleButton.cs b/FinalSuspect/Templates/SimpleButton.cs index dc6a6137..f2ecd225 100644 --- a/FinalSuspect/Templates/SimpleButton.cs +++ b/FinalSuspect/Templates/SimpleButton.cs @@ -9,15 +9,21 @@ namespace FinalSuspect.Templates; public class SimpleButton { - /// 新しいボタンを作成する - /// 親オブジェクト - /// オブジェクト名 - /// - /// 通常時の背景色 - /// マウスホバー時の背景色 - /// クリック時に発火するアクション - /// ボタンのラベル - /// 初期状態でアクティブにするかどうか(デフォルトtrue) + private static PassiveButton baseButton; + private readonly BoxCollider2D buttonCollider; + + private float _fontSize; + private Vector2 _scale; + + /// 创建新按钮 + /// 父对象 + /// 对象名称 + /// 本地坐标 + /// 正常状态背景色 + /// 鼠标悬停时背景色 + /// 点击时触发的动作 + /// 按钮标签文本 + /// 初始激活状态(默认true) public SimpleButton( Transform parent, string name, @@ -28,10 +34,7 @@ public SimpleButton( string label, bool isActive = true) { - if (baseButton == null) - { - throw new InvalidOperationException("baseButtonが未設定"); - } + if (!baseButton) throw new InvalidOperationException("baseButtonが未設定"); Button = Object.Instantiate(baseButton, parent); Label = Button.transform.Find("FontPlacer/Text_TMP").GetComponent(); @@ -39,7 +42,6 @@ public SimpleButton( HoverSprite = Button.activeSprites.GetComponent(); buttonCollider = Button.GetComponent(); - // ラベルをセンタリング var container = Label.transform.parent; Object.Destroy(Label.GetComponent()); container.SetLocalX(0f); @@ -54,49 +56,49 @@ public SimpleButton( Label.text = label; Button.gameObject.SetActive(isActive); } + public PassiveButton Button { get; } public TextMeshPro Label { get; } - public SpriteRenderer NormalSprite { get; } - public SpriteRenderer HoverSprite { get; } - private readonly BoxCollider2D buttonCollider; - private Vector2 _scale; + private SpriteRenderer NormalSprite { get; } + private SpriteRenderer HoverSprite { get; } + public Vector2 Scale { get => _scale; set => _scale = NormalSprite.size = HoverSprite.size = buttonCollider.size = value; } - private float _fontSize; + public float FontSize { get => _fontSize; set => _fontSize = Label.fontSize = Label.fontSizeMin = Label.fontSizeMax = value; } - private static PassiveButton baseButton; public static void SetBase(PassiveButton passiveButton) { - if (baseButton != null || passiveButton == null) - { - return; - } - // 複製 + if (baseButton || !passiveButton) return; + + // 复制按钮 baseButton = Object.Instantiate(passiveButton); var label = baseButton.transform.Find("FontPlacer/Text_TMP").GetComponent(); baseButton.gameObject.SetActive(false); - // シーン切替時に破棄されないようにする + // 防止场景切换时被销毁 Object.DontDestroyOnLoad(baseButton); baseButton.name = "FinalSuspect_SimpleButtonBase"; - // 不要なコンポーネントを無効化 + // 移除不需要的组件 Object.Destroy(baseButton.GetComponent()); label.DestroyTranslator(); label.fontSize = label.fontSizeMax = label.fontSizeMin = 3.5f; label.enableWordWrapping = false; label.text = "FinalSuspect SIMPLE BUTTON BASE"; - // 当たり判定がズレてるのを直す + // 修复碰撞体偏移问题 var buttonCollider = baseButton.GetComponent(); buttonCollider.offset = new Vector2(0f, 0f); baseButton.OnClick = new Button.ButtonClickedEvent(); } - - public static bool IsNullOrDestroyed(SimpleButton button) => button == null || button.Button == null; -} + + public static bool IsNullOrDestroyed(SimpleButton button) + { + return button == null || !button.Button; + } +} \ No newline at end of file diff --git a/FinalSuspect/Templates/TMPTemplate.cs b/FinalSuspect/Templates/TMPTemplate.cs index 490ec42b..1d6bec6b 100644 --- a/FinalSuspect/Templates/TMPTemplate.cs +++ b/FinalSuspect/Templates/TMPTemplate.cs @@ -3,12 +3,13 @@ namespace FinalSuspect.Templates; -public sealed class TMPTemplate +public static class TMPTemplate { private static TextMeshPro baseTMP; + public static void SetBase(TextMeshPro tmp) { - if (baseTMP != null) return; + if (baseTMP) return; baseTMP = Object.Instantiate(tmp); Object.Destroy(baseTMP.GetComponent()); @@ -16,6 +17,7 @@ public static void SetBase(TextMeshPro tmp) baseTMP.gameObject.SetActive(false); baseTMP.gameObject.name = "TMPTemplateBase"; } + public static TextMeshPro Create( string name, string text = null, @@ -26,7 +28,7 @@ public static TextMeshPro Create( Transform parent = null ) { - var replicatedObject = parent == null + var replicatedObject = !parent ? Object.Instantiate(baseTMP) : Object.Instantiate(baseTMP, parent); replicatedObject.text = text ?? ""; diff --git a/FinalSuspect/main.cs b/FinalSuspect/main.cs index 0b9edd58..9713e88b 100644 --- a/FinalSuspect/main.cs +++ b/FinalSuspect/main.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using System.Threading.Tasks; using AmongUs.GameOptions; using BepInEx; @@ -10,12 +7,16 @@ using BepInEx.Unity.IL2CPP; using FinalSuspect; using FinalSuspect.Attributes; +using FinalSuspect.DataHandling.FinalAntiCheat.Core; using FinalSuspect.Helpers; using FinalSuspect.Internal; +using FinalSuspect.Modules.Core.Game.PlayerControlExtension; using FinalSuspect.Modules.Random; using Il2CppInterop.Runtime.Injection; using UnityEngine; +// ReSharper disable MemberCanBePrivate.Global + [assembly: AssemblyFileVersion(Main.PluginVersion)] [assembly: AssemblyInformationalVersion(Main.PluginVersion)] [assembly: AssemblyVersion(Main.PluginVersion)] @@ -29,52 +30,40 @@ public class Main : BasePlugin // == 程序基本设定 / Program Config == public const string ModName = "Final Suspect"; public const string ForkId = "Final Suspect"; - public const string PluginVersion = "1.1.3"; - public const string PluginGuid = "cn.finalsuspect.xtremewave"; + public const string PluginVersion = "1.2.0"; + public const string PluginGuid = "cn.slok.finalsuspect"; public const int PluginCreation = 0; - - // == 认证设定 / Authentication Config == - public static HashAuth DebugKeyAuth { get; private set; } public const string DebugKeyHash = "c0fd562955ba56af3ae20d7ec9e64c664f0facecef4b3e366e109306adeae29d"; public const string DebugKeySalt = "59687b"; - public static ConfigEntry DebugKeyInput { get; private set; } + // == 版本相关设定 / Version Config == - public const string LowestSupportedVersion = "2025.3.31"; // 16.0.2 + public const string LowestSupportedVersion = "2025.6.10"; // 16.1.0 + + private const string DisplayedVersion_Head = "1.2"; - public const string DisplayedVersion_Head = "1.1"; - - private static string DisplayedVersion_Date => BuildTime.Date; + private const string DisplayedVersion_Date = BuildTime.Date; /// - /// 测试信息; - /// 支持的内容:Alpha, Beta, Canary, Dev, RC, Preview, Scrapter - /// Alpha: 早期内测版 - /// Beta: 内测版 - /// Canary: 测试版(不稳定) - /// Dev: 开发版 - /// RC: 发行候选版Release Candidate - /// Preview: 预览/预发行版 - /// Scrapter: 废弃版 + /// 表示当前显示的版本类型。 /// - private const VersionTypes DisplayedVersion_Type = VersionTypes.Canary; + private const VersionTypes DisplayedVersion_Type = VersionTypes.RC; + + private const int DisplayedVersion_TestCreation = 3; - private const int DisplayedVersion_TestCreation = 1; - - public static readonly string DisplayedVersion = - $"{DisplayedVersion_Head}_{DisplayedVersion_Date}" + - $"{(DisplayedVersion_Type != VersionTypes.Release - ? $"_{DisplayedVersion_Type}_{DisplayedVersion_TestCreation}" - : "") - }"; // == 链接相关设定 / Link Config == - //public static readonly string WebsiteUrl = IsChineseLanguageUser ? "https://www.xtreme.net.cn/project/FS/" : "https://www.xtreme.net.cn/en/project/FS/"; + //public static readonly string WebsiteUrl = IsChineseLanguageUser ? "https://www.Final.net.cn/project/FS/" : "https://www.Final.net.cn/en/project/FS/"; public const string QQInviteUrl = "https://qm.qq.com/q/GNbm9UjfCa"; public const string DiscordInviteUrl = "https://discord.gg/kz787Zg7h8/"; - public const string GithubRepoUrl = "https://github.com/XtremeWave/FinalSuspect/"; + public const string GithubRepoUrl = "https://github.com/Slok7565/FinalSuspect/"; + public const float RoleTextSize = 2f; - // ========== - public Harmony Harmony { get; } = new(PluginGuid); + public static readonly string DisplayedVersion = +#if RELEASE + $"{DisplayedVersion_Head}_{DisplayedVersion_Date}"; +#else + $"{DisplayedVersion_Head}_{DisplayedVersion_Date}_{DisplayedVersion_Type}_{DisplayedVersion_TestCreation}"; +#endif public static readonly Version version = Version.Parse(PluginVersion); public static ManualLogSource Logger; public static bool hasArgumentException; @@ -82,64 +71,27 @@ public class Main : BasePlugin public static bool ExceptionMessageIsShown; public static string CredentialsText; -#pragma warning disable CS0618 // 类型或成员已过时 - public const string GitBaseTag = ThisAssembly.Git.BaseTag; - - public const string GitCommit = ThisAssembly.Git.Commit; - public const string GitCommits = ThisAssembly.Git.Commits; - public const string GitBranch = ThisAssembly.Git.Branch; - public const bool GitIsDirty = ThisAssembly.Git.IsDirty; - public const string GitSha = ThisAssembly.Git.Sha; - public const string GitTag = ThisAssembly.Git.Tag; -#pragma warning restore CS0618 - public static NormalGameOptionsV09 NormalOptions => GameOptionsManager.Instance.currentNormalGameOptions; - public static HideNSeekGameOptionsV09 HideNSeekOptions => GameOptionsManager.Instance.currentHideNSeekGameOptions; - - //Client Options - public static ConfigEntry KickPlayerWhoFriendCodeNotExist { get; private set; } - public static ConfigEntry KickPlayerWithDenyName { get; private set; } - public static ConfigEntry KickPlayerInBanList { get; private set; } - public static ConfigEntry SpamDenyWord { get; private set; } - public static ConfigEntry UnlockFPS { get; private set; } - public static ConfigEntry ChangeOutfit { get; private set; } - public static ConfigEntry AutoStartGame { get; private set; } - public static ConfigEntry AutoEndGame { get; private set; } - public static ConfigEntry DisableVanillaSound { get; private set; } - public static ConfigEntry DisableFAC { get; private set; } - //public static ConfigEntry PrunkMode { get; private set; } - public static ConfigEntry ShowPlayerInfo { get; private set; } - public static ConfigEntry UseModCursor { get; private set; } - public static ConfigEntry FastBoot { get; private set; } - public static ConfigEntry VersionCheat { get; private set; } - public static ConfigEntry GodMode { get; private set; } - public static ConfigEntry NoGameEnd { get; private set; } - - public static readonly string[] OutfitType = - [ - "BeanMode", "HorseMode", "LongMode" - ]; - - //Other Configs - public static ConfigEntry HideName { get; private set; } - public static ConfigEntry HideColor { get; private set; } - public static ConfigEntry ShowResults { get; private set; } - public static ConfigEntry WebhookURL { get; private set; } - public static ConfigEntry EnableFinalSuspect { get; private set; } - public static ConfigEntry LastStartVersion { get; private set; } + public static readonly Dictionary roleColors = new() + { + { RoleTypes.CrewmateGhost, "#8CFFFF" }, + { RoleTypes.GuardianAngel, "#8CFFDB" }, + { RoleTypes.Crewmate, "#8CFFFF" }, + { RoleTypes.Scientist, "#F8FF8C" }, + { RoleTypes.Engineer, "#A5A8FF" }, + { RoleTypes.Noisemaker, "#FFC08C" }, + { RoleTypes.Tracker, "#93FF8C" }, + { RoleTypes.ImpostorGhost, "#FF1919" }, + { RoleTypes.Impostor, "#FF1919" }, + { RoleTypes.Shapeshifter, "#FF819E" }, + { RoleTypes.Phantom, "#CA8AFF" } + }; - public static Dictionary roleColors; public static List clientIdList = []; public static string HostNickName = ""; - public static bool IsInitialRelease = DateTime.Now.Month == 8 && DateTime.Now.Day is 14; - public static bool IsAprilFools = DateTime.Now.Month == 4 && DateTime.Now.Day is 1; - public const float RoleTextSize = 2f; - - public static IEnumerable AllPlayerControls => - PlayerControl.AllPlayerControls.ToArray().Where(p => p != null); - - public static IEnumerable AllAlivePlayerControls => - PlayerControl.AllPlayerControls.ToArray().Where(p => p != null && p.IsAlive() && !p.Data.Disconnected); + public static readonly bool IsInitialRelease = DateTime.Now.Month == 8 && DateTime.Now.Day is 15; + public static readonly bool IsAprilFools = DateTime.Now is { Month: 4, Day: >= 1 and <= 10 }; + public static readonly bool IsValentines = DateTime.Now.Month == 2 && DateTime.Now.Day is 14; public static Main Instance; @@ -163,37 +115,87 @@ public class Main : BasePlugin "toffee" ]; - public static string Get_TName_Snacks => TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese - ? TName_Snacks_CN[IRandom.Instance.Next(0, TName_Snacks_CN.Count)] - : TName_Snacks_EN[IRandom.Instance.Next(0, TName_Snacks_EN.Count)]; + // == 认证设定 / Authentication Config == + public static HashAuth DebugKeyAuth { get; private set; } + public static ConfigEntry DebugKeyInput { get; private set; } + + // ========== + public Harmony Harmony { get; } = new(PluginGuid); + public static NormalGameOptionsV09 NormalOptions => GameOptionsManager.Instance.currentNormalGameOptions; + public static HideNSeekGameOptionsV09 HideNSeekOptions => GameOptionsManager.Instance.currentHideNSeekGameOptions; + + //Client Options + public static ConfigEntry KickPlayerWithAbnormalFriendCode { get; private set; } + public static ConfigEntry KickPlayerWithDenyName { get; private set; } + public static ConfigEntry KickPlayerInBanList { get; private set; } + public static ConfigEntry SpamDenyWord { get; private set; } + public static ConfigEntry UnlockFPS { get; private set; } + public static ConfigEntry SwitchOutfitType { get; private set; } + public static ConfigEntry AutoStartGame { get; private set; } + public static ConfigEntry AutoEndGame { get; private set; } + public static ConfigEntry DisableVanillaSound { get; private set; } + public static ConfigEntry EnableFAC { get; private set; } + public static ConfigEntry EnableGuardian { get; private set; } + public static ConfigEntry ShowPlayerInfo { get; private set; } + public static ConfigEntry UseModCursor { get; private set; } + public static ConfigEntry FastLaunchMode { get; private set; } + public static ConfigEntry OfflineMode { get; private set; } + public static ConfigEntry VersionCheat { get; private set; } + public static ConfigEntry GodMode { get; private set; } + public static ConfigEntry NoGameEnd { get; private set; } + + //Other Configs + public static ConfigEntry HideName { get; private set; } + public static ConfigEntry HideColor { get; private set; } + public static ConfigEntry ShowResults { get; private set; } + public static ConfigEntry WebhookURL { get; private set; } + public static ConfigEntry EnableFinalSuspect { get; private set; } + public static ConfigEntry LastStartVersion { get; private set; } + public static ConfigEntry LanguageUpdateBypass { get; private set; } + public static ConfigEntry CurrentStyleId { get; private set; } + + public static IEnumerable AllPlayerControls => + PlayerControl.AllPlayerControls.ToArray().Where(p => p); + + public static IEnumerable AllAlivePlayerControls => + PlayerControl.AllPlayerControls.ToArray().Where(p => p && p.IsAlive() && !p.Data.Disconnected); + + public static string Get_TName_Snacks => + TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese + ? TName_Snacks_CN[IRandom.Instance.Next(0, TName_Snacks_CN.Count)] + : TName_Snacks_EN[IRandom.Instance.Next(0, TName_Snacks_EN.Count)]; public override void Load() { Instance = this; - //Client Options - HideName = Config.Bind("Xtreme System", "Hide Game Code Name", "Final Suspect"); - HideColor = Config.Bind("Xtreme System", "Hide Game Code Color", $"{ColorHelper.ModColor}"); - EnableFinalSuspect = Config.Bind("Xtreme System", "Enable Final Suspect", true); - ShowResults = Config.Bind("Xtreme System", "Show Results", true); - LastStartVersion = Config.Bind("Xtreme System", "Last Start Version", "0.0.0"); - + //Configs + HideName = Config.Bind("Final System", "Hide Game Code Name", "Final Suspect"); + HideColor = Config.Bind("Final System", "Hide Game Code Color", $"{ColorHelper.FSColorHex}"); + EnableFinalSuspect = Config.Bind("Final System", "Enable Final Suspect", true); + ShowResults = Config.Bind("Final System", "Show Results", true); + LastStartVersion = Config.Bind("Final System", "Last Start Version", "0.0.0"); + LanguageUpdateBypass = Config.Bind("Final System", "Language Update Bypass", BypassType.Dont); + CurrentStyleId = Config.Bind("Final System", "BG Id", 0); + DebugKeyInput = Config.Bind("Authentication", "Debug Key", ""); UnlockFPS = Config.Bind("Client Options", "Unlock FPS", false); - ChangeOutfit = Config.Bind("Client Options", "Change Outfit", OutfitType[0]); - KickPlayerWhoFriendCodeNotExist = Config.Bind("Client Options", "Kick Player FriendCode Not Exist", true); + SwitchOutfitType = Config.Bind("Client Options", "Switch Outfit", OutfitType.BeanMode); + KickPlayerWithAbnormalFriendCode = Config.Bind("Client Options", "Kick Player FriendCode Not Exist", true); KickPlayerInBanList = Config.Bind("Client Options", "Kick Player In BanList", true); KickPlayerWithDenyName = Config.Bind("Client Options", "Kick Player With Deny Name", true); SpamDenyWord = Config.Bind("Client Options", "Spam Deny Word", true); AutoStartGame = Config.Bind("Client Options", "Auto Start Game", false); AutoEndGame = Config.Bind("Client Options", "Auto End Game", false); DisableVanillaSound = Config.Bind("Client Options", "Disable Vanilla Sound", false); - DisableFAC = Config.Bind("Client Options", "Disable FAC", false); + EnableFAC = Config.Bind("Client Options", "Enable FAC", false); + EnableGuardian = Config.Bind("Client Options", "Enable Guardian", true); //PrunkMode = Config.Bind("Client Options", "Prunk Mode", false); ShowPlayerInfo = Config.Bind("Client Options", "Show Player Info", true); UseModCursor = Config.Bind("Client Options", "Use Mod Cursor", true); - FastBoot = Config.Bind("Client Options", "Fast Boot", false); + FastLaunchMode = Config.Bind("Client Options", "Fast Launch Mode", false); + OfflineMode = Config.Bind("Client Options", "Offline Mode", false); VersionCheat = Config.Bind("Client Options", "Version Cheat", false); GodMode = Config.Bind("Client Options", "God Mode", false); NoGameEnd = Config.Bind("Client Options", "No Game End", false); @@ -203,13 +205,13 @@ public override void Load() Disable("SwitchSystem"); Disable("ModNews"); Disable("CancelPet"); - if (!DebugModeManager.AmDebugger) + if (!DebugModeManager.IsDebugMode) { Disable("Download Resources"); Disable("GetAnnouncements"); Disable("GetConfigs"); } - + isDetail = true; // 認証関連-初期化 @@ -222,33 +224,9 @@ public override void Load() hasArgumentException = false; ExceptionMessage = ""; - try - { - roleColors = new Dictionary - { - { RoleTypes.CrewmateGhost, "#8CFFFF" }, - { RoleTypes.GuardianAngel, "#8CFFDB" }, - { RoleTypes.Crewmate, "#8CFFFF" }, - { RoleTypes.Scientist, "#F8FF8C" }, - { RoleTypes.Engineer, "#A5A8FF" }, - { RoleTypes.Noisemaker, "#FFC08C" }, - { RoleTypes.Tracker, "#93FF8C" }, - { RoleTypes.ImpostorGhost, "#FF1919" }, - { RoleTypes.Impostor, "#FF1919" }, - { RoleTypes.Shapeshifter, "#FF819E" }, - { RoleTypes.Phantom, "#CA8AFF" }, - }; - } - catch (ArgumentException ex) - { - Error("错误:字典出现重复项", "LoadDictionary"); - Exception(ex, "LoadDictionary"); - hasArgumentException = true; - ExceptionMessage = ex.Message; - ExceptionMessageIsShown = false; - } RegistryManager.Init(); // 这是优先级最高的模块初始化方法,不能使用模块初始化属性 + DllChecker.Init(); PluginModuleInitializerAttribute.InitializeAll(); @@ -269,23 +247,68 @@ public override void Load() Task.Run(SystemEnvironment.SetEnvironmentVariablesAsync); Harmony.PatchAll(); - - if (DebugModeManager.AmDebugger) ConsoleManager.CreateConsole(); + + if (DebugModeManager.IsDebugMode) ConsoleManager.CreateConsole(); else ConsoleManager.DetachConsole(); Msg("========= FinalSuspect loaded! =========", "Plugin Load"); Application.quitting += new Action(SaveNowLog); } + +#pragma warning disable CS0618 // 类型或成员已过时 + public const string GitBaseTag = ThisAssembly.Git.BaseTag; + public const string GitCommit = ThisAssembly.Git.Commit; + public const string GitCommits = ThisAssembly.Git.Commits; + public const string GitBranch = ThisAssembly.Git.Branch; + public const bool GitIsDirty = ThisAssembly.Git.IsDirty; + public const string GitSha = ThisAssembly.Git.Sha; + public const string GitTag = ThisAssembly.Git.Tag; +#pragma warning restore CS0618 } +/// +/// 表示软件版本的不同类型。 +/// public enum VersionTypes { - Alpha, // 早期内测版 - Beta, // 内测版 - Canary, // 测试版(不稳定) - Dev, // 开发版 - RC, // 发行候选版Release Candidate - Preview, // 预览/预发行版 - Scrapter, // 废弃版 - Release, // 发行版 + /// 早期内测版。 + Alpha, + + /// 内测版。 + Beta, + + /// 测试版(不稳定)。 + Canary, + + /// 开发版。 + Dev, + + /// 发行候选版 (Release Candidate)。 + RC, + + /// 预览/预发行版。 + Preview, + + /// 废弃版。 + Scrapter, + + /// + /// 正式发行版。 + /// 除此之外若要发行,全部使用OpenBeta。 + /// + Release +} + +public enum BypassType +{ + Dont, + Once, + LongTerm +} + +public enum OutfitType +{ + BeanMode, + HorseMode, + LongMode } \ No newline at end of file diff --git a/README.md b/README.md index 8e80dad8..ce6df365 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ **Final Suspect: The Ultimate Among Us Mod for the Original Experience.** -Licences -LatestTag -Stars +Licences +LatestTag +Stars [English] | [简体中文](README_zh.md) @@ -25,12 +25,12 @@ FinalSuspect is an original optimization plugin designed for Among Us.\ The plugin introduces 20+ optimizations to the original game, enhancing the overall experience.\ -For detailed documentation on how to use this plugin (installation, updates, etc.), please refer to the [website's documentation](https://www.xtreme.net.cn/docs/FS/en-us/Guide/Installation). +For detailed documentation on how to use this plugin (installation, updates, etc.), please refer to the [website's documentation](https://www.Final.net.cn/docs/FS/en-us/Guide/Installation). ## Contributors diff --git a/README_zh.md b/README_zh.md index e89b27e5..7b827777 100644 --- a/README_zh.md +++ b/README_zh.md @@ -24,12 +24,12 @@ FinalSuspect是一个适用于Among Us的原版优化插件。\ 插件为原版带来了20+项优化以及许多功能,使得原版也能获得更好的体验。\ -有关该插件的详细使用文档(安装,更新以及完整的配置说明),请参阅[网站文档](https://www.xtreme.net.cn/docs/FS/)。 +有关该插件的详细使用文档(安装,更新以及完整的配置说明),请参阅[网站文档](https://www.Final.net.cn/docs/FS/)。 ## 贡献者 diff --git a/SECURITY.md b/SECURITY.md index 898c187c..841299cd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,12 +7,12 @@ - :x: 表示该版本已被标记为**过时版本**,我们**不会受理**此版本的安全问题。 > 若您的版本未受到支持,不妨考虑更新到最新受支持的版本再尝试复现此安全问题。 -| 版本 | 是否支持 | -| ------- | ------------------ | -| v1.1_20250501 | :white_check_mark: | -| v1.1_20250412 | :white_check_mark: | -| v1.1_20250216 | :x: | -| v1.0开头的版本 | :x: | +| 版本 | 是否支持 | +|----------------| ------------------ | +| v1.1_20250501 | :white_check_mark: | +| v,1.1_20250412 | :white_check_mark: | +| v1.1_20250216 | :x: | +| v1.0开头的版本 | :x: | ## 报告安全问题 @@ -25,4 +25,4 @@ - 微信号: qingfengawa - (建议)QQ号: 3094606169 -你也可以通过在[安全](https://github.com/XtremeWave/FinalSuspect/security)页面点击`Report a vulnerability`以通过GitHub向我们报告一个安全问题。 +你也可以通过在[安全](https://github.com/Slok7565/FinalSuspect/security)页面点击`Report a vulnerability`以通过GitHub向我们报告一个安全问题。 diff --git a/fs_info.json b/fs_info.json index f573294a..a23d8c56 100644 --- a/fs_info.json +++ b/fs_info.json @@ -1,32 +1,31 @@ { - "version": "1.1.2", - "verHead": "1.1", - "verDate": "20250501", + "version": "1.2.0", + "verHead": "1.2", + "verDate": "20250815", "minVer": "latest", "creation": "0", "allowStart": "true", "CanUpdate": "true", - "DebugVer": "0.0.0", "md5": "c3fc5dd5e97f3538ab85b2cd940681d6", "announcement": { - "English": "If the update is unsuccessful, please try to visit the website to update manually.\nDue to the developer's personal academic and current situation, the next version update may take a long time.\nThank you for your support!", - "Latam": "Si la actualización no tiene éxito, por favor intente visitar el sitio web para actualizar manualmente.\nDebido a la situación académica y personal del desarrollador, la próxima actualización de versión podría tardar mucho tiempo.\n¡Gracias por su apoyo!\nEl idioma que estás utilizando se traduce mediante IA y puede contener inexactitudes. Por favor, proporciona retroalimentación al desarrollador de manera oportuna.", - "Brazilian": "Se a atualização não for bem-sucedida, tente acessar o site para atualizar manualmente.\nDevido à situação acadêmica e pessoal do desenvolvedor, a próxima atualização pode demorar bastante.\nAgradecemos seu apoio!\nO idioma que você está usando é traduzido por IA e pode conter imprecisões. Por favor, forneça feedback ao desenvolvedor prontamente.", - "Portuguese": "Se a atualização não for bem-sucedida, tente visitar o site para atualizar manualmente.\nDevido à situação acadêmica e pessoal do desenvolvedor, a próxima atualização pode demorar bastante.\nAgradecemos o seu apoio!\nA linguagem que você está usando é traduzida por IA e pode conter imprecisões. Por favor, forneça feedback ao desenvolvedor prontamente.", - "Korean": "업데이트가 성공적이지 않으면 웹사이트를 방문하여 수동으로 업데이트해 주십시오.\n개발자의 학업 및 개인 사정으로 인해 다음 버전 업데이트는 상당 기간이 걸릴 수 있습니다.\n지원해 주셔서 감사합니다!\n사용 중인 언어는 AI로 번역되어 오류가 있을 수 있습니다. 개발자에게 신속히 피드백을 제공해 주세요.", - "Russian": "Если обновление не удалось, пожалуйста, посетите сайт для ручного обновления.\nВ связи с учебой и личными обстоятельствами разработчика, следующее обновление версии может занять много времени.\nСпасибо за поддержку!\nЯзык, который вы используете, переводится с помощью ИИ и может содержать неточности. Пожалуйста, своевременно сообщите разработчику.", - "Dutch": "Als de update niet slaagt, probeer dan de website te bezoeken om handmatig te updaten.\nVanwege de studie en de persoonlijke situatie van de ontwikkelaar kan het volgende versie-update wellicht lang duren.\nBedankt voor uw steun!\nDe taal die je gebruikt wordt vertaald door AI en kan onnauwkeurigheden bevatten. Geef alsjeblieft prompt feedback aan de ontwikkelaar.", - "Filipino": "Kung hindi matagumpay ang update, subukan ang pagbisita sa website upang manu-manual na i-update.\nDahil sa personal na kalagayan at pagsisikap sa pag-aaral ng developer, maaaring magtagal ang susunod na update ng bersyon.\nSalamat sa iyong suporta!\nAng wika na ginagamit mo ay tinatranslate ng AI at maaaring magkaroon ng mga hindi katatapos. Mangyaring magbigay ng feedback sa developer nang maingay.", - "French": "Si la mise à jour n'a pas réussi, veuillez essayer de visiter le site web pour mettre à jour manuellement.\nEn raison des études et de la situation personnelle du développeur, la prochaine mise à jour de version pourrait prendre beaucoup de temps.\nMerci pour votre soutien!\nLa langue que vous utilisez est traduite par l'IA et peut contenir des inexactitudes. Veuillez fournir un retour d'information au développeur sans tarder.", - "German": "Wenn das Update nicht erfolgreich war, versuchen Sie bitte, die Website zu besuchen, um manuell zu aktualisieren.\nAufgrund der Studienlage und der persönlichen Situation des Entwicklers kann es länger dauern, bis die nächste Versionsaktualisierung erfolgt.\nVielen Dank für Ihre Unterstützung!\nDie von Ihnen verwendete Sprache wird von einer KI übersetzt und kann Ungenauigkeiten enthalten. Bitte geben Sie dem Entwickler umgehend ein Feedback.", - "Italian": "Se l'aggiornamento non è riuscito, provi a visitare il sito web per aggiornare manualmente.\nA causa degli studi e della situazione personale dello sviluppatore, il prossimo aggiornamento della versione potrebbe richiedere molto tempo.\nGrazie per il tuo supporto!\nLa lingua che stai utilizzando è tradotta da IA e può contenere inesattezze. Per favore, fornisci feedback allo sviluppatore prontamente.", - "Japanese": "更新が成功しない場合は、ウェブサイトを訪れて手動で更新してください。\n開発者の学業や個人状況のため、次回のバージョン更新はかなり時間がかかるかもしれません。\nご支援ありがとうございます!\n使用している言語は AI で翻訳されており、不正確な部分がある場合があります。開発者に速やかにフィードバックを提供してください。", - "Spanish": "Si la actualización no tiene éxito, por favor intente visitar el sitio web para actualizar manualmente.\nDebido a la situación académica y personal del desarrollador, la próxima actualización de versión podría tardar mucho tiempo.\n¡Gracias por tu apoyo!\nEl idioma que estás utilizando se traduce mediante IA y puede contener inexactitudes. Por favor, proporciona retroalimentación al desarrollador de manera oportuna.", - "SChinese": "如果更新没有成功,请您尝试访问网站手动更新。\n因开发者个人学业及状态原因,下次版本更新或许需要很久。\n感谢支持!", - "TChinese": "如果更新没有成功,請您嘗試訪問網站手動更新。\n因開發者個人學業及狀態原因,下次版本更新或許需要很久。\n感謝支持!\n您所使用的語言使用AI翻譯,可能會出現偏差,請及時向開發者反饋。", - "Irish": "Má theip ar an nuashonrú, déan iarracht an suíomh a cuairt ar son nuashonrú de láimh.\nMar thoradh ar staid acadaimigh agus personaltais an feabhsaitheora, b'fhéidir go mbeidh an chéad nuashonrú eile ag dul thart.\nGo raibh maith agat as an tacaíocht!\nIs féidir go mbeadh eolais ar na teanga a bhí agat agus go mbeadh deachtlúchán ann. Tabhair feadhbach chun an t-ábhar." + "English": "If the update is unsuccessful, please try to visit the website to update manually.", + "Latam": "Si la actualización no tiene éxito, por favor intente visitar el sitio web para actualizar manualmente.\nEl idioma que estás utilizando se traduce mediante IA y puede contener inexactitudes. Por favor, proporciona retroalimentación al desarrollador de manera oportuna.", + "Brazilian": "Se a atualização não for bem-sucedida, tente acessar o site para atualizar manualmente.\nO idioma que você está usando é traduzido por IA e pode conter imprecisões. Por favor, forneça feedback ao desenvolvedor prontamente.", + "Portuguese": "Se a atualização não for bem-sucedida, tente visitar o site para atualizar manualmente.\nAgradecemos o seu apoio!\nA linguagem que você está usando é traduzida por IA e pode conter imprecisões. Por favor, forneça feedback ao desenvolvedor prontamente.", + "Korean": "업데이트가 성공적이지 않으면 웹사이트를 방문하여 수동으로 업데이트해 주십시오.\n사용 중인 언어는 AI로 번역되어 오류가 있을 수 있습니다. 개발자에게 신속히 피드백을 제공해 주세요.", + "Russian": "Если обновление не удалось, пожалуйста, посетите сайт для ручного обновления.\nЯзык, который вы используете, переводится с помощью ИИ и может содержать неточности. Пожалуйста, своевременно сообщите разработчику.", + "Dutch": "Als de update niet slaagt, probeer dan de website te bezoeken om handmatig te updaten.\nDe taal die je gebruikt wordt vertaald door AI en kan onnauwkeurigheden bevatten. Geef alsjeblieft prompt feedback aan de ontwikkelaar.", + "Filipino": "Kung hindi matagumpay ang update, subukan ang pagbisita sa website upang manu-manual na i-update.\nAng wika na ginagamit mo ay tinatranslate ng AI at maaaring magkaroon ng mga hindi katatapos. Mangyaring magbigay ng feedback sa developer nang maingay.", + "French": "Si la mise à jour n'a pas réussi, veuillez essayer de visiter le site web pour mettre à jour manuellement.\nLa langue que vous utilisez est traduite par l'IA et peut contenir des inexactitudes. Veuillez fournir un retour d'information au développeur sans tarder.", + "German": "Wenn das Update nicht erfolgreich war, versuchen Sie bitte, die Website zu besuchen, um manuell zu aktualisieren.\nDie von Ihnen verwendete Sprache wird von einer KI übersetzt und kann Ungenauigkeiten enthalten. Bitte geben Sie dem Entwickler umgehend ein Feedback.", + "Italian": "Se l'aggiornamento non è riuscito, provi a visitare il sito web per aggiornare manualmente.\nLa lingua che stai utilizzando è tradotta da IA e può contenere inesattezze. Per favore, fornisci feedback allo sviluppatore prontamente.", + "Japanese": "更新が成功しない場合は、ウェブサイトを訪れて手動で更新してください。\n使用している言語は AI で翻訳されており、不正確な部分がある場合があります。開発者に速やかにフィードバックを提供してください。", + "Spanish": "Si la actualización no tiene éxito, por favor intente visitar el sitio web para actualizar manualmente.\nEl idioma que estás utilizando se traduce mediante IA y puede contener inexactitudes. Por favor, proporciona retroalimentación al desarrollador de manera oportuna.", + "SChinese": "如果更新没有成功,请您尝试访问网站手动更新。", + "TChinese": "如果更新没有成功,請您嘗試訪問網站手動更新。\n您所使用的語言使用AI翻譯,可能會出現偏差,請及時向開發者反饋。", + "Irish": "Má theip ar an nuashonrú, déan iarracht an suíomh a cuairt ar son nuashonrú de láimh.\nIs féidir go mbeadh eolais ar na teanga a bhí agat agus go mbeadh deachtlúchán ann. Tabhair feadhbach chun an t-ábhar." } } diff --git a/fs_resources.json b/fs_resources.json new file mode 100644 index 00000000..a29def38 --- /dev/null +++ b/fs_resources.json @@ -0,0 +1,10 @@ +{ + "MainMenuStyle": + [ + "FinalSuspect-BG-MiraStudio.png", + "FinalSuspect-BG-MiraStudioNewYear.png", + "FinalSuspect-BG-NewYear.png", + "FinalSuspect-BG-Security.png", + "FinalSuspect-BG-XtremeWave.png" + ] +} \ No newline at end of file