Add files via upload

This commit is contained in:
Gurge44 authored and GitHub committed 2023-09-08 16:29:40 +02:00
1 parent ddf811cb6e
commit 93fda42abc
22 files changed
+425 -187

No files matched your search

+7 -8
View File
@@ -1,7 +1,5 @@
using AmongUs.Data;
using HarmonyLib;
using System.Collections.Generic;
using System.Linq;
using TOHE.Roles.Impostor;
namespace TOHE;
@@ -87,13 +85,14 @@ public static class Camouflage
if (oldIsCamouflage != IsCamouflage)
{
Main.AllPlayerControls.Do(pc => RpcSetSkin(pc));
if (Options.RemovePetsAtDeadPlayers.GetBool())
foreach (var pc in Main.AllPlayerControls)
{
Main.AllPlayerControls
.Where(pc => PlayerSkins[pc.PlayerId].PetId != string.Empty && pc.Data.IsDead && !pc.IsAlive())
.Where(pc => pc.Data.IsDead && !pc.IsAlive() && PlayerSkins[pc.PlayerId].PetId != string.Empty)
.Do(pc => pc.RpcSetPet(string.Empty));
RpcSetSkin(pc);
if (!IsCamouflage)
{
pc.RpcRemovePet();
}
}
Utils.NotifyRoles();
}
+6 -6
View File
@@ -73,7 +73,7 @@ internal static class CustomRolesHelper
CustomRoles.Luckey => CustomRoles.Crewmate,
CustomRoles.CyberStar => CustomRoles.Crewmate,
CustomRoles.Demolitionist => CustomRoles.Crewmate,
CustomRoles.Ventguard => CustomRoles.Crewmate,
CustomRoles.Ventguard => CustomRoles.Engineer,
CustomRoles.Express => CustomRoles.Crewmate,
CustomRoles.NiceEraser => CustomRoles.Crewmate,
CustomRoles.TaskManager => CustomRoles.Crewmate,
@@ -177,7 +177,7 @@ internal static class CustomRolesHelper
CustomRoles.Lookout => CustomRoles.CrewmateTOHE,
CustomRoles.CyberStar => CustomRoles.CrewmateTOHE,
CustomRoles.Demolitionist => CustomRoles.CrewmateTOHE,
CustomRoles.Ventguard => CustomRoles.CrewmateTOHE,
CustomRoles.Ventguard => CustomRoles.EngineerTOHE,
CustomRoles.Express => CustomRoles.CrewmateTOHE,
CustomRoles.NiceEraser => CustomRoles.CrewmateTOHE,
CustomRoles.TaskManager => CustomRoles.CrewmateTOHE,
@@ -534,8 +534,8 @@ internal static class CustomRolesHelper
//CustomRoles.Pirate or
CustomRoles.Revolutionist or
CustomRoles.Vulture or
CustomRoles.Famine or
CustomRoles.Baker or
//CustomRoles.Famine or
//CustomRoles.Baker or
CustomRoles.CursedSoul or
CustomRoles.Phantom or
CustomRoles.Workaholic or
@@ -676,8 +676,8 @@ internal static class CustomRolesHelper
//CustomRoles.Masochist or
CustomRoles.Amnesiac or
CustomRoles.Medusa or
CustomRoles.Famine or
CustomRoles.Baker or
//CustomRoles.Famine or
//CustomRoles.Baker or
CustomRoles.HexMaster or
CustomRoles.Glitch or
CustomRoles.Crewpostor or
+3 -3
View File
@@ -25,7 +25,7 @@ public class DevUser
UpName = upName;
}
public bool HasTag() => Tag != "null";
public string GetTag() => Color == "null" ? $"<size=1>{Tag}</size>\r\n" : $"<color={Color}><size=1.7>{(Tag == "#Dev" ? Translator.GetString("Developer") : Tag)}</size></color>\r\n";
public string GetTag() => Color == "null" ? $"<size=1>{Tag}</size>\r\n" : $"<color={Color}><size=1>{(Tag == "#Dev" ? Translator.GetString("Developer") : Tag)}</size></color>\r\n";
}
public static class DevManager
@@ -67,8 +67,8 @@ public static class DevManager
DevUserList.Add(new(code: "unlikecity#4086", color: "#eD2F91", tag: "Ward", isUp: true, isDev: false, deBug: false, colorCmd: true, upName: "Ward"));
DevUserList.Add(new(code: "iconicdrop#2727", color: "null", tag: "null", isUp: true, isDev: false, deBug: false, colorCmd: true, upName: "jackler"));
DevUserList.Add(new(code: "neatnet#5851", color: "#FFFF00", tag: "The 200IQ guy", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "The 200IQ guy"));
DevUserList.Add(new(code: "contenthue#0404", color: "#FFFF00", tag: "The 200IQ guy", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "The 200IQ guy"));
DevUserList.Add(new(code: "neatnet#5851", color: "#FFFF00", tag: "[Developer] The 200IQ guy", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "[Developer] The 200IQ guy"));
DevUserList.Add(new(code: "contenthue#0404", color: "#FFFF00", tag: "[Developer] The 200IQ guy", isUp: true, isDev: true, deBug: true, colorCmd: true, upName: "[Developer] The 200IQ guy"));
DevUserList.Add(new(code: "theseform#5686", color: "#FFFF00", tag: "Role idea creator", isUp: false, isDev: false, deBug: false, colorCmd: false, upName: "Role idea creator"));
DevUserList.Add(new(code: "heavyclod#2286", color: "#FFFF00", tag: "小叨.exe已停止运行", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "小叨院长"));
DevUserList.Add(new(code: "storeroan#0331", color: "#FF0066", tag: "Night_瓜", isUp: true, isDev: false, deBug: false, colorCmd: false, upName: "Night_瓜"));
+92
View File
@@ -0,0 +1,92 @@
namespace TOHE.Modules;
public static class DoorsReset
{
private static bool isEnabled = false;
private static ResetMode mode;
private static DoorsSystemType DoorsSystem => ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Doors, out var system) ? system.TryCast<DoorsSystemType>() : null;
private static readonly LogHandler logger = Logger.Handler(nameof(DoorsReset));
public static void Initialize()
{
// AirshipとPolus以外は非対応
if ((MapNames)Main.NormalOptions.MapId is not (MapNames.Airship or MapNames.Polus))
{
isEnabled = false;
return;
}
isEnabled = Options.ResetDoorsEveryTurns.GetBool();
mode = (ResetMode)Options.DoorsResetMode.GetValue();
logger.Info($"初期化: [ {isEnabled}, {mode} ]");
}
/// <summary>設定に応じてドア状況をリセット</summary>
public static void ResetDoors()
{
if (!isEnabled || DoorsSystem == null)
{
return;
}
logger.Info("リセット");
switch (mode)
{
case ResetMode.AllOpen: OpenAllDoors(); break;
case ResetMode.AllClosed: CloseAllDoors(); break;
case ResetMode.RandomByDoor: OpenOrCloseAllDoorsRandomly(); break;
default: logger.Warn($"無効なモード: {mode}"); break;
}
}
/// <summary>マップ上の全ドアを開放</summary>
private static void OpenAllDoors()
{
foreach (var door in ShipStatus.Instance.AllDoors)
{
SetDoorOpenState(door, true);
}
DoorsSystem.IsDirty = true;
}
/// <summary>マップ上の全ドアを閉鎖</summary>
private static void CloseAllDoors()
{
foreach (var door in ShipStatus.Instance.AllDoors)
{
SetDoorOpenState(door, false);
}
DoorsSystem.IsDirty = true;
}
/// <summary>マップ上の全ドアをランダムに開閉</summary>
private static void OpenOrCloseAllDoorsRandomly()
{
foreach (var door in ShipStatus.Instance.AllDoors)
{
var isOpen = IRandom.Instance.Next(2) > 0;
SetDoorOpenState(door, isOpen);
}
DoorsSystem.IsDirty = true;
}
/// <summary>ドアの開閉状況を設定する.サボタージュで閉められないドアに対しては何もしない</summary>
/// <param name="door">対象のドア</param>
/// <param name="isOpen">開けるならtrue,閉めるならfalse</param>
private static void SetDoorOpenState(PlainDoor door, bool isOpen)
{
if (IsValidDoor(door))
{
door.SetDoorway(isOpen);
}
}
/// <summary>リセット対象のドアかどうか判定する</summary>
/// <returns>リセット対象ならtrue</returns>
private static bool IsValidDoor(PlainDoor door)
{
// エアシラウンジトイレとPolus除染室のドアは対象外
if (door.Room is SystemTypes.Lounge or SystemTypes.Decontamination)
{
return false;
}
return true;
}
public enum ResetMode { AllOpen, AllClosed, RandomByDoor, }
}
+19
View File
@@ -0,0 +1,19 @@
using System;
namespace TOHE;
public static class EnumHelper
{
/// <summary>
/// Get all values of enum
/// </summary>
/// <typeparam name="T">Type of enum to be obtained</typeparam>
/// <returns>All values of T</returns>
public static T[] GetAllValues<T>() where T : Enum => Enum.GetValues(typeof(T)) as T[];
/// <summary>
/// enumのすべての名前を取得します
/// </summary>
/// <typeparam name="T">Type of enum to be obtained</typeparam>
/// <returns>All values of T</returns>
public static string[] GetAllNames<T>() where T : Enum => Enum.GetNames(typeof(T));
}
@@ -20,8 +20,7 @@ public class NormalGameOptionsSender : GameOptionsSender
}
protected set
{
if (_logicOptions != null)
_logicOptions.ClearDirtyFlag();
_logicOptions?.ClearDirtyFlag();
}
}
private LogicOptions _logicOptions;
+2 -2
View File
@@ -1126,8 +1126,8 @@ public static class GuessManager
or CustomRoles.Crewmate
// or CustomRoles.Loyal
or CustomRoles.Oblivious
or CustomRoles.Baker
or CustomRoles.Famine
//or CustomRoles.Baker
//or CustomRoles.Famine
or CustomRoles.Rogue
or CustomRoles.Scientist
or CustomRoles.Impostor
+2 -2
View File
@@ -83,8 +83,8 @@ public static class NameColorManager
if (seer.Is(CustomRoles.Glitch) && target.Is(CustomRoles.Glitch)) color = Main.roleColors[CustomRoles.Glitch];
if (seer.Is(CustomRoles.Succubus) && target.Is(CustomRoles.Succubus)) color = Main.roleColors[CustomRoles.Succubus];
if (seer.Is(CustomRoles.HeadHunter) && HeadHunter.Targets.Contains(target.PlayerId)) color = Color.black.ToString();
if (seer.Is(CustomRoles.BountyHunter) && BountyHunter.GetTarget(seer) == target.PlayerId) color = Color.black.ToString();
if (seer.Is(CustomRoles.HeadHunter) && HeadHunter.Targets.Contains(target.PlayerId)) color = "000000";
if (seer.Is(CustomRoles.BountyHunter) && BountyHunter.GetTarget(seer) == target.PlayerId) color = "000000";
if (seer.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) color = Main.roleColors[CustomRoles.ImpostorTOHE];
if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Refugee)) color = Main.roleColors[CustomRoles.Refugee];
+48 -18
View File
@@ -8,7 +8,9 @@ using TOHE.Roles.AddOns.Impostor;
using TOHE.Roles.Crewmate;
using TOHE.Roles.Impostor;
using TOHE.Roles.Neutral;
using TOHE.Modules;
using UnityEngine;
using Il2CppSystem.Diagnostics.Tracing;
namespace TOHE;
@@ -38,8 +40,6 @@ public static class Options
//taskOptionsLoad.Wait();
//Logger.Info("Options.Load End", "Options");
}
// オプションId
public const int PresetId = 0;
// プリセット
private static readonly string[] presets =
@@ -110,7 +110,7 @@ public static class Options
// 各役職の詳細設定
public static OptionItem EnableGM;
public static float DefaultKillCooldown = Main.NormalOptions?.KillCooldown ?? 20;
public static float DefaultKillCooldown = Main.NormalOptions?.KillCooldown ?? 25;
public static OptionItem GhostsDoTasks;
public static OptionItem DisableMeeting;
@@ -119,6 +119,8 @@ public static class Options
public static OptionItem DisableTaskWin;
public static OptionItem KillFlashDuration;
public static OptionItem EnableKillerLeftCommand;
public static OptionItem SeeEjectedRolesInMeeting;
//public static OptionItem ShareLobby;
//public static OptionItem ShareLobbyMinPlayer;
public static OptionItem DisableShieldAnimations;
@@ -126,6 +128,8 @@ public static class Options
public static OptionItem DisableVanillaRoles;
public static OptionItem DisableHiddenRoles;
public static OptionItem DisableSunnyboy;
public static OptionItem SabotageCooldownControl;
public static OptionItem SabotageCooldown;
public static OptionItem SunnyboyChance;
public static OptionItem DisableBard;
public static OptionItem BardChance;
@@ -155,8 +159,6 @@ public static class Options
public static OptionItem NonNeutralKillingRolesMaxPlayer;
public static OptionItem NeutralKillingRolesMinPlayer;
public static OptionItem NeutralKillingRolesMaxPlayer;
public static OptionItem CovenRolesMinPlayer;
public static OptionItem CovenRolesMaxPlayer;
public static OptionItem NeutralRoleWinTogether;
public static OptionItem NeutralWinTogether;
@@ -437,6 +439,8 @@ public static class Options
public static OptionItem CrewCanBeDoubleShot;
public static OptionItem NeutralCanBeDoubleShot;
public static OptionItem MimicCanSeeDeadRoles;
public static OptionItem ResetDoorsEveryTurns;
public static OptionItem DoorsResetMode;
//public static OptionItem NSerialKillerKillCD;
//public static OptionItem NSerialKillerHasImpostorVision;
@@ -880,6 +884,9 @@ public static class Options
MainLoadingText = "Building system settings";
if (IsLoaded) return;
OptionSaver.Initialize();
// 预设
_ = PresetOptionItem.Create(0, TabGroup.SystemSettings)
.SetColor(new Color32(255, 235, 4, byte.MaxValue))
@@ -1156,7 +1163,7 @@ public static class Options
TimeThief.SetupCustomOption();
RoleLoadingText = "Impostor roles\nVindicator";
SetupRoleOptions(3400, TabGroup.ImpostorRoles, CustomRoles.Vindicator);
VindicatorAdditionalVote = IntegerOptionItem.Create(3410, "MayorAdditionalVote", new(1, 99, 1), 3, TabGroup.ImpostorRoles, false)
VindicatorAdditionalVote = IntegerOptionItem.Create(3410, "MayorAdditionalVote", new(1, 10, 1), 1, TabGroup.ImpostorRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.Vindicator])
.SetValueFormat(OptionFormat.Votes);
VindicatorHideVote = BooleanOptionItem.Create(3411, "MayorHideVote", false, TabGroup.ImpostorRoles, false)
@@ -1179,10 +1186,10 @@ public static class Options
.SetValueFormat(OptionFormat.Seconds);
RoleLoadingText = "Impostor roles\nSoul Catcher";
SetupRoleOptions(3700, TabGroup.ImpostorRoles, CustomRoles.ImperiusCurse);
ShapeImperiusCurseShapeshiftDuration = FloatOptionItem.Create(3710, "ShapeshiftDuration", new(2.5f, 300f, 2.5f), 300f, TabGroup.ImpostorRoles, false)
ShapeImperiusCurseShapeshiftDuration = FloatOptionItem.Create(3710, "ShapeshiftDuration", new(2.5f, 300f, 2.5f), 20f, TabGroup.ImpostorRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.ImperiusCurse])
.SetValueFormat(OptionFormat.Seconds);
ImperiusCurseShapeshiftCooldown = FloatOptionItem.Create(3711, "ShapeshiftCooldown", new(1f, 180f, 1f), 15f, TabGroup.ImpostorRoles, false)
ImperiusCurseShapeshiftCooldown = FloatOptionItem.Create(3711, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.ImperiusCurse])
.SetValueFormat(OptionFormat.Seconds);
RoleLoadingText = "Impostor roles\nMiner";
@@ -1303,7 +1310,7 @@ public static class Options
RoleLoadingText = "Crewmate roles\nVentguard";
SetupSingleRoleOptions(5525, TabGroup.CrewmateRoles, CustomRoles.Ventguard, 1);
VentguardAbilityUseGainWithEachTaskCompleted = FloatOptionItem.Create(5527, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.Veteran])
.SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard])
.SetValueFormat(OptionFormat.Times);
VentguardMaxGuards = IntegerOptionItem.Create(5528, "VentguardMaxGuards", new(1, 20, 1), 3, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]);
VentguardBlockDoesNotAffectCrew = BooleanOptionItem.Create(5529, "VentguardBlockDoesNotAffectCrew", true, TabGroup.CrewmateRoles, false).SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]);
@@ -1717,14 +1724,14 @@ public static class Options
Vulture.SetupCustomOption();
RoleLoadingText = "Neutral roles\nWorkaholic";
SetupRoleOptions(11700, TabGroup.NeutralRoles, CustomRoles.Workaholic); //TOH_Y
WorkaholicCannotWinAtDeath = BooleanOptionItem.Create(11710, "WorkaholicCannotWinAtDeath", false, TabGroup.NeutralRoles, false)
WorkaholicCannotWinAtDeath = BooleanOptionItem.Create(11710, "WorkaholicCannotWinAtDeath", true, TabGroup.NeutralRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]);
WorkaholicVentCooldown = FloatOptionItem.Create(11711, "VentCooldown", new(0f, 180f, 2.5f), 0f, TabGroup.NeutralRoles, false)
WorkaholicVentCooldown = FloatOptionItem.Create(11711, "VentCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic])
.SetValueFormat(OptionFormat.Seconds);
WorkaholicVisibleToEveryone = BooleanOptionItem.Create(11712, "WorkaholicVisibleToEveryone", true, TabGroup.NeutralRoles, false)
WorkaholicVisibleToEveryone = BooleanOptionItem.Create(11712, "WorkaholicVisibleToEveryone", false, TabGroup.NeutralRoles, false)
.SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]);
WorkaholicGiveAdviceAlive = BooleanOptionItem.Create(11713, "WorkaholicGiveAdviceAlive", true, TabGroup.NeutralRoles, false)
WorkaholicGiveAdviceAlive = BooleanOptionItem.Create(11713, "WorkaholicGiveAdviceAlive", false, TabGroup.NeutralRoles, false)
.SetParent(WorkaholicVisibleToEveryone);
WorkaholicTasks = OverrideTasksData.Create(11714, TabGroup.NeutralRoles, CustomRoles.Workaholic);
WorkaholicCanGuess = BooleanOptionItem.Create(11725, "CanGuess", true, TabGroup.NeutralRoles, false)
@@ -2381,6 +2388,10 @@ public static class Options
KickLowLevelPlayer = IntegerOptionItem.Create(19300, "KickLowLevelPlayer", new(0, 100, 1), 0, TabGroup.SystemSettings, false)
.SetValueFormat(OptionFormat.Level)
.SetHeader(true);
EnableKillerLeftCommand = BooleanOptionItem.Create(44428, "EnableKillerLeftCommand", true, TabGroup.SystemSettings, false)
.SetColor(Color.green);
SeeEjectedRolesInMeeting = BooleanOptionItem.Create(44429, "SeeEjectedRolesInMeeting", true, TabGroup.SystemSettings, false)
.SetColor(Color.green);
KickAndroidPlayer = BooleanOptionItem.Create(19301, "KickAndroidPlayer", false, TabGroup.SystemSettings, false);
KickPlayerFriendCodeNotExist = BooleanOptionItem.Create(19302, "KickPlayerFriendCodeNotExist", false, TabGroup.SystemSettings, true);
ApplyDenyNameList = BooleanOptionItem.Create(19303, "ApplyDenyNameList", true, TabGroup.SystemSettings, true);
@@ -2582,6 +2593,13 @@ public static class Options
.SetGameMode(CustomGameMode.Standard)
.SetColor(new Color32(19, 188, 233, byte.MaxValue));
// Reset Doors After Meeting
ResetDoorsEveryTurns = BooleanOptionItem.Create(22120, "ResetDoorsEveryTurns", false, TabGroup.GameSettings, false)
.SetColor(new Color32(19, 188, 233, byte.MaxValue));
// Reset Doors Mode
DoorsResetMode = StringOptionItem.Create(22122, "DoorsResetMode", EnumHelper.GetAllNames<DoorsReset.ResetMode>(), 2, TabGroup.GameSettings, false)
.SetColor(new Color32(19, 188, 233, byte.MaxValue))
.SetParent(ResetDoorsEveryTurns);
LoadingPercentage = 70;
@@ -2603,15 +2621,25 @@ public static class Options
LoadingPercentage = 71;
// SabotageTimeControl
SabotageTimeControl = BooleanOptionItem.Create(22400, "SabotageTimeControl", false, TabGroup.GameSettings, false)
// Sabotage Cooldown Control
SabotageCooldownControl = BooleanOptionItem.Create(22400, "SabotageCooldownControl", false, TabGroup.GameSettings, false)
.SetColor(new Color32(243, 96, 96, byte.MaxValue))
.SetGameMode(CustomGameMode.Standard);
PolusReactorTimeLimit = FloatOptionItem.Create(22410, "PolusReactorTimeLimit", new(1f, 60f, 1f), 30f, TabGroup.GameSettings, false)
SabotageCooldown = FloatOptionItem.Create(22405, "SabotageCooldown", new(1f, 60f, 1f), 30f, TabGroup.GameSettings, false)
.SetParent(SabotageCooldownControl)
.SetValueFormat(OptionFormat.Seconds)
.SetGameMode(CustomGameMode.Standard);
// Sabotage Duration Control
SabotageTimeControl = BooleanOptionItem.Create(22410, "SabotageTimeControl", false, TabGroup.GameSettings, false)
.SetColor(new Color32(243, 96, 96, byte.MaxValue))
.SetGameMode(CustomGameMode.Standard);
PolusReactorTimeLimit = FloatOptionItem.Create(22412, "PolusReactorTimeLimit", new(1f, 60f, 1f), 30f, TabGroup.GameSettings, false)
.SetParent(SabotageTimeControl)
.SetValueFormat(OptionFormat.Seconds)
.SetGameMode(CustomGameMode.Standard);
AirshipReactorTimeLimit = FloatOptionItem.Create(22411, "AirshipReactorTimeLimit", new(1f, 90f, 1f), 60f, TabGroup.GameSettings, false)
AirshipReactorTimeLimit = FloatOptionItem.Create(22414, "AirshipReactorTimeLimit", new(1f, 90f, 1f), 60f, TabGroup.GameSettings, false)
.SetParent(SabotageTimeControl)
.SetValueFormat(OptionFormat.Seconds)
.SetGameMode(CustomGameMode.Standard);
@@ -3162,7 +3190,9 @@ public static class Options
LoadingPercentage = 100;
#endregion
#endregion
OptionSaver.Load();
IsLoaded = true;
}
+2 -6
View File
@@ -26,12 +26,8 @@ public class BooleanOptionItem : OptionItem
}
// Setter
public override void SetValue(int value)
public override void SetValue(int value, bool doSync = true)
{
base.SetValue(value % 2 == 0 ? 0 : 1);
}
public override void SetValueNoRpc(int value)
{
base.SetValueNoRpc(value % 2 == 0 ? 0 : 1);
base.SetValue(value % 2 == 0 ? 0 : 1, doSync);
}
}
+2 -6
View File
@@ -31,12 +31,8 @@ public class FloatOptionItem : OptionItem
=> Rule.RepeatIndex(base.GetValue());
// Setter
public override void SetValue(int value)
public override void SetValue(int value, bool doSync = true)
{
base.SetValue(Rule.RepeatIndex(value));
}
public override void SetValueNoRpc(int value)
{
base.SetValueNoRpc(Rule.RepeatIndex(value));
base.SetValue(Rule.RepeatIndex(value), doSync);
}
}
+2 -6
View File
@@ -31,12 +31,8 @@ public class IntegerOptionItem : OptionItem
=> Rule.RepeatIndex(base.GetValue());
// Setter
public override void SetValue(int value)
public override void SetValue(int value, bool doSync = true)
{
base.SetValue(Rule.RepeatIndex(value));
}
public override void SetValueNoRpc(int value)
{
base.SetValueNoRpc(Rule.RepeatIndex(value));
base.SetValue(Rule.RepeatIndex(value), doSync);
}
}
+51 -36
View File
@@ -1,7 +1,7 @@
using BepInEx.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using TOHE.Modules;
using UnityEngine;
namespace TOHE;
@@ -10,7 +10,9 @@ public abstract class OptionItem
{
#region static
public static IReadOnlyList<OptionItem> AllOptions => _allOptions;
private static List<OptionItem> _allOptions = new();
private static List<OptionItem> _allOptions = new(1024);
public static IReadOnlyDictionary<int, OptionItem> FastOptions => _fastOptions;
private static Dictionary<int, OptionItem> _fastOptions = new(1024);
public static int CurrentPreset { get; set; }
#endregion
@@ -39,24 +41,18 @@ public abstract class OptionItem
}
private Dictionary<string, string> _replacementDictionary;
// 設定値情報 (オプションの値に関わる情報)
public int[] AllValues { get; private set; } = new int[NumPresets];
public int CurrentValue
{
get => GetValue();
set => SetValue(value);
}
public int SingleValue { get; private set; }
// 親子情報
public OptionItem Parent { get; private set; }
public List<OptionItem> Children;
// 内部情報 (外部から参照することを想定していない情報)
public ConfigEntry<int> CurrentEntry =>
IsSingleValue ? singleEntry : AllConfigEntries[CurrentPreset];
private ConfigEntry<int>[] AllConfigEntries;
private ConfigEntry<int> singleEntry;
public OptionBehaviour OptionBehaviour;
// イベント
@@ -86,30 +82,32 @@ public abstract class OptionItem
// オブジェクト初期化
Children = new();
// ConfigEntry初期化
AllConfigEntries = new ConfigEntry<int>[5];
if (Id == 0)
// デフォルト値に設定
if (Id == PresetId)
{
singleEntry = Main.Instance.Config.Bind("Current Preset", id.ToString(), DefaultValue);
CurrentPreset = singleEntry.Value;
SingleValue = DefaultValue;
CurrentPreset = SingleValue;
}
else if (IsSingleValue)
{
singleEntry = Main.Instance.Config.Bind("SingleEntryOptions", id.ToString(), DefaultValue);
SingleValue = DefaultValue;
}
else
{
for (int i = 0; i < 5; i++)
for (int i = 0; i < NumPresets; i++)
{
AllConfigEntries[i] = Main.Instance.Config.Bind($"Preset{i + 1}", id.ToString(), DefaultValue);
AllValues[i] = DefaultValue;
}
}
if (AllOptions.Any(op => op.Id == id))
if (_fastOptions.TryAdd(id, this))
{
Logger.Error($"Repeat option ID:{id}", "OptionItem");
_allOptions.Add(this);
}
else
{
Logger.Error($"Duplicate ID: {id}", "OptionItem");
}
_allOptions.Add(this);
}
// Setter
@@ -166,7 +164,7 @@ public abstract class OptionItem
{
return ApplyFormat(CurrentValue.ToString());
}
public virtual int GetValue() => CurrentEntry.Value;
public virtual int GetValue() => IsSingleValue ? SingleValue : AllValues[CurrentPreset];
// 旧IsHidden関数
public virtual bool IsHiddenOn(CustomGameMode mode)
@@ -190,22 +188,36 @@ public abstract class OptionItem
opt.oldValue = opt.Value = CurrentValue;
}
}
public virtual void SetValue(int value)
public void SetValue(int afterValue, bool doSave, bool doSync = true)
{
int beforeValue = CurrentEntry.Value;
int afterValue = CurrentEntry.Value = value;
int beforeValue = CurrentValue;
if (IsSingleValue)
{
SingleValue = afterValue;
}
else
{
AllValues[CurrentPreset] = afterValue;
}
CallUpdateValueEvent(beforeValue, afterValue);
Refresh();
RPC.SyncCustomSettingsRPCforOneOption(this);
if (doSync)
{
SyncAllOptions();
}
if (doSave)
{
OptionSaver.Save();
}
}
public virtual void SetValueNoRpc(int value)
public virtual void SetValue(int afterValue, bool doSync = true)
{
int beforeValue = CurrentEntry.Value;
int afterValue = CurrentEntry.Value = value;
CallUpdateValueEvent(beforeValue, afterValue);
Refresh();
SetValue(afterValue, true, doSync);
}
public void SetAllValues(int[] values)
{
AllValues = values;
}
// 演算子オーバーロード
@@ -217,7 +229,7 @@ public abstract class OptionItem
// 全体操作用
public static void SwitchPreset(int newPreset)
{
CurrentPreset = Math.Clamp(newPreset, 0, 4);
CurrentPreset = Math.Clamp(newPreset, 0, NumPresets - 1);
for (int i = 0; i < AllOptions.Count; i++)
{
@@ -227,7 +239,7 @@ public abstract class OptionItem
SyncAllOptions();
}
public static void SyncAllOptions(int targetId = -1)
public static void SyncAllOptions()
{
if (
Main.AllPlayerControls.Count() <= 1 ||
@@ -235,7 +247,7 @@ public abstract class OptionItem
PlayerControl.LocalPlayer == null
) return;
RPC.SyncCustomSettingsRPC(targetId);
RPC.SyncCustomSettingsRPC();
}
@@ -249,7 +261,7 @@ public abstract class OptionItem
}
catch (Exception ex)
{
Logger.Error($"[{Name}] UpdateValueEventの呼び出し時に例外が発生しました", "OptionItem.UpdateValueEvent");
Logger.Error($"[{Name}] - Exception occurred when calling UpdateValueEvent", "OptionItem.UpdateValueEvent");
Logger.Exception(ex, "OptionItem.UpdateValueEvent");
}
}
@@ -264,6 +276,9 @@ public abstract class OptionItem
BeforeValue = beforeValue;
}
}
public const int NumPresets = 5;
public const int PresetId = 0;
}
public enum TabGroup
+3 -3
View File
@@ -9,7 +9,7 @@ public class PresetOptionItem : OptionItem
public PresetOptionItem(int defaultValue, TabGroup tab)
: base(0, "Preset", defaultValue, tab, true)
{
Rule = (0, 4, 1);
Rule = (0, NumPresets - 1, 1);
}
public static PresetOptionItem Create(int defaultValue, TabGroup tab)
{
@@ -35,9 +35,9 @@ public class PresetOptionItem : OptionItem
=> Rule.RepeatIndex(base.GetValue());
// Setter
public override void SetValue(int value)
public override void SetValue(int value, bool doSync = true)
{
base.SetValue(Rule.RepeatIndex(value));
base.SetValue(Rule.RepeatIndex(value), doSync);
SwitchPreset(Rule.RepeatIndex(value));
}
}
+2 -6
View File
@@ -47,12 +47,8 @@ public class StringOptionItem : OptionItem
=> Rule.RepeatIndex(base.GetValue());
// Setter
public override void SetValue(int value)
public override void SetValue(int value, bool doSync = true)
{
base.SetValue(Rule.RepeatIndex(value));
}
public override void SetValueNoRpc(int value)
{
base.SetValueNoRpc(Rule.RepeatIndex(value));
base.SetValue(Rule.RepeatIndex(value), doSync);
}
}
-8
View File
@@ -28,12 +28,4 @@ public class TextOptionItem : OptionItem
{
return Translator.GetString(Name);
}
// Setter
public override void SetValue(int value)
{
}
public override void SetValueNoRpc(int value)
{
}
}
+118
View File
@@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
namespace TOHE.Modules;
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionSaver.cs
public static class OptionSaver
{
private static readonly DirectoryInfo SaveDataDirectoryInfo = new("./TOHE-DATA/SaveData/");
private static readonly FileInfo OptionSaverFileInfo = new($"{SaveDataDirectoryInfo.FullName}/Options.json");
private static readonly LogHandler logger = Logger.Handler(nameof(OptionSaver));
public static void Initialize()
{
if (!SaveDataDirectoryInfo.Exists)
{
SaveDataDirectoryInfo.Create();
SaveDataDirectoryInfo.Attributes |= FileAttributes.Hidden;
}
if (!OptionSaverFileInfo.Exists)
{
OptionSaverFileInfo.Create().Dispose();
}
}
/// <summary>現在のオプションからjsonシリアライズ用のオブジェクトを生成</summary>
private static SerializableOptionsData GenerateOptionsData()
{
Dictionary<int, int> singleOptions = new();
Dictionary<int, int[]> presetOptions = new();
for (int i = 0; i < OptionItem.AllOptions.Count; i++)
{
OptionItem option = OptionItem.AllOptions[i];
if (option.IsSingleValue)
{
if (!singleOptions.TryAdd(option.Id, option.SingleValue))
{
logger.Warn($"Duplicate SingleOption ID: {option.Id}");
}
}
else if (!presetOptions.TryAdd(option.Id, option.AllValues))
{
logger.Warn($"Duplicate preset option ID: {option.Id}");
}
}
return new SerializableOptionsData
{
Version = Version,
SingleOptions = singleOptions,
PresetOptions = presetOptions,
};
}
/// <summary>デシリアライズされたオブジェクトを読み込み,オプション値を設定</summary>
private static void LoadOptionsData(SerializableOptionsData serializableOptionsData)
{
if (serializableOptionsData.Version != Version)
{
// 今後バージョン間の移行方法を用意する場合,ここでバージョンごとの変換メソッドに振り分ける
logger.Info($"読み込まれたオプションのバージョン {serializableOptionsData.Version} が現在のバージョン {Version} と一致しないためデフォルト値で上書きします");
Save();
return;
}
Dictionary<int, int> singleOptions = serializableOptionsData.SingleOptions;
Dictionary<int, int[]> presetOptions = serializableOptionsData.PresetOptions;
foreach (var singleOption in singleOptions)
{
var id = singleOption.Key;
var value = singleOption.Value;
if (OptionItem.FastOptions.TryGetValue(id, out var optionItem))
{
optionItem.SetValue(value, doSave: false);
}
}
foreach (var presetOption in presetOptions)
{
var id = presetOption.Key;
var values = presetOption.Value;
if (OptionItem.FastOptions.TryGetValue(id, out var optionItem))
{
optionItem.SetAllValues(values);
}
}
}
/// <summary>現在のオプションをjsonファイルに保存</summary>
public static void Save()
{
if (AmongUsClient.Instance != null && !AmongUsClient.Instance.AmHost) return;
var jsonString = JsonSerializer.Serialize(GenerateOptionsData(), new JsonSerializerOptions { WriteIndented = true, });
File.WriteAllText(OptionSaverFileInfo.FullName, jsonString);
}
/// <summary>jsonファイルからオプションを読み込み</summary>
public static void Load()
{
var jsonString = File.ReadAllText(OptionSaverFileInfo.FullName);
// 空なら読み込まず,デフォルト値をセーブする
if (jsonString.Length <= 0)
{
logger.Info("オプションデータが空のためデフォルト値を保存");
Save();
return;
}
LoadOptionsData(JsonSerializer.Deserialize<SerializableOptionsData>(jsonString));
}
/// <summary>json保存に適したオプションデータ</summary>
public class SerializableOptionsData
{
public int Version { get; init; }
/// <summary>プリセット外のオプション</summary>
public Dictionary<int, int> SingleOptions { get; init; }
/// <summary>プリセット内のオプション</summary>
public Dictionary<int, int[]> PresetOptions { get; init; }
}
/// <summary>オプションの形式に互換性のない変更(プリセット数変更など)を加えるときはここの数字を上げる</summary>
public static readonly int Version = 0;
}
+17 -59
View File
@@ -47,7 +47,7 @@ enum CustomRPC
// TOHE
AntiBlackout,
RestTOHESetting,
//RestTOHESetting,
PlayCustomSound,
SetKillTimer,
SyncAllPlayerNames,
@@ -259,20 +259,11 @@ internal class RPCHandlerPatch
RPC.RpcVersionCheck();
break;
case CustomRPC.SyncCustomSettings:
if (AmongUsClient.Instance.AmHost) break;
List<OptionItem> list = new();
var startAmount = reader.ReadInt32();
var lastAmount = reader.ReadInt32();
for (var i = startAmount; i < OptionItem.AllOptions.Count && i <= lastAmount; i++)
list.Add(OptionItem.AllOptions[i]);
Logger.Info($"{startAmount}-{lastAmount}:{list.Count}/{OptionItem.AllOptions.Count}", "SyncCustomSettings");
for (int i1 = 0; i1 < list.Count; i1++)
for (int i = 0; i < OptionItem.AllOptions.Count; i++)
{
OptionItem co = list[i1];
OptionItem co = OptionItem.AllOptions[i];
co.SetValue(reader.ReadInt32());
}
OptionShower.GetText();
break;
case CustomRPC.SetDeathReason:
RPC.GetDeathReason(reader);
@@ -451,10 +442,10 @@ internal class RPCHandlerPatch
case CustomRPC.SetQuickShooterShotLimit:
QuickShooter.ReceiveRPC(reader);
break;
case CustomRPC.RestTOHESetting:
OptionItem.AllOptions.ToArray().Where(x => x.Id > 0).Do(x => x.SetValueNoRpc(x.DefaultValue));
OptionShower.GetText();
break;
//case CustomRPC.RestTOHESetting:
// OptionItem.AllOptions.ToArray().Where(x => x.Id > 0).Do(x => x.SetValueNoRpc(x.DefaultValue));
// OptionShower.GetText();
// break;
case CustomRPC.SetEraseLimit:
Eraser.ReceiveRPC(reader);
break;
@@ -577,9 +568,9 @@ internal class RPCHandlerPatch
case CustomRPC.SetVultureArrow:
Vulture.ReceiveRPC(reader);
break;
case CustomRPC.DoPoison:
Baker.ReceiveRPC(reader);
break;
//case CustomRPC.DoPoison:
// Baker.ReceiveRPC(reader);
// break;
case CustomRPC.SetCleanserCleanLimit:
Cleanser.ReceiveRPC(reader);
break;
@@ -611,47 +602,14 @@ internal class RPCHandlerPatch
internal static class RPC
{
//来源:https://github.com/music-discussion/TownOfHost-TheOtherRoles/blob/main/Modules/RPC.cs
public static void SyncCustomSettingsRPC(int targetId = -1)
public static void SyncCustomSettingsRPC()
{
if (targetId != -1)
if (!AmongUsClient.Instance.AmHost) return;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, 80, Hazel.SendOption.Reliable, -1);
foreach (var co in OptionItem.AllOptions)
{
var client = Utils.GetClientById(targetId);
if (client == null || client.Character == null || !Main.playerVersion.ContainsKey(client.Character.PlayerId)) return;
}
if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) return;
var amount = OptionItem.AllOptions.Count;
int divideBy = amount / 10;
for (var i = 0; i <= 10; i++)
SyncOptionsBetween(i * divideBy, (i + 1) * divideBy, targetId);
}
public static void SyncCustomSettingsRPCforOneOption(OptionItem option)
{
List<OptionItem> allOptions = new(OptionItem.AllOptions);
var placement = allOptions.IndexOf(option);
if (placement != -1)
SyncOptionsBetween(placement, placement);
}
static void SyncOptionsBetween(int startAmount, int lastAmount, int targetId = -1)
{
if (targetId != -1)
{
var client = Utils.GetClientById(targetId);
if (client == null || client.Character == null || !Main.playerVersion.ContainsKey(client.Character.PlayerId)) return;
}
if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) return;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, 80, SendOption.Reliable, targetId);
List<OptionItem> list = new();
writer.Write(startAmount);
writer.Write(lastAmount);
for (var i = startAmount; i < OptionItem.AllOptions.Count && i <= lastAmount; i++)
list.Add(OptionItem.AllOptions[i]);
Logger.Info($"{startAmount}-{lastAmount}:{list.Count}/{OptionItem.AllOptions.Count}", "SyncCustomSettings");
for (int i1 = 0; i1 < list.Count; i1++)
{
OptionItem co = list[i1];
writer.Write(co.GetValue());
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
public static void PlaySoundRPC(byte PlayerID, Sounds sound)
@@ -949,9 +907,9 @@ internal static class RPC
case CustomRoles.ParityCop:
ParityCop.Add(targetId);
break;
case CustomRoles.Baker:
Baker.Add(targetId);
break;
//case CustomRoles.Baker:
// Baker.Add(targetId);
// break;
case CustomRoles.Councillor:
Councillor.Add(targetId);
break;
+25 -10
View File
@@ -467,7 +467,7 @@ public static class Utils
case CustomRoles.Jester:
//case CustomRoles.Pirate:
// case CustomRoles.Baker:
case CustomRoles.Famine:
//case CustomRoles.Famine:
//case CustomRoles.NWitch:
case CustomRoles.Mario:
case CustomRoles.Vulture:
@@ -521,7 +521,7 @@ public static class Utils
case CustomRoles.Convict:
case CustomRoles.Opportunist:
case CustomRoles.Phantom:
case CustomRoles.Baker:
//case CustomRoles.Baker:
// case CustomRoles.Famine:
if (ForRecompute)
hasTasks = false;
@@ -1326,15 +1326,32 @@ public static class Utils
var sb = new StringBuilder();
if (intro)
{
sb.Append("<size=40%>");
sb.Append("<size=15%>");
for (int i = 0; i < SubRoles.Count; i++)
{
CustomRoles role = SubRoles[i];
if (role is CustomRoles.NotAssigned or CustomRoles.LastImpostor) continue;
if (role is CustomRoles.NotAssigned or CustomRoles.LastImpostor) SubRoles.Remove(SubRoles[i]);
}
if (SubRoles.Count == 1)
{
CustomRoles role = SubRoles[0];
var RoleText = ColorString(GetRoleColor(role), GetRoleName(role));
sb.Append($"{ColorString(Color.gray, "\nModifier: ")}{RoleText}");
}
else
{
sb.Append($"{ColorString(Color.gray, "\nModifiers: ")}");
for (int i = 0; i < SubRoles.Count; i++)
{
if (i != 0) sb.Append(", ");
CustomRoles role = SubRoles[i];
if (role is CustomRoles.NotAssigned or CustomRoles.LastImpostor) continue;
var RoleText = ColorString(GetRoleColor(role), GetRoleName(role));
sb.Append($"{RoleText}");
}
}
sb.Append("</size>");
}
else if (!summary)
@@ -1747,8 +1764,8 @@ public static class Utils
//呪われている場合
SelfMark.Append(Witch.GetSpelledMark(seer.PlayerId, isForMeeting));
SelfMark.Append(HexMaster.GetHexedMark(seer.PlayerId, isForMeeting));
if (Baker.IsPoisoned(seer) && isForMeeting && seer.IsAlive())
SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Famine), "θ"));
//if (Baker.IsPoisoned(seer) && isForMeeting && seer.IsAlive())
// SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Famine), "θ"));
//如果是大明星
@@ -2065,6 +2082,7 @@ public static class Utils
(target.Is(CustomRoles.Doctor) && !target.GetCustomRole().IsEvilAddons() && Options.DoctorVisibleToEveryone.GetBool()) ||
(target.Is(CustomRoles.Mayor) && Options.MayorRevealWhenDoneTasks.GetBool() && target.GetPlayerTaskState().IsTaskFinished) ||
(seer.Is(CustomRoleTypes.Crewmate) && target.Is(CustomRoles.Marshall) && target.GetPlayerTaskState().IsTaskFinished) ||
(Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) ||
Totocalcio.KnowRole(seer, target) ||
Romantic.KnowRole(seer, target) ||
Lawyer.KnowRole(seer, target) ||
@@ -2358,11 +2376,8 @@ public static class Utils
if (Options.AirshipVariableElectrical.GetBool())
AirshipElectricalDoors.Initialize();
/* if (Options.DiseasedCDReset.GetBool())
Main.KilledDiseased.Clear();
if (Options.AntidoteCDReset.GetBool())
Main.KilledAntidote.Clear(); */
DoorsReset.ResetDoors();
}
public static void AfterPlayerDeathTasks(PlayerControl target, bool onMeeting = false)
{
+12
View File
@@ -152,3 +152,15 @@
> - Ideas for some roles
> - Customize Main Menu
> - Reference: Auto Play Again
>
### :star: [Stellar Roles](https://github.com/Mr-Fluuff/StellarRolesAU)
>
> - Many role ideas (this mod is seriously amazing)
> - Reference: Some custom sounds and button images
>
### :star: [TOHEX](https://github.com/TOHEX-Official/TownOfHostEdited-Xi)
>
> - Provided role: Swapper
>
+5
View File
@@ -5,6 +5,11 @@ VisualStudioVersion = 17.4.33403.182
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TOHE", "TOHE.csproj", "{807AD310-21DA-4160-9D72-926745B02A85}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DBDFE762-E378-4B3D-8BB3-3F4F13C92ADB}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+6 -6
View File
@@ -112,7 +112,7 @@ public class Main : BasePlugin
public static List<byte> DemolitionistDead = new();
public static Dictionary<byte, long> ExpressSpeedUp = new();
public static float ExpressSpeedNormal;
public static List<Vent> BlockedVents = new();
public static List<int> BlockedVents = new();
public static float VentguardNumberOfAbilityUses = 0;
public static List<byte> WorkaholicAlive = new();
public static List<byte> SpeedrunnerAlive = new();
@@ -423,8 +423,8 @@ public class Main : BasePlugin
{CustomRoles.Traitor, "#BA2E05"},
{CustomRoles.Vulture, "#556B2F"},
{CustomRoles.Medusa, "#9900CC"},
{CustomRoles.Baker, "#b58428"},
{CustomRoles.Famine, "#cb4d4d"},
//{CustomRoles.Baker, "#b58428"},
//{CustomRoles.Famine, "#cb4d4d"},
{CustomRoles.Spiritcaller, "#003366"},
{CustomRoles.EvilSpirit, "#003366"},
{CustomRoles.Convict, "#ff1919"},
@@ -749,8 +749,8 @@ public enum CustomRoles
Pestilence,
Medusa,
Sidekick,
Baker,
Famine,
//Baker,
//Famine,
Spiritcaller,
Amnesiac,
Doomsayer,
@@ -873,7 +873,7 @@ public enum CustomWinner
Vulture = CustomRoles.Vulture,
Pestilence = CustomRoles.Pestilence,
Medusa = CustomRoles.Medusa,
Famine = CustomRoles.Famine,
//Famine = CustomRoles.Famine,
Spiritcaller = CustomRoles.Spiritcaller,
Glitch = CustomRoles.Glitch,
Plaguebearer = CustomRoles.PlagueBearer,