diff --git a/.github/workflows/Release Action.yml b/.github/workflows/Release Action.yml index 6060f61..913c3bc 100644 --- a/.github/workflows/Release Action.yml +++ b/.github/workflows/Release Action.yml @@ -22,8 +22,7 @@ jobs: - name: uzip run: unzip TheIdealShip.zip - - uses: ncipollo/release-action@v1 with: - tag: v0.3.2 测试 + tag: v0.3.3 bodyFile: "TheIdealShip.dll" diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 48adc48..470f8d8 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -2,6 +2,8 @@ name: dotnet-build on: pull_request: + paths: + - '**.yml' branches: [ "main" ] jobs: diff --git a/TheIdealShip/Patches/EmergencyMinigamePatch.cs b/TheIdealShip/Patches/EmergencyMinigamePatch.cs index eba05c7..effa470 100644 --- a/TheIdealShip/Patches/EmergencyMinigamePatch.cs +++ b/TheIdealShip/Patches/EmergencyMinigamePatch.cs @@ -13,7 +13,7 @@ public static void Postfix(EmergencyMinigame __instance) string statusText = ""; var player = CachedPlayer.LocalPlayer.PlayerControl; - var info = RoleInfo.GetRoleInfo(player); + var info = RoleHelpers.GetRoleInfo(player); var id = info.roleId; if (id == RoleId.Jester) diff --git a/TheIdealShip/Patches/ExileControllPatch.cs b/TheIdealShip/Patches/ExileControllPatch.cs index c21568d..4ff3497 100644 --- a/TheIdealShip/Patches/ExileControllPatch.cs +++ b/TheIdealShip/Patches/ExileControllPatch.cs @@ -11,11 +11,11 @@ class ExileControllerBeginPatch public static void Postfix(ExileController __instance, [HarmonyArgument(0)] ref GameData.PlayerInfo exiled, [HarmonyArgument(1)] bool tie) { var player = Helpers.GetPlayerForId(exiled.PlayerId); - var info = RoleInfo.GetRoleInfo(player); + var info = RoleHelpers.GetRoleInfo(player); var eText = "\n" + string.Format(GetString("exileText"),exiled.PlayerName,info.name); if (CustomOptionHolder.showExilePlayerConcreteRoleTeam.getBool()) { - eText += "\n" + string.Format(GetString("exileTeamText"),RoleInfo.GetRoleTeam(player)); + eText += "\n" + string.Format(GetString("exileTeamText"),RoleHelpers.GetRoleTeam(player)); } if (__instance.ImpostorText.text != null) { diff --git a/TheIdealShip/Patches/HudPatch.cs b/TheIdealShip/Patches/HudPatch.cs index b03a81e..7783ff9 100644 --- a/TheIdealShip/Patches/HudPatch.cs +++ b/TheIdealShip/Patches/HudPatch.cs @@ -12,12 +12,12 @@ class TaskPanelBehaviourPatch public static void Postfix(TaskPanelBehaviour __instance) { var LPlayer = CachedPlayer.LocalPlayer.PlayerControl; - var roleInfo = RoleInfo.GetRoleInfo(LPlayer, false); - var modifierInfo = RoleInfo.GetRoleInfo(LPlayer, true); + var roleInfo = RoleHelpers.GetRoleInfo(LPlayer, false); + var modifierInfo = RoleHelpers.GetRoleInfo(LPlayer, true); string roleText = ""; string modifierText = ""; - if (roleInfo != null) roleText = Helpers.cs(roleInfo.color, GetString("Roles") + ":" + RoleInfo.GetRolesString(LPlayer, false) + "\n"+ roleInfo.TaskText +"\n"); - if (modifierInfo != null) modifierText = Helpers.cs(modifierInfo.color, GetString("Modifiers") + ":" + RoleInfo.GetRolesString(LPlayer, false, true) + "\n" + modifierInfo.TaskText + "\n"); + if (roleInfo != null) roleText = Helpers.cs(roleInfo.color, GetString("Roles") + ":" + RoleHelpers.GetRolesString(LPlayer, false) + "\n"+ roleInfo.TaskText +"\n"); + if (modifierInfo != null) modifierText = Helpers.cs(modifierInfo.color, GetString("Modifiers") + ":" + RoleHelpers.GetRolesString(LPlayer, false, true) + "\n" + modifierInfo.TaskText + "\n"); __instance.taskText.text = roleText + modifierText + "\n" + __instance.taskText.text; // __instance.taskText.text.Select(x => roleText + modifierText + ((roleInfo == null)&&(modifierInfo == null) ? "" : "\n") + x); } diff --git a/TheIdealShip/Patches/IntroPatch.cs b/TheIdealShip/Patches/IntroPatch.cs index 0f84c3c..4e5026a 100644 --- a/TheIdealShip/Patches/IntroPatch.cs +++ b/TheIdealShip/Patches/IntroPatch.cs @@ -16,8 +16,8 @@ public static void SetRoleTexts(IntroCutscene __instance) { var LocalP = CachedPlayer.LocalPlayer.PlayerControl; // 获取本地玩家角色信息 - RoleInfo roleInfo = RoleInfo.GetRoleInfo(LocalP, false); - RoleInfo modifierInfo = RoleInfo.GetRoleInfo(LocalP, true); + RoleInfo roleInfo = RoleHelpers.GetRoleInfo(LocalP, false); + RoleInfo modifierInfo = RoleHelpers.GetRoleInfo(LocalP, true); if (roleInfo != null) { __instance.YouAreText.color = roleInfo.color; @@ -46,7 +46,7 @@ class ShowTeamPatch public static void setRoleTeamText(IntroCutscene __instance) { var LocalP = CachedPlayer.LocalPlayer.PlayerControl; - var teamText = RoleInfo.GetRoleTeam(LocalP); + var teamText = RoleHelpers.GetRoleTeam(LocalP); __instance.TeamTitle.text = teamText; } public static bool Prefix(IntroCutscene __instance) diff --git a/TheIdealShip/Patches/MainUIPatch.cs b/TheIdealShip/Patches/MainUIPatch.cs index 95492b8..5c7195d 100644 --- a/TheIdealShip/Patches/MainUIPatch.cs +++ b/TheIdealShip/Patches/MainUIPatch.cs @@ -61,19 +61,19 @@ public static void Start_Prefix(MainMenuManager __instance) DiscordButtonSprite.color = DiscordText.color = DiscordColor; DiscordButton.gameObject.SetActive(true); - // 生成Kook按钮 + // 生成Kook按钮 改为QQ频道 if (kookButton == null) kookButton = UnityEngine.Object.Instantiate(Template,Template.transform.parent); - kookButton.name = "KooKButton"; + kookButton.name = "QQButton"; kookButton.transform.position = DiscordButton.transform.position + new Vector3(0, 0.65f); var kookText = kookButton.transform.GetChild(0).GetComponent(); - Color kookColor = new Color32(122, 204, 53, byte.MaxValue); + Color kookColor = new Color32(187, 255, 255, byte.MaxValue); PassiveButton kookPassiveButton = kookButton.GetComponent(); SpriteRenderer kookButtonSprite = kookButton.GetComponent(); kookPassiveButton.OnClick = new(); - kookPassiveButton.OnClick.AddListener((Action)(() => Application.OpenURL(TheIdealShipPlugin.KOOKURL))); + kookPassiveButton.OnClick.AddListener((Action)(() => Application.OpenURL(TheIdealShipPlugin.QQURL))); kookPassiveButton.OnMouseOut.AddListener((Action)(() => kookButtonSprite.color = kookText.color = kookColor)); - __instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => kookText.SetText("KooK")))); + __instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => kookText.SetText("QQ频道")))); kookButtonSprite.color = kookText.color = kookColor; kookButton.gameObject.SetActive(true); diff --git a/TheIdealShip/Patches/PlayerControlPatch.cs b/TheIdealShip/Patches/PlayerControlPatch.cs index 70d334f..42922cc 100644 --- a/TheIdealShip/Patches/PlayerControlPatch.cs +++ b/TheIdealShip/Patches/PlayerControlPatch.cs @@ -105,7 +105,7 @@ public static void updatePlayerInfo() playerInfo.color = playerInfo.color.SetAlpha(1f); } - string roleNames = RoleInfo.GetRolesString(p, true); + string roleNames = RoleHelpers.GetRolesString(p, true); string playerInfoText = ""; if (p == CachedPlayer.LocalPlayer.PlayerControl || p.isDummy) diff --git a/TheIdealShip/Patches/RoleAssignmentPatch.cs b/TheIdealShip/Patches/RoleAssignmentPatch.cs index 6889c8a..234b387 100644 --- a/TheIdealShip/Patches/RoleAssignmentPatch.cs +++ b/TheIdealShip/Patches/RoleAssignmentPatch.cs @@ -30,17 +30,7 @@ private enum RoleType Neutral = 1, Impostor = 2 } - public class RoleAssignmentData - { - public List crewmates { get; set; } - public List impostors { get; set; } - public Dictionary impSettings = new Dictionary(); - public Dictionary neutralSettings = new Dictionary(); - public Dictionary crewSettings = new Dictionary(); - public int maxCrewmateRoles { get; set; } - public int maxNeutralRoles { get; set; } - public int maxImpostorRoles { get; set; } - } + private static int crewValues; private static int impValues; private static List> playerRoleMap = new List>(); @@ -60,58 +50,13 @@ private static void assignRoles() // selectFactionForFactionIndependentRoles(data); assignEnsuredRoles(data); // Assign roles that should always be in the game next // assignDependentRoles(data); // Assign roles that may have a dependent role - // assignChanceRoles(data); // Assign roles that may or may not be in the game last + assignChanceRoles(data); // Assign roles that may or may not be in the game last // assignRoleTargets(data); // Assign targets for Lawyer & Prosecutor assignModifiers(); // Assign modifier setRolesAgain(); } - public static RoleAssignmentData GetRoleAssignmentData() - { - List crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); - crewmates.RemoveAll(x => x.Data.Role.IsImpostor); - List impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); - impostors.RemoveAll(x => !x.Data.Role.IsImpostor); - - var crewmateMin = CustomOptionHolder.crewmateRolesCountMin.getSelection(); - var crewmateMax = CustomOptionHolder.crewmateRolesCountMax.getSelection(); - var neutralMin = CustomOptionHolder.neutralRolesCountMin.getSelection(); - var neutralMax = CustomOptionHolder.neutralRolesCountMax.getSelection(); - var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection(); - var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection(); - - if (crewmateMin > crewmateMax) crewmateMin = crewmateMax; - if (neutralMin > neutralMax) neutralMin = neutralMax; - if (impostorMin > impostorMax) impostorMin = impostorMax; - - int crewCountSettings = rnd.Next(crewmateMin, crewmateMax + 1); - int neutralCountSettings = rnd.Next(neutralMin, neutralMax + 1); - int impCountSettings = rnd.Next(impostorMin, impostorMax + 1); - - int maxCrewmateRoles = Mathf.Min(crewmates.Count, crewCountSettings); - int maxNeutralRoles = Mathf.Min(crewmates.Count, neutralCountSettings); - int maxImpostorRoles = Mathf.Min(impostors.Count, impCountSettings); - - Dictionary impSettings = new Dictionary(); - Dictionary neutralSettings = new Dictionary(); - Dictionary crewSettings = new Dictionary(); - - crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection()); - - return new RoleAssignmentData - { - crewmates = crewmates, - impostors = impostors, - crewSettings = crewSettings, - neutralSettings = neutralSettings, - impSettings = impSettings, - maxCrewmateRoles = maxCrewmateRoles, - maxNeutralRoles = maxNeutralRoles, - maxImpostorRoles = maxImpostorRoles - }; - } - - private static void assignEnsuredRoles (RoleAssignmentData data) + private static void assignEnsuredRoles (Roles.RoleAssignmentData data) { List ensuredCrewmateRoles = data.crewSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList(); List ensuredNeutralRoles = data.neutralSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList(); @@ -189,7 +134,7 @@ private static void assignEnsuredRoles (RoleAssignmentData data) } } - private static void assignChanceRoles(RoleAssignmentData data) + private static void assignChanceRoles(Roles.RoleAssignmentData data) { // Get all roles where the chance to occur is set grater than 0% but not 100% and build a ticket pool based on their weight List crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList(); diff --git a/TheIdealShip/Patches/UpdatePatch.cs b/TheIdealShip/Patches/UpdatePatch.cs index dbb0134..ff05f12 100644 --- a/TheIdealShip/Patches/UpdatePatch.cs +++ b/TheIdealShip/Patches/UpdatePatch.cs @@ -75,7 +75,7 @@ static void setPlayerNameColor(PlayerControl p,Color color) static void SetNameColors() { var localPlayer = CachedPlayer.LocalPlayer.PlayerControl; - var localRoleInfo = RoleInfo.GetRoleInfo(localPlayer, false); + var localRoleInfo = RoleHelpers.GetRoleInfo(localPlayer, false); setPlayerNameColor(localPlayer, localRoleInfo.color); // if (localPlayer == Sheriff.sheriff) // setPlayerNameColor(localPlayer, RoleInfo.sheriff.color); diff --git a/TheIdealShip/RPC.cs b/TheIdealShip/RPC.cs index 19a8195..e5f9038 100644 --- a/TheIdealShip/RPC.cs +++ b/TheIdealShip/RPC.cs @@ -104,7 +104,7 @@ public static void RestoreRole(byte id) public static void ChangeRole(byte playerId, byte targetRoleId) { var player = Helpers.GetPlayerForId(playerId); - var info = RoleInfo.GetRoleInfo(player); + var info = RoleHelpers.GetRoleInfo(player); RestoreRole((byte)info.roleId); setRole(targetRoleId, playerId); } diff --git a/TheIdealShip/Roles/Role.cs b/TheIdealShip/Roles/Role.cs index ea19d96..3783112 100644 --- a/TheIdealShip/Roles/Role.cs +++ b/TheIdealShip/Roles/Role.cs @@ -1,5 +1,8 @@ using HarmonyLib; +using UnityEngine; using System; +using System.Linq; +using System.Collections.Generic; namespace TheIdealShip.Roles { @@ -18,9 +21,68 @@ public static void clearAndReloadRoles() // Modifier 附加职业 Flash.clearAndReload(); } + + public static RoleAssignmentData GetRoleAssignmentData() + { + List crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); + crewmates.RemoveAll(x => x.Data.Role.IsImpostor); + List impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); + impostors.RemoveAll(x => !x.Data.Role.IsImpostor); + + var crewmateMin = CustomOptionHolder.crewmateRolesCountMin.getSelection(); + var crewmateMax = CustomOptionHolder.crewmateRolesCountMax.getSelection(); + var neutralMin = CustomOptionHolder.neutralRolesCountMin.getSelection(); + var neutralMax = CustomOptionHolder.neutralRolesCountMax.getSelection(); + var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection(); + var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection(); + + if (crewmateMin > crewmateMax) crewmateMin = crewmateMax; + if (neutralMin > neutralMax) neutralMin = neutralMax; + if (impostorMin > impostorMax) impostorMin = impostorMax; + + int crewCountSettings = rnd.Next(crewmateMin, crewmateMax + 1); + int neutralCountSettings = rnd.Next(neutralMin, neutralMax + 1); + int impCountSettings = rnd.Next(impostorMin, impostorMax + 1); + + int maxCrewmateRoles = Mathf.Min(crewmates.Count, crewCountSettings); + int maxNeutralRoles = Mathf.Min(crewmates.Count, neutralCountSettings); + int maxImpostorRoles = Mathf.Min(impostors.Count, impCountSettings); + + Dictionary impSettings = new Dictionary(); + Dictionary neutralSettings = new Dictionary(); + Dictionary crewSettings = new Dictionary(); + + crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection()); + + neutralSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection()); + + return new RoleAssignmentData + { + crewmates = crewmates, + impostors = impostors, + crewSettings = crewSettings, + neutralSettings = neutralSettings, + impSettings = impSettings, + maxCrewmateRoles = maxCrewmateRoles, + maxNeutralRoles = maxNeutralRoles, + maxImpostorRoles = maxImpostorRoles + }; + } + } + + public class RoleAssignmentData + { + public List crewmates { get; set; } + public List impostors { get; set; } + public Dictionary impSettings = new Dictionary(); + public Dictionary neutralSettings = new Dictionary(); + public Dictionary crewSettings = new Dictionary(); + public int maxCrewmateRoles { get; set; } + public int maxNeutralRoles { get; set; } + public int maxImpostorRoles { get; set; } } - enum RoleId + public enum RoleId { // Crewmate 船员 Crewmate, diff --git a/TheIdealShip/Roles/RoleHelpers.cs b/TheIdealShip/Roles/RoleHelpers.cs new file mode 100644 index 0000000..04de0ca --- /dev/null +++ b/TheIdealShip/Roles/RoleHelpers.cs @@ -0,0 +1,71 @@ +using System.Xml.Schema; +using System; +using System.Linq; +using System.Collections.Generic; +using static TheIdealShip.Languages.Language; +using static TheIdealShip.Roles.RoleInfo; +using RoleTeam = TheIdealShip.Roles.RoleInfo.RoleTeam; + +namespace TheIdealShip.Roles +{ + public static class RoleHelpers + { + public static RoleInfo GetRoleInfo(PlayerControl player, bool isModifier = false) + { + var info = getRoleInfoForPlayer(player, isModifier, false).FirstOrDefault(); + return info; + } + public static string GetRoleTeam(PlayerControl p) + { + string roleTeam; + string cre = GetString("Crewmate"); + string imp = GetString("Impostor"); + string neu = GetString("Neutral"); + var info = GetRoleInfo(p); + switch (info.team) + { + case RoleTeam.Crewmate: + roleTeam = cre; + break; + + case RoleTeam.Impostor: + roleTeam = imp; + break; + + case RoleTeam.Neutral: + roleTeam = neu; + break; + + default: + roleTeam = ""; + break; + } + return roleTeam; + } + + public static bool Is(this PlayerControl player, RoleId id) + { + var info = GetRoleInfo(player); + return id == info.roleId; + } + + public static bool Is(this PlayerControl player, RoleTeam team) + { + var info = GetRoleInfo(player); + return team == info.team; + } + + public static bool Is(this RoleId id, RoleType type) + { + var info = allRoleInfos.Where(x => x.roleId == id).FirstOrDefault(); + return info.type == type; + } + + public static String GetRolesString(PlayerControl p, bool useColors = true, bool isModifier = false) + { + string roleName; + roleName = String.Join("", getRoleInfoForPlayer(p, isModifier).Select(x => useColors ? Helpers.cs(x.color, GetString(x.namekey)) : GetString(x.namekey)).ToArray()); + return roleName; + } + } +} \ No newline at end of file diff --git a/TheIdealShip/Roles/RoleInfo.cs b/TheIdealShip/Roles/RoleInfo.cs index 7928e7d..f70d17e 100644 --- a/TheIdealShip/Roles/RoleInfo.cs +++ b/TheIdealShip/Roles/RoleInfo.cs @@ -6,7 +6,7 @@ namespace TheIdealShip.Roles { - class RoleInfo + public class RoleInfo { public enum RoleType { @@ -64,10 +64,13 @@ RoleType type // Role 普通职业 - public static RoleInfo sheriff; public static RoleInfo impostor; + + public static RoleInfo sheriff; public static RoleInfo crewmate; + public static RoleInfo jester; + // Modifier 附加职业 public static RoleInfo flash; @@ -75,20 +78,26 @@ RoleType type public static void Init() { // Role 普通职业 - sheriff = new RoleInfo("Sheriff", Sheriff.color, RoleId.Sheriff, RoleType.Crewmate , RoleTeam.Crewmate); impostor = new RoleInfo("Impostor", Palette.ImpostorRed, RoleId.Impostor, RoleType.Impostor, RoleTeam.Impostor); + + sheriff = new RoleInfo("Sheriff", Sheriff.color, RoleId.Sheriff, RoleType.Crewmate , RoleTeam.Crewmate); crewmate = new RoleInfo("Crewmate", Color.white, RoleId.Crewmate, RoleType.Crewmate, RoleTeam.Crewmate); + jester = new RoleInfo("Jester", Jester.color, RoleId.Jester, RoleType.Neutral, RoleTeam.Neutral); + // Modifier 附加职业 flash = new RoleInfo("Flash", Flash.color, RoleId.Flash, RoleType.Modifier); allRoleInfos = new List() { // Role - sheriff, impostor, + + sheriff, crewmate, + jester, + // Modifier flash }; @@ -111,6 +120,7 @@ public static List getRoleInfoForPlayer(PlayerControl p, bool isModifi { // roles if (p == Sheriff.sheriff) infos.Add(sheriff); + if (p == Jester.jester) infos.Add(jester); if (infos.Count == count) { @@ -122,45 +132,5 @@ public static List getRoleInfoForPlayer(PlayerControl p, bool isModifi } return infos; } - - public static RoleInfo GetRoleInfo(PlayerControl player, bool isModifier = false) - { - var info = getRoleInfoForPlayer(player,isModifier,false).FirstOrDefault(); - return info; - } - public static string GetRoleTeam(PlayerControl p) - { - string roleTeam; - string cre = GetString("Crewmate"); - string imp = GetString("Impostor"); - string neu = GetString("Neutral"); - var info = GetRoleInfo(p); - switch (info.team) - { - case RoleTeam.Crewmate : - roleTeam = cre; - break; - - case RoleTeam.Impostor : - roleTeam = imp; - break; - - case RoleTeam.Neutral : - roleTeam = neu; - break; - - default : - roleTeam = ""; - break; - } - return roleTeam; - } - - public static String GetRolesString(PlayerControl p, bool useColors = true, bool isModifier = false) - { - string roleName; - roleName = String.Join("",getRoleInfoForPlayer(p,isModifier).Select(x => useColors ? Helpers.cs(x.color, GetString(x.namekey)) : GetString(x.namekey)).ToArray()); - return roleName; - } } } \ No newline at end of file diff --git a/TheIdealShip/TheIdealShip.csproj b/TheIdealShip/TheIdealShip.csproj index c133e48..aa56878 100644 --- a/TheIdealShip/TheIdealShip.csproj +++ b/TheIdealShip/TheIdealShip.csproj @@ -1,7 +1,7 @@ net6.0 - 0.3.2 + 0.3.3 latest embedded The Ideal Ship diff --git a/TheIdealShip/main.cs b/TheIdealShip/main.cs index 75798fb..0b492d1 100644 --- a/TheIdealShip/main.cs +++ b/TheIdealShip/main.cs @@ -24,7 +24,7 @@ public class TheIdealShipPlugin : BasePlugin // 模组id public const string Id = "me.huier.TheIdealShip"; // 模组版本 - public const string VersionString = "0.3.2"; + public const string VersionString = "0.3.3"; /* // 模组构建时间 public const string BuildTime = ""; @@ -38,7 +38,7 @@ public class TheIdealShipPlugin : BasePlugin // bilibili链接 public const string bilibiliURL = "https://space.bilibili.com/394107547"; // KOOK链接 - public const string KOOKURL = "https://kook.top/T9DTrC"; + public const string QQURL = "https://pd.qq.com/s/hpldx5uja"; public static Version Version = Version.Parse(VersionString); internal static BepInEx.Logging.ManualLogSource Logger; public Harmony Harmony { get; } = new Harmony(Id);