diff --git a/Helpers/CustomRolesHelper.cs b/Helpers/CustomRolesHelper.cs index 98d4d18f7..b030b9c49 100644 --- a/Helpers/CustomRolesHelper.cs +++ b/Helpers/CustomRolesHelper.cs @@ -47,9 +47,12 @@ public static bool IsVanilla(this CustomRoles role) role is CustomRoles.Crewmate or CustomRoles.Engineer or CustomRoles.Scientist or + CustomRoles.Tracker or + CustomRoles.Noisemaker or CustomRoles.GuardianAngel or CustomRoles.Impostor or - CustomRoles.Shapeshifter; + CustomRoles.Shapeshifter or + CustomRoles.Phantom; } public static CustomRoleTypes GetCustomRoleTypes(this CustomRoles role) @@ -74,7 +77,10 @@ public static int GetCount(this CustomRoles role) { CustomRoles.Engineer => roleOpt.GetNumPerGame(RoleTypes.Engineer), CustomRoles.Scientist => roleOpt.GetNumPerGame(RoleTypes.Scientist), + CustomRoles.Tracker => roleOpt.GetNumPerGame(RoleTypes.Tracker), + CustomRoles.Noisemaker => roleOpt.GetNumPerGame(RoleTypes.Noisemaker), CustomRoles.Shapeshifter => roleOpt.GetNumPerGame(RoleTypes.Shapeshifter), + CustomRoles.Phantom => roleOpt.GetNumPerGame(RoleTypes.Phantom), CustomRoles.GuardianAngel => roleOpt.GetNumPerGame(RoleTypes.GuardianAngel), CustomRoles.Crewmate => roleOpt.GetNumPerGame(RoleTypes.Crewmate), _ => 0 @@ -94,7 +100,10 @@ public static int GetChance(this CustomRoles role) { CustomRoles.Engineer => roleOpt.GetChancePerGame(RoleTypes.Engineer), CustomRoles.Scientist => roleOpt.GetChancePerGame(RoleTypes.Scientist), + CustomRoles.Noisemaker => roleOpt.GetChancePerGame(RoleTypes.Noisemaker), + CustomRoles.Tracker => roleOpt.GetChancePerGame(RoleTypes.Tracker), CustomRoles.Shapeshifter => roleOpt.GetChancePerGame(RoleTypes.Shapeshifter), + CustomRoles.Phantom => roleOpt.GetChancePerGame(RoleTypes.Phantom), CustomRoles.GuardianAngel => roleOpt.GetChancePerGame(RoleTypes.GuardianAngel), CustomRoles.Crewmate => roleOpt.GetChancePerGame(RoleTypes.Crewmate), _ => 0 diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index ff6419124..f0f10aa3b 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -59,25 +59,28 @@ public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string c public static void SendGameData([CallerMemberName] string callerMethodName = "") { logger.Info($"SendGameData is called from {callerMethodName}"); - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - // 書き込み {}は読みやすさのためです。 - writer.StartMessage(5); //0x05 GameData + foreach (var playerinfo in GameData.Instance.AllPlayers) { - writer.Write(AmongUsClient.Instance.GameId); - writer.StartMessage(1); //0x01 Data + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + // 書き込み {}は読みやすさのためです。 + writer.StartMessage(5); //0x05 GameData { - writer.WritePacked(GameData.Instance.NetId); - GameData.Instance.Serialize(writer, true); + writer.Write(AmongUsClient.Instance.GameId); + writer.StartMessage(1); //0x01 Data + { + writer.WritePacked(playerinfo.NetId); + playerinfo.Serialize(writer, true); + } + writer.EndMessage(); } writer.EndMessage(); - } - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } } - public static void OnDisconnect(GameData.PlayerInfo player) + public static void OnDisconnect(NetworkedPlayerInfo player) { // 実行条件: クライアントがホストである, IsDeadが上書きされている, playerが切断済み if (!AmongUsClient.Instance.AmHost || !IsCached || !player.Disconnected) return; diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index a7c24dd5b..9b0e3baf0 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -5,7 +5,7 @@ namespace TownOfHost { static class PlayerOutfitExtension { - public static GameData.PlayerOutfit Set(this GameData.PlayerOutfit instance, string playerName, int colorId, string hatId, string skinId, string visorId, string petId) + public static NetworkedPlayerInfo.PlayerOutfit Set(this NetworkedPlayerInfo.PlayerOutfit instance, string playerName, int colorId, string hatId, string skinId, string visorId, string petId) { instance.PlayerName = playerName; instance.ColorId = colorId; @@ -15,7 +15,7 @@ public static GameData.PlayerOutfit Set(this GameData.PlayerOutfit instance, str instance.PetId = petId; return instance; } - public static bool Compare(this GameData.PlayerOutfit instance, GameData.PlayerOutfit targetOutfit) + public static bool Compare(this NetworkedPlayerInfo.PlayerOutfit instance, NetworkedPlayerInfo.PlayerOutfit targetOutfit) { return instance.ColorId == targetOutfit.ColorId && instance.HatId == targetOutfit.HatId && @@ -24,17 +24,17 @@ public static bool Compare(this GameData.PlayerOutfit instance, GameData.PlayerO instance.PetId == targetOutfit.PetId; } - public static string GetString(this GameData.PlayerOutfit instance) + public static string GetString(this NetworkedPlayerInfo.PlayerOutfit instance) { return $"{instance.PlayerName} Color:{instance.ColorId} {instance.HatId} {instance.SkinId} {instance.VisorId} {instance.PetId}"; } } public static class Camouflage { - static GameData.PlayerOutfit CamouflageOutfit = new GameData.PlayerOutfit().Set("", 15, "", "", "", ""); + static NetworkedPlayerInfo.PlayerOutfit CamouflageOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "", ""); public static bool IsCamouflage; - public static Dictionary PlayerSkins = new(); + public static Dictionary PlayerSkins = new(); [GameModuleInitializer] public static void Init() @@ -104,27 +104,32 @@ public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bo target.SetColor(newOutfit.ColorId); sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) + .Write(target.Data.NetId) .Write(newOutfit.ColorId) .EndRpc(); target.SetHat(newOutfit.HatId, newOutfit.ColorId); sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) .Write(newOutfit.HatId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) .EndRpc(); target.SetSkin(newOutfit.SkinId, newOutfit.ColorId); sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) .Write(newOutfit.SkinId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) .EndRpc(); target.SetVisor(newOutfit.VisorId, newOutfit.ColorId); sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) .Write(newOutfit.VisorId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) .EndRpc(); target.SetPet(newOutfit.PetId); sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) .Write(newOutfit.PetId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) .EndRpc(); sender.SendMessage(); diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index f96808134..ac8e68bd3 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -257,6 +257,7 @@ public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, { sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) .Write((ushort)role) + .Write(false) .EndRpc(); } public static void RpcMurderPlayer(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index 5699d1beb..2257d583d 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -45,7 +45,7 @@ public static void Enable(string tag, bool toGame = false) public static void SendInGame(string text, bool isAlways = false) { if (!isEnable) return; - if (DestroyableSingleton._instance) DestroyableSingleton.Instance.Notifier.AddItem(text); + if (DestroyableSingleton._instance) DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(text); } private static void SendToFile(string text, LogLevel level = LogLevel.Info, string tag = "", bool escapeCRLF = true, int lineNumber = 0, string fileName = "") { diff --git a/Modules/ErrorText.cs b/Modules/ErrorText.cs index 63895075c..f750b4ae5 100644 --- a/Modules/ErrorText.cs +++ b/Modules/ErrorText.cs @@ -42,12 +42,32 @@ public static void Create(TMPro.TextMeshPro baseText) Text.color = Color.red; Text.outlineColor = Color.black; Text.alignment = TMPro.TextAlignmentOptions.Top; + + // 背景 + var bgObject = new GameObject("Background") { layer = LayerMask.NameToLayer("UI") }; + var bgRenderer = instance.background = bgObject.AddComponent(); + var bgTexture = new Texture2D(Screen.width, 150, TextureFormat.ARGB32, false); + for (var x = 0; x < bgTexture.width; x++) + { + for (var y = 0; y < bgTexture.height; y++) + { + bgTexture.SetPixel(x, y, new(0f, 0f, 0f, 0.6f)); + } + } + bgTexture.Apply(); + var bgSprite = Sprite.Create(bgTexture, new(0, 0, bgTexture.width, bgTexture.height), new(0.5f, 1f /* 上端の真ん中を中心とする */ )); + bgRenderer.sprite = bgSprite; + var bgTransform = bgObject.transform; + bgTransform.parent = instance.transform; + bgTransform.localPosition = new(0f, TextOffsetY, 1f); + bgObject.SetActive(false); } public TMPro.TextMeshPro Text; + private SpriteRenderer background; public Camera Camera; public List AllErrors = new(); - public Vector3 TextOffset = new(0, 0.3f, -1000f); + public Vector3 TextOffset = new(0, TextOffsetY, -1000f); public void Update() { AllErrors.ForEach(err => err.IncreaseTimer()); @@ -95,13 +115,13 @@ public void UpdateText() } if (maxLevel == 0) { - Text.enabled = false; + Hide(); } else { if (!HnSFlag) text += $"{GetString($"ErrorLevel{maxLevel}")}"; - Text.enabled = true; + Show(); } if (GameStates.IsInGame && maxLevel != 3) text += $"\n{GetString("TerminateCommand")}: Shift+L+Enter"; @@ -112,6 +132,16 @@ public void Clear() AllErrors.RemoveAll(err => err.ErrorLevel != 3); UpdateText(); } + private void Show() + { + Text.enabled = true; + background.gameObject.SetActive(true); + } + private void Hide() + { + Text.enabled = false; + background.gameObject.SetActive(false); + } public class ErrorData { @@ -138,6 +168,8 @@ public override string ToString() } public bool HnSFlag; + + const float TextOffsetY = 0.3f; } public enum ErrorCode { @@ -155,6 +187,11 @@ public enum ErrorCode OptionIDDuplicate = 001_010_3, // 001-010-3 オプションIDが重複している(DEBUGビルド時のみ) // 002 サポート関連 UnsupportedVersion = 002_000_1, // 002-000-1 AmongUsのバージョンが古い + + // 010 参加/退出関連 + OnPlayerLeftPostfixFailedInGame = 010_000_2, // 010-000-2 OnPlayerLeftPatch.Postfixがゲーム中に失敗 + OnPlayerLeftPostfixFailedInLobby = 010_001_2, // 010-001-2 OnPlayerLeftPatch.Postfixがロビーで失敗 + // ========== // 000 Test NoError = 0000000, // 000-000-0 No Error diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 8e5c7ae33..1159c7b22 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -73,7 +73,7 @@ public static int GetClientId(this PlayerControl player) var client = player.GetClient(); return client == null ? -1 : client.Id; } - public static CustomRoles GetCustomRole(this GameData.PlayerInfo player) + public static CustomRoles GetCustomRole(this NetworkedPlayerInfo player) { return player == null || player.Object == null ? CustomRoles.Crewmate : player.Object.GetCustomRole(); } @@ -148,6 +148,7 @@ public static void RpcSetNamePrivate(this PlayerControl player, string name, boo var clientId = seer.GetClientId(); MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, Hazel.SendOption.Reliable, clientId); + writer.Write(player.Data.NetId); writer.Write(name); writer.Write(DontShowOnModdedClient); AmongUsClient.Instance.FinishRpcImmediately(writer); @@ -159,11 +160,12 @@ public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, i if (player == null) return; if (AmongUsClient.Instance.ClientId == clientId) { - player.SetRole(role); + player.StartCoroutine(player.CoSetRole(role, false)); return; } MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, Hazel.SendOption.Reliable, clientId); writer.Write((ushort)role); + writer.Write(false); AmongUsClient.Instance.FinishRpcImmediately(writer); } @@ -477,13 +479,13 @@ public static void RpcProtectedMurderPlayer(this PlayerControl killer, PlayerCon // Other Clients if (killer.PlayerId != 0) { - var writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable); + var writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable, killer.GetClientId()); writer.WriteNetObject(target); writer.Write((int)MurderResultFlags.FailedProtected); AmongUsClient.Instance.FinishRpcImmediately(writer); } } - public static void NoCheckStartMeeting(this PlayerControl reporter, GameData.PlayerInfo target) + public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPlayerInfo target) { /*サボタージュ中でも関係なしに会議を起こせるメソッド targetがnullの場合はボタンとなる*/ MeetingRoomManager.Instance.AssignSelf(reporter, target); diff --git a/Modules/Extensions/IGameManagerEx.cs b/Modules/Extensions/IGameManagerEx.cs index 0aa2257b3..e64dedf01 100644 --- a/Modules/Extensions/IGameManagerEx.cs +++ b/Modules/Extensions/IGameManagerEx.cs @@ -5,23 +5,23 @@ namespace TownOfHost.Modules.Extensions public static class IGameManagerEx { public static void Set(this BoolOptionNames name, bool value, IGameOptions opt) => opt.SetBool(name, value); - public static void Set(this BoolOptionNames name, bool value, NormalGameOptionsV07 opt) => opt.SetBool(name, value); - public static void Set(this BoolOptionNames name, bool value, HideNSeekGameOptionsV07 opt) => opt.SetBool(name, value); + public static void Set(this BoolOptionNames name, bool value, NormalGameOptionsV08 opt) => opt.SetBool(name, value); + public static void Set(this BoolOptionNames name, bool value, HideNSeekGameOptionsV08 opt) => opt.SetBool(name, value); public static void Set(this Int32OptionNames name, int value, IGameOptions opt) => opt.SetInt(name, value); - public static void Set(this Int32OptionNames name, int value, NormalGameOptionsV07 opt) => opt.SetInt(name, value); - public static void Set(this Int32OptionNames name, int value, HideNSeekGameOptionsV07 opt) => opt.SetInt(name, value); + public static void Set(this Int32OptionNames name, int value, NormalGameOptionsV08 opt) => opt.SetInt(name, value); + public static void Set(this Int32OptionNames name, int value, HideNSeekGameOptionsV08 opt) => opt.SetInt(name, value); public static void Set(this FloatOptionNames name, float value, IGameOptions opt) => opt.SetFloat(name, value); - public static void Set(this FloatOptionNames name, float value, NormalGameOptionsV07 opt) => opt.SetFloat(name, value); - public static void Set(this FloatOptionNames name, float value, HideNSeekGameOptionsV07 opt) => opt.SetFloat(name, value); + public static void Set(this FloatOptionNames name, float value, NormalGameOptionsV08 opt) => opt.SetFloat(name, value); + public static void Set(this FloatOptionNames name, float value, HideNSeekGameOptionsV08 opt) => opt.SetFloat(name, value); public static void Set(this ByteOptionNames name, byte value, IGameOptions opt) => opt.SetByte(name, value); - public static void Set(this ByteOptionNames name, byte value, NormalGameOptionsV07 opt) => opt.SetByte(name, value); - public static void Set(this ByteOptionNames name, byte value, HideNSeekGameOptionsV07 opt) => opt.SetByte(name, value); + public static void Set(this ByteOptionNames name, byte value, NormalGameOptionsV08 opt) => opt.SetByte(name, value); + public static void Set(this ByteOptionNames name, byte value, HideNSeekGameOptionsV08 opt) => opt.SetByte(name, value); public static void Set(this UInt32OptionNames name, uint value, IGameOptions opt) => opt.SetUInt(name, value); - public static void Set(this UInt32OptionNames name, uint value, NormalGameOptionsV07 opt) => opt.SetUInt(name, value); - public static void Set(this UInt32OptionNames name, uint value, HideNSeekGameOptionsV07 opt) => opt.SetUInt(name, value); + public static void Set(this UInt32OptionNames name, uint value, NormalGameOptionsV08 opt) => opt.SetUInt(name, value); + public static void Set(this UInt32OptionNames name, uint value, HideNSeekGameOptionsV08 opt) => opt.SetUInt(name, value); } } \ No newline at end of file diff --git a/Modules/GameOptionsSender/GameOptionsSender.cs b/Modules/GameOptionsSender/GameOptionsSender.cs index ca24af682..507b98c94 100644 --- a/Modules/GameOptionsSender/GameOptionsSender.cs +++ b/Modules/GameOptionsSender/GameOptionsSender.cs @@ -30,16 +30,18 @@ public static void SendAllGameOptions() public virtual void SendGameOptions() { var opt = BuildGameOptions(); + var currentGameMode = AprilFoolsMode.IsAprilFoolsModeToggledOn //April fools mode toggled on by host + ? opt.AprilFoolsOnMode : opt.GameMode; //Change game mode, same as well as in "RpcSyncSettings()" // option => byte[] MessageWriter writer = MessageWriter.Get(SendOption.None); writer.Write(opt.Version); writer.StartMessage(0); - writer.Write((byte)opt.GameMode); - if (opt.TryCast(out var normalOpt)) - NormalGameOptionsV07.Serialize(writer, normalOpt); - else if (opt.TryCast(out var hnsOpt)) - HideNSeekGameOptionsV07.Serialize(writer, hnsOpt); + writer.Write((byte)currentGameMode); + if (opt.TryCast(out var normalOpt)) + NormalGameOptionsV08.Serialize(writer, normalOpt); + else if (opt.TryCast(out var hnsOpt)) + HideNSeekGameOptionsV08.Serialize(writer, hnsOpt); else { writer.Recycle(); @@ -93,4 +95,4 @@ protected virtual void SendOptionsArray(Il2CppStructArray optionArray, byt public virtual bool AmValid() => true; } -} \ No newline at end of file +} diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index 0f499ae88..adece0901 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -22,7 +22,7 @@ public static void SetDirtyToAll() => .ToList().ForEach(sender => sender.SetDirty()); public override IGameOptions BasedGameOptions => - Main.RealOptionsData.Restore(new NormalGameOptionsV07(new UnityLogger().Cast()).Cast()); + Main.RealOptionsData.Restore(new NormalGameOptionsV08(new UnityLogger().Cast()).Cast()); public override bool IsDirty { get; protected set; } public PlayerControl player; diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 223cbad59..ebce5cfc3 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -58,9 +58,12 @@ public CustomRoles GetCustomRole() RoleTypes.Crewmate => CustomRoles.Crewmate, RoleTypes.Engineer => CustomRoles.Engineer, RoleTypes.Scientist => CustomRoles.Scientist, + RoleTypes.Noisemaker => CustomRoles.Noisemaker, + RoleTypes.Tracker => CustomRoles.Tracker, RoleTypes.GuardianAngel => CustomRoles.GuardianAngel, RoleTypes.Impostor => CustomRoles.Impostor, RoleTypes.Shapeshifter => CustomRoles.Shapeshifter, + RoleTypes.Phantom => CustomRoles.Phantom, _ => CustomRoles.Crewmate, }; } @@ -225,7 +228,7 @@ public static class GameStates public static class MeetingStates { public static DeadBody[] DeadBodies = null; - public static GameData.PlayerInfo ReportTarget = null; + public static NetworkedPlayerInfo ReportTarget = null; public static bool IsEmergencyMeeting => ReportTarget == null; public static bool IsExistDeadBody => DeadBodies.Length > 0; public static bool MeetingCalled = false; diff --git a/Modules/MeetingVoteManager.cs b/Modules/MeetingVoteManager.cs index 06e701ac1..027dbc6bc 100644 --- a/Modules/MeetingVoteManager.cs +++ b/Modules/MeetingVoteManager.cs @@ -277,7 +277,7 @@ public readonly struct VoteResult /// /// 追放されるプレイヤー /// - public readonly GameData.PlayerInfo Exiled; + public readonly NetworkedPlayerInfo Exiled; /// /// 同数投票かどうか /// diff --git a/Modules/OptionBackup/OptionBackupData.cs b/Modules/OptionBackup/OptionBackupData.cs index 10ca2f47e..2b99724f2 100644 --- a/Modules/OptionBackup/OptionBackupData.cs +++ b/Modules/OptionBackup/OptionBackupData.cs @@ -37,7 +37,7 @@ public OptionBackupData(IGameOptions option) // TryGetUIntが実装されていないため、別で取得する AllValues.Add(new UIntOptionBackupValue(UInt32OptionNames.Keywords, (uint)option.Keywords)); - foreach (RoleTypes role in new RoleTypes[] { RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter }) + foreach (RoleTypes role in new RoleTypes[] { RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.Noisemaker, RoleTypes.Tracker, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter, RoleTypes.Phantom }) { AllValues.Add(new RoleRateBackupValue(role, option.RoleOptions.GetNumPerGame(role), option.RoleOptions.GetChancePerGame(role))); } diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index ed89244c6..44fa7f46d 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -11,6 +11,7 @@ using TownOfHost.Roles.AddOns.Common; using TownOfHost.Roles.AddOns.Impostor; using TownOfHost.Roles.AddOns.Crewmate; +using TownOfHost.Modules.OptionItems; namespace TownOfHost { @@ -423,7 +424,7 @@ public static void Load() .SetValueFormat(OptionFormat.Seconds); // Add-Ons - SetupRoleOptions(50300, TabGroup.Addons, CustomRoles.Lovers, assignCountRule: new(2, 2, 2)); + SetupRoleOptions(50300, TabGroup.Addons, CustomRoles.Lovers, Utils.GetRoleColor(CustomRoles.Lovers), assignCountRule: new(2, 2, 2)); LastImpostor.SetupCustomOption(); Watcher.SetupCustomOption(); Workhorse.SetupCustomOption(); @@ -435,8 +436,8 @@ public static void Load() .SetGameMode(CustomGameMode.Standard); // HideAndSeek - SetupRoleOptions(100000, TabGroup.MainSettings, CustomRoles.HASFox, customGameMode: CustomGameMode.HideAndSeek); - SetupRoleOptions(100100, TabGroup.MainSettings, CustomRoles.HASTroll, customGameMode: CustomGameMode.HideAndSeek); + SetupRoleOptions(100000, TabGroup.MainSettings, CustomRoles.HASFox, Utils.GetRoleColor(CustomRoles.HASFox), customGameMode: CustomGameMode.HideAndSeek); + SetupRoleOptions(100100, TabGroup.MainSettings, CustomRoles.HASTroll, Utils.GetRoleColor(CustomRoles.HASTroll), customGameMode: CustomGameMode.HideAndSeek); AllowCloseDoors = BooleanOptionItem.Create(101000, "AllowCloseDoors", false, TabGroup.MainSettings, false) .SetHeader(true) .SetGameMode(CustomGameMode.HideAndSeek); @@ -690,17 +691,17 @@ public static void Load() } public static void SetupRoleOptions(SimpleRoleInfo info) => - SetupRoleOptions(info.ConfigId, info.Tab, info.RoleName, info.AssignInfo.AssignCountRule); - public static void SetupRoleOptions(int id, TabGroup tab, CustomRoles role, IntegerValueRule assignCountRule = null, CustomGameMode customGameMode = CustomGameMode.Standard) + SetupRoleOptions(info.ConfigId, info.Tab, info.RoleName, info.RoleColor, info.AssignInfo.AssignCountRule); + public static void SetupRoleOptions(int id, TabGroup tab, CustomRoles role, Color roleColor, IntegerValueRule assignCountRule = null, CustomGameMode customGameMode = CustomGameMode.Standard) { if (role.IsVanilla()) return; assignCountRule ??= new(1, 15, 1); - var spawnOption = IntegerOptionItem.Create(id, role.ToString(), new(0, 100, 10), 0, tab, false) - .SetColor(Utils.GetRoleColor(role)) + var spawnOption = new RoleSpawnChanceOptionItem(id, role.ToString(), 0, tab, false, new(0, 100, 10), role, roleColor); + spawnOption.SetColor(Utils.GetRoleColor(role)) .SetValueFormat(OptionFormat.Percent) .SetHeader(true) - .SetGameMode(customGameMode) as IntegerOptionItem; + .SetGameMode(customGameMode); var countOption = IntegerOptionItem.Create(id + 1, "Maximum", assignCountRule, assignCountRule.Step, tab, false) .SetParent(spawnOption) .SetValueFormat(OptionFormat.Players) diff --git a/Modules/OptionItem/BooleanOptionItem.cs b/Modules/OptionItems/BooleanOptionItem.cs similarity index 100% rename from Modules/OptionItem/BooleanOptionItem.cs rename to Modules/OptionItems/BooleanOptionItem.cs diff --git a/Modules/OptionItem/FloatOptionItem.cs b/Modules/OptionItems/FloatOptionItem.cs similarity index 100% rename from Modules/OptionItem/FloatOptionItem.cs rename to Modules/OptionItems/FloatOptionItem.cs diff --git a/Modules/OptionItem/IntegerOptionItem.cs b/Modules/OptionItems/IntegerOptionItem.cs similarity index 100% rename from Modules/OptionItem/IntegerOptionItem.cs rename to Modules/OptionItems/IntegerOptionItem.cs diff --git a/Modules/OptionItems/Interfaces/IRoleOptionItem.cs b/Modules/OptionItems/Interfaces/IRoleOptionItem.cs new file mode 100644 index 000000000..f15f03342 --- /dev/null +++ b/Modules/OptionItems/Interfaces/IRoleOptionItem.cs @@ -0,0 +1,10 @@ +using TownOfHost.Roles.Core; +using UnityEngine; + +namespace TownOfHost.Modules.OptionItems.Interfaces; + +public interface IRoleOptionItem +{ + public CustomRoles RoleId { get; } + public Color RoleColor { get; } +} diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItems/OptionItem.cs similarity index 87% rename from Modules/OptionItem/OptionItem.cs rename to Modules/OptionItems/OptionItem.cs index dd5a604f6..8ff55920b 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItems/OptionItem.cs @@ -11,6 +11,16 @@ public abstract class OptionItem #region static public static IReadOnlyList AllOptions => _allOptions; private static List _allOptions = new(1024); + public static IReadOnlyList MainOptions => _mainOptions; + private static List _mainOptions = new(512); + public static IReadOnlyList ImpostorRoleOptions => _impostorRoleOptions; + private static List _impostorRoleOptions = new(512); + public static IReadOnlyList CrewmateRoleOptions => _crewmateRoleOptions; + private static List _crewmateRoleOptions = new(512); + public static IReadOnlyList NeutralRoleOptions => _neutralRoleOptions; + private static List _neutralRoleOptions = new(512); + public static IReadOnlyList AddOnOptions => _addOnOptions; + private static List _addOnOptions = new(512); public static IReadOnlyDictionary FastOptions => _fastOptions; private static Dictionary _fastOptions = new(1024); public static int CurrentPreset { get; set; } @@ -56,7 +66,7 @@ public int CurrentValue public OptionItem Parent { get; private set; } public List Children; - public OptionBehaviour OptionBehaviour; + public StringOption OptionBehaviour; // イベント // eventキーワードにより、クラス外からのこのフィールドに対する以下の操作は禁止されます。 @@ -105,6 +115,15 @@ public OptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSi if (_fastOptions.TryAdd(id, this)) { _allOptions.Add(this); + switch (tab) + { + case TabGroup.MainSettings: _mainOptions.Add(this); break; + case TabGroup.ImpostorRoles: _impostorRoleOptions.Add(this); break; + case TabGroup.CrewmateRoles: _crewmateRoleOptions.Add(this); break; + case TabGroup.NeutralRoles: _neutralRoleOptions.Add(this); break; + case TabGroup.Addons: _addOnOptions.Add(this); break; + default: Logger.Warn($"Encountered unknown option category \"{tab}\" (ID: {id}, Name: {name})", nameof(OptionItem)); break; + } } else { diff --git a/Modules/OptionItem/PresetOptionItem.cs b/Modules/OptionItems/PresetOptionItem.cs similarity index 100% rename from Modules/OptionItem/PresetOptionItem.cs rename to Modules/OptionItems/PresetOptionItem.cs diff --git a/Modules/OptionItems/RoleSpawnChanceOptionItem.cs b/Modules/OptionItems/RoleSpawnChanceOptionItem.cs new file mode 100644 index 000000000..a4dca3ce5 --- /dev/null +++ b/Modules/OptionItems/RoleSpawnChanceOptionItem.cs @@ -0,0 +1,42 @@ +using TownOfHost.Modules.OptionItems.Interfaces; +using TownOfHost.Roles.Core; +using UnityEngine; + +namespace TownOfHost.Modules.OptionItems; + +public sealed class RoleSpawnChanceOptionItem : IntegerOptionItem, IRoleOptionItem +{ + public RoleSpawnChanceOptionItem( + int id, + string name, + int defaultValue, + TabGroup tab, + bool isSingleValue, + IntegerValueRule rule, + CustomRoles roleId, + Color roleColor) : base(id, name, defaultValue, tab, isSingleValue, rule) + { + RoleId = roleId; + RoleColor = roleColor; + } + public RoleSpawnChanceOptionItem( + int id, + string name, + int defaultValue, + TabGroup tab, + bool isSingleValue, + IntegerValueRule rule, + SimpleRoleInfo roleInfo) : this(id, name, defaultValue, tab, isSingleValue, rule, roleInfo.RoleName, roleInfo.RoleColor) { } + + public CustomRoles RoleId { get; } + public Color RoleColor { get; } + + public override void Refresh() + { + base.Refresh(); + if (OptionBehaviour != null && OptionBehaviour.TitleText != null) + { + OptionBehaviour.TitleText.text = GetName(true); + } + } +} diff --git a/Modules/OptionItem/StringOptionItem.cs b/Modules/OptionItems/StringOptionItem.cs similarity index 100% rename from Modules/OptionItem/StringOptionItem.cs rename to Modules/OptionItems/StringOptionItem.cs diff --git a/Modules/OptionItem/ValueRule.cs b/Modules/OptionItems/ValueRule.cs similarity index 100% rename from Modules/OptionItem/ValueRule.cs rename to Modules/OptionItems/ValueRule.cs diff --git a/Modules/RPC.cs b/Modules/RPC.cs index d2208c4f8..fc240cd32 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -40,6 +40,7 @@ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte ca switch (rpcType) { case RpcCalls.SetName: //SetNameRPC + subReader.ReadUInt32(); string name = subReader.ReadString(); if (subReader.BytesRemaining > 0 && subReader.ReadBoolean()) return false; Logger.Info("名前変更:" + __instance.GetNameWithRole() + " => " + name, "SetName"); diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ee3462609..3bcfda84a 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -368,7 +368,7 @@ public static (string, Color) GetRoleTextHideAndSeek(RoleTypes oRole, CustomRole return (text, color); } - public static bool HasTasks(GameData.PlayerInfo p, bool ForRecompute = true) + public static bool HasTasks(NetworkedPlayerInfo p, bool ForRecompute = true) { if (GameStates.IsLobby) return false; //Tasksがnullの場合があるのでその場合タスク無しとする @@ -796,7 +796,7 @@ public static PlayerControl GetPlayerById(byte playerId) cachedPlayers[playerId] = player; return player; } - public static GameData.PlayerInfo GetPlayerInfoById(int PlayerId) => + public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) => GameData.Instance.AllPlayers.ToArray().Where(info => info.PlayerId == PlayerId).FirstOrDefault(); private static StringBuilder SelfMark = new(20); private static StringBuilder SelfSuffix = new(20); diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index f320701e4..4c6cb87d2 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -455,12 +455,14 @@ public static void Postfix(ChatController __instance) var writer = CustomRpcSender.Create("MessagesToSend", SendOption.None); writer.StartMessage(clientId); writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) .Write(title) .EndRpc(); writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) .Write(msg) .EndRpc(); writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) .Write(player.Data.PlayerName) .EndRpc(); writer.EndMessage(); diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 008870575..82bdb14ac 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -108,7 +108,8 @@ private static IEnumerator CoEndGame(AmongUsClient self, GameOverReason reason) void SetGhostRole(bool ToGhostImpostor) { - if (!pc.Data.IsDead) ReviveRequiredPlayerIds.Add(pc.PlayerId); + var isDead = pc.Data.IsDead; + if (!isDead) ReviveRequiredPlayerIds.Add(pc.PlayerId); if (ToGhostImpostor) { Logger.Info($"{pc.GetNameWithRole()}: ImpostorGhostに変更", "ResetRoleAndEndGame"); @@ -119,6 +120,8 @@ void SetGhostRole(bool ToGhostImpostor) Logger.Info($"{pc.GetNameWithRole()}: CrewmateGhostに変更", "ResetRoleAndEndGame"); pc.RpcSetRole(RoleTypes.CrewmateGhost); } + // 蘇生までの遅延の間にオートミュートをかけられないように元に戻しておく + pc.Data.IsDead = isDead; } } @@ -140,7 +143,7 @@ void SetGhostRole(bool ToGhostImpostor) // 蘇生 playerInfo.IsDead = false; // 送信 - GameData.Instance.SetDirtyBit(0b_1u << playerId); + playerInfo.MarkDirty(); AmongUsClient.Instance.SendAllStreamedObjects(); } // ゲーム終了を確実に最後に届けるための遅延 diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index dfc9c266a..29ec27702 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -173,7 +173,7 @@ public static void Postfix(ControllerManager __instance) if (Input.GetKeyDown(KeyCode.Equals)) { Main.VisibleTasksCount = !Main.VisibleTasksCount; - DestroyableSingleton.Instance.Notifier.AddItem("VisibleTaskCountが" + Main.VisibleTasksCount.ToString() + "に変更されました。"); + DestroyableSingleton.Instance.Notifier.AddDisconnectMessage("VisibleTaskCountが" + Main.VisibleTasksCount.ToString() + "に変更されました。"); } //エアシップのトイレのドアを全て開ける if (Input.GetKeyDown(KeyCode.P)) diff --git a/Patches/CredentialsPatch.cs b/Patches/CredentialsPatch.cs index 2128c3f79..84e2b3735 100644 --- a/Patches/CredentialsPatch.cs +++ b/Patches/CredentialsPatch.cs @@ -15,6 +15,8 @@ namespace TownOfHost public static class CredentialsPatch { public static SpriteRenderer TohLogo { get; private set; } + private static TextMeshPro pingTrackerCredential = null; + private static AspectPosition pingTrackerCredentialAspectPos = null; [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] class PingTrackerUpdatePatch @@ -22,30 +24,39 @@ class PingTrackerUpdatePatch static StringBuilder sb = new(); static void Postfix(PingTracker __instance) { - __instance.text.alignment = TextAlignmentOptions.TopRight; + if (pingTrackerCredential == null) + { + var uselessPingTracker = Object.Instantiate(__instance, __instance.transform.parent); + pingTrackerCredential = uselessPingTracker.GetComponent(); + Object.Destroy(uselessPingTracker); + pingTrackerCredential.alignment = TextAlignmentOptions.TopRight; + pingTrackerCredential.color = new(1f, 1f, 1f, 0.7f); + pingTrackerCredential.rectTransform.pivot = new(1f, 1f); // 中心を右上角に設定 + pingTrackerCredentialAspectPos = pingTrackerCredential.GetComponent(); + pingTrackerCredentialAspectPos.Alignment = AspectPosition.EdgeAlignments.RightTop; + } + if (pingTrackerCredentialAspectPos) + { + pingTrackerCredentialAspectPos.DistanceFromEdge = DestroyableSingleton.InstanceExists && DestroyableSingleton.Instance.Chat.chatButton.gameObject.active + ? new(2.5f, 0f, -800f) + : new(1.8f, 0f, -800f); + } sb.Clear(); - - sb.Append("\r\n").Append(Main.credentialsText); - + sb.Append(Main.credentialsText); if (Options.NoGameEnd.GetBool()) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("NoGameEnd"))); if (Options.IsStandardHAS) sb.Append($"\r\n").Append(Utils.ColorString(Color.yellow, GetString("StandardHAS"))); if (Options.CurrentGameMode == CustomGameMode.HideAndSeek) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("HideAndSeek"))); if (!GameStates.IsModHost) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("Warning.NoModHost"))); if (DebugModeManager.IsDebugMode) sb.Append("\r\n").Append(Utils.ColorString(Color.green, "デバッグモード")); - var offset_x = 1.2f; //右端からのオフセット - if (HudManager.InstanceExists && HudManager._instance.Chat.chatButton.active) offset_x += 0.8f; //チャットボタンがある場合の追加オフセット - if (FriendsListManager.InstanceExists && FriendsListManager._instance.FriendsListButton.Button.active) offset_x += 0.8f; //フレンドリストボタンがある場合の追加オフセット - __instance.GetComponent().DistanceFromEdge = new Vector3(offset_x, 0f, 0f); - if (GameStates.IsLobby) { if (Options.IsStandardHAS && !CustomRoles.Sheriff.IsEnable() && !CustomRoles.SerialKiller.IsEnable() && CustomRoles.Egoist.IsEnable()) sb.Append($"\r\n").Append(Utils.ColorString(Color.red, GetString("Warning.EgoistCannotWin"))); } - __instance.text.text += sb.ToString(); + pingTrackerCredential.text = sb.ToString(); } } [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] @@ -56,6 +67,10 @@ static void Postfix(VersionShower __instance) { TMPTemplate.SetBase(__instance.text); Main.credentialsText = $"{Main.ModName} v{Main.PluginVersion}"; + if (Main.IsPrerelease) + { + Main.credentialsText += $"\r\n<#F39C12>{GetString("Prerelease")}"; + } #if DEBUG Main.credentialsText += $"\r\n{ThisAssembly.Git.Branch}({ThisAssembly.Git.Commit})"; #endif diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 38e29e423..e2614bc06 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -8,7 +8,7 @@ namespace TownOfHost { class ExileControllerWrapUpPatch { - public static GameData.PlayerInfo AntiBlackout_LastExiled; + public static NetworkedPlayerInfo AntiBlackout_LastExiled; [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] class BaseExileControllerPatch { @@ -40,7 +40,7 @@ public static void Postfix(AirshipExileController __instance) } } } - static void WrapUpPostfix(GameData.PlayerInfo exiled) + static void WrapUpPostfix(NetworkedPlayerInfo exiled) { if (AntiBlackout.OverrideExiledPlayer) { @@ -113,7 +113,7 @@ static void WrapUpPostfix(GameData.PlayerInfo exiled) Utils.NotifyRoles(); } - static void WrapUpFinalizer(GameData.PlayerInfo exiled) + static void WrapUpFinalizer(NetworkedPlayerInfo exiled) { //WrapUpPostfixで例外が発生しても、この部分だけは確実に実行されます。 if (AmongUsClient.Instance.AmHost) diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 1dc430bfd..be86fb1e3 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -1,29 +1,176 @@ using System; -using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using HarmonyLib; -using Il2CppInterop.Runtime.InteropTypes.Arrays; +using TownOfHost.Modules.OptionItems; +using TownOfHost.Modules.OptionItems.Interfaces; using UnityEngine; using static TownOfHost.Translator; using Object = UnityEngine.Object; namespace TownOfHost { - [HarmonyPatch(typeof(GameSettingMenu), nameof(GameSettingMenu.InitializeOptions))] + [HarmonyPatch(typeof(GameSettingMenu))] public static class GameSettingMenuPatch { - public static void Prefix(GameSettingMenu __instance) + private static GameOptionsMenu tohSettingsTab; + private static PassiveButton tohSettingsButton; + public static CategoryHeaderMasked MainCategoryHeader { get; private set; } + public static CategoryHeaderMasked ImpostorRoleCategoryHeader { get; private set; } + public static CategoryHeaderMasked CrewmateRoleCategoryHeader { get; private set; } + public static CategoryHeaderMasked NeutralRoleCategoryHeader { get; private set; } + public static CategoryHeaderMasked AddOnCategoryHeader { get; private set; } + + [HarmonyPatch(nameof(GameSettingMenu.Start)), HarmonyPostfix] + public static void StartPostfix(GameSettingMenu __instance) { - // Unlocks map/impostor amount changing in online (for testing on your custom servers) - // オンラインモードで部屋を立て直さなくてもマップを変更できるように変更 - __instance.HideForOnline = new Il2CppReferenceArray(0); + tohSettingsTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); + tohSettingsTab.name = TOHMenuName; + var vanillaOptions = tohSettingsTab.GetComponentsInChildren(); + foreach (var vanillaOption in vanillaOptions) + { + Object.Destroy(vanillaOption.gameObject); + } + + // TOH設定ボタンのスペースを作るため,左側の要素を上に詰める + var gameSettingsLabel = __instance.transform.Find("GameSettingsLabel"); + if (gameSettingsLabel) + { + gameSettingsLabel.localPosition += Vector3.up * 0.2f; + } + __instance.MenuDescriptionText.transform.parent.localPosition += Vector3.up * 0.4f; + __instance.GamePresetsButton.transform.parent.localPosition += Vector3.up * 0.5f; + + // TOH設定ボタン + tohSettingsButton = Object.Instantiate(__instance.GameSettingsButton, __instance.GameSettingsButton.transform.parent); + tohSettingsButton.name = "TOHSettingsButton"; + tohSettingsButton.transform.localPosition = __instance.RoleSettingsButton.transform.localPosition + (__instance.RoleSettingsButton.transform.localPosition - __instance.GameSettingsButton.transform.localPosition); + tohSettingsButton.buttonText.DestroyTranslator(); + tohSettingsButton.buttonText.text = GetString("TOHSettingsButtonLabel"); + var activeSprite = tohSettingsButton.activeSprites.GetComponent(); + var selectedSprite = tohSettingsButton.selectedSprites.GetComponent(); + activeSprite.color = selectedSprite.color = Main.UnityModColor; + tohSettingsButton.OnClick.AddListener((Action)(() => + { + __instance.ChangeTab(-1, false); // バニラタブを閉じる + tohSettingsTab.gameObject.SetActive(true); + __instance.MenuDescriptionText.text = GetString("TOHSettingsDescription"); + tohSettingsButton.SelectButton(true); + })); + + // 各カテゴリの見出しを作成 + MainCategoryHeader = CreateCategoryHeader(__instance, tohSettingsTab, "TabGroup.MainSettings"); + ImpostorRoleCategoryHeader = CreateCategoryHeader(__instance, tohSettingsTab, "TabGroup.ImpostorRoles"); + CrewmateRoleCategoryHeader = CreateCategoryHeader(__instance, tohSettingsTab, "TabGroup.CrewmateRoles"); + NeutralRoleCategoryHeader = CreateCategoryHeader(__instance, tohSettingsTab, "TabGroup.NeutralRoles"); + AddOnCategoryHeader = CreateCategoryHeader(__instance, tohSettingsTab, "TabGroup.Addons"); + + // 各設定スイッチを作成 + var template = __instance.GameSettingsTab.stringOptionOrigin; + var scOptions = new Il2CppSystem.Collections.Generic.List(); + foreach (var option in OptionItem.AllOptions) + { + if (option.OptionBehaviour == null) + { + var stringOption = Object.Instantiate(template, tohSettingsTab.settingsContainer); + scOptions.Add(stringOption); + stringOption.SetClickMask(__instance.GameSettingsButton.ClickMask); + stringOption.SetUpFromData(stringOption.data, GameOptionsMenu.MASK_LAYER); + stringOption.OnValueChanged = new Action((o) => { }); + stringOption.TitleText.text = option.Name; + stringOption.Value = stringOption.oldValue = option.CurrentValue; + stringOption.ValueText.text = option.GetString(); + stringOption.name = option.Name; + + // タイトルの枠をデカくする + var indent = 0f; // 親オプションがある場合枠の左を削ってインデントに見せる + var parent = option.Parent; + while (parent != null) + { + indent += 0.15f; + parent = parent.Parent; + } + stringOption.LabelBackground.size += new Vector2(2f - indent * 2, 0f); + stringOption.LabelBackground.transform.localPosition += new Vector3(-1f + indent, 0f, 0f); + stringOption.TitleText.rectTransform.sizeDelta += new Vector2(2f - indent * 2, 0f); + stringOption.TitleText.transform.localPosition += new Vector3(-1f + indent, 0f, 0f); + + option.OptionBehaviour = stringOption; + } + option.OptionBehaviour.gameObject.SetActive(true); + } + tohSettingsTab.Children = scOptions; + tohSettingsTab.gameObject.SetActive(false); + + // 各カテゴリまでスクロールするボタンを作成 + var jumpButtonY = -0.6f; + var jumpToMainButton = CreateJumpToCategoryButton(__instance, tohSettingsTab, "TownOfHost.Resources.TabIcon_MainSettings.png", ref jumpButtonY, MainCategoryHeader); + var jumpToImpButton = CreateJumpToCategoryButton(__instance, tohSettingsTab, "TownOfHost.Resources.TabIcon_ImpostorRoles.png", ref jumpButtonY, ImpostorRoleCategoryHeader); + var jumpToCrewButton = CreateJumpToCategoryButton(__instance, tohSettingsTab, "TownOfHost.Resources.TabIcon_CrewmateRoles.png", ref jumpButtonY, CrewmateRoleCategoryHeader); + var jumpToNeutralButton = CreateJumpToCategoryButton(__instance, tohSettingsTab, "TownOfHost.Resources.TabIcon_NeutralRoles.png", ref jumpButtonY, NeutralRoleCategoryHeader); + var jumpToAddOnButton = CreateJumpToCategoryButton(__instance, tohSettingsTab, "TownOfHost.Resources.TabIcon_Addons.png", ref jumpButtonY, AddOnCategoryHeader); + } + private static MapSelectButton CreateJumpToCategoryButton(GameSettingMenu __instance, GameOptionsMenu tohTab, string resourcePath, ref float localY, CategoryHeaderMasked jumpTo) + { + var image = Utils.LoadSprite(resourcePath, 100f); + var button = Object.Instantiate(__instance.GameSettingsTab.MapPicker.MapButtonOrigin, Vector3.zero, Quaternion.identity, tohTab.transform); + button.SetImage(image, GameOptionsMenu.MASK_LAYER); + button.transform.localPosition = new(7.1f, localY, -10f); + button.Button.ClickMask = tohTab.ButtonClickMask; + button.Button.OnClick.AddListener((Action)(() => + { + tohTab.scrollBar.velocity = Vector2.zero; // ドラッグの慣性によるスクロールを止める + var relativePosition = tohTab.scrollBar.transform.InverseTransformPoint(jumpTo.transform.position); // Scrollerのローカル空間における座標に変換 + var scrollAmount = CategoryJumpY - relativePosition.y; + tohTab.scrollBar.Inner.localPosition = tohTab.scrollBar.Inner.localPosition + Vector3.up * scrollAmount; // 強制スクロール + tohTab.scrollBar.ScrollRelative(Vector2.zero); // スクロール範囲内に収め,スクロールバーを更新する + })); + button.Button.activeSprites.transform.GetChild(0).gameObject.SetActive(false); // チェックボックスを消す + localY -= JumpButtonSpacing; + return button; + } + private const float JumpButtonSpacing = 0.6f; + // ジャンプしたカテゴリヘッダのScrollerとの相対Y座標がこの値になる + private const float CategoryJumpY = 2f; + private static CategoryHeaderMasked CreateCategoryHeader(GameSettingMenu __instance, GameOptionsMenu tohTab, string translationKey) + { + var categoryHeader = Object.Instantiate(__instance.GameSettingsTab.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, tohTab.settingsContainer); + categoryHeader.name = translationKey; + categoryHeader.Title.text = GetString(translationKey); + var maskLayer = GameOptionsMenu.MASK_LAYER; + categoryHeader.Background.material.SetInt(PlayerMaterial.MaskLayer, maskLayer); + if (categoryHeader.Divider != null) + { + categoryHeader.Divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer); + } + categoryHeader.Title.fontMaterial.SetFloat("_StencilComp", 3f); + categoryHeader.Title.fontMaterial.SetFloat("_Stencil", (float)maskLayer); + categoryHeader.transform.localScale = Vector3.one * GameOptionsMenu.HEADER_SCALE; + return categoryHeader; + } + + // 初めてロール設定を表示したときに発生する例外(バニラバグ)の影響を回避するためPrefix + [HarmonyPatch(nameof(GameSettingMenu.ChangeTab)), HarmonyPrefix] + public static void ChangeTabPrefix(bool previewOnly) + { + if (!previewOnly) + { + if (tohSettingsTab) + { + tohSettingsTab.gameObject.SetActive(false); + } + if (tohSettingsButton) + { + tohSettingsButton.SelectButton(false); + } + } } + + public const string TOHMenuName = "TownOfHostTab"; } - [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] - [HarmonyPriority(Priority.First)] - public static class GameOptionsMenuPatch + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Initialize))] + public static class GameOptionsMenuInitializePatch { public static void Postfix(GameOptionsMenu __instance) { @@ -49,96 +196,6 @@ public static void Postfix(GameOptionsMenu __instance) break; } } - var template = Object.FindObjectsOfType().FirstOrDefault(); - if (template == null) return; - - var gameSettings = GameObject.Find("Game Settings"); - if (gameSettings == null) return; - gameSettings.transform.FindChild("GameGroup").GetComponent().ScrollWheelSpeed = 1f; - - var gameSettingMenu = Object.FindObjectsOfType().FirstOrDefault(); - if (gameSettingMenu == null) return; - List menus = new() { gameSettingMenu.RegularGameSettings, gameSettingMenu.RolesSettings.gameObject }; - List highlights = new() { gameSettingMenu.GameSettingsHightlight, gameSettingMenu.RolesSettingsHightlight }; - - var roleTab = GameObject.Find("RoleTab"); - var gameTab = GameObject.Find("GameTab"); - List tabs = new() { gameTab, roleTab }; - - foreach (var tab in EnumHelper.GetAllValues()) - { - var obj = gameSettings.transform.parent.Find(tab + "Tab"); - if (obj != null) - { - obj.transform.FindChild("../../GameGroup/Text").GetComponent().SetText(GetString("TabGroup." + tab)); - continue; - } - - var tohSettings = Object.Instantiate(gameSettings, gameSettings.transform.parent); - tohSettings.name = tab + "Tab"; - tohSettings.transform.FindChild("BackPanel").transform.localScale = - tohSettings.transform.FindChild("Bottom Gradient").transform.localScale = new Vector3(1.2f, 1f, 1f); - tohSettings.transform.FindChild("Background").transform.localScale = new Vector3(1.3f, 1f, 1f); - tohSettings.transform.FindChild("UI_Scrollbar").transform.localPosition += new Vector3(0.35f, 0f, 0f); - tohSettings.transform.FindChild("UI_ScrollbarTrack").transform.localPosition += new Vector3(0.35f, 0f, 0f); - tohSettings.transform.FindChild("GameGroup/SliderInner").transform.localPosition += new Vector3(-0.15f, 0f, 0f); - var tohMenu = tohSettings.transform.FindChild("GameGroup/SliderInner").GetComponent(); - - //OptionBehaviourを破棄 - tohMenu.GetComponentsInChildren().Do(x => Object.Destroy(x.gameObject)); - - var scOptions = new List(); - foreach (var option in OptionItem.AllOptions) - { - if (option.Tab != (TabGroup)tab) continue; - if (option.OptionBehaviour == null) - { - var stringOption = Object.Instantiate(template, tohMenu.transform); - scOptions.Add(stringOption); - stringOption.OnValueChanged = new System.Action((o) => { }); - stringOption.TitleText.text = option.Name; - stringOption.Value = stringOption.oldValue = option.CurrentValue; - stringOption.ValueText.text = option.GetString(); - stringOption.name = option.Name; - stringOption.transform.FindChild("Background").localScale = new Vector3(1.2f, 1f, 1f); - stringOption.transform.FindChild("Plus_TMP").localPosition += new Vector3(0.3f, 0f, 0f); - stringOption.transform.FindChild("Minus_TMP").localPosition += new Vector3(0.3f, 0f, 0f); - stringOption.transform.FindChild("Value_TMP").localPosition += new Vector3(0.3f, 0f, 0f); - stringOption.transform.FindChild("Title_TMP").localPosition += new Vector3(0.15f, 0f, 0f); - stringOption.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(3.5f, 0.37f); - - option.OptionBehaviour = stringOption; - } - option.OptionBehaviour.gameObject.SetActive(true); - } - tohMenu.Children = scOptions.ToArray(); - tohSettings.gameObject.SetActive(false); - menus.Add(tohSettings.gameObject); - - var tohTab = Object.Instantiate(roleTab, roleTab.transform.parent); - tohTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Utils.LoadSprite($"TownOfHost.Resources.TabIcon_{tab}.png", 100f); - tabs.Add(tohTab); - var tohTabHighlight = tohTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - highlights.Add(tohTabHighlight); - } - - for (var i = 0; i < tabs.Count; i++) - { - tabs[i].transform.localPosition = new(0.8f * (i - 1) - tabs.Count / 3f, tabs[i].transform.localPosition.y, tabs[i].transform.localPosition.z); - var button = tabs[i].GetComponentInChildren(); - if (button == null) continue; - var copiedIndex = i; - button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); - Action value = () => - { - for (var j = 0; j < menus.Count; j++) - { - menus[j].SetActive(j == copiedIndex); - highlights[j].enabled = j == copiedIndex; - } - }; - button.OnClick.AddListener(value); - } } } @@ -149,85 +206,90 @@ public class GameOptionsMenuUpdatePatch public static void Postfix(GameOptionsMenu __instance) { - if (__instance.transform.parent.parent.name == "Game Settings") return; - foreach (var tab in EnumHelper.GetAllValues()) - { - if (__instance.transform.parent.parent.name != tab + "Tab") continue; - __instance.transform.FindChild("../../GameGroup/Text").GetComponent().SetText(GetString("TabGroup." + tab)); + if (__instance.name != GameSettingMenuPatch.TOHMenuName) return; - _timer += Time.deltaTime; - if (_timer < 0.1f) return; - _timer = 0f; + _timer += Time.deltaTime; + if (_timer < 0.1f) return; + _timer = 0f; - float numItems = __instance.Children.Length; - var offset = 2.7f; + var offset = 2.7f; + var isOdd = true; - foreach (var option in OptionItem.AllOptions) - { - if ((TabGroup)tab != option.Tab) continue; - if (option?.OptionBehaviour == null || option.OptionBehaviour.gameObject == null) continue; + UpdateCategoryHeader(GameSettingMenuPatch.MainCategoryHeader, ref offset); + foreach (var option in OptionItem.MainOptions) + { + UpdateOption(ref isOdd, option, ref offset); + } + UpdateCategoryHeader(GameSettingMenuPatch.ImpostorRoleCategoryHeader, ref offset); + foreach (var option in OptionItem.ImpostorRoleOptions) + { + UpdateOption(ref isOdd, option, ref offset); + } + UpdateCategoryHeader(GameSettingMenuPatch.CrewmateRoleCategoryHeader, ref offset); + foreach (var option in OptionItem.CrewmateRoleOptions) + { + UpdateOption(ref isOdd, option, ref offset); + } + UpdateCategoryHeader(GameSettingMenuPatch.NeutralRoleCategoryHeader, ref offset); + foreach (var option in OptionItem.NeutralRoleOptions) + { + UpdateOption(ref isOdd, option, ref offset); + } + UpdateCategoryHeader(GameSettingMenuPatch.AddOnCategoryHeader, ref offset); + foreach (var option in OptionItem.AddOnOptions) + { + UpdateOption(ref isOdd, option, ref offset); + } - var enabled = true; - var parent = option.Parent; + __instance.scrollBar.ContentYBounds.max = (-offset) - 1.5f; + } + private static void UpdateCategoryHeader(CategoryHeaderMasked categoryHeader, ref float offset) + { + offset -= GameOptionsMenu.HEADER_HEIGHT; + categoryHeader.transform.localPosition = new(GameOptionsMenu.HEADER_X, offset, -2f); + } + private static void UpdateOption(ref bool isOdd, OptionItem item, ref float offset) + { + if (item?.OptionBehaviour == null || item.OptionBehaviour.gameObject == null) return; - enabled = AmongUsClient.Instance.AmHost && - !option.IsHiddenOn(Options.CurrentGameMode); + var enabled = true; + var parent = item.Parent; - var opt = option.OptionBehaviour.transform.Find("Background").GetComponent(); - opt.size = new(5.0f, 0.45f); - while (parent != null && enabled) - { - enabled = parent.GetBool(); - parent = parent.Parent; - opt.color = new(0f, 1f, 0f); - opt.size = new(4.8f, 0.45f); - opt.transform.localPosition = new Vector3(0.11f, 0f); - option.OptionBehaviour.transform.Find("Title_TMP").transform.localPosition = new Vector3(-0.95f, 0f); - option.OptionBehaviour.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(3.4f, 0.37f); - if (option.Parent?.Parent != null) - { - opt.color = new(0f, 0f, 1f); - opt.size = new(4.6f, 0.45f); - opt.transform.localPosition = new Vector3(0.24f, 0f); - option.OptionBehaviour.transform.Find("Title_TMP").transform.localPosition = new Vector3(-0.7f, 0f); - option.OptionBehaviour.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(3.3f, 0.37f); - if (option.Parent?.Parent?.Parent != null) - { - opt.color = new(1f, 0f, 0f); - opt.size = new(4.4f, 0.45f); - opt.transform.localPosition = new Vector3(0.37f, 0f); - option.OptionBehaviour.transform.Find("Title_TMP").transform.localPosition = new Vector3(-0.55f, 0f); - option.OptionBehaviour.transform.FindChild("Title_TMP").GetComponent().sizeDelta = new Vector2(3.2f, 0.37f); - } - } - } + // 親オプションの値を見て表示するか決める + enabled = AmongUsClient.Instance.AmHost && !item.IsHiddenOn(Options.CurrentGameMode); + var stringOption = item.OptionBehaviour; + while (parent != null && enabled) + { + enabled = parent.GetBool(); + parent = parent.Parent; + } - option.OptionBehaviour.gameObject.SetActive(enabled); - if (enabled) - { - offset -= option.IsHeader ? 0.7f : 0.5f; - option.OptionBehaviour.transform.localPosition = new Vector3( - option.OptionBehaviour.transform.localPosition.x, - offset, - option.OptionBehaviour.transform.localPosition.z); + item.OptionBehaviour.gameObject.SetActive(enabled); + if (enabled) + { + // 見やすさのため交互に色を変える + stringOption.LabelBackground.color = item is IRoleOptionItem roleOption ? roleOption.RoleColor : (isOdd ? Color.cyan : Color.white); - if (option.IsHeader) - { - numItems += 0.5f; - } - } - else - { - numItems--; - } + offset -= GameOptionsMenu.SPACING_Y; + if (item.IsHeader) + { + // IsHeaderなら隙間を広くする + offset -= HeaderSpacingY; } - __instance.GetComponentInParent().ContentYBounds.max = (-offset) - 1.5f; + item.OptionBehaviour.transform.localPosition = new Vector3( + GameOptionsMenu.START_POS_X, + offset, + -2f); + + isOdd = !isOdd; } } + + private const float HeaderSpacingY = 0.2f; } - [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))] - public class StringOptionEnablePatch + [HarmonyPatch(typeof(StringOption), nameof(StringOption.Initialize))] + public class StringOptionInitializePatch { public static bool Prefix(StringOption __instance) { @@ -235,7 +297,7 @@ public static bool Prefix(StringOption __instance) if (option == null) return true; __instance.OnValueChanged = new Action((o) => { }); - __instance.TitleText.text = option.GetName(); + __instance.TitleText.text = option.GetName(option is RoleSpawnChanceOptionItem); __instance.Value = __instance.oldValue = option.CurrentValue; __instance.ValueText.text = option.GetString(); @@ -282,7 +344,7 @@ public static class RolesSettingsMenuPatch { public static void Postfix(RolesSettingsMenu __instance) { - foreach (var ob in __instance.Children) + foreach (var ob in __instance.advancedSettingChildren) { switch (ob.Title) { @@ -298,10 +360,19 @@ public static void Postfix(RolesSettingsMenu __instance) } } } - [HarmonyPatch(typeof(NormalGameOptionsV07), nameof(NormalGameOptionsV07.SetRecommendations))] + [HarmonyPatch(typeof(NormalGameOptionsV08), nameof(NormalGameOptionsV08.SetRecommendations), [typeof(int), typeof(bool), typeof(RulesPresets)])] public static class SetRecommendationsPatch { - public static bool Prefix(NormalGameOptionsV07 __instance, int numPlayers, bool isOnline) + public static bool Prefix(NormalGameOptionsV08 __instance, int numPlayers, bool isOnline, RulesPresets rulesPresets) + { + switch (rulesPresets) + { + case RulesPresets.Standard: SetStandardRecommendations(__instance, numPlayers, isOnline); return false; + // スタンダード以外のプリセットは一旦そのままにしておく + default: return true; + } + } + private static void SetStandardRecommendations(NormalGameOptionsV08 __instance, int numPlayers, bool isOnline) { numPlayers = Mathf.Clamp(numPlayers, 4, 15); __instance.PlayerSpeedMod = __instance.MapId == 4 ? 1.25f : 1f; //AirShipなら1.25、それ以外は1 @@ -313,7 +384,7 @@ public static bool Prefix(NormalGameOptionsV07 __instance, int numPlayers, bool __instance.NumShortTasks = 6; __instance.NumEmergencyMeetings = 1; if (!isOnline) - __instance.NumImpostors = NormalGameOptionsV07.RecommendedImpostors[numPlayers]; + __instance.NumImpostors = NormalGameOptionsV08.RecommendedImpostors[numPlayers]; __instance.KillDistance = 0; __instance.DiscussionTime = 0; __instance.VotingTime = 150; @@ -322,13 +393,19 @@ public static bool Prefix(NormalGameOptionsV07 __instance, int numPlayers, bool __instance.VisualTasks = false; __instance.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Phantom, 0, 0); __instance.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); __instance.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); __instance.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Noisemaker, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Tracker, 0, 0); __instance.roleOptions.SetRoleRecommended(RoleTypes.Shapeshifter); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Phantom); __instance.roleOptions.SetRoleRecommended(RoleTypes.Scientist); __instance.roleOptions.SetRoleRecommended(RoleTypes.GuardianAngel); __instance.roleOptions.SetRoleRecommended(RoleTypes.Engineer); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Noisemaker); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Tracker); if (Options.CurrentGameMode == CustomGameMode.HideAndSeek) //HideAndSeek { @@ -352,7 +429,6 @@ public static bool Prefix(NormalGameOptionsV07 __instance, int numPlayers, bool __instance.NumShortTasks = 10; __instance.KillCooldown = 10f; } - return false; } } } diff --git a/Patches/GameOptionsPatch.cs b/Patches/GameOptionsPatch.cs index 2514b8ae9..00fa73b27 100644 --- a/Patches/GameOptionsPatch.cs +++ b/Patches/GameOptionsPatch.cs @@ -11,33 +11,25 @@ class ChanceChangePatch { public static void Postfix(RoleOptionSetting __instance) { - string DisableText = $" ({GetString("Disabled")})"; - if (__instance.Role.Role == RoleTypes.Scientist) + // The Phantom does not work together with desynchronized impostor roles e.g. Sheriff so we need to disable it. + // This may be removed in the future when we have implemented changing vanilla role or some other stuff. + if (__instance.Role.Role is RoleTypes.GuardianAngel || (__instance.Role.Role is RoleTypes.Phantom && !DebugModeManager.IsDebugMode)) { - __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.Scientist); - } - if (__instance.Role.Role == RoleTypes.Engineer) - { - __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.Engineer); - } - if (__instance.Role.Role == RoleTypes.GuardianAngel) - { - //+-ボタン, 設定値, 詳細設定ボタンを非表示 - var tf = __instance.transform; - tf.Find("Count Plus_TMP").gameObject.active - = tf.Find("Chance Minus_TMP").gameObject.active - = tf.Find("Chance Value_TMP").gameObject.active - = tf.Find("Chance Plus_TMP").gameObject.active - = tf.Find("More Options").gameObject.active - = false; + string disableText = $" ({GetString("Disabled")})"; + //+-ボタンを非表示 + foreach (var button in __instance.GetComponentsInChildren()) + { + button.gameObject.SetActive(false); + } - if (!__instance.TitleText.text.Contains(DisableText)) - __instance.TitleText.text += DisableText; - __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.GuardianAngel); - } - if (__instance.Role.Role == RoleTypes.Shapeshifter) - { - __instance.TitleText.color = Utils.GetRoleColor(CustomRoles.Shapeshifter); + if (!__instance.titleText.text.Contains(disableText)) + __instance.titleText.text += disableText; + if (__instance.roleChance != 0 || __instance.roleMaxCount != 0) + { + __instance.roleChance = 0; + __instance.roleMaxCount = 0; + __instance.OnValueChanged.Invoke(__instance); + } } } } diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs index 5a83ca876..d09bda5fa 100644 --- a/Patches/GameStartManagerPatch.cs +++ b/Patches/GameStartManagerPatch.cs @@ -20,7 +20,7 @@ public class GameStartManagerPatch private static TextMeshPro warningText; public static TextMeshPro HideName; private static TextMeshPro timerText; - private static SpriteRenderer cancelButton; + private static PassiveButton cancelButton; [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))] public class GameStartManagerStartPatch @@ -51,31 +51,32 @@ public static void Postfix(GameStartManager __instance) timerText.fontSize = 3.2f; timerText.name = "Timer"; timerText.DestroyChildren(); - timerText.transform.localPosition += Vector3.down * 0.2f; + timerText.transform.localPosition += new Vector3(0.3f, -3.4f, 0f); timerText.gameObject.SetActive(AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame && AmongUsClient.Instance.AmHost); cancelButton = Object.Instantiate(__instance.StartButton, __instance.transform); cancelButton.name = "CancelButton"; - var cancelLabel = cancelButton.GetComponentInChildren(); + var cancelLabel = cancelButton.buttonText; cancelLabel.DestroyTranslator(); cancelLabel.text = GetString("Cancel"); - cancelButton.transform.localScale = new(0.4f, 0.4f, 1f); - cancelButton.color = Color.red; - cancelButton.transform.localPosition = new(0f, -0.37f, 0f); - var buttonComponent = cancelButton.GetComponent(); - buttonComponent.OnClick = new(); - buttonComponent.OnClick.AddListener((Action)(() => __instance.ResetStartState())); + cancelButton.transform.localScale = new(0.5f, 0.5f, 1f); + var cancelButtonInactiveRenderer = cancelButton.inactiveSprites.GetComponent(); + cancelButtonInactiveRenderer.color = new(0.8f, 0f, 0f, 1f); + var cancelButtonActiveRenderer = cancelButton.activeSprites.GetComponent(); + cancelButtonActiveRenderer.color = Color.red; + var cancelButtonInactiveShine = cancelButton.inactiveSprites.transform.Find("Shine"); + if (cancelButtonInactiveShine) + { + cancelButtonInactiveShine.gameObject.SetActive(false); + } + cancelButton.activeTextColor = cancelButton.inactiveTextColor = Color.white; + cancelButton.transform.localPosition = new(2f, 0.13f, 0f); + cancelButton.OnClick = new(); + cancelButton.OnClick.AddListener((Action)(() => __instance.ResetStartState())); cancelButton.gameObject.SetActive(false); if (!AmongUsClient.Instance.AmHost) return; - // Make Public Button - if (ModUpdater.isBroken || ModUpdater.hasUpdate || !Main.AllowPublicRoom || !VersionChecker.IsSupported || !Main.IsPublicAvailableOnThisVersion) - { - __instance.MakePublicButton.color = Palette.DisabledClear; - __instance.privatePublicText.color = Palette.DisabledClear; - } - if (Main.NormalOptions.KillCooldown == 0f) Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; @@ -170,7 +171,7 @@ public static void Postfix(GameStartManager __instance) timer = Mathf.Max(0f, timer -= Time.deltaTime); int minutes = (int)timer / 60; int seconds = (int)timer % 60; - string countDown = $"({minutes:00}:{seconds:00})"; + string countDown = $"{minutes:00}:{seconds:00}"; if (timer <= 60) countDown = Utils.ColorString(Color.red, countDown); timerText.text = countDown; } diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 9ab983002..823d06b40 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -42,12 +42,6 @@ public static void Postfix(HudManager __instance) player.Collider.offset = new Vector2(0f, -0.3636f); } } - if (GameStates.IsLobby) - { - __instance.GameSettings.text = OptionShower.GetText(); - __instance.GameSettings.fontSizeMin = - __instance.GameSettings.fontSizeMax = (TranslationController.Instance.currentLanguage.languageID == SupportedLangs.Japanese || Main.ForceJapanese.Value) ? 1.05f : 1.2f; - } //ゲーム中でなければ以下は実行されない if (!AmongUsClient.Instance.IsGameStarted) return; @@ -194,8 +188,8 @@ public static void Postfix(HudManager __instance, [HarmonyArgument(2)] bool isAc __instance.ReportButton.ToggleVisible(!GameStates.IsLobby && isActive); if (!GameStates.IsModHost) return; IsActive = isActive; + if (GameStates.IsLobby) return; if (!isActive) return; - var player = PlayerControl.LocalPlayer; __instance.KillButton.ToggleVisible(player.CanUseKillButton()); __instance.ImpostorVentButton.ToggleVisible(player.CanUseImpostorVentButton()); @@ -225,6 +219,7 @@ class TaskPanelBehaviourPatch // タスク表示の文章が更新・適用された後に実行される public static void Postfix(TaskPanelBehaviour __instance) { + if (GameStates.IsLobby) return; if (!GameStates.IsModHost) return; PlayerControl player = PlayerControl.LocalPlayer; diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 99f8ade37..fbf7f0372 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -45,7 +45,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; //winnerListリセット - TempData.winners = new Il2CppSystem.Collections.Generic.List(); + EndGameResult.CachedWinners = new Il2CppSystem.Collections.Generic.List(); var winner = new List(); foreach (var pc in Main.AllPlayerControls) { @@ -95,7 +95,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ref En { if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw && pc.Is(CustomRoles.GM)) continue; - TempData.winners.Add(new WinningPlayerData(pc.Data)); + EndGameResult.CachedWinners.Add(new CachedPlayerData(pc.Data)); Main.winnerList.Add(pc.PlayerId); } diff --git a/Patches/PlayerContorolPatch.cs b/Patches/PlayerContorolPatch.cs index b9783a9cc..b6d913562 100644 --- a/Patches/PlayerContorolPatch.cs +++ b/Patches/PlayerContorolPatch.cs @@ -334,8 +334,8 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerC class ReportDeadBodyPatch { public static Dictionary CanReport; - public static Dictionary> WaitReport = new(); - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] GameData.PlayerInfo target) + public static Dictionary> WaitReport = new(); + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] NetworkedPlayerInfo target) { if (GameStates.IsMeeting) return false; Logger.Info($"{__instance.GetNameWithRole()} => {target?.Object?.GetNameWithRole() ?? "null"}", "ReportDeadBody"); @@ -642,17 +642,10 @@ public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) !user.CanUseImpostorVentButton()) //インポスターベントも使えない ) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, -1); - writer.WritePacked(127); - AmongUsClient.Instance.FinishRpcImmediately(writer); _ = new LateTask(() => { - int clientId = user.GetClientId(); - MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, clientId); - writer2.Write(id); - AmongUsClient.Instance.FinishRpcImmediately(writer2); - }, 0.5f, "Fix DesyncImpostor Stuck"); - return false; + __instance.RpcBootFromVent(id); + }, 0.5f, "Cancel Vent"); } } return true; diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 9359822f1..bb3635b35 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using AmongUs.Data; using AmongUs.GameOptions; @@ -82,28 +83,61 @@ static void Prefix([HarmonyArgument(0)] ClientData data) } public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) { - // Logger.info($"RealNames[{data.Character.PlayerId}]を削除"); - // main.RealNames.Remove(data.Character.PlayerId); - if (GameStates.IsInGame) + var isFailure = false; + + try { - if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) - foreach (var lovers in Main.LoversPlayers.ToArray()) + if (data == null) + { + isFailure = true; + Logger.Warn("退出者のClientDataがnull", nameof(OnPlayerLeftPatch)); + } + else if (data.Character == null) + { + isFailure = true; + Logger.Warn("退出者のPlayerControlがnull", nameof(OnPlayerLeftPatch)); + } + else if (data.Character.Data == null) + { + isFailure = true; + Logger.Warn("退出者のPlayerInfoがnull", nameof(OnPlayerLeftPatch)); + } + else + { + if (GameStates.IsInGame) { - Main.isLoversDead = true; - Main.LoversPlayers.Remove(lovers); - PlayerState.GetByPlayerId(lovers.PlayerId).RemoveSubRole(CustomRoles.Lovers); + if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) + foreach (var lovers in Main.LoversPlayers.ToArray()) + { + Main.isLoversDead = true; + Main.LoversPlayers.Remove(lovers); + PlayerState.GetByPlayerId(lovers.PlayerId).RemoveSubRole(CustomRoles.Lovers); + } + var state = PlayerState.GetByPlayerId(data.Character.PlayerId); + if (state.DeathReason == CustomDeathReason.etc) //死因が設定されていなかったら + { + state.DeathReason = CustomDeathReason.Disconnected; + state.SetDead(); + } + AntiBlackout.OnDisconnect(data.Character.Data); + PlayerGameOptionsSender.RemoveSender(data.Character); } - var state = PlayerState.GetByPlayerId(data.Character.PlayerId); - if (state.DeathReason == CustomDeathReason.etc) //死因が設定されていなかったら - { - state.DeathReason = CustomDeathReason.Disconnected; - state.SetDead(); + Main.playerVersion.Remove(data.Character.PlayerId); + Logger.Info($"{data.PlayerName}(ClientID:{data.Id})が切断(理由:{reason}, ping:{AmongUsClient.Instance.Ping})", "Session"); } - AntiBlackout.OnDisconnect(data.Character.Data); - PlayerGameOptionsSender.RemoveSender(data.Character); } - Main.playerVersion.Remove(data.Character.PlayerId); - Logger.Info($"{data.PlayerName}(ClientID:{data.Id})が切断(理由:{reason}, ping:{AmongUsClient.Instance.Ping})", "Session"); + catch (Exception e) + { + Logger.Warn("切断処理中に例外が発生", nameof(OnPlayerLeftPatch)); + Logger.Exception(e, nameof(OnPlayerLeftPatch)); + isFailure = true; + } + + if (isFailure) + { + Logger.Warn($"正常に完了しなかった切断 - 名前:{(data == null || data.PlayerName == null ? "(不明)" : data.PlayerName)}, 理由:{reason}, ping:{AmongUsClient.Instance.Ping}", "Session"); + ErrorText.Instance.AddError(AmongUsClient.Instance.GameState is InnerNetClient.GameStates.Started ? ErrorCode.OnPlayerLeftPostfixFailedInGame : ErrorCode.OnPlayerLeftPostfixFailedInLobby); + } } } [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))] diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs index 118a25ea0..92daa19a5 100644 --- a/Patches/RandomSpawnPatch.cs +++ b/Patches/RandomSpawnPatch.cs @@ -101,7 +101,7 @@ public static bool Prefix(CustomNetworkTransform __instance, [HarmonyArgument(0) if (IsAirshipVanillaSpawnPosition(position)) { AirshipSpawn(player); - return false; + return !IsRandomSpawn(); } else { diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index 91893dc22..7e9c1c4fb 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -88,7 +88,7 @@ public static void Postfix() [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.StartMeeting))] class StartMeetingPatch { - public static void Prefix(ShipStatus __instance, PlayerControl reporter, GameData.PlayerInfo target) + public static void Prefix(ShipStatus __instance, PlayerControl reporter, NetworkedPlayerInfo target) { MeetingStates.ReportTarget = target; MeetingStates.DeadBodies = UnityEngine.Object.FindObjectsOfType(); diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs index 4f3035fb3..4a4e6deff 100644 --- a/Patches/TaskAssignPatch.cs +++ b/Patches/TaskAssignPatch.cs @@ -36,14 +36,13 @@ public static void Prefix(ShipStatus __instance, } } - [HarmonyPatch(typeof(GameData), nameof(GameData.RpcSetTasks))] + [HarmonyPatch(typeof(NetworkedPlayerInfo), nameof(NetworkedPlayerInfo.RpcSetTasks))] class RpcSetTasksPatch { //タスクを割り当ててRPCを送る処理が行われる直前にタスクを上書きするPatch //バニラのタスク割り当て処理自体には干渉しない - public static void Prefix(GameData __instance, - [HarmonyArgument(0)] byte playerId, - [HarmonyArgument(1)] ref Il2CppStructArray taskTypeIds) + public static void Prefix(NetworkedPlayerInfo __instance, + [HarmonyArgument(0)] ref Il2CppStructArray taskTypeIds) { //null対策 if (Main.RealOptionsData == null) @@ -52,7 +51,7 @@ public static void Prefix(GameData __instance, return; } - var pc = Utils.GetPlayerById(playerId); + var pc = __instance.Object; CustomRoles? RoleNullable = pc?.GetCustomRole(); if (RoleNullable == null) return; CustomRoles role = RoleNullable.Value; diff --git a/Patches/UsablesPatch.cs b/Patches/UsablesPatch.cs index 0e3227d4c..d1d3ec165 100644 --- a/Patches/UsablesPatch.cs +++ b/Patches/UsablesPatch.cs @@ -9,7 +9,7 @@ namespace TownOfHost [HarmonyPatch(typeof(Console), nameof(Console.CanUse))] class CanUsePatch { - public static bool Prefix(ref float __result, Console __instance, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) + public static bool Prefix(ref float __result, Console __instance, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) { canUse = couldUse = false; //こいつをfalseでreturnしても、タスク(サボ含む)以外の使用可能な物は使えるまま(ボタンなど) @@ -27,7 +27,7 @@ public static void Postfix(EmergencyMinigame __instance) [HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))] class CanUseVentPatch { - public static bool Prefix(Vent __instance, [HarmonyArgument(0)] GameData.PlayerInfo pc, + public static bool Prefix(Vent __instance, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse, ref float __result) diff --git a/Patches/onGameStartedPatch.cs b/Patches/onGameStartedPatch.cs index 218807e7f..860f63609 100644 --- a/Patches/onGameStartedPatch.cs +++ b/Patches/onGameStartedPatch.cs @@ -82,7 +82,7 @@ public static void Postfix(AmongUsClient __instance) pc.cosmetics.nameText.text = pc.name; var outfit = pc.Data.DefaultOutfit; - Camouflage.PlayerSkins[pc.PlayerId] = new GameData.PlayerOutfit().Set(outfit.PlayerName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId); + Camouflage.PlayerSkins[pc.PlayerId] = new NetworkedPlayerInfo.PlayerOutfit().Set(outfit.PlayerName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId); Main.clientIdList.Add(pc.GetClientId()); } Main.VisibleTasksCount = true; @@ -124,7 +124,7 @@ public static void Prefix() if (Options.CurrentGameMode != CustomGameMode.HideAndSeek) { - RoleTypes[] RoleTypesList = { RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.Shapeshifter }; + RoleTypes[] RoleTypesList = [RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.Noisemaker, RoleTypes.Tracker, RoleTypes.Shapeshifter, RoleTypes.Phantom]; foreach (var roleTypes in RoleTypesList) { var roleOpt = Main.NormalOptions.roleOptions; @@ -178,6 +178,9 @@ public static void Postfix() List Engineers = new(); List GuardianAngels = new(); List Shapeshifters = new(); + List trackers = []; + List noisemakers = []; + List phantoms = []; foreach (var pc in Main.AllPlayerControls) { @@ -211,6 +214,18 @@ public static void Postfix() Shapeshifters.Add(pc); role = CustomRoles.Shapeshifter; break; + case RoleTypes.Tracker: + trackers.Add(pc); + role = CustomRoles.Tracker; + break; + case RoleTypes.Noisemaker: + noisemakers.Add(pc); + role = CustomRoles.Noisemaker; + break; + case RoleTypes.Phantom: + phantoms.Add(pc); + role = CustomRoles.Phantom; + break; default: Logger.SendInGame(string.Format(GetString("Error.InvalidRoleAssignment"), pc?.Data?.PlayerName)); break; @@ -252,8 +267,11 @@ public static void Postfix() { RoleTypes.Impostor => Impostors, RoleTypes.Shapeshifter => Shapeshifters, + RoleTypes.Phantom => phantoms, RoleTypes.Scientist => Scientists, RoleTypes.Engineer => Engineers, + RoleTypes.Noisemaker => noisemakers, + RoleTypes.Tracker => trackers, RoleTypes.GuardianAngel => GuardianAngels, _ => Crewmates, }; @@ -288,7 +306,7 @@ public static void Postfix() } } - RoleTypes[] RoleTypesList = { RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.Shapeshifter }; + RoleTypes[] RoleTypesList = [RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.Noisemaker, RoleTypes.Tracker, RoleTypes.Shapeshifter, RoleTypes.Phantom]; foreach (var roleTypes in RoleTypesList) { var roleOpt = Main.NormalOptions.roleOptions; @@ -359,7 +377,7 @@ private static void AssignDesyncRole(CustomRoles role, List AllPl } RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); //ホスト視点はロール決定 - player.SetRole(othersRole); + player.StartCoroutine(player.CoSetRole(othersRole, false)); player.Data.IsDead = true; } } @@ -475,9 +493,10 @@ public static void Release() foreach (var pair in StoragedData) { - pair.Item1.SetRole(pair.Item2); + pair.Item1.StartCoroutine(pair.Item1.CoSetRole(pair.Item2, false)); sender.Value.AutoStartRpc(pair.Item1.NetId, (byte)RpcCalls.SetRole, Utils.GetPlayerById(sender.Key).GetClientId()) .Write((ushort)pair.Item2) + .Write(false) .EndRpc(); } sender.Value.EndMessage(); diff --git a/README-EN.md b/README-EN.md index 35be9dfbb..3dff4a9da 100644 --- a/README-EN.md +++ b/README-EN.md @@ -15,7 +15,7 @@ This mod is not affiliated with Among Us or Innersloth LLC, and the content cont ## Releases -AmongUs Version: **2024.03.05** +AmongUs Version: **2024.6.18** **Latest Version: [Here](https://github.com/tukasa0001/TownOfHost/releases/latest)** diff --git a/README.md b/README.md index 31341871c..4bf706573 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ## リリース -AmongUsバージョン : **2024.03.05** +AmongUsバージョン : **2024.6.18** **最新版は[こちら](https://github.com/tukasa0001/TownOfHost/releases/latest)** diff --git a/Resources/TabIcon_MainSettings.png b/Resources/TabIcon_MainSettings.png index 26a5e42b4..6efc0af8c 100644 Binary files a/Resources/TabIcon_MainSettings.png and b/Resources/TabIcon_MainSettings.png differ diff --git a/Resources/string.csv b/Resources/string.csv index 6b6de901b..9d2263416 100644 --- a/Resources/string.csv +++ b/Resources/string.csv @@ -11,9 +11,12 @@ "Crewmate","Crewmate","クルーメイト","船员","船員","Член Экипажа","Tripulante","Tripulante" "Engineer","Engineer","エンジニア","工程师","工程師","Инженер","Engenheiro","Engenheiro" "Scientist","Scientist","科学者","科学家","科學家","Ученый","Cientista","Cientista" +"Noisemaker","Noisemaker","ノイズメーカー","","","","","" +"Tracker","Tracker","トラッカー","","","","","" "GuardianAngel","Guardian Angel","守護天使","守护天使","守護天使","Ангел-хранитель","Anjo da Guarda","Anjo da Guarda" "Impostor","Impostor","インポスター","内鬼","偽裝者","Предателей","Impostor","Impostor" "Shapeshifter","Shapeshifter","シェイプシフター","变形者","變形者","Оборотень","Metamorfo","Metamorfo" +"Phantom","Phantom","亡霊","","","","","" "# 特殊インポスター役職" "BountyHunter","Bounty Hunter","バウンティハンター","赏金猎人","賞金獵人","Охотник за головами","Caçador de Recompensas","Caçador de Recompensas" @@ -538,6 +541,10 @@ "ModifySabotageCooldown","Sabotage Cooldown Control","サボタージュのクールダウン制御","修改破坏冷却时间","","Контролировать откат саботажа","Controle do Tepo de Recarga da Sabotagem","" "SabotageCooldown","Sabotage Cooldown","サボタージュのクールダウン","破坏冷却时间","","Откат саботажа","Tempo de Recarga da Sabotagem","" +"## ゲーム設定UI" +"TOHSettingsButtonLabel","TOH Settings","TOH設定","","","","","" +"TOHSettingsDescription","Edit mod settings for your game lobby.","Modの設定を編集する。","","","","","" + "## クライアント設定" "Close","Close","閉じる","关闭","","Закрыть","Fechar","" "TOHOptions","TOH Options","TOHの設定","TOH 选项","","Настройки TOH","TOH Opções","" @@ -602,8 +609,8 @@ "Error.InvalidColor","Error: Only default colors are available.","エラー:デフォルトカラー以外は使えません","错误: 仅默认颜色可用","錯誤: 無法使用遊戲自帶以外的顏色。","Ошибка: Нельзя использовать другие цвета, кроме цветов по умолчанию","Erro: Apenas as cores padrão estão disponíveis.","" "### ErrorText関連" -"ErrorLevel1","Bugs may occur.","何らかのバグが発生する可能性があります。","可能同时产生多个bug","可能同時產生多個Bug","Могут возникнуть некоторые ошибки.","Bugs podem acontecer.","" -"ErrorLevel2","This may be a bug.","バグが発生している可能性があります。","可能出现bug","可能出現Bug","Возможно вы столкнулись с ошибкой.","Talvez isso seja um bug.","" +"ErrorLevel1","Some failure may occur.","何らかの不具合が発生する可能性があります。","","","","","" +"ErrorLevel2","Some failure may have occurred.","不具合が発生している可能性があります。","","","","","" "ErrorLevel3","This version shouldn't have been released.","このバージョンはリリースされるべきではありません。","未发布版本","未發布版本","Эта версия никогда не должна быть выпущена.","Esta versão não deveria ter sido lançada.","" "TerminateCommand","Abort Command","廃村コマンド","强制结束游戏命令","強制結束遊戲指令","Команда устарела","Abortar Comando","" "#### 000 Test" @@ -618,6 +625,9 @@ "ERR-001-010-3","Duplicate Options ID","オプションIDが重複しています","选项ID重复","","Повторяющийся идентификатор настроек","","" "#### 002 Support" "ERR-002-000-1","Unsupported AmongUs version. Please update.","サポートされていないAmongUsバージョンです。ゲームをアップデートしてください。","不支持的 AmongUs 版本,请更新","","Неподдерживаемая версия AmongUs. Пожалуйста, обновите игру","Versão não suportada do Among Us. Por favor, atualize.","" +"#### 010 Session" +"ERR-010-000-2","An error occurred while processing disconnection. Please abort the game.","プレイヤーの切断処理中にエラーが発生しました。廃村してください。","","","","" +"ERR-010-001-2","An error occurred while processing disconnection. Please recreate the lobby.","プレイヤーの切断処理中にエラーが発生しました。ロビーを作り直してください。","","","","" "## その他" "DefaultSystemMessageTitle","【===== System Message =====】","【===== システムメッセージ ======】","【===== 系统信息 ======】","【===== 系統訊息 ======】","【=== Системное сообщение ===】","【===== Mensagem do Sistema ======】","" @@ -655,6 +665,7 @@ "FireworksReadyFirePhase","Fire!","打ち上げろ!","烟花来咯,大型烟花秀开始!","準備就緒,煙火秀開始!","Огонь!","Lançar!","" "StandardHAS","Hide And Seek with Roles","役職入りでかくれんぼ","在躲猫猫模式中加入职业","在躲貓貓中加入多職業模式中的職業","Прятки с Ролями","Esconde-Esconde com Classes","" "StandardHASWaitingTime","Standby","待機時間","躲猫猫猎人等待时间","躲貓貓偽裝者等待時間","Время ожидания","Tempo de Espera","" +"Prerelease","Pre-release","プレリリース","","","","","" "InvalidArgs","Invalid Args","無効な引数","无效参数","無效的參數","Недопустимые Аргументы","Argumento Inválido","" "On","ON","オン","开启","開啟","ВКЛ","Ativado","" "Off","OFF","オフ","关闭","關閉","ВЫКЛ","Desativado","" diff --git a/Roles/AddOns/Common/Watcher.cs b/Roles/AddOns/Common/Watcher.cs index 2ad1e95f4..6400c137e 100644 --- a/Roles/AddOns/Common/Watcher.cs +++ b/Roles/AddOns/Common/Watcher.cs @@ -15,7 +15,7 @@ public static class Watcher public static void SetupCustomOption() { - SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Watcher); + SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Watcher, RoleColor); AddOnsAssignData.Create(Id + 10, CustomRoles.Watcher, true, true, true); } [GameModuleInitializer] diff --git a/Roles/AddOns/Crewmate/Workhorse.cs b/Roles/AddOns/Crewmate/Workhorse.cs index 1fb22b978..464a51141 100644 --- a/Roles/AddOns/Crewmate/Workhorse.cs +++ b/Roles/AddOns/Crewmate/Workhorse.cs @@ -21,7 +21,7 @@ public static class Workhorse public static int NumShortTasks; public static void SetupCustomOption() { - SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Workhorse); + SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Workhorse, RoleColor); OptionAssignOnlyToCrewmate = BooleanOptionItem.Create(Id + 10, "AssignOnlyTo%role%", true, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); OptionAssignOnlyToCrewmate.ReplacementDictionary = new Dictionary { { "%role%", Utils.ColorString(Palette.CrewmateBlue, Utils.GetRoleName(CustomRoles.Crewmate)) } }; OptionNumLongTasks = IntegerOptionItem.Create(Id + 11, "WorkhorseNumLongTasks", new(0, 5, 1), 1, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) @@ -67,7 +67,7 @@ public static bool OnCompleteTask(PlayerControl pc) if (AmongUsClient.Instance.AmHost) { Add(pc.PlayerId); - GameData.Instance.RpcSetTasks(pc.PlayerId, Array.Empty()); //タスクを再配布 + pc.Data.RpcSetTasks(Array.Empty()); //タスクを再配布 pc.SyncSettings(); Utils.NotifyRoles(); } diff --git a/Roles/AddOns/Impostor/LastImpostor.cs b/Roles/AddOns/Impostor/LastImpostor.cs index 5d24e1072..88375b5a4 100644 --- a/Roles/AddOns/Impostor/LastImpostor.cs +++ b/Roles/AddOns/Impostor/LastImpostor.cs @@ -12,7 +12,7 @@ public static class LastImpostor public static OptionItem KillCooldown; public static void SetupCustomOption() { - SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.LastImpostor, new(1, 1, 1)); + SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.LastImpostor, Palette.ImpostorRed, new(1, 1, 1)); KillCooldown = FloatOptionItem.Create(Id + 10, "KillCooldown", new(0f, 180f, 1f), 15f, TabGroup.Addons, false).SetParent(CustomRoleSpawnChances[CustomRoles.LastImpostor]) .SetValueFormat(OptionFormat.Seconds); } diff --git a/Roles/Core/CustomRoleManager.cs b/Roles/Core/CustomRoleManager.cs index ac20737b3..4b58f7c5c 100644 --- a/Roles/Core/CustomRoleManager.cs +++ b/Roles/Core/CustomRoleManager.cs @@ -365,6 +365,7 @@ public enum CustomRoles //Impostor(Vanilla) Impostor, Shapeshifter, + Phantom, //Impostor BountyHunter, FireWorks, @@ -393,6 +394,8 @@ public enum CustomRoles Engineer, GuardianAngel, Scientist, + Tracker, + Noisemaker, //Crewmate Bait, Lighter, diff --git a/Roles/Core/RoleBase.cs b/Roles/Core/RoleBase.cs index cbb72ff31..bbd27c56f 100644 --- a/Roles/Core/RoleBase.cs +++ b/Roles/Core/RoleBase.cs @@ -46,8 +46,10 @@ public RoleBase( this.hasTasks = hasTasks ?? (roleInfo.CustomRoleType == CustomRoleTypes.Crewmate ? () => HasTask.True : () => HasTask.False); HasAbility = hasAbility ?? roleInfo.BaseRoleType.Invoke() is RoleTypes.Shapeshifter or + RoleTypes.Phantom or RoleTypes.Engineer or RoleTypes.Scientist or + RoleTypes.Tracker or RoleTypes.GuardianAngel or RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost; @@ -183,7 +185,7 @@ public virtual void OnFixedUpdate(PlayerControl player) /// /// 通報したプレイヤー /// 通報されたプレイヤー - public virtual void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public virtual void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { } /// @@ -192,7 +194,7 @@ public virtual void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo /// /// /// - /// falseを返すとベントから追い出され、他人からアニメーションも見られません + /// falseを返すとベントから追い出されます public virtual bool OnEnterVent(PlayerPhysics physics, int ventId) => true; /// @@ -224,7 +226,7 @@ public virtual void OnStartMeeting() /// /// 追放されるプレイヤー /// 勝者を確定させるか - public virtual void OnExileWrapUp(GameData.PlayerInfo exiled, ref bool DecidedWinner) + public virtual void OnExileWrapUp(NetworkedPlayerInfo exiled, ref bool DecidedWinner) { } /// @@ -343,6 +345,8 @@ public virtual string GetAbilityButtonText() { StringNames? str = Player.Data.Role.Role switch { + RoleTypes.Phantom => Player.Data.Role.TryCast(out var phantomRole) ? (phantomRole.IsInvisible ? StringNames.PhantomAbilityUndo : StringNames.PhantomAbility) : null, + RoleTypes.Tracker => Player.Data.Role.TryCast(out var trackerRole) ? (trackerRole.isTrackingActive ? StringNames.TrackerAbilityUndo : StringNames.TrackerAbility) : null, RoleTypes.Engineer => StringNames.VentAbility, RoleTypes.Scientist => StringNames.VitalsAbility, RoleTypes.Shapeshifter => StringNames.ShapeshiftAbility, diff --git a/Roles/Core/SimpleRoleInfo.cs b/Roles/Core/SimpleRoleInfo.cs index 4621ee7ce..86f51a815 100644 --- a/Roles/Core/SimpleRoleInfo.cs +++ b/Roles/Core/SimpleRoleInfo.cs @@ -154,6 +154,14 @@ public static SimpleRoleInfo CreateForVanilla( roleName = CustomRoles.Scientist; customRoleType = CustomRoleTypes.Crewmate; break; + case RoleTypes.Noisemaker: + roleName = CustomRoles.Noisemaker; + customRoleType = CustomRoleTypes.Crewmate; + break; + case RoleTypes.Tracker: + roleName = CustomRoles.Tracker; + customRoleType = CustomRoleTypes.Crewmate; + break; case RoleTypes.GuardianAngel: roleName = CustomRoles.GuardianAngel; customRoleType = CustomRoleTypes.Crewmate; @@ -168,6 +176,11 @@ public static SimpleRoleInfo CreateForVanilla( customRoleType = CustomRoleTypes.Impostor; countType = CountTypes.Impostor; break; + case RoleTypes.Phantom: + roleName = CustomRoles.Phantom; + customRoleType = CustomRoleTypes.Impostor; + countType = CountTypes.Impostor; + break; default: roleName = CustomRoles.Crewmate; customRoleType = CustomRoleTypes.Crewmate; diff --git a/Roles/Crewmate/Mayor.cs b/Roles/Crewmate/Mayor.cs index f4cfdf299..d4d0d37e7 100644 --- a/Roles/Crewmate/Mayor.cs +++ b/Roles/Crewmate/Mayor.cs @@ -61,7 +61,7 @@ public override void ApplyGameOptions(IGameOptions opt) : opt.GetInt(Int32OptionNames.EmergencyCooldown); AURoleOptions.EngineerInVentMaxTime = 1; } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { if (Is(reporter) && target == null) //ボタン LeftButtonCount--; diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index fdedc02e2..ac696bbab 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -156,8 +156,8 @@ public void OnCheckMurderAsKiller(MurderInfo info) SendRPC(); if (!CanBeKilledBy(target)) { - killer.RpcMurderPlayer(killer); PlayerState.GetByPlayerId(killer.PlayerId).DeathReason = CustomDeathReason.Misfire; + killer.RpcMurderPlayer(killer); if (!MisfireKillsTarget.GetBool()) { info.DoKill = false; diff --git a/Roles/Impostor/EvilHacker.cs b/Roles/Impostor/EvilHacker.cs index eaf92a522..f3817be52 100644 --- a/Roles/Impostor/EvilHacker.cs +++ b/Roles/Impostor/EvilHacker.cs @@ -82,7 +82,7 @@ private static void HandleMurderRoomNotify(MurderInfo info) } } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { if (!Player.IsAlive()) { diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index 4c6912c40..a1584f075 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -144,7 +144,7 @@ public override bool CanUseAbilityButton() { return AbductVictim != null; } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { stopCount = true; // 時間切れ状態で会議を迎えたらはしご中でも構わずキルする @@ -199,7 +199,7 @@ public override void OnFixedUpdate(PlayerControl player) { // 先にIsDeadをtrueにする(はしごチェイス封じ) AbductVictim.Data.IsDead = true; - GameData.Instance.SetDirty(); + AbductVictim.Data.MarkDirty(); // ペンギン自身がはしご上にいる場合,はしごを降りてからキルする if (!AbductVictim.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) { diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index ad8349bb3..04cf4ed22 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -75,7 +75,7 @@ public void OnCheckMurderAsKiller(MurderInfo info) Utils.NotifyRoles(SpecifySeer: puppeteer); info.DoKill = false; } - public override void OnReportDeadBody(PlayerControl _, GameData.PlayerInfo __) + public override void OnReportDeadBody(PlayerControl _, NetworkedPlayerInfo __) { Puppets.Clear(); SendRPC(byte.MaxValue, 0); @@ -110,7 +110,7 @@ private void CheckPuppetKill(PlayerControl puppet) var min = targetDistance.OrderBy(c => c.Value).FirstOrDefault();//一番値が小さい var target = min.Key; - var KillRange = NormalGameOptionsV07.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; + var KillRange = NormalGameOptionsV08.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; if (min.Value <= KillRange && puppet.CanMove && target.CanMove) { RPC.PlaySoundRPC(Player.PlayerId, Sounds.KillSound); diff --git a/Roles/Impostor/SerialKiller.cs b/Roles/Impostor/SerialKiller.cs index 21aa56667..5e75c7237 100644 --- a/Roles/Impostor/SerialKiller.cs +++ b/Roles/Impostor/SerialKiller.cs @@ -68,7 +68,7 @@ public void OnCheckMurderAsKiller(MurderInfo info) SuicideTimer = null; killer.MarkDirtySettings(); } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { SuicideTimer = null; } diff --git a/Roles/Impostor/Sniper.cs b/Roles/Impostor/Sniper.cs index 840fc33fa..aa98aac01 100644 --- a/Roles/Impostor/Sniper.cs +++ b/Roles/Impostor/Sniper.cs @@ -291,7 +291,7 @@ public override void OnFixedUpdate(PlayerControl player) Utils.NotifyRoles(SpecifySeer: Player); } } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { MeetingReset = true; } diff --git a/Roles/Impostor/Vampire.cs b/Roles/Impostor/Vampire.cs index 7ff5e3754..c01537bab 100644 --- a/Roles/Impostor/Vampire.cs +++ b/Roles/Impostor/Vampire.cs @@ -84,7 +84,7 @@ public override void OnFixedUpdate(PlayerControl _) } } } - public override void OnReportDeadBody(PlayerControl _, GameData.PlayerInfo __) + public override void OnReportDeadBody(PlayerControl _, NetworkedPlayerInfo __) { foreach (var targetId in BittenPlayers.Keys) { diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index e30f72156..9148fb7df 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -128,7 +128,7 @@ public void OnCheckMurderAsKiller(MurderInfo info) } info.DoKill = false; } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { TargetInfo = null; } diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index c16ae5dea..a52051b50 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -137,7 +137,7 @@ public override string GetMark(PlayerControl seer, PlayerControl seen, bool _ = return TargetId == seen.PlayerId ? Utils.ColorString(RoleInfo.RoleColor, "♦") : ""; } - public override void OnExileWrapUp(GameData.PlayerInfo exiled, ref bool DecidedWinner) + public override void OnExileWrapUp(NetworkedPlayerInfo exiled, ref bool DecidedWinner) { if (!AmongUsClient.Instance.AmHost) return; if (Player?.IsAlive() != true) return; diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index ff64fdf5e..b24e2709e 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -24,7 +24,7 @@ public Jester(PlayerControl player) ) { } - public override void OnExileWrapUp(GameData.PlayerInfo exiled, ref bool DecidedWinner) + public override void OnExileWrapUp(NetworkedPlayerInfo exiled, ref bool DecidedWinner) { if (!AmongUsClient.Instance.AmHost || Player.PlayerId != exiled.PlayerId) return; diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index a45a9d4c5..9a13293c8 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -173,7 +173,7 @@ public static void OnMurderPlayerOthers(MurderInfo info) //非感染者が死んだ場合勝利するかもしれない LateCheckWin = true; } - public override void OnReportDeadBody(PlayerControl reporter, GameData.PlayerInfo target) + public override void OnReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) { InfectActive = false; } diff --git a/Roles/Neutral/SchrodingerCat.cs b/Roles/Neutral/SchrodingerCat.cs index 447bfd48d..93f40c41b 100644 --- a/Roles/Neutral/SchrodingerCat.cs +++ b/Roles/Neutral/SchrodingerCat.cs @@ -165,7 +165,7 @@ public override void OverrideTrueRoleName(ref Color roleColor, ref string roleTe } roleColor = DisplayRoleColor; } - public override void OnExileWrapUp(GameData.PlayerInfo exiled, ref bool DecidedWinner) + public override void OnExileWrapUp(NetworkedPlayerInfo exiled, ref bool DecidedWinner) { if (exiled.PlayerId != Player.PlayerId || Team != TeamType.None || !ChangeTeamWhenExile) { diff --git a/Roles/Neutral/Terrorist.cs b/Roles/Neutral/Terrorist.cs index 87c78b60e..3c7c47f21 100644 --- a/Roles/Neutral/Terrorist.cs +++ b/Roles/Neutral/Terrorist.cs @@ -58,7 +58,7 @@ public override void OnMurderPlayerAsTarget(MurderInfo info) Win(); } } - public override void OnExileWrapUp(GameData.PlayerInfo exiled, ref bool DecidedWinner) + public override void OnExileWrapUp(NetworkedPlayerInfo exiled, ref bool DecidedWinner) { if (exiled.PlayerId != Player.PlayerId) { diff --git a/Roles/Vanilla/Noisemaker.cs b/Roles/Vanilla/Noisemaker.cs new file mode 100644 index 000000000..a9fa36138 --- /dev/null +++ b/Roles/Vanilla/Noisemaker.cs @@ -0,0 +1,10 @@ +using AmongUs.GameOptions; +using TownOfHost.Roles.Core; + +namespace TownOfHost.Roles.Vanilla; + +public sealed class Noisemaker : RoleBase +{ + public Noisemaker(PlayerControl player) : base(RoleInfo, player) { } + public readonly static SimpleRoleInfo RoleInfo = SimpleRoleInfo.CreateForVanilla(typeof(Noisemaker), player => new Noisemaker(player), RoleTypes.Noisemaker, "#8cffff"); +} diff --git a/Roles/Vanilla/Phantom.cs b/Roles/Vanilla/Phantom.cs new file mode 100644 index 000000000..337dc236d --- /dev/null +++ b/Roles/Vanilla/Phantom.cs @@ -0,0 +1,11 @@ +using AmongUs.GameOptions; +using TownOfHost.Roles.Core; +using TownOfHost.Roles.Core.Interfaces; + +namespace TownOfHost.Roles.Vanilla; + +public sealed class Phantom : RoleBase, IImpostor +{ + public Phantom(PlayerControl player) : base(RoleInfo, player) { } + public static readonly SimpleRoleInfo RoleInfo = SimpleRoleInfo.CreateForVanilla(typeof(Phantom), player => new Phantom(player), RoleTypes.Phantom); +} diff --git a/Roles/Vanilla/Tracker.cs b/Roles/Vanilla/Tracker.cs new file mode 100644 index 000000000..46068392d --- /dev/null +++ b/Roles/Vanilla/Tracker.cs @@ -0,0 +1,10 @@ +using AmongUs.GameOptions; +using TownOfHost.Roles.Core; + +namespace TownOfHost.Roles.Vanilla; + +public sealed class Tracker : RoleBase +{ + public Tracker(PlayerControl player) : base(RoleInfo, player) { } + public readonly static SimpleRoleInfo RoleInfo = SimpleRoleInfo.CreateForVanilla(typeof(Tracker), player => new Tracker(player), RoleTypes.Tracker, "#8cffff"); +} diff --git a/main.cs b/main.cs index d842d1303..e7bbe660c 100644 --- a/main.cs +++ b/main.cs @@ -51,20 +51,42 @@ public class Main : BasePlugin // ========== //Sorry for many Japanese comments. public const string PluginGuid = "com.emptybottle.townofhost"; - public const string PluginVersion = "5.1.6"; + public const string PluginVersion = "5.1.7"; // サポートされている最低のAmongUsバージョン - public static readonly string LowestSupportedVersion = "2024.3.5"; + public static readonly string LowestSupportedVersion = "2024.6.18"; // このバージョンのみで公開ルームを無効にする場合 public static readonly bool IsPublicAvailableOnThisVersion = false; + // プレリリースかどうか + public static bool IsPrerelease { get; } = true; public Harmony Harmony { get; } = new Harmony(PluginGuid); public static Version version = Version.Parse(PluginVersion); + public static Color UnityModColor + { + get + { + if (!_unityModColor.HasValue) + { + if (ColorUtility.TryParseHtmlString(ModColor, out var unityColor)) + { + _unityModColor = unityColor; + } + else + { + // failure + return Color.gray; + } + } + return _unityModColor.Value; + } + } + private static Color? _unityModColor; public static BepInEx.Logging.ManualLogSource Logger; public static bool hasArgumentException = false; public static string ExceptionMessage; public static bool ExceptionMessageIsShown = false; public static string credentialsText; - public static NormalGameOptionsV07 NormalOptions => GameOptionsManager.Instance.currentNormalGameOptions; - public static HideNSeekGameOptionsV07 HideNSeekSOptions => GameOptionsManager.Instance.currentHideNSeekGameOptions; + public static NormalGameOptionsV08 NormalOptions => GameOptionsManager.Instance.currentNormalGameOptions; + public static HideNSeekGameOptionsV08 HideNSeekSOptions => GameOptionsManager.Instance.currentHideNSeekGameOptions; //Client Options public static ConfigEntry HideName { get; private set; } public static ConfigEntry HideColor { get; private set; }