From 5f7ddf35e7a53cb6fe212a3e475770bdc7f10750 Mon Sep 17 00:00:00 2001 From: Ape <155788660+ApeMV@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:06:43 +0100 Subject: [PATCH] Number converting and translation --- AmongUsRevamped.cs | 28 +++---- Modules/Translator.cs | 126 ++++++++++++++++++++++++++++++++ Modules/Utils.cs | 10 --- Patches/ChatPatches.cs | 62 +++++++++++----- Patches/IntroPatch.cs | 4 +- Resources/Language/English.json | 15 ++++ Resources/Language/Strings.cs | 43 ----------- 7 files changed, 202 insertions(+), 86 deletions(-) create mode 100644 Modules/Translator.cs create mode 100644 Resources/Language/English.json delete mode 100644 Resources/Language/Strings.cs diff --git a/AmongUsRevamped.cs b/AmongUsRevamped.cs index 1e67680..f6f945f 100644 --- a/AmongUsRevamped.cs +++ b/AmongUsRevamped.cs @@ -91,6 +91,7 @@ public partial class Main : BasePlugin DarkTheme = Config.Bind("Client Options", "Dark Theme", false); LobbyMusic = Config.Bind("Client Options", "Lobby Music", false); + Translator.Init(); BanManager.Init(); Harmony.PatchAll(); @@ -129,19 +130,18 @@ public partial class Main : BasePlugin Fortegreen = 18 } - // Innersloth now censors messages with more than 5 numbers. This is the best bypass I can give - public enum BasicNumberToLetter + // Innersloth now censors messages with more than 5 numbers. Sucks to be them. + public static readonly char[] CircledDigits = { - Zero = 0, - One = 1, - Two = 2, - Three = 3, - Four = 4, - Five = 5, - Six = 6, - Seven = 7, - Eight = 8, - Nine = 9, - Ten = 10 - } + '⓪', // 0 + '①', // 1 + '②', // 2 + '③', // 3 + '④', // 4 + '⑤', // 5 + '⑥', // 6 + '⑦', // 7 + '⑧', // 8 + '⑨' // 9 + }; } \ No newline at end of file diff --git a/Modules/Translator.cs b/Modules/Translator.cs new file mode 100644 index 0000000..e0f4cae --- /dev/null +++ b/Modules/Translator.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text.Json; + +namespace AmongUsRevamped; + +public static class Translator +{ + public enum SupportedLangs + { + English + } + + private static readonly Dictionary> _languages = new(); + private static SupportedLangs _currentLang = SupportedLangs.English; + private static string LanguageFolder => $"{BanManager.DataPath}/Language"; + + public static void Init() + { + CheckLanguageFile(_currentLang); + LoadLanguage(_currentLang); + } + + private static void CheckLanguageFile(SupportedLangs lang) + { + if (!Directory.Exists(LanguageFolder)) Directory.CreateDirectory(LanguageFolder); + + string path = GetLanguagePath(lang); + var assembly = Assembly.GetExecutingAssembly(); + string targetFile = $"{lang}.json"; + + Dictionary embeddedDict = new(); + Dictionary diskDict = new(); + + foreach (var name in assembly.GetManifestResourceNames()) + { + if (name.EndsWith(targetFile, StringComparison.OrdinalIgnoreCase)) + { + using var stream = assembly.GetManifestResourceStream(name); + using var reader = new StreamReader(stream); + string json = reader.ReadToEnd(); + + embeddedDict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); + break; + } + } + + if (File.Exists(path)) + { + try + { + string json = File.ReadAllText(path); + diskDict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); + } + catch + { + diskDict = new Dictionary(); + } + } + + bool updated = false; + foreach (var kvp in embeddedDict) + { + if (!diskDict.ContainsKey(kvp.Key)) + { + diskDict[kvp.Key] = kvp.Value; + updated = true; + } + } + + if (!File.Exists(path) || updated) + { + var options = new JsonSerializerOptions { WriteIndented = true }; + File.WriteAllText(path, JsonSerializer.Serialize(diskDict, options)); + } + } + + private static void LoadLanguage(SupportedLangs lang) + { + string path = GetLanguagePath(lang); + + if (!File.Exists(path)) return; + + try + { + string json = File.ReadAllText(path); + var dict = JsonSerializer.Deserialize>(json); + + if (dict != null) _languages[lang] = dict; + } + catch + { + _languages[lang] = new Dictionary(); + } + } + + private static string GetLanguagePath(SupportedLangs lang) + { + return $"{LanguageFolder}/{lang}.json"; + } + + public static string Get(string key) + { + if (_languages.TryGetValue(_currentLang, out var dict) && dict.TryGetValue(key, out var value)) + { + return SendChatPatch.ConvertNum(value); + } + + return $""; + } + public static string Get(string key, params object[] args) + { + if (_languages.TryGetValue(_currentLang, out var dict) && dict.TryGetValue(key, out var value)) + { + return SendChatPatch.ConvertNum(string.Format(value, args)); + } + return $""; + } + + public static void Reload() + { + LoadLanguage(_currentLang); + } +} \ No newline at end of file diff --git a/Modules/Utils.cs b/Modules/Utils.cs index e981b6a..3977d57 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -405,16 +405,6 @@ public static class Utils }, 6.6f, "ModeratorChatCommand3"); } - public static string BasicIntToWord(int value) - { - if (Enum.IsDefined(typeof(Main.BasicNumberToLetter), value)) - { - return ((Main.BasicNumberToLetter)value).ToString(); - } - - return value.ToString(); - } - // 0 = no winner. 1 = solo winner. 2 = add winner. public static void CustomWinnerEndGame(PlayerControl winner, int winnerType) { diff --git a/Patches/ChatPatches.cs b/Patches/ChatPatches.cs index 15c3bbb..dad3409 100644 --- a/Patches/ChatPatches.cs +++ b/Patches/ChatPatches.cs @@ -59,10 +59,35 @@ internal static class ChatBubbleSetNamePatch [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] internal static class SendChatPatch { + public static string ConvertNum(string input) + { + if (string.IsNullOrEmpty(input)) return input; + int digitCount = 0; + + for (int i = 0; i < input.Length; i++) + { + if (char.IsDigit(input[i]) && ++digitCount > 5) + { + var sb = new System.Text.StringBuilder(input.Length); + + foreach (char c in input) + { + if (char.IsDigit(c)) + sb.Append(Main.CircledDigits[c - '0']); + else + sb.Append(c); + } + return sb.ToString(); + } + } + return input; + } + public static bool Prefix(ChatController __instance) { string msgtext = __instance.freeChatField.textArea.text.Trim(); string text = msgtext.ToLower(); + string converted = ConvertNum(msgtext); if (!AmongUsClient.Instance.AmHost) return true; @@ -76,7 +101,7 @@ internal static class SendChatPatch if (text == "/h" || text == "/help" || text == "/cmd" || text == "/commands") { - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{String.allCommandsFull}"); + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{Translator.Get("allCommandsFull")}"); __instance.freeChatField.textArea.Clear(); __instance.freeChatField.textArea.SetText(string.Empty); return false; @@ -115,25 +140,25 @@ internal static class SendChatPatch if (text == "/aur" || text == "/amongusrevamped" || text == "/socials" || text == "/github" || text == "/discord") { - Utils.ChatCommand(__instance, $"{String.SocialsAll}", "", false); + Utils.ChatCommand(__instance, Translator.Get("socialsAll"), "", false); return false; } if (text == "/0kc" || text == "/0kcd" || text == "/0killcooldown") { - Utils.ChatCommand(__instance, $"{String.noKcdMode}", "", false); + Utils.ChatCommand(__instance, Translator.Get("noKcdMode"), "", false); return false; } if (text == "/sns" || text == "/shiftandseek" || text == "/shift&seek") { - Utils.ChatCommand(__instance, $"{String.SnSModeOne}", $"{String.SnSModeTwo}", true); + Utils.ChatCommand(__instance, Translator.Get("SnSModeOne"), Translator.Get("SnSModeTwo", Options.CrewAutoWinsGameAfter.GetInt(), Options.CantKillTime.GetInt(), Options.MisfiresToSuicide.GetInt()), true); return false; } if (text == "/sp" || text == "/sr" || text == "/speedrun") { - Utils.ChatCommand(__instance, $"{String.speedrunMode}", "", false); + Utils.ChatCommand(__instance, Translator.Get("speedrunMode", Options.GameAutoEndsAfter.GetInt()), "", false); return false; } @@ -146,15 +171,15 @@ internal static class SendChatPatch break; case 1: - Utils.ChatCommand(__instance, $"{String.noKcdMode}", "", false); + Utils.ChatCommand(__instance, Translator.Get("noKcdMode"), "", false); break; case 2: - Utils.ChatCommand(__instance, $"{String.SnSModeOne}", $"{String.SnSModeTwo}", true); + Utils.ChatCommand(__instance, Translator.Get("SnSModeOne"), Translator.Get("SnSModeTwo", Options.CrewAutoWinsGameAfter.GetInt(), Options.CantKillTime.GetInt(), Options.MisfiresToSuicide.GetInt()), true); break; case 3: - Utils.ChatCommand(__instance, $"{String.speedrunMode}", "", false); + Utils.ChatCommand(__instance, Translator.Get("speedrunMode", Options.GameAutoEndsAfter.GetInt()), "", false); break; } @@ -195,8 +220,11 @@ internal static class SendChatPatch if (!isKick && !isBan && !isColorKick && !isColorBan) { + + __instance.freeChatField.textArea.SetText(converted); + Utils.ChatCommand(__instance, $"{converted}", "", false); Logger.Info($" {PlayerControl.LocalPlayer.Data.PlayerName}: {msgtext}", "SendChat"); - return true; + return false; } string arg = text.Substring(isKick ? 6 : isBan ? 5 : isColorKick ? 7 : isColorBan ? 6 : 0).Trim(); @@ -291,12 +319,12 @@ public static class RPCHandlerPatch new LateTask(() => { - Utils.SendPrivateMessage(__instance, $"{String.allCommandsOne}"); + Utils.SendPrivateMessage(__instance, Translator.Get("allCommandsOne")); }, 2.2f, "MHP1"); new LateTask(() => { - Utils.SendPrivateMessage(__instance, $"{String.allCommandsTwo}"); + Utils.SendPrivateMessage(__instance, Translator.Get("allCommandsTwo")); }, 4.4f, "MHP2"); new LateTask(() => @@ -315,18 +343,18 @@ public static class RPCHandlerPatch if (text == "/0kc" || text == "/0kcd" || text == "/0killcooldown") { if (!Utils.IsPlayerModerator(__instance.Data.FriendCode) || !Options.ModeratorCanUseCommand.GetBool()) return; - Utils.ModeratorChatCommand($"{String.noKcdMode}", "", false); + Utils.ModeratorChatCommand(Translator.Get("noKcdMode"), "", false); } if (text == "/sns" || text == "/shiftandseek" || text == "/shift&seek") { if (!Utils.IsPlayerModerator(__instance.Data.FriendCode) || !Options.ModeratorCanUseCommand.GetBool()) return; - Utils.ModeratorChatCommand($"{String.SnSModeOne}", $"{String.SnSModeTwo}", true); + Utils.ModeratorChatCommand(Translator.Get("SnSModeOne"), Translator.Get("SnSModeTwo", Options.CrewAutoWinsGameAfter.GetInt(), Options.CantKillTime.GetInt(), Options.MisfiresToSuicide.GetInt()), true); } if (text == "/sp" || text == "/sr" || text == "/speedrun") { if (!Utils.IsPlayerModerator(__instance.Data.FriendCode) || !Options.ModeratorCanUseCommand.GetBool()) return; - Utils.ModeratorChatCommand($"{String.speedrunMode}", "", false); + Utils.ModeratorChatCommand(Translator.Get("speedrunMode", Options.GameAutoEndsAfter.GetInt()), "", false); } if (text == "/r" || text == "/roles" || text == "/gamemode" || text == "/gm") @@ -338,15 +366,15 @@ public static class RPCHandlerPatch break; case 1: - Utils.ModeratorChatCommand($"{String.noKcdMode}", "", false); + Utils.ModeratorChatCommand(Translator.Get("noKcdMode"), "", false); break; case 2: - Utils.ModeratorChatCommand($"{String.SnSModeOne}", $"{String.SnSModeTwo}", true); + Utils.ModeratorChatCommand(Translator.Get("SnSModeOne"), Translator.Get("SnSModeTwo", Options.CrewAutoWinsGameAfter.GetInt(), Options.CantKillTime.GetInt(), Options.MisfiresToSuicide.GetInt()), true); break; case 3: - Utils.ModeratorChatCommand($"{String.speedrunMode}", "", false); + Utils.ModeratorChatCommand(Translator.Get("speedrunMode", Options.GameAutoEndsAfter.GetInt()), "", false); break; } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 375e1e7..062a9fd 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -21,8 +21,8 @@ internal static class CoShowIntroPatch CustomRoleManagement.SendRoleMessages(new Dictionary { - { "Jester", $"{String.Jester}"}, - { "Mayor", $"{String.Mayor}"} + { "Jester", Translator.Get("Jester")}, + { "Mayor", Translator.Get("Mayor")} }); if (!Utils.isHideNSeek) return; diff --git a/Resources/Language/English.json b/Resources/Language/English.json new file mode 100644 index 0000000..b6d4074 --- /dev/null +++ b/Resources/Language/English.json @@ -0,0 +1,15 @@ +{ + "LanguageID": "0", + + "noKcdMode": "0 Kill Cooldown:\n\nImpostors have no kill cooldown, Crewmates have low tasks\nThink fast and pay attention!", + "SnSModeOne": "Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nEmergency Meetings = Off", + "SnSModeTwo": "Crew wins by tasks/surviving {0}s\nImp wins by killing\nOne wrong kill = Can't kill for {1}s\n{2} wrong kills = suicide", + "speedrunMode": "Speedrun:\n\nEveryone is a crewmate. The 1st player to finish tasks wins the game. Game auto ends after {0}s", + "allCommandsFull": "Commands:\n/r - Current mode description\n/0kc, /sns, /sp - Specific mode description\n/l - Shows last winner info\n/kick, /ban - Bans or kicks a player by name\n/ckick, /cban - Bans or kicks a player by color", + "allCommandsOne": "Commands:\n/r - Current mode description\n/0kc, /sns, /sp - Specific mode description\n/l - Shows last winner info", + "allCommandsTwo": "/kick, /ban - Bans or kicks a player by name\n/ckick, /cban - Bans or kicks a player by color", + "SocialsAll": "AUR socials:\n\ng i t h u b . c o m /\nApeMV/AmongUsRevamped\n\nd i s c o r d . g g /\n83Zhzhyhya", + + "Mayor": "PRIVATE:\n\nYour role is Mayor!\n\nYou are a crewmate with i total votes. Use your voting power to eject Impostors.", + "Jester": "PRIVATE:\n\nYour role is Jester!\nYou are NOT a crewmate!\n\nYou win by getting voted. Act as suspicious as possible." +} diff --git a/Resources/Language/Strings.cs b/Resources/Language/Strings.cs deleted file mode 100644 index e719ea7..0000000 --- a/Resources/Language/Strings.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Reflection; - -namespace AmongUsRevamped; - -// Used post-1.4.0 incase AUR ever gets translations. Not fully updated. - -/* TEMPLATE - - public static string x{get{ - return $"y";}} -*/ -public static class String -{ - public static string noKcdMode{get{ - return $"0 Kill Cooldown:\n\nImpostors have no kill cooldown, Crewmates have low tasks\nThink fast and pay attention!";}} - - public static string SnSModeOne{get{ - return $"Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nSabotages & Meetings = Off";}} - - public static string SnSModeTwo{get{ - return $"Crew wins by tasks/surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\n" + "Imp wins by killing\n" + (Options.MisfiresToSuicide.GetInt() == 1 ? "" : $"One wrong kill = Can't kill for {Options.CantKillTime.GetInt()}s\n") + $"{Utils.BasicIntToWord(Options.MisfiresToSuicide.GetInt())} wrong kill{(Options.MisfiresToSuicide.GetInt() == 1 ? "" : "s")} = suicide";}} - - public static string speedrunMode{get{ - return $"Speedrun:\n\nEveryone is a crewmate. The 1st player to finish tasks wins the game. Game auto ends after {Options.GameAutoEndsAfter.GetInt()}s";}} - - public static string allCommandsFull{get{ - return $"Commands:\n/r - Current mode description\n/0kc, /sns, /sp - Specific mode description\n/l - Shows last winner info\n/kick, /ban - Bans or kicks a player by name\n/ckick, /cban - Bans or kicks a player by color";}} - - public static string allCommandsOne{get{ - return $"Commands:\n/r - Current mode description\n/0kc, /sns, /sp - Specific mode description\n/l - Shows last winner info";}} - - public static string allCommandsTwo{get{ - return $"/kick, /ban - Bans or kicks a player by name\n/ckick, /cban - Bans or kicks a player by color";}} - - public static string SocialsAll{get{ - return $"AUR socials:\n\ng i t h u b . c o m /\nApeMV/AmongUsRevamped\n\nd i s c o r d . g g /\n83Zhzhyhya";}} - - public static string Mayor{get{ - return $"PRIVATE:\n\nYour role is Mayor!\n\nYou are a crewmate with i total votes. Use your voting power to eject Impostors.";}} - - public static string Jester{get{ - return $"PRIVATE:\n\nYour role is Jester!\nYou are NOT a crewmate!\n\nYou win by getting voted. Act as suspicious as possible.";}} -}