diff --git a/AI_ReverseTrap/AI_ReverseTrap.csproj b/AI_ReverseTrap/AI_ReverseTrap.csproj new file mode 100644 index 0000000..9468c2f --- /dev/null +++ b/AI_ReverseTrap/AI_ReverseTrap.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {24E7EF57-8992-404A-B9A6-FFB9ABFEB4AB} + Library + Properties + AI_ReverseTrap + AI_ReverseTrap + v4.6 + 512 + true + + + true + embedded + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + + + embedded + true + ..\bin\ + TRACE + prompt + 4 + true + + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\BepInEx\0Harmony.dll + False + + + ..\..\..\KKEC\KKAPI\bin\AIAPI.dll + False + + + ..\..\BepisPlugins\bin\BepInEx\plugins\AI_BepisPlugins\AI_ExtensibleSaveFormat.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\AISyoujyo\Assembly-CSharp.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\BepInEx\BepInEx.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\BepInEx\BepInEx.Harmony.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\AISyoujyo\Sirenix.Serialization.dll + False + + + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\AISyoujyo\UnityEngine.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\AISyoujyo\UnityEngine.AnimationModule.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\AISyoujyo\UnityEngine.AssetBundleModule.dll + False + + + ..\..\..\KKEC\KKAPI\paket-files\IllusionMods\IllusionLibs\lib\AISyoujyo\UnityEngine.CoreModule.dll + False + + + + + + + + + + \ No newline at end of file diff --git a/AI_ReverseTrap/Properties/AssemblyInfo.cs b/AI_ReverseTrap/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b285c09 --- /dev/null +++ b/AI_ReverseTrap/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using AI_ReverseTrap; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AI_ReverseTrap")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AI_ReverseTrap")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("24e7ef57-8992-404a-b9a6-ffb9abfeb4ab")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion(ReverseTrap.Version)] diff --git a/AI_ReverseTrap/ReverseTrap.Hooks.cs b/AI_ReverseTrap/ReverseTrap.Hooks.cs new file mode 100644 index 0000000..30e4352 --- /dev/null +++ b/AI_ReverseTrap/ReverseTrap.Hooks.cs @@ -0,0 +1,34 @@ +using AIChara; +using AIProject; +using HarmonyLib; +using UnityEngine; + +namespace AI_ReverseTrap +{ + public partial class ReverseTrap + { + private static class Hooks + { + [HarmonyPostfix] + // void ActorAnimation.SetAnimatorController(RuntimeAnimatorController rac) + [HarmonyPatch(typeof(ActorAnimation), nameof(ActorAnimation.SetAnimatorController), typeof(RuntimeAnimatorController))] + private static void SetAnimatorPost(ActorAnimation __instance) + { + if (__instance.Actor != null && __instance.Actor.ChaControl != null) + { + var ctrl = __instance.Actor.ChaControl.GetComponent(); + ctrl?.RefreshOverrideAnimations(); + } + } + + [HarmonyPostfix] + // RuntimeAnimatorController LoadAnimation(string assetBundleName, string assetName, string manifestName) + [HarmonyPatch(typeof(ChaControl), nameof(ChaControl.LoadAnimation), typeof(string), typeof(string), typeof(string))] + private static void SetAnimatorPost2(ChaControl __instance) + { + var ctrl = __instance.GetComponent(); + ctrl?.RefreshOverrideAnimations(); + } + } + } +} diff --git a/AI_ReverseTrap/ReverseTrap.cs b/AI_ReverseTrap/ReverseTrap.cs new file mode 100644 index 0000000..5b69b84 --- /dev/null +++ b/AI_ReverseTrap/ReverseTrap.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BepInEx; +using BepInEx.Harmony; +using BepInEx.Logging; +using KKAPI; +using KKAPI.Chara; +using KKAPI.Maker; +using KKAPI.Maker.UI; +using UnityEngine; + +namespace AI_ReverseTrap +{ + [BepInPlugin(GUID, "Reverse Trap", Version)] + [BepInProcess("AI-Syoujyo")] + [BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)] + public partial class ReverseTrap : BaseUnityPlugin + { + public const string GUID = "ReverseTrap"; + public const string Version = "1.0"; + + internal static new ManualLogSource Logger { get; private set; } + + internal const int MaleSex = 0; + internal static AnimationClip[] MaleAnimations { get; private set; } + internal static readonly Dictionary ToMaleAnimationLookup = new Dictionary + { + {"Idle_00", "m_Idle_00"}, + {"mc_f_move_00", "mc_m_move_00"}, + {"mc_f_move_05", "mc_m_move_01"}, + {"mc_f_move_08_L", "mc_m_move_05_L"}, + {"mc_f_move_07_L", "mc_m_move_04_L"}, + {"Turn_Idle_00", "m_Turn_Idle_00"}, + {"mc_f_move_07_R", "mc_m_move_04_R"}, + {"mc_f_move_08_R", "mc_m_move_05_R"}, + {"mc_pf_move_02_S_in", "mc_m_move_02_S_in"}, + {"mc_pf_move_02_M_in", "mc_m_move_02_M_in"}, + {"mc_pf_move_02_L_in", "mc_m_move_02_L_in"}, + {"mc_pf_move_02_S_loop", "mc_m_move_02_S_loop"}, + {"mc_pf_move_02_M_loop", "mc_m_move_02_M_loop"}, + {"mc_pf_move_02_L_loop", "mc_m_move_02_L_loop"}, + {"mc_pf_move_03_S", "mc_m_move_03_S"}, + {"mc_pf_move_03_M", "mc_m_move_03_M"}, + {"mc_pf_move_03_L", "mc_m_move_03_L"}, + {"mc_f_action_00", "mc_m_action_00"}, + {"mc_pf_action_01", "mc_m_action_01"}, + {"mc_pf_action_02", "mc_m_action_02"}, + {"mc_pf_action_03", "mc_m_action_03"}, + {"mc_pf_action_04", "mc_m_action_04"}, + {"neko_01", "m_neko_00"}, + {"pf_neko_04_in", "m_neko_01_in"}, + {"pf_neko_04_loop", "m_neko_01_loop"}, + {"pf_neko_05", "m_neko_02"}, + {"pf_neko_06", "m_neko_03"}, + {"chair_Idle_00_M", "m_chair_Idle_00"}, + {"chair_16_S", "m_chair_00_S"}, + {"chair_16_M", "m_chair_00_M"}, + {"chair_16_L", "m_chair_00_L"}, + {"chair_15_S", "m_chair_01_S"}, + {"chair_15_M", "m_chair_01_M"}, + {"chair_15_L", "m_chair_01_L"}, + {"deskchair_Idle_00_M", "m_deskchair_Idle_00"}, + {"mc_pf_move_00_01", "mc_m_move_00_01"}, + {"mc_pf_move_05_01", "mc_m_move_01_01"}, + {"mc_pf_move_00_02", "mc_m_move_00_02"}, + {"mc_pf_move_05_02", "mc_m_move_01_02"}, + {"pf_Idle_00_03_S", "m_Idle_00_03_S"}, + {"pf_Idle_00_03_M", "m_Idle_00_03_M"}, + {"pf_Idle_00_03_L", "m_Idle_00_03_L"}, + {"mc_pf_move_00_03_S", "mc_m_move_00_03_S"}, + {"mc_pf_move_00_03_M", "mc_m_move_00_03_M"}, + {"mc_pf_move_00_03_L", "mc_m_move_00_03_L"} + }; + + private void Start() + { + Logger = base.Logger; + + try + { + // Load male animation clips for overriding + var ab = AssetBundle.LoadFromFile(Application.dataPath + @"\..\abdata\animator\action\male\00.unity3d"); + var anim = ab.LoadAsset("m_player.controller"); + MaleAnimations = anim.animationClips.ToArray(); + ab.Unload(false); + Destroy(anim); + } + catch (Exception ex) + { + Logger.LogError("Failed to read male player animation data - " + ex); + } + + if (MaleAnimations != null && MaleAnimations.Any()) + { + CharacterApi.RegisterExtraBehaviour(GUID); + + MakerAPI.RegisterCustomSubCategories += MakerAPI_RegisterCustomSubCategories; + + HarmonyWrapper.PatchAll(typeof(Hooks)); + } + } + + private void MakerAPI_RegisterCustomSubCategories(object sender, RegisterSubCategoriesEvent e) + { + if (MakerAPI.GetMakerSex() != MaleSex) + { + var makerToggle = e.AddControl(new MakerToggle(MakerConstants.Body.All, "Male walking animations", this)); + + makerToggle.BindToFunctionController( + controller => controller.ForceMaleAnimations, + (controller, value) => controller.ForceMaleAnimations = value); + } + } + } +} diff --git a/AI_ReverseTrap/ReverseTrapController.cs b/AI_ReverseTrap/ReverseTrapController.cs new file mode 100644 index 0000000..62070ea --- /dev/null +++ b/AI_ReverseTrap/ReverseTrapController.cs @@ -0,0 +1,91 @@ +using System.Linq; +using ExtensibleSaveFormat; +using KKAPI; +using KKAPI.Chara; +using UnityEngine; + +namespace AI_ReverseTrap +{ + public class ReverseTrapController : CharaCustomFunctionController + { + private bool _forceMaleAnimations; + + public bool ForceMaleAnimations + { + get => _forceMaleAnimations; + set + { + value = value && ChaControl.sex != ReverseTrap.MaleSex; + + if (_forceMaleAnimations != value) + { + _forceMaleAnimations = value; + + RefreshOverrideAnimations(); + } + } + } + + protected override void OnCardBeingSaved(GameMode currentGameMode) + { + if (ChaControl.sex != ReverseTrap.MaleSex) + { + var data = new PluginData { data = { [nameof(ForceMaleAnimations)] = ForceMaleAnimations } }; + SetExtendedData(data); + } + else + SetExtendedData(null); + } + + protected override void OnReload(GameMode currentGameMode) + { + if (ChaControl.sex != ReverseTrap.MaleSex) + { + var data = GetExtendedData()?.data; + ForceMaleAnimations = data != null && data.TryGetValue(nameof(ForceMaleAnimations), out var force) && force as bool? == true; + } + else + ForceMaleAnimations = false; + } + + internal void RefreshOverrideAnimations() + { + var animBody = ChaControl.animBody; + + if (ReverseTrap.MaleAnimations == null || animBody == null || animBody.runtimeAnimatorController == null) return; + + var overrideControler = animBody.runtimeAnimatorController as AnimatorOverrideController; + if (overrideControler == null) + { + if (!ForceMaleAnimations) return; + + overrideControler = new AnimatorOverrideController(animBody.runtimeAnimatorController); + animBody.runtimeAnimatorController = overrideControler; + } + else if (!ForceMaleAnimations) + { + animBody.runtimeAnimatorController = overrideControler.runtimeAnimatorController; + return; + } + + var animationClips = animBody.runtimeAnimatorController.animationClips.ToArray(); + + foreach (var animationClip in animationClips) + { + if (ReverseTrap.ToMaleAnimationLookup.TryGetValue(animationClip.name, out var targetClipName)) + { + var replacement = ReverseTrap.MaleAnimations.FirstOrDefault(clip => clip.name == targetClipName); + if (replacement != null) + { + ReverseTrap.Logger.LogDebug($"Replacing animation {animationClip.name} with {replacement.name}"); + overrideControler[animationClip] = replacement; + } + else + { + ReverseTrap.Logger.LogWarning($"Failed to replace animation {animationClip.name} with {targetClipName} because replacement clip was not found"); + } + } + } + } + } +} diff --git a/KK_BecomeTrap.sln b/KK_BecomeTrap.sln index 4bd5879..1ff5208 100644 --- a/KK_BecomeTrap.sln +++ b/KK_BecomeTrap.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.28010.2016 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KK_BecomeTrap", "KK_BecomeTrap\KK_BecomeTrap.csproj", "{F47E187C-9A8F-45E7-A778-5057E1D9B3F0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI_ReverseTrap", "AI_ReverseTrap\AI_ReverseTrap.csproj", "{24E7EF57-8992-404A-B9A6-FFB9ABFEB4AB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {F47E187C-9A8F-45E7-A778-5057E1D9B3F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {F47E187C-9A8F-45E7-A778-5057E1D9B3F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {F47E187C-9A8F-45E7-A778-5057E1D9B3F0}.Release|Any CPU.Build.0 = Release|Any CPU + {24E7EF57-8992-404A-B9A6-FFB9ABFEB4AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24E7EF57-8992-404A-B9A6-FFB9ABFEB4AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24E7EF57-8992-404A-B9A6-FFB9ABFEB4AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24E7EF57-8992-404A-B9A6-FFB9ABFEB4AB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/KK_BecomeTrap/KK_BecomeTrap.csproj b/KK_BecomeTrap/KK_BecomeTrap.csproj index caa4894..cd4425e 100644 --- a/KK_BecomeTrap/KK_BecomeTrap.csproj +++ b/KK_BecomeTrap/KK_BecomeTrap.csproj @@ -15,20 +15,21 @@ true - full + embedded false - ..\..\..\..\Games\Koikatsu\BepInEx\ + ..\bin\ DEBUG;TRACE prompt 4 - pdbonly + embedded true - ..\..\..\..\Games\Koikatsu\BepInEx\ + ..\bin\ TRACE prompt 4 + true