diff --git a/src/MultiplayerMod.Test/AbstractGameTest.cs b/src/MultiplayerMod.Test/AbstractGameTest.cs index 8f08dd16..1a46f04f 100644 --- a/src/MultiplayerMod.Test/AbstractGameTest.cs +++ b/src/MultiplayerMod.Test/AbstractGameTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using HarmonyLib; using MultiplayerMod.Core.Dependency; using MultiplayerMod.ModRuntime; @@ -9,7 +8,7 @@ using MultiplayerMod.Multiplayer.Objects; using MultiplayerMod.Test.Environment.Patches; using MultiplayerMod.Test.Environment.Unity; -using MultiplayerMod.Test.Multiplayer.Commands.Chores; +using MultiplayerMod.Test.Multiplayer.Commands.Chores.Patches; using NUnit.Framework; using UnityEngine; @@ -19,15 +18,13 @@ public abstract class AbstractGameTest { private static Harmony harmony = null!; - protected HashSet patches = typeof(CreateHostChoreTest).Assembly.GetTypes() - .Where(type => type.Namespace == typeof(CreateHostChoreTest).Namespace + ".Patches") - .ToHashSet(); + protected readonly HashSet Patches = new(new[] { typeof(DbPatch) }); [SetUp] public void SetUp() { - harmony = new Harmony("CreateHostChoreTest"); + harmony = new Harmony("AbstractGameTest"); UnityTestRuntime.Install(); - PatchesSetup.Install(harmony, patches); + PatchesSetup.Install(harmony, Patches); SetUpUnityAndGame(); SetupDependencies(); } @@ -58,19 +55,23 @@ private static void SetUpUnityAndGame() { ReportManager.Instance.Awake(); ReportManager.Instance.todaysReport = new ReportManager.DailyReport(ReportManager.Instance); - GlobalChoreProvider.Instance = new GlobalChoreProvider(); - StateMachineDebuggerSettings._Instance = new StateMachineDebuggerSettings(); StateMachineDebuggerSettings._Instance.Initialize(); + + worldGameObject.AddComponent().OnPrefabInit(); + worldGameObject.AddComponent().OnPrefabInit(); + worldGameObject.AddComponent().OnPrefabInit(); } private static void InitGame(GameObject worldGameObject) { // From global Singleton.CreateInstance(); Singleton.CreateInstance(); + Singleton.CreateInstance(); var game = worldGameObject.AddComponent(); global::Game.Instance = game; + game.obj = KObjectManager.Instance.GetOrCreateObject(game.gameObject); var widthInCells = 40; var heightInCells = 40; @@ -85,6 +86,7 @@ private static void InitGame(GameObject worldGameObject) { game.travelTubeSystem = new UtilityNetworkTubesManager(widthInCells, heightInCells, 35); game.gasConduitFlow = new ConduitFlow(ConduitType.Gas, numCells, game.gasConduitSystem, 1f, 0.25f); game.liquidConduitFlow = new ConduitFlow(ConduitType.Liquid, numCells, game.liquidConduitSystem, 10f, 0.75f); + game.mingleCellTracker = worldGameObject.AddComponent(); GridSettings.Reset(widthInCells, heightInCells); @@ -103,7 +105,8 @@ private static void SetupDependencies() { new DependencyInfo(nameof(ExecutionContextManager), typeof(ExecutionContextManager), false) ); dependencyContainer.Register( - new DependencyInfo(nameof(DependencyContainer), typeof(DependencyContainer), false)); + new DependencyInfo(nameof(DependencyContainer), typeof(DependencyContainer), false) + ); new Runtime(dependencyContainer); Runtime.Instance.Dependencies.Get().Refresh(MultiplayerMode.Host); Runtime.Instance.Dependencies.Get().EnterOverrideSection(ExecutionLevel.Game); diff --git a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ApplicationPatch.cs b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ApplicationPatch.cs index d0e82142..3f62079f 100644 --- a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ApplicationPatch.cs +++ b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ApplicationPatch.cs @@ -12,7 +12,7 @@ public class ApplicationPatch { [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Application), "get_isPlaying")] + [HarmonyPatch("get_isPlaying")] private static IEnumerable Application_get_isPlaying(IEnumerable instructions) { return new List { new(OpCodes.Ldc_I4_1), // true @@ -22,8 +22,10 @@ private static IEnumerable Application_get_isPlaying(IEnumerabl [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Application), "get_streamingAssetsPath")] - private static IEnumerable Application_get_streamingAssetsPath(IEnumerable instructions) { + [HarmonyPatch("get_streamingAssetsPath")] + private static IEnumerable Application_get_streamingAssetsPath( + IEnumerable instructions + ) { return new List { new(OpCodes.Ldstr, ""), // "" new(OpCodes.Ret) @@ -32,8 +34,10 @@ private static IEnumerable Application_get_streamingAssetsPath( [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Application), "get_persistentDataPath")] - private static IEnumerable Application_persistentDataPath(IEnumerable instructions) { + [HarmonyPatch("get_persistentDataPath")] + private static IEnumerable Application_persistentDataPath( + IEnumerable instructions + ) { return new List { new(OpCodes.Ldstr, ""), // "" new(OpCodes.Ret) @@ -42,7 +46,7 @@ private static IEnumerable Application_persistentDataPath(IEnum [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Application), "get_consoleLogPath")] + [HarmonyPatch("get_consoleLogPath")] private static IEnumerable Application_consoleLogPath(IEnumerable instructions) { return new List { new(OpCodes.Ldstr, ""), // "" diff --git a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ComponentPatch.cs b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ComponentPatch.cs index 51971af9..d86035cb 100644 --- a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ComponentPatch.cs +++ b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ComponentPatch.cs @@ -12,7 +12,7 @@ public class ComponentPatch { [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Component), "get_gameObject")] + [HarmonyPatch("get_gameObject")] private static IEnumerable Component_get_gameObject(IEnumerable instructions) { return new List { new(OpCodes.Ldarg_0), // this @@ -21,6 +21,17 @@ private static IEnumerable Component_get_gameObject(IEnumerable }; } + [UsedImplicitly] + [HarmonyTranspiler] + [HarmonyPatch("get_transform")] + private static IEnumerable Component_get_transform(IEnumerable instructions) { + return new List { + new(OpCodes.Ldarg_0), // this + CodeInstruction.Call(typeof(ComponentPatch), nameof(GetTransform)), + new(OpCodes.Ret) + }; + } + [UsedImplicitly] [HarmonyTranspiler] [HarmonyPatch("GetComponentFastPath")] @@ -35,4 +46,8 @@ IEnumerable instructions new(OpCodes.Ret) }; } + + public static Transform GetTransform(Component component) { + return component.gameObject.GetComponent(); + } } diff --git a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/GameObjectPatch.cs b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/GameObjectPatch.cs index 29a3141b..b33a0cb7 100644 --- a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/GameObjectPatch.cs +++ b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/GameObjectPatch.cs @@ -65,38 +65,54 @@ private static IEnumerable GameObject_GetComponent(IEnumerable< [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(GameObject), "get_transform")] - private static IEnumerable GameObject_get_transform(IEnumerable instructions) { + [HarmonyPatch("GetComponentsInternal")] + private static IEnumerable GameObject_GetComponentsInternal( + IEnumerable instructions + ) { return new List { new(OpCodes.Ldarg_0), // this - CodeInstruction.Call(typeof(GameObjectPatch), nameof(GetTransform)), + new(OpCodes.Ldarg_1), // type + CodeInstruction.Call(typeof(UnityTestRuntime), nameof(UnityTestRuntime.GetComponent)), new(OpCodes.Ret) }; } [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch("Internal_AddComponentWithType")] - private static IEnumerable GameObject_Internal_AddComponentWithType( + [HarmonyPatch("GetComponentInChildren", typeof(Type), typeof(bool))] + private static IEnumerable GameObject_GetComponentInChildren( IEnumerable instructions ) { return new List { - new(OpCodes.Ldarg_0), // this (go) - new(OpCodes.Ldarg_1), // componentType - CodeInstruction.Call(typeof(UnityTestRuntime), nameof(UnityTestRuntime.AddComponent)), + new(OpCodes.Ldarg_0), // this + new(OpCodes.Ldarg_1), // type + new(OpCodes.Ldarg_2), // includeInactive + CodeInstruction.Call(typeof(UnityTestRuntime), nameof(UnityTestRuntime.GetComponentInChildren)), + new(OpCodes.Ret) + }; + } + + [UsedImplicitly] + [HarmonyTranspiler] + [HarmonyPatch("get_transform")] + private static IEnumerable GameObject_get_transform(IEnumerable instructions) { + return new List { + new(OpCodes.Ldarg_0), // this + CodeInstruction.Call(typeof(GameObjectPatch), nameof(GetTransform)), new(OpCodes.Ret) }; } [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Time), "get_frameCount")] - private static IEnumerable Time_get_frameCount(IEnumerable instructions) { + [HarmonyPatch("Internal_AddComponentWithType")] + private static IEnumerable GameObject_Internal_AddComponentWithType( + IEnumerable instructions + ) { return new List { - new( - OpCodes.Call, - AccessTools.PropertyGetter(typeof(UnityTestRuntime), nameof(UnityTestRuntime.FrameCount)) - ), + new(OpCodes.Ldarg_0), // this (go) + new(OpCodes.Ldarg_1), // componentType + CodeInstruction.Call(typeof(UnityTestRuntime), nameof(UnityTestRuntime.AddComponent)), new(OpCodes.Ret) }; } diff --git a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/MaterialPatch.cs b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/MaterialPatch.cs new file mode 100644 index 00000000..cf48b214 --- /dev/null +++ b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/MaterialPatch.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Reflection.Emit; +using HarmonyLib; +using JetBrains.Annotations; +using UnityEngine; + +namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; + +[UsedImplicitly] +[HarmonyPatch(typeof(Material))] +public class MaterialPatch { + + [UsedImplicitly] + [HarmonyTranspiler] + [HarmonyPatch("CreateWithString")] + private static IEnumerable Material_CreateWithString(IEnumerable instructions) { + return new List { + new(OpCodes.Ret) + }; + } + + [UsedImplicitly] + [HarmonyTranspiler] + [HarmonyPatch("GetFirstPropertyNameIdByAttribute")] + private static IEnumerable Material_GetFirstPropertyNameIdByAttribute( + IEnumerable instructions + ) { + return new List { + new(OpCodes.Ldc_I4_0), + new(OpCodes.Ret) + }; + } + + [UsedImplicitly] + [HarmonyTranspiler] + [HarmonyPatch("SetColorImpl")] + private static IEnumerable Material_SetColorImpl(IEnumerable instructions) { + return new List { + new(OpCodes.Ret) + }; + } +} diff --git a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/RendererPatch.cs b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/RendererPatch.cs new file mode 100644 index 00000000..543814f0 --- /dev/null +++ b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/RendererPatch.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Reflection.Emit; +using HarmonyLib; +using JetBrains.Annotations; +using UnityEngine; + +namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity; + +[UsedImplicitly] +[HarmonyPatch(typeof(Renderer))] +public class RendererPatch { + + [UsedImplicitly] + [HarmonyTranspiler] + [HarmonyPatch("get_material")] + private static IEnumerable Renderer_get_material(IEnumerable instructions) { + return new List { + new(OpCodes.Ldarg_0), // this + CodeInstruction.Call(typeof(RendererPatch), nameof(CreateMaterial)), + new(OpCodes.Ret) + }; + } + + public static Material CreateMaterial(Renderer _) { +#pragma warning disable CS0618 // Type or member is obsolete + return new Material(""); +#pragma warning restore CS0618 // Type or member is obsolete + } +} diff --git a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/TimePatch.cs b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/TimePatch.cs index a56889c8..2b52df2b 100644 --- a/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/TimePatch.cs +++ b/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/TimePatch.cs @@ -12,7 +12,7 @@ public class TimePatch { [UsedImplicitly] [HarmonyTranspiler] - [HarmonyPatch(typeof(Time), "get_frameCount")] + [HarmonyPatch("get_frameCount")] private static IEnumerable Time_get_frameCount(IEnumerable instructions) { return new List { new( @@ -22,5 +22,4 @@ private static IEnumerable Time_get_frameCount(IEnumerable Transform_get_position_Injected( IEnumerable instructions ) { diff --git a/src/MultiplayerMod.Test/Environment/Unity/UnityTestRuntime.cs b/src/MultiplayerMod.Test/Environment/Unity/UnityTestRuntime.cs index 430dc44a..92e46bc6 100644 --- a/src/MultiplayerMod.Test/Environment/Unity/UnityTestRuntime.cs +++ b/src/MultiplayerMod.Test/Environment/Unity/UnityTestRuntime.cs @@ -98,6 +98,9 @@ public static Component GetComponent(GameObject gameObject, Type type) { Objects[gameObject].SingleOrDefault(component => type.IsAssignableFrom(component.GetType())); } + public static Component? GetComponentInChildren(GameObject gameObject, Type type, bool includeInactive) => + GetComponent(gameObject, type); + public static unsafe void GetComponentFastPath(GameObject gameObject, Type type, IntPtr oneFurtherThanResultValue) { var component = GetComponent(gameObject, type); diff --git a/src/MultiplayerMod.Test/Game/Chores/AbstractChoreTest.cs b/src/MultiplayerMod.Test/Game/Chores/AbstractChoreTest.cs new file mode 100644 index 00000000..b382534b --- /dev/null +++ b/src/MultiplayerMod.Test/Game/Chores/AbstractChoreTest.cs @@ -0,0 +1,380 @@ +using System; +using System.Linq; +using MultiplayerMod.Game.Chores; +using NUnit.Framework; +using UnityEngine; + +#pragma warning disable CS8974 // Converting method group to non-delegate type + +namespace MultiplayerMod.Test.Game.Chores; + +public class AbstractChoreTest : AbstractGameTest { + + private static KMonoBehaviour target = null!; + private static GameObject gameObject = null!; + private static ChoreType choreType = null!; + private static KPrefabID kPrefabID = null!; + private static MedicinalPillWorkable medicinalPillWorkable = null!; + + private static Death death = null!; + + // private static Emote emote = null!; + private static Storage storage = null!; + private static Constructable constructable = null!; + private static TestMonoBehaviour testMonoBehaviour = null!; + private static Db db = null!; + + [SetUp] + public new void SetUp() { + base.SetUp(); + + target = createGameObject().GetComponent(); + target.gameObject.AddComponent(); + target.gameObject.AddComponent(); + target.gameObject.AddComponent(); + target.gameObject.AddComponent(); + + var sensors = target.gameObject.AddComponent(); + sensors.Add(new SafeCellSensor(sensors)); + sensors.Add(new IdleCellSensor(sensors)); + sensors.Add(new MingleCellSensor(sensors)); + + gameObject = createGameObject(); + gameObject.AddComponent(); + gameObject.AddComponent(); + + db = Db.Get(); + + choreType = db.ChoreTypes.Astronaut; + kPrefabID = createGameObject().AddComponent(); + kPrefabID.gameObject.AddComponent(); // required for RancherChore + medicinalPillWorkable = createGameObject().AddComponent(); + medicinalPillWorkable.Awake(); + death = db.Deaths.Frozen; + // emote = db.Emotes.Minion.Cheer; + + storage = createGameObject().AddComponent(); + storage.Awake(); + + constructable = createGameObject().AddComponent(); + + testMonoBehaviour = createGameObject().AddComponent(); + } + + protected Chore CreateChore(Type choreType, object?[] args) { + return (Chore) choreType.GetConstructors()[0].Invoke(args); + } + + protected static object[][] GetTestArgs() { + // var notification = new Notification("", NotificationType.Bad); + // var statusItem = new StatusItem("", ""); + + var testArgs = new[] { + new object[] { + typeof(AttackChore), + new Func(() => new object[] { target, gameObject }) + }, + new object[] { + typeof(DeliverFoodChore), + new Func(() => new object[] { target }) + }, + new object[] { + typeof(DieChore), + new Func(() => new object[] { target, death }) + }, + new object[] { + typeof(DropUnusedInventoryChore), + new Func(() => new object[] { choreType, target }) + }, + // new object[] { + // new Func(() => new EquipChore(target)), + // typeof(EquipChore), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // new Func(() => new FixedCaptureChore(kPrefabID)), + // typeof(FixedCaptureChore), + // new Func(() => new object[] { kPrefabID }) + // }, + new object[] { + typeof(MoveToQuarantineChore), + new Func(() => new object[] { target, target }) + }, + new object[] { + typeof(PartyChore), + new Func( + () => new object?[] { + target, medicinalPillWorkable, constructable.UpdateBuildState, constructable.UpdateBuildState, + constructable.UpdateBuildState + } + ) + }, + new object[] { + typeof(PeeChore), + new Func(() => new object[] { target }) + }, + new object[] { + typeof(PutOnHatChore), + new Func(() => new object[] { target, choreType }) + }, + // new object[] { + // new Func(() => new RancherChore(kPrefabID)), + // typeof(RancherChore), + // new Func(() => new object[] { kPrefabID }) + // }, + // new object[] { + // typeof(ReactEmoteChore), + // new Func( + // () => new ReactEmoteChore( + // target, + // choreType, + // null, + // new HashedString(1), + // null, + // KAnim.PlayMode.Loop, + // null + // ) + // ), + // new Func( + // () => new object?[] + // { target, choreType, null, new HashedString(1), null, KAnim.PlayMode.Loop, null } + // ) + // }, + // new object[] { + // new Func(() => new RescueIncapacitatedChore(target, gameObject)), + // typeof(RescueIncapacitatedChore), + // new Func(() => new object[] { target, gameObject }) + // }, + // new object[] { + // new Func(() => new RescueSweepBotChore(target, gameObject, gameObject)), + // typeof(RescueSweepBotChore), + // new Func(() => new object[] { target, gameObject, gameObject }) + // }, + new object[] { + typeof(SighChore), + new Func(() => new object[] { target }) + }, + new object[] { + typeof(StressIdleChore), + new Func(() => new object[] { target }) + }, + new object[] { + typeof(SwitchRoleHatChore), + new Func(() => new object[] { target, choreType }) + }, + // new object[] { + // new Func(() => new TakeMedicineChore(medicinalPillWorkable)), + // typeof(TakeMedicineChore), + // new Func(() => new object[] { medicinalPillWorkable }) + // }, + new object[] { + typeof(TakeOffHatChore), + new Func(() => new object[] { target, choreType }) + }, + new object[] { + typeof(UglyCryChore), + new Func(() => new object?[] { choreType, target, constructable.UpdateBuildState }) + }, + new object[] { + typeof(WaterCoolerChore), + new Func( + () => new object?[] { + target, medicinalPillWorkable, constructable.UpdateBuildState, constructable.UpdateBuildState, + constructable.UpdateBuildState + } + ) + }, + // new object[] { + // new Func( + // () => new WorkChore( + // choreType, + // medicinalPillWorkable, + // GlobalChoreProvider.Instance + // ) + // ), + // typeof(WorkChore), + // new Func( + // () => new object?[] { + // choreType, medicinalPillWorkable, GlobalChoreProvider.Instance, true, null, null, null, true, + // null, false, true, null, false, true, true, PriorityScreen.PriorityClass.basic, 5, false, true + // } + // ) + // } + // new object[] { + // typeof(AggressiveChore), + // new Func(() => new AggressiveChore(target, ChoreCallback)), + // new Func(() => new object?[] { target, ChoreCallback }) + // }, + // new object[] { + // typeof(BeIncapacitatedChore), + // new Func(() => new BeIncapacitatedChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(BingeEatChore), + // new Func(() => new BingeEatChore(target, ChoreCallback)), + // new Func(() => new object?[] { target, ChoreCallback }) + // }, + // new object[] { + // typeof(EatChore), + // new Func(() => new EatChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(EmoteChore), + // new Func( + // () => new EmoteChore( + // target, + // choreType, + // db.Emotes.Minion.Cheer, + // 1, + // testMonoBehaviour.TestStressEmoteFunc + // ) + // ), + // new Func( + // () => new object?[] + // { target, choreType, db.Emotes.Minion.Cheer, 1, testMonoBehaviour.TestStressEmoteFunc } + // ) + // }, + new object[] { + typeof(EntombedChore), + new Func(() => new object[] { target, "override" }) + }, + // new object[] { + // new Func(() => new FetchAreaChore(new Chore.Precondition.Context())), + // typeof(FetchAreaChore), + // new Func(() => new object[] { new Chore.Precondition.Context() }) + // }, + // new object[] { + // typeof(FleeChore), + // new Func(() => new FleeChore(target, gameObject)), + // new Func(() => new object[] { target, gameObject }) + // }, + // new object[] { + // typeof(FoodFightChore), + // new Func(() => new FoodFightChore(target, gameObject)), + // new Func(() => new object[] { target, gameObject }) + // }, + // new object[] { + // typeof(MoveChore), + // new Func(() => new MoveChore(target, choreType, _ => 15)), + // new Func(() => new object[] { target, choreType, 15, false }) + // }, + // new object[] { + // typeof(MournChore), + // new Func(() => new MournChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(MovePickupableChore), + // new Func(() => new MovePickupableChore(target, gameObject, ChoreCallback)), + // new Func(() => new object?[] { target, gameObject, ChoreCallback }) + // }, + // new object[] { + // typeof(MoveToSafetyChore), + // new Func(() => new MoveToSafetyChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(RecoverBreathChore), + // new Func(() => new RecoverBreathChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(SleepChore), + // new Func(() => new SleepChore(choreType, target, gameObject, false, false)), + // new Func(() => new object[] { choreType, target, gameObject, false, false }) + // }, + // new object[] { + // typeof(StressEmoteChore), + // new Func( + // () => new StressEmoteChore( + // target, + // choreType, + // new HashedString(1), + // new HashedString[] { new(2) }, + // KAnim.PlayMode.Paused, + // testMonoBehaviour.TestStressEmoteFunc + // ) + // ), + // new Func( + // () => new object?[] { + // target, choreType, new HashedString(1), new HashedString[] { new(2) }, + // KAnim.PlayMode.Paused, testMonoBehaviour.TestStressEmoteFunc + // } + // ) + // }, + // new object[] { + // typeof(BalloonArtistChore), + // new Func(() => new BalloonArtistChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(BansheeChore), + // new Func(() => new BansheeChore(choreType, target, null!)), + // new Func(() => new object?[] { choreType, target, null, null }) + // }, + // new object[] { + // typeof(FetchChore), + // new Func( + // () => new FetchChore( + // choreType, + // storage, + // 0f, + // new HashSet(), + // FetchChore.MatchCriteria.MatchTags, + // new Tag(), + // Array.Empty(), + // target.GetComponent(), + // true, + // ChoreCallback, + // ChoreCallback, + // ChoreCallback, + // Operational.State.Operational, + // 0 + // ) + // ), + // new Func( + // () => new object?[] { + // choreType, storage, 0f, new HashSet(), FetchChore.MatchCriteria.MatchTags, new Tag(), + // Array.Empty(), target.GetComponent(), true, ChoreCallback, ChoreCallback, + // ChoreCallback, + // Operational.State.Operational, 0 + // } + // ) + // }, + // new object[] { + // typeof(IdleChore), + // new Func(() => new IdleChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(MingleChore), + // new Func(() => new MingleChore(target)), + // new Func(() => new object[] { target }) + // }, + // new object[] { + // typeof(VomitChore), + // new Func(() => new VomitChore(choreType, target, statusItem, notification)), + // new Func(() => new object?[] { choreType, target, statusItem, notification, null }) + // }, + }; + var expectedChoreTypes = ChoreList.Config + .Where(config => config.Value.CreationSync == ChoreList.CreationStatusEnum.On) + .Select(config => config.Key) + .OrderBy(a => a.FullName) + .ToList(); + var actualChoreTypes = testArgs.Select(config => (Type) config[0]).OrderBy(a => a.FullName).ToList(); + Assert.AreEqual(expectedChoreTypes, actualChoreTypes); + return testArgs; + } + + private class TestMonoBehaviour : KMonoBehaviour { + + public StatusItem TestStressEmoteFunc() { + return new StatusItem("", ""); + } + } + + private static void ChoreCallback(Chore _) { } +} diff --git a/src/MultiplayerMod.Test/Game/Chores/ChoreEventsTest.cs b/src/MultiplayerMod.Test/Game/Chores/ChoreEventsTest.cs index f666e849..eab6223a 100644 --- a/src/MultiplayerMod.Test/Game/Chores/ChoreEventsTest.cs +++ b/src/MultiplayerMod.Test/Game/Chores/ChoreEventsTest.cs @@ -1,206 +1,29 @@ using System; -using Klei.AI; using MultiplayerMod.Game.Chores; using NUnit.Framework; -using UnityEngine; namespace MultiplayerMod.Test.Game.Chores; [TestFixture] -public class ChoreEventsTest : AbstractGameTest { - - private static KMonoBehaviour target = null!; - private static GameObject gameObject = null!; - private static ChoreType choreType = null!; - private static KPrefabID kPrefabID = null!; - private static MedicinalPillWorkable medicinalPillWorkable = null!; - private static Death death = null!; - private static Emote emote = null!; +public class ChoreEventsTest : AbstractChoreTest { [SetUp] public new void SetUp() { - patches.Add(typeof(ChoreEvents)); - base.SetUp(); + Patches.Add(typeof(ChoreEvents)); - target = createGameObject().GetComponent(); - gameObject = createGameObject(); - choreType = Db.Get().ChoreTypes.Astronaut; - kPrefabID = createGameObject().AddComponent(); - kPrefabID.gameObject.AddComponent(); // required for RancherChore - medicinalPillWorkable = createGameObject().AddComponent(); - medicinalPillWorkable.Awake(); - death = Db.Get().Deaths.Frozen; - emote = Db.Get().Emotes.Minion.Cheer; + base.SetUp(); } [Test, TestCaseSource(nameof(GetTestArgs))] - public void TestEventFiring(System.Action createNewChore, Type choreType, Func expectedArgsFunc) { + public void TestEventFiring(Type choreType, Func expectedArgsFunc) { CreateNewChoreArgs? firedArgs = null; ChoreEvents.CreateNewChore += args => firedArgs = args; - createNewChore.Invoke(); + CreateChore(choreType, expectedArgsFunc.Invoke()); Assert.NotNull(firedArgs); Assert.AreEqual(choreType, firedArgs!.ChoreType); var expected = expectedArgsFunc.Invoke(); Assert.AreEqual(expected, firedArgs.Args); } - - // ReSharper disable ObjectCreationAsStatement - private static object[][] GetTestArgs() { - // var cellCallback = new Func(_ => 5); - - var testArgs = new[] { - new object[] { - new System.Action(() => new AttackChore(target, gameObject)), - typeof(AttackChore), - new Func(() => new object[] { target, gameObject }) - }, - - new object[] { - new System.Action(() => new DeliverFoodChore(target)), - typeof(DeliverFoodChore), - new Func(() => new object[] { target }) - }, - new object[] { - new System.Action(() => new DieChore(target, death)), - typeof(DieChore), - new Func(() => new object[] { target, death }) - }, - new object[] { - new System.Action(() => new DropUnusedInventoryChore(choreType, target)), - typeof(DropUnusedInventoryChore), - new Func(() => new object[] { choreType, target }) - }, - // new object[] { - // new System.Action(() => new EmoteChore(target, choreType, emote)), - // typeof(EmoteChore), - // new Func(() => new object?[] { target, choreType, emote, 1, null }) - // }, - new object[] { - new System.Action(() => new EquipChore(target)), - typeof(EquipChore), - new Func(() => new object[] { target }) - }, - new object[] { - new System.Action(() => new FixedCaptureChore(kPrefabID)), - typeof(FixedCaptureChore), - new Func(() => new object[] { kPrefabID }) - }, - // new object[] { - // new System.Action(() => new MoveChore(target, choreType, cellCallback)), - // typeof(MoveChore), - // new Func(() => new object[] { target, choreType, cellCallback, false }) - // }, - new object[] { - new System.Action(() => new MoveToQuarantineChore(target, target)), - typeof(MoveToQuarantineChore), - new Func(() => new object[] { target, target }) - }, - new object[] { - new System.Action(() => new PartyChore(target, medicinalPillWorkable)), - typeof(PartyChore), - new Func(() => new object?[] { target, medicinalPillWorkable, null, null, null }) - }, - new object[] { - new System.Action(() => new PeeChore(target)), - typeof(PeeChore), - new Func(() => new object[] { target }) - }, - new object[] { - new System.Action(() => new PutOnHatChore(target, choreType)), - typeof(PutOnHatChore), - new Func(() => new object[] { target, choreType }) - }, - new object[] { - new System.Action(() => new RancherChore(kPrefabID)), - typeof(RancherChore), - new Func(() => new object[] { kPrefabID }) - }, - // new object[] { - // new System.Action( - // () => new ReactEmoteChore( - // target, - // choreType, - // null, - // new HashedString(1), - // null, - // KAnim.PlayMode.Loop, - // null - // ) - // ), - // typeof(ReactEmoteChore), - // new Func( - // () => new object?[] - // { target, choreType, null, new HashedString(1), null, KAnim.PlayMode.Loop, null } - // ) - // }, - new object[] { - new System.Action(() => new RescueIncapacitatedChore(target, gameObject)), - typeof(RescueIncapacitatedChore), - new Func(() => new object[] { target, gameObject }) - }, - new object[] { - new System.Action(() => new RescueSweepBotChore(target, gameObject, gameObject)), - typeof(RescueSweepBotChore), - new Func(() => new object[] { target, gameObject, gameObject }) - }, - new object[] { - new System.Action(() => new SighChore(target)), - typeof(SighChore), - new Func(() => new object[] { target }) - }, - // new object[] { - // new System.Action( - // () => new StressEmoteChore(target, choreType, new HashedString(1), null, KAnim.PlayMode.Loop, null) - // ), - // typeof(StressEmoteChore), - // new Func( - // () => new object?[] { target, choreType, new HashedString(1), null, KAnim.PlayMode.Loop, null } - // ) - // }, - new object[] { - new System.Action(() => new StressIdleChore(target)), - typeof(StressIdleChore), - new Func(() => new object[] { target }) - }, - new object[] { - new System.Action(() => new SwitchRoleHatChore(target, choreType)), - typeof(SwitchRoleHatChore), - new Func(() => new object[] { target, choreType }) - }, - new object[] { - new System.Action(() => new TakeMedicineChore(medicinalPillWorkable)), - typeof(TakeMedicineChore), - new Func(() => new object[] { medicinalPillWorkable }) - }, - new object[] { - new System.Action(() => new TakeOffHatChore(target, choreType)), - typeof(TakeOffHatChore), - new Func(() => new object[] { target, choreType }) - }, - new object[] { - new System.Action(() => new UglyCryChore(choreType, target)), - typeof(UglyCryChore), - new Func(() => new object?[] { choreType, target, null }) - }, - new object[] { - new System.Action(() => new WaterCoolerChore(target, medicinalPillWorkable)), - typeof(WaterCoolerChore), - new Func(() => new object?[] { target, medicinalPillWorkable, null, null, null }) - }, - new object[] { - new System.Action(() => new WorkChore(choreType, medicinalPillWorkable)), - typeof(WorkChore), - new Func( - () => new object?[] { - choreType, medicinalPillWorkable, null, true, null, null, null, true, null, false, true, null, - false, true, true, PriorityScreen.PriorityClass.basic, 5, false, true - } - ) - } - }; - Assert.AreEqual(ChoreList.DeterministicChores.Count, testArgs.Length); - return testArgs; - } } diff --git a/src/MultiplayerMod.Test/Game/Chores/ChoreListTest.cs b/src/MultiplayerMod.Test/Game/Chores/ChoreListTest.cs index 1219c7db..894dd465 100644 --- a/src/MultiplayerMod.Test/Game/Chores/ChoreListTest.cs +++ b/src/MultiplayerMod.Test/Game/Chores/ChoreListTest.cs @@ -8,7 +8,7 @@ namespace MultiplayerMod.Test.Game.Chores; [TestFixture] public class ChoreListTest { [Test] - public void EnsureChoreListContainsAllChores() { + public void ContainsAllChores() { var foundChoreTypes = Assembly.GetAssembly(typeof(Chore)) .GetTypes() .Where( @@ -20,6 +20,11 @@ public void EnsureChoreListContainsAllChores() { .OrderBy(a => a.FullName) .ToHashSet(); - Assert.AreEqual(foundChoreTypes, ChoreList.AllChoreTypes); + Assert.AreEqual( + foundChoreTypes, + ChoreList.Config.Keys + .OrderBy(a => a.FullName) + .ToHashSet() + ); } } diff --git a/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/CreateHostChoreTest.cs b/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/CreateHostChoreTest.cs index af00fa7f..83661739 100644 --- a/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/CreateHostChoreTest.cs +++ b/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/CreateHostChoreTest.cs @@ -4,285 +4,37 @@ using MultiplayerMod.Multiplayer.Commands.Chores; using MultiplayerMod.Network; using MultiplayerMod.Platform.Steam.Network.Messaging; +using MultiplayerMod.Test.Game.Chores; using NUnit.Framework; -using UnityEngine; namespace MultiplayerMod.Test.Multiplayer.Commands.Chores; [TestFixture] -public class CreateHostChoreTest : AbstractGameTest { - - private static KMonoBehaviour target = null!; - private static GameObject gameObject = null!; - private static ChoreType choreType = null!; - private static Db db = null!; - private static KPrefabID kPrefabId = null!; - private static MedicinalPillWorkable medicineWorkable = null!; - private static Constructable constructable = null!; - // private static TestMonoBehaviour testMonoBehaviour = null!; - - [SetUp] - public new void SetUp() { - base.SetUp(); - - var targetGameObject = createGameObject(); - target = targetGameObject.GetComponent(); - - db = Db.Get(); - gameObject = createGameObject(); - kPrefabId = createGameObject().AddComponent(); - kPrefabId.gameObject.AddComponent(); // required for RancherChore - choreType = db.ChoreTypes.Astronaut; - medicineWorkable = createGameObject().AddComponent(); - medicineWorkable.Awake(); - constructable = createGameObject().AddComponent(); - // testMonoBehaviour = createGameObject().AddComponent(); - } +public class CreateHostChoreTest : AbstractChoreTest { [Test, TestCaseSource(nameof(GetTestArgs))] - public void ExecutionTest(Type _, Func getTestArgsFunc) { - var arg = getTestArgsFunc.Invoke(); + public void ExecutionTest(Type choreType, Func expectedArgsFunc) { + var arg = new CreateNewChoreArgs(choreType, expectedArgsFunc.Invoke()); + var command = new CreateHostChore(arg); command.Execute(null!); } [Test, TestCaseSource(nameof(GetTestArgs))] - public void SerializationTest(Type _, Func getTestArgsFunc) { - var arg = getTestArgsFunc.Invoke(); + public void SerializationTest(Type choreType, Func expectedArgsFunc) { + var arg = new CreateNewChoreArgs(choreType, expectedArgsFunc.Invoke()); var command = new CreateHostChore(arg); var messageFactory = new NetworkMessageFactory(); var messageProcessor = new NetworkMessageProcessor(); NetworkMessage? networkMessage = null; - var handles = messageFactory.Create(command, MultiplayerCommandOptions.SkipHost).ToArray(); - - foreach (var messageHandle in handles) { + foreach (var messageHandle in messageFactory.Create(command, MultiplayerCommandOptions.SkipHost).ToArray()) { networkMessage = messageProcessor.Process(1u, messageHandle); } + Assert.AreEqual(command.GetType(), networkMessage?.Command.GetType()); Assert.AreEqual(command.ChoreType, ((CreateHostChore) networkMessage!.Command).ChoreType); Assert.AreEqual(command.Args, ((CreateHostChore) networkMessage!.Command).Args); } - -#pragma warning disable CS8974 // Converting method group to non-delegate type - private static object[] GetTestArgs() { - var result = new object[] { - new object[] { - typeof(AttackChore), - new Func( - () => new CreateNewChoreArgs(typeof(AttackChore), new object[] { target, gameObject }) - ) - }, - new object[] { - typeof(DeliverFoodChore), - new Func( - () => new CreateNewChoreArgs(typeof(DeliverFoodChore), new object[] { target }) - ) - }, - new object[] { - typeof(DieChore), - new Func( - () => new CreateNewChoreArgs(typeof(DieChore), new object[] { target, db.Deaths.Frozen }) - ) - }, - new object[] { - typeof(DropUnusedInventoryChore), - new Func( - () => new CreateNewChoreArgs(typeof(DropUnusedInventoryChore), new object[] { choreType, target }) - ) - }, - // new object[] { - // typeof(EmoteChore), - // new Func( - // () => new CreateNewChoreArgs( - // typeof(EmoteChore), - // new object[] - // { target, choreType, db.Emotes.Minion.Cheer, 1, testMonoBehaviour.TestStressEmoteFunc } - // ) - // ) - // }, - new object[] { - typeof(EquipChore), - new Func(() => new CreateNewChoreArgs(typeof(EquipChore), new object[] { target })) - }, - new object[] { - typeof(FixedCaptureChore), - new Func( - () => new CreateNewChoreArgs(typeof(FixedCaptureChore), new object[] { kPrefabId }) - ) - }, - // new object[] { - // typeof(MoveChore), - // new Func( - // () => new CreateNewChoreArgs( - // typeof(MoveChore), - // new object[] { target, choreType, testMonoBehaviour.TestMoveFunc, false } - // ) - // ) - // }, - new object[] { - typeof(MoveToQuarantineChore), - new Func( - () => new CreateNewChoreArgs(typeof(MoveToQuarantineChore), new object[] { target, target }) - ) - }, - new object[] { - typeof(PartyChore), - new Func( - () => new CreateNewChoreArgs( - typeof(PartyChore), - new object[] { - target, medicineWorkable, constructable.UpdateBuildState, constructable.UpdateBuildState, - constructable.UpdateBuildState - } - ) - ) - }, - new object[] { - typeof(PeeChore), - new Func(() => new CreateNewChoreArgs(typeof(PeeChore), new object[] { target })) - }, - new object[] { - typeof(PutOnHatChore), - new Func( - () => new CreateNewChoreArgs(typeof(PutOnHatChore), new object[] { target, choreType }) - ) - }, - new object[] { - typeof(RancherChore), - new Func( - () => new CreateNewChoreArgs(typeof(RancherChore), new object[] { kPrefabId }) - ) - }, - // new Func( - // () => new CreateNewChoreArgs( - // typeof(ReactEmoteChore), - // new object[] { - // target, choreType, - // new EmoteReactable( - // gameObject, - // (HashedString) "WorkPasserbyAcknowledgement", - // Db.Get().ChoreTypes.Emote, - // 5, - // 5, - // localCooldown: 600f - // ), - // new HashedString(1), new HashedString[] { new(2) }, - // KAnim.PlayMode.Loop, null - // } - // ) - // ), - new object[] { - typeof(RescueIncapacitatedChore), - new Func( - () => new CreateNewChoreArgs(typeof(RescueIncapacitatedChore), new object[] { target, gameObject }) - ) - }, - new object[] { - typeof(RescueSweepBotChore), - new Func( - () => new CreateNewChoreArgs( - typeof(RescueSweepBotChore), - new object[] { target, gameObject, gameObject } - ) - ) - }, - new object[] { - typeof(SighChore), - new Func(() => new CreateNewChoreArgs(typeof(SighChore), new object[] { target })) - }, - // new object[] { - // typeof(StressEmoteChore), - // new Func( - // () => new CreateNewChoreArgs( - // typeof(StressEmoteChore), - // new object[] { - // target, choreType, new HashedString(1), new HashedString[] { new(2) }, - // KAnim.PlayMode.Paused, testMonoBehaviour.TestStressEmoteFunc - // } - // ) - // ) - // }, - new object[] { - typeof(StressIdleChore), - new Func( - () => new CreateNewChoreArgs(typeof(StressIdleChore), new object[] { target }) - ) - }, - new object[] { - typeof(SwitchRoleHatChore), - new Func( - () => new CreateNewChoreArgs(typeof(SwitchRoleHatChore), new object[] { target, choreType }) - ) - }, - new object[] { - typeof(TakeMedicineChore), - new Func( - () => new CreateNewChoreArgs(typeof(TakeMedicineChore), new object[] { medicineWorkable }) - ) - }, - new object[] { - typeof(TakeOffHatChore), - new Func( - () => new CreateNewChoreArgs(typeof(TakeOffHatChore), new object[] { target, choreType }) - ) - }, - - new object[] { - typeof(UglyCryChore), - new Func( - () => new CreateNewChoreArgs( - typeof(UglyCryChore), - new object[] { choreType, target, constructable.UpdateBuildState } - ) - ) - }, - new object[] { - typeof(WaterCoolerChore), - new Func( - () => new CreateNewChoreArgs( - typeof(WaterCoolerChore), - new object[] { - target, medicineWorkable, constructable.UpdateBuildState, constructable.UpdateBuildState, - constructable.UpdateBuildState - } - ) - ) - }, - new object[] { - typeof(WorkChore), - new Func( - () => new CreateNewChoreArgs( - typeof(WorkChore), - new object[] { - choreType, medicineWorkable, createGameObject().AddComponent(), false, - constructable.UpdateBuildState, constructable.UpdateBuildState, - constructable.UpdateBuildState, - false, - db.ScheduleBlockTypes.Work, true, false, - Assets.GetAnim("anim_interacts_medicine_nuclear_kanim"), true, false, false, - PriorityScreen.PriorityClass.high, 6, true, false - } - ) - ) - } - }; - Assert.AreEqual(result.Length, ChoreList.DeterministicChores.Count); - - return result; - } -#pragma warning restore CS8974 // Converting method group to non-delegate type - - // private class TestMonoBehaviour : KMonoBehaviour { - // - // public int TestMoveFunc(object obj) { - // return 0; - // } - // - // public StatusItem TestStressEmoteFunc() { - // return new StatusItem("", ""); - // } - // } - } diff --git a/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/Patches/DbPatch.cs b/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/Patches/DbPatch.cs index c4026657..25957fc4 100644 --- a/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/Patches/DbPatch.cs +++ b/src/MultiplayerMod.Test/Multiplayer/Commands/Chores/Patches/DbPatch.cs @@ -36,6 +36,7 @@ private static bool DbInitialize(Db __instance) { __instance.BuildingStatusItems = new BuildingStatusItems(root); __instance.effects = new ResourceSet(); __instance.AttributeConverters = new AttributeConverters(); + __instance.StatusItemCategories = new StatusItemCategories(root); return false; } } diff --git a/src/MultiplayerMod.Test/Multiplayer/Patches/Chores/DisableClientChoreCreationPatchTest.cs b/src/MultiplayerMod.Test/Multiplayer/Patches/Chores/DisableClientChoreCreationPatchTest.cs new file mode 100644 index 00000000..14d3883a --- /dev/null +++ b/src/MultiplayerMod.Test/Multiplayer/Patches/Chores/DisableClientChoreCreationPatchTest.cs @@ -0,0 +1,34 @@ +using System; +using MultiplayerMod.Core.Dependency; +using MultiplayerMod.Core.Scheduling; +using MultiplayerMod.ModRuntime; +using MultiplayerMod.Multiplayer; +using MultiplayerMod.Multiplayer.Patches.Chores; +using MultiplayerMod.Test.Game.Chores; +using NUnit.Framework; + +namespace MultiplayerMod.Test.Multiplayer.Patches.Chores; + +[TestFixture] +public class DisableClientChoreCreationPatchTest : AbstractChoreTest { + [SetUp] + public new void SetUp() { + Patches.Add(typeof(DisableClientChoreCreationPatch)); + + base.SetUp(); + + Runtime.Instance.Dependencies.Get().Refresh(MultiplayerMode.Client); + var di = (DependencyContainer) Runtime.Instance.Dependencies; + di.Register(new DependencyInfo(nameof(UnityTaskScheduler), typeof(UnityTaskScheduler), false)); + } + + [Test, TestCaseSource(nameof(GetTestArgs))] + public void ClientChoresMustBeCancelled(Type choreType, Func expectedArgsFunc) { + var chore = CreateChore(choreType, expectedArgsFunc.Invoke()); + var provider = chore.provider; + Runtime.Instance.Dependencies.Get().Tick(); + + Assert.Null(chore.provider); + Assert.AreEqual(0, provider.choreWorldMap[chore.gameObject.GetMyParentWorldId()].Count); + } +} diff --git a/src/MultiplayerMod/Game/Chores/ChoreEvents.cs b/src/MultiplayerMod/Game/Chores/ChoreEvents.cs index ff899ec4..5456e15a 100644 --- a/src/MultiplayerMod/Game/Chores/ChoreEvents.cs +++ b/src/MultiplayerMod/Game/Chores/ChoreEvents.cs @@ -17,7 +17,9 @@ public static class ChoreEvents { [UsedImplicitly] private static IEnumerable TargetMethods() { - return ChoreList.SupportedChores + return ChoreList.Config + .Where(config => config.Value.CreationSync == ChoreList.CreationStatusEnum.On) + .Select(config => config.Key) .Select( type => { if (!type.IsGenericType) return type.GetConstructors()[0]; @@ -34,12 +36,6 @@ private static IEnumerable TargetMethods() { [RequireExecutionLevel(ExecutionLevel.Game)] [RequireMultiplayerMode(MultiplayerMode.Host)] private static void Chore_Constructor(Chore __instance, object[] __args) { - var type = __instance.GetType(); - if (!ChoreList.SupportedChores.Contains(type) && - !(type.IsGenericType && ChoreList.SupportedChores.Contains(type.GetGenericTypeDefinition()))) { - return; - } - CreateNewChore?.Invoke(new CreateNewChoreArgs(__instance.GetType(), __args)); } } diff --git a/src/MultiplayerMod/Game/Chores/ChoreList.cs b/src/MultiplayerMod/Game/Chores/ChoreList.cs index 5b7731b2..53bdafd9 100644 --- a/src/MultiplayerMod/Game/Chores/ChoreList.cs +++ b/src/MultiplayerMod/Game/Chores/ChoreList.cs @@ -1,91 +1,278 @@ using System; using System.Collections.Generic; -using System.Linq; namespace MultiplayerMod.Game.Chores; public static class ChoreList { - /** - * Those chores are fully determined by the input. - */ - public static readonly HashSet DeterministicChores = new( - new[] { - typeof(AttackChore), - typeof(DeliverFoodChore), - typeof(DieChore), - typeof(DropUnusedInventoryChore), - typeof(EquipChore), - typeof(FixedCaptureChore), - typeof(MoveToQuarantineChore), - typeof(PartyChore), - typeof(PeeChore), - typeof(PutOnHatChore), - typeof(RancherChore), - typeof(RescueIncapacitatedChore), - typeof(RescueSweepBotChore), - typeof(SighChore), - typeof(StressIdleChore), - typeof(SwitchRoleHatChore), - typeof(TakeMedicineChore), - typeof(TakeOffHatChore), - typeof(UglyCryChore), - typeof(WaterCoolerChore), - typeof(WorkChore<>), - } - ); + public static readonly Dictionary Config = + new() { + { + typeof(AttackChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(DeliverFoodChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(DieChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(DropUnusedInventoryChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(EntombedChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(MoveChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(MoveToQuarantineChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(PartyChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(PeeChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(PutOnHatChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(SighChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(StressIdleChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(SwitchRoleHatChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(TakeOffHatChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(UglyCryChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(WaterCoolerChore), + new ChoreSyncConfig( + // fully determined by the input. + CreationStatusEnum.On + ) + }, { + typeof(AggressiveChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(BingeEatChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(FleeChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(BeIncapacitatedChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(EmoteChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(StressEmoteChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(ReactEmoteChore), + new ChoreSyncConfig( + // an argument is not serializable. + CreationStatusEnum.Off + ) + }, { + typeof(FoodFightChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(MournChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(MoveToSafetyChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(RecoverBreathChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(EatChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(MovePickupableChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(SleepChore), + new ChoreSyncConfig( + CreationStatusEnum.Off + ) + }, { + typeof(EquipChore), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(FixedCaptureChore), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(RancherChore), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(RescueIncapacitatedChore), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(RescueSweepBotChore), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(TakeMedicineChore), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(WorkChore<>), + new ChoreSyncConfig( + // using global providers. Hence they depends on consumer who will pick them up. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(FetchAreaChore), + new ChoreSyncConfig( + // an argument is not serializable. + // fully determined by the input. + CreationStatusEnum.Off + ) + }, { + typeof(BalloonArtistChore), + new ChoreSyncConfig( + // changes its parameters dynamically without using target parameters. + CreationStatusEnum.Off + ) + }, { + typeof(BansheeChore), + new ChoreSyncConfig( + // changes its parameters dynamically without using target parameters. + CreationStatusEnum.Off + ) + }, { + typeof(FetchChore), + new ChoreSyncConfig( + // changes its parameters dynamically without using target parameters. + CreationStatusEnum.Off + ) + }, { + typeof(IdleChore), + new ChoreSyncConfig( + // changes its parameters dynamically without using target parameters. + CreationStatusEnum.Off + ) + }, { + typeof(MingleChore), + new ChoreSyncConfig( + // changes its parameters dynamically without using target parameters. + CreationStatusEnum.Off + ) + }, { + typeof(VomitChore), + new ChoreSyncConfig( + // changes its parameters dynamically without using target parameters. + CreationStatusEnum.Off + ) + } + }; - /** - * Those chores have some issues and are disabled for now. - */ - public static readonly HashSet DeterministicChoresOff = new( - new[] { - // No usage has been found in the game dlls. - // one argument of type EmoteReactable is not serializable. - typeof(ReactEmoteChore) - } - ); + public record ChoreSyncConfig(CreationStatusEnum CreationSync); - public static readonly HashSet SupportedChores = new(DeterministicChores); - - /** - * Those chores changes its parameters dynamically via target parameters. - */ - public static readonly HashSet DynamicTargetedChores = new( - new[] { - typeof(AggressiveChore), - typeof(BeIncapacitatedChore), - typeof(BingeEatChore), - typeof(EatChore), - typeof(EmoteChore), - typeof(EntombedChore), - typeof(FetchAreaChore), - typeof(FleeChore), - typeof(FoodFightChore), - typeof(MoveChore), - typeof(MournChore), - typeof(MovePickupableChore), - typeof(MoveToSafetyChore), - typeof(RecoverBreathChore), - typeof(SleepChore), - typeof(StressEmoteChore), - } - ); - - /** - * Those chores changes its parameters dynamically without using target parameters. - */ - public static readonly HashSet DynamicNonTargetedChores = new( - new[] { - typeof(BalloonArtistChore), - typeof(BansheeChore), - typeof(FetchChore), - typeof(IdleChore), - typeof(MingleChore), - typeof(VomitChore), - } - ); - - public static readonly IEnumerable AllChoreTypes = DeterministicChores.Concat(DeterministicChoresOff) - .Concat(DynamicTargetedChores).Concat(DynamicNonTargetedChores).OrderBy(a => a.FullName).ToList(); + public enum CreationStatusEnum { + On, + Off + } } diff --git a/src/MultiplayerMod/Multiplayer/Patches/ChoreConsumerPatch.cs b/src/MultiplayerMod/Multiplayer/Patches/Chores/ChoreConsumerPatch.cs similarity index 97% rename from src/MultiplayerMod/Multiplayer/Patches/ChoreConsumerPatch.cs rename to src/MultiplayerMod/Multiplayer/Patches/Chores/ChoreConsumerPatch.cs index 4d0789f1..ab0918ee 100644 --- a/src/MultiplayerMod/Multiplayer/Patches/ChoreConsumerPatch.cs +++ b/src/MultiplayerMod/Multiplayer/Patches/Chores/ChoreConsumerPatch.cs @@ -6,7 +6,7 @@ using MultiplayerMod.Multiplayer.Objects; using MultiplayerMod.Multiplayer.World; -namespace MultiplayerMod.Multiplayer.Patches; +namespace MultiplayerMod.Multiplayer.Patches.Chores; // [HarmonyPatch(typeof(ChoreConsumer), nameof(ChoreConsumer.FindNextChore))] public class ChoreConsumerPatch { diff --git a/src/MultiplayerMod/Multiplayer/Patches/DisableClientChorePatch.cs b/src/MultiplayerMod/Multiplayer/Patches/Chores/DisableClientChoreCreationPatch.cs similarity index 52% rename from src/MultiplayerMod/Multiplayer/Patches/DisableClientChorePatch.cs rename to src/MultiplayerMod/Multiplayer/Patches/Chores/DisableClientChoreCreationPatch.cs index 863905ee..6cc15945 100644 --- a/src/MultiplayerMod/Multiplayer/Patches/DisableClientChorePatch.cs +++ b/src/MultiplayerMod/Multiplayer/Patches/Chores/DisableClientChoreCreationPatch.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Reflection; using HarmonyLib; using JetBrains.Annotations; @@ -8,13 +9,26 @@ using MultiplayerMod.ModRuntime.Context; using MultiplayerMod.Multiplayer.CoreOperations; -namespace MultiplayerMod.Multiplayer.Patches; +namespace MultiplayerMod.Multiplayer.Patches.Chores; [HarmonyPatch(typeof(Chore))] -public static class DisableClientChorePatch { +public static class DisableClientChoreCreationPatch { [UsedImplicitly] - private static IEnumerable TargetMethods() => new[] { typeof(Chore).GetConstructors()[0] }; + private static IEnumerable TargetMethods() { + return ChoreList.Config + .Where(config => config.Value.CreationSync == ChoreList.CreationStatusEnum.On) + .Select(config => config.Key) + .Select( + type => { + if (!type.IsGenericType) return type.GetConstructors()[0]; + if (type == typeof(WorkChore<>)) + return type.MakeGenericType(typeof(Workable)).GetConstructors()[0]; + + return type.GetConstructors()[0]; + } + ); + } [UsedImplicitly] [HarmonyPostfix] @@ -22,10 +36,6 @@ public static class DisableClientChorePatch { [RequireExecutionLevel(ExecutionLevel.Game)] private static void Chore_Constructor(Chore __instance, object[] __args) { var type = __instance.GetType(); - if (!ChoreList.SupportedChores.Contains(type) && - !(type.IsGenericType && ChoreList.SupportedChores.Contains(type.GetGenericTypeDefinition()))) { - return; - } Runtime.Instance.Dependencies.Get().Run( () => { __instance.Cancel($"Client chore {type} must be created by host only."); }