2 Commits 622be52f12 ... 0667a046e9

Autore SHA1 Messaggio Data
  habeebweeb 0667a046e9 Remove stock requirement from Meido 2 anni fa
  habeebweeb dce7a836e4 Support KC and preset body swapping 2 anni fa

+ 285 - 200
src/MeidoPhotoStudio.Plugin/Meido/Meido.cs

@@ -43,14 +43,13 @@ public class Meido
     private readonly FieldInfo m_eMaskMode = Utility.GetFieldInfo<TBody>("m_eMaskMode");
 #pragma warning restore SA1308
 
-    private bool initialized;
+    private bool faceNeedsInitialization = true;
     private float[] blendSetValueBackup;
     private bool freeLook;
 
-    public Meido(int stockMaidIndex)
+    public Meido(Maid maid)
     {
-        StockNo = stockMaidIndex;
-        Maid = GameMain.Instance.CharacterMgr.GetStockMaid(stockMaidIndex);
+        Maid = maid;
 
         IKManager = new(this);
         IKManager.SelectMaid += (_, args) =>
@@ -96,8 +95,6 @@ public class Meido
 
     public bool Loading { get; private set; }
 
-    public int StockNo { get; }
-
     public Maid Maid { get; }
 
     public MeidoDragPointManager IKManager { get; }
@@ -128,21 +125,25 @@ public class Meido
 
     public bool HairGravityActive
     {
-        get => HairGravityControl.Active;
+        get => HairGravityControl && HairGravityControl.Active;
         set
         {
-            if (HairGravityControl.Valid)
-                HairGravityControl.gameObject.SetActive(value);
+            if (!HairGravityControl || !HairGravityControl.Valid)
+                return;
+
+            HairGravityControl.gameObject.SetActive(value);
         }
     }
 
     public bool SkirtGravityActive
     {
-        get => SkirtGravityControl.Active;
+        get => SkirtGravityControl && SkirtGravityControl.Active;
         set
         {
-            if (SkirtGravityControl.Valid)
-                SkirtGravityControl.gameObject.SetActive(value);
+            if (!SkirtGravityControl || !SkirtGravityControl.Valid)
+                return;
+
+            SkirtGravityControl.gameObject.SetActive(value);
         }
     }
 
@@ -326,16 +327,30 @@ public class Meido
 
         if (!Body.isLoadedBody)
         {
+            AllProcPropSeqStartPatcher.SequenceEnded += LoadMaid;
+
             Maid.DutPropAll();
             Maid.AllProcPropSeqStart();
-        }
 
-        StartLoad(OnBodyLoad);
+            void LoadMaid(object sender, ProcStartEventArgs e)
+            {
+                if (e.Maid.status.guid != Maid.status.guid)
+                    return;
+
+                OnBodyLoad();
+
+                AllProcPropSeqStartPatcher.SequenceEnded -= LoadMaid;
+            }
+        }
+        else
+        {
+            OnBodyLoad();
+        }
     }
 
     public void Unload()
     {
-        if (Body.isLoadedBody && Maid.Visible)
+        if (Maid.Visible)
         {
             DetachAllMpnAttach();
 
@@ -363,7 +378,11 @@ public class Meido
             SetFaceBlendSet(DefaultFaceBlendSet);
         }
 
-        AllProcPropSeqStartPatcher.SequenceStart -= ReinitializeBody;
+        AllProcPropSeqStartPatcher.SequenceStarting -= OnMaidPropChanging;
+
+#if COM25
+        SwapNewMaidPropPatcher.NewMaidPropSwapping -= OnNewBodySwapping;
+#endif
 
         MuneYureLEnabled = true;
         MuneYureREnabled = true;
@@ -394,7 +413,6 @@ public class Meido
         Body.transform.localScale = Vector3.one;
         Maid.ResetAll();
         Maid.MabatakiUpdateStop = false;
-        Maid.ActiveSlotNo = -1;
     }
 
     public void SetPose(PoseInfo poseInfo)
@@ -580,8 +598,133 @@ public class Meido
         return faceData;
     }
 
-    public void SetFaceBlendSet(string blendSet)
+    public void SetFaceBlendSet(string blendSet) =>
+        SetFaceBlendSet(blendSet, true);
+
+    public void SetFaceBlendValue(string faceKey, float value)
+    {
+        var morph = Body.Face.morph;
+        var hash = Utility.GP01FbFaceHash(morph, faceKey);
+
+        if (!morph.Contains(hash))
+            return;
+
+        var blendIndex = (int)morph.hash[hash];
+
+        if (faceKey is "nosefook")
+            Maid.boNoseFook = morph.boNoseFook = value > 0f;
+        else
+            morph.dicBlendSet[CurrentFaceBlendSet][blendIndex] = value;
+
+        morph.SetBlendValues(blendIndex, value);
+        morph.FixBlendValues_Face();
+    }
+
+    public float GetFaceBlendValue(string hash)
     {
+        var morph = Body.Face.morph;
+
+        if (hash is "nosefook")
+            return (Maid.boNoseFook || morph.boNoseFook) ? 1f : 0f;
+
+        hash = Utility.GP01FbFaceHash(morph, hash);
+
+        return morph.dicBlendSet[CurrentFaceBlendSet][(int)morph.hash[hash]];
+    }
+
+    public void StopBlink()
+    {
+        Maid.MabatakiUpdateStop = true;
+        Body.Face.morph.EyeMabataki = 0f;
+        Utility.SetFieldValue(Maid, "MabatakiVal", 0f);
+    }
+
+    public void SetMaskMode(Mask maskMode) =>
+        SetMaskMode(maskMode is Mask.Nude ? MaskMode.Nude : (MaskMode)maskMode);
+
+    public void SetMaskMode(MaskMode maskMode)
+    {
+        var invisibleBody = !Body.GetMask(SlotID.body);
+
+        Body.SetMaskMode(maskMode);
+
+        if (invisibleBody)
+            SetBodyMask(false);
+    }
+
+    public void SetBodyMask(bool enabled)
+    {
+        var table = Utility.GetFieldValue<TBody, Hashtable>(Body, "m_hFoceHide");
+
+        foreach (var bodySlot in MaidDressingPane.BodySlots)
+            table[bodySlot] = enabled;
+
+        Body.FixMaskFlag();
+        Body.FixVisibleFlag(false);
+    }
+
+    public void SetCurling(Curl curling, bool enabled)
+    {
+        var name = curling is Curl.Shift
+            ? new[] { "panz", "mizugi" }
+            : new[] { "skirt", "onepiece" };
+
+        if (enabled)
+        {
+            var action = curling switch
+            {
+                Curl.Shift => "パンツずらし",
+                Curl.Front => "めくれスカート",
+                _ => "めくれスカート後ろ",
+            };
+
+            Maid.ItemChangeTemp(name[0], action);
+            Maid.ItemChangeTemp(name[1], action);
+        }
+        else
+        {
+            Maid.ResetProp(name[0]);
+            Maid.ResetProp(name[1]);
+        }
+
+        Maid.AllProcProp();
+        HairGravityControl.Control.OnChangeMekure();
+        SkirtGravityControl.Control.OnChangeMekure();
+    }
+
+    public void SetMpnProp(MpnAttachProp prop, bool detach)
+    {
+        if (detach)
+            Maid.ResetProp(prop.Tag, false);
+        else
+            Maid.SetProp(prop.Tag, prop.MenuFile, 0, true);
+
+        Maid.AllProcProp();
+    }
+
+    public void DetachAllMpnAttach()
+    {
+        if (!Body.isLoadedBody)
+            return;
+
+        Maid.ResetProp(MPN.kousoku_lower, false);
+        Maid.ResetProp(MPN.kousoku_upper, false);
+        Maid.AllProcProp();
+    }
+
+    public void ApplyGravity(Vector3 position, bool skirt = false)
+    {
+        var dragPoint = skirt ? SkirtGravityControl : HairGravityControl;
+
+        if (dragPoint && dragPoint.Valid)
+            dragPoint.Control.transform.localPosition = position;
+    }
+
+    private void SetFaceBlendSet(string blendSet, bool withNotify)
+    {
+        if (!Body.isLoadedBody)
+            return;
+
         if (blendSet.StartsWith(Constants.CustomFacePath))
         {
             var blendSetFileName = Path.GetFileNameWithoutExtension(blendSet);
@@ -690,184 +833,87 @@ public class Meido
         }
 
         StopBlink();
-        OnUpdateMeido();
-    }
 
-    public void SetFaceBlendValue(string faceKey, float value)
-    {
-        var morph = Body.Face.morph;
-        var hash = Utility.GP01FbFaceHash(morph, faceKey);
-
-        if (!morph.Contains(hash))
-            return;
-
-        var blendIndex = (int)morph.hash[hash];
-
-        if (faceKey is "nosefook")
-            Maid.boNoseFook = morph.boNoseFook = value > 0f;
-        else
-            morph.dicBlendSet[CurrentFaceBlendSet][blendIndex] = value;
-
-        morph.SetBlendValues(blendIndex, value);
-        morph.FixBlendValues();
-    }
-
-    public float GetFaceBlendValue(string hash)
-    {
-        var morph = Body.Face.morph;
-
-        if (hash is "nosefook")
-            return (Maid.boNoseFook || morph.boNoseFook) ? 1f : 0f;
-
-        hash = Utility.GP01FbFaceHash(morph, hash);
-
-        return morph.dicBlendSet[CurrentFaceBlendSet][(int)morph.hash[hash]];
-    }
-
-    public void StopBlink()
-    {
-        Maid.MabatakiUpdateStop = true;
-        Body.Face.morph.EyeMabataki = 0f;
-        Utility.SetFieldValue(Maid, "MabatakiVal", 0f);
+        if (withNotify)
+            OnUpdateMeido();
     }
 
-    public void SetMaskMode(Mask maskMode) =>
-        SetMaskMode(maskMode is Mask.Nude ? MaskMode.Nude : (MaskMode)maskMode);
-
-    public void SetMaskMode(MaskMode maskMode)
+    private void OnBodyLoad()
     {
-        var invisibleBody = !Body.GetMask(SlotID.body);
-
-        Body.SetMaskMode(maskMode);
+        InitializeFace();
 
-        if (invisibleBody)
-            SetBodyMask(false);
-    }
+        InitializeGravityControls();
 
-    public void SetBodyMask(bool enabled)
-    {
-        var table = Utility.GetFieldValue<TBody, Hashtable>(Body, "m_hFoceHide");
+        InitializeBody();
 
-        foreach (var bodySlot in MaidDressingPane.BodySlots)
-            table[bodySlot] = enabled;
-
-        Body.FixMaskFlag();
-        Body.FixVisibleFlag(false);
-    }
-
-    public void SetCurling(Curl curling, bool enabled)
-    {
-        var name = curling is Curl.Shift
-            ? new[] { "panz", "mizugi" }
-            : new[] { "skirt", "onepiece" };
-
-        if (enabled)
+        if (MeidoPhotoStudio.EditMode)
         {
-            var action = curling switch
-            {
-                Curl.Shift => "パンツずらし",
-                Curl.Front => "めくれスカート",
-                _ => "めくれスカート後ろ",
-            };
+            AllProcPropSeqStartPatcher.SequenceStarting += OnMaidPropChanging;
 
-            Maid.ItemChangeTemp(name[0], action);
-            Maid.ItemChangeTemp(name[1], action);
-        }
-        else
-        {
-            Maid.ResetProp(name[0]);
-            Maid.ResetProp(name[1]);
+#if COM25
+            SwapNewMaidPropPatcher.NewMaidPropSwapping += OnNewBodySwapping;
+#endif
         }
 
-        Maid.AllProcProp();
-        HairGravityControl.Control.OnChangeMekure();
-        SkirtGravityControl.Control.OnChangeMekure();
-    }
-
-    public void SetMpnProp(MpnAttachProp prop, bool detach)
-    {
-        if (detach)
-            Maid.ResetProp(prop.Tag, false);
-        else
-            Maid.SetProp(prop.Tag, prop.MenuFile, 0, true);
-
-        Maid.AllProcProp();
-    }
+        IK = true;
+        Stop = false;
+        Bone = false;
 
-    public void DetachAllMpnAttach()
-    {
-        Maid.ResetProp(MPN.kousoku_lower, false);
-        Maid.ResetProp(MPN.kousoku_upper, false);
-        Maid.AllProcProp();
+        Active = true;
     }
 
-    public void ApplyGravity(Vector3 position, bool skirt = false)
+#if COM25
+    private void OnNewBodySwapping(object sender, ProcStartEventArgs args)
     {
-        var dragPoint = skirt ? SkirtGravityControl : HairGravityControl;
-
-        if (dragPoint.Valid)
-            dragPoint.Control.transform.localPosition = position;
-    }
+        if (Loading || !Body.isLoadedBody)
+            return;
 
-    private void StartLoad(Action callback)
-    {
-        if (Loading)
+        if (args.Maid.status.guid != Maid.status.guid)
             return;
 
-        GameMain.Instance.StartCoroutine(Load(callback));
-    }
+        IKManager.Destroy();
 
-    private IEnumerator Load(Action callback)
-    {
-        Loading = true;
+        SetFaceBlendSet(DefaultFaceBlendSet);
 
-        while (Maid.IsBusy)
-            yield return null;
+        DestroyGravityControl(HairGravityControl);
+        DestroyGravityControl(SkirtGravityControl);
 
-        yield return new WaitForEndOfFrame();
+        // Prevent reinitializing again
+        AllProcPropSeqStartPatcher.SequenceStarting -= OnMaidPropChanging;
 
-        callback();
-        Loading = false;
-    }
+        AllProcPropSeqStartPatcher.SequenceEnded += OnSequenceEnded;
 
-    private void OnBodyLoad()
-    {
-        if (!initialized)
+        void OnSequenceEnded(object sender, ProcStartEventArgs e)
         {
-            DefaultEyeRotL = Body.quaDefEyeL;
-            DefaultEyeRotR = Body.quaDefEyeR;
-
-            initialized = true;
-        }
+            if (e.Maid.status.guid != Maid.status.guid)
+                return;
 
-        if (blendSetValueBackup is null)
-            BackupBlendSetValues();
+            InitializeFace(true);
 
-        if (!HairGravityControl)
             InitializeGravityControls();
 
-        HairGravityControl.Move += OnGravityEvent;
-        SkirtGravityControl.Move += OnGravityEvent;
+            InitializeBody();
 
-        if (MeidoPhotoStudio.EditMode)
-            AllProcPropSeqStartPatcher.SequenceStart += ReinitializeBody;
+            if (!Maid.IsCrcBody)
+            {
+                // Maid animation needs to be set again for custom parts edit
+                var uiRoot = GameObject.Find("UI Root");
+                var customPartsWindow = UTY.GetChildObject(uiRoot, "Window/CustomPartsWindow")
+                    .GetComponent<SceneEditWindow.CustomPartsWindow>();
 
-#if COM25
-        // NOTE: This is required for IK to work in COM3D2.5
-        Body.motionBlendTime = 0f;
-#endif
-        IKManager.Initialize();
+                Utility.SetFieldValue(customPartsWindow, "animation", Maid.GetAnimation());
+            }
 
-        SetFaceBlendSet(DefaultFaceBlendSet);
+            OnUpdateMeido();
 
-        IK = true;
-        Stop = false;
-        Bone = false;
+            AllProcPropSeqStartPatcher.SequenceEnded -= OnSequenceEnded;
 
-        Active = true;
+            AllProcPropSeqStartPatcher.SequenceStarting += OnMaidPropChanging;
+        }
     }
+#endif
 
-    private void ReinitializeBody(object sender, ProcStartEventArgs args)
+    private void OnMaidPropChanging(object sender, ProcStartEventArgs args)
     {
         if (Loading || !Body.isLoadedBody)
             return;
@@ -878,76 +924,72 @@ public class Meido
         var gravityControlProps =
             new[]
             {
-                MPN.skirt, MPN.onepiece, MPN.mizugi, MPN.panz, MPN.set_maidwear, MPN.set_mywear, MPN.set_underwear,
-                MPN.hairf, MPN.hairr, MPN.hairs, MPN.hairt,
+                // NOTE: body is checked because the gravity control is destroyed along with the body.
+                MPN.body, MPN.skirt, MPN.onepiece, MPN.mizugi, MPN.panz, MPN.set_maidwear, MPN.set_mywear,
+                MPN.set_underwear, MPN.hairf, MPN.hairr, MPN.hairs, MPN.hairt,
             };
 
-        Action action = null;
+        Action reinitializationCallback = null;
 
         // Change body
         if (Maid.GetProp(MPN.body).boDut)
         {
             IKManager.Destroy();
-            action += ReinitializeBody;
+
+            reinitializationCallback += () =>
+            {
+                // Maid animation needs to be set again for custom parts edit
+                var uiRoot = GameObject.Find("UI Root");
+                var customPartsWindow = UTY.GetChildObject(uiRoot, "Window/CustomPartsWindow")
+                    .GetComponent<SceneEditWindow.CustomPartsWindow>();
+
+                Utility.SetFieldValue(customPartsWindow, "animation", Maid.GetAnimation());
+
+                InitializeBody();
+            };
         }
 
         // Change face
         if (Maid.GetProp(MPN.head).boDut)
         {
             SetFaceBlendSet(DefaultFaceBlendSet);
-            action += ReinitializeFace;
+
+            reinitializationCallback += () =>
+                InitializeFace(true);
         }
 
         // Gravity control clothing/hair change
         if (gravityControlProps.Any(prop => Maid.GetProp(prop).boDut))
         {
-            if (HairGravityControl)
-                Object.Destroy(HairGravityControl.gameObject);
-
-            if (SkirtGravityControl)
-                Object.Destroy(SkirtGravityControl.gameObject);
+            DestroyGravityControl(HairGravityControl);
+            DestroyGravityControl(SkirtGravityControl);
 
-            action += ReinitializeGravity;
+            reinitializationCallback += InitializeGravityControls;
         }
 
         // Clothing/accessory changes
         // Includes null_mpn too but any button click results in null_mpn bodut I think
-        action ??= Default;
+        reinitializationCallback += () =>
+            OnUpdateMeido();
 
-        StartLoad(action);
+        AllProcPropSeqStartPatcher.SequenceEnded += OnSequenceEnded;
 
-        void ReinitializeBody()
+        void OnSequenceEnded(object sender, ProcStartEventArgs e)
         {
-            IKManager.Initialize();
-            Stop = false;
-
-            // Maid animation needs to be set again for custom parts edit
-            var uiRoot = GameObject.Find("UI Root");
-            var customPartsWindow = UTY.GetChildObject(uiRoot, "Window/CustomPartsWindow")
-                .GetComponent<SceneEditWindow.CustomPartsWindow>();
-
-            Utility.SetFieldValue(customPartsWindow, "animation", Maid.GetAnimation());
-        }
+            if (e.Maid.status.guid != Maid.status.guid)
+                return;
 
-        void ReinitializeFace()
-        {
-            DefaultEyeRotL = Body.quaDefEyeL;
-            DefaultEyeRotR = Body.quaDefEyeR;
-            BackupBlendSetValues();
-        }
+            AllProcPropSeqStartPatcher.SequenceEnded -= OnSequenceEnded;
 
-        void ReinitializeGravity()
-        {
-            InitializeGravityControls();
-            OnUpdateMeido();
+            reinitializationCallback?.Invoke();
         }
-
-        void Default() =>
-            OnUpdateMeido();
     }
 
     private void BackupBlendSetValues()
     {
+        if (!Body.isLoadedBody)
+            return;
+
         var values = Body.Face.morph.dicBlendSet[CurrentFaceBlendSet];
 
         blendSetValueBackup = new float[values.Length];
@@ -982,10 +1024,44 @@ public class Meido
         return cache;
     }
 
+    private void InitializeBody()
+    {
+        if (!Body.isLoadedBody)
+            return;
+
+#if COM25
+        // NOTE: This is required for IK to work in COM3D2.5
+        Body.motionBlendTime = 0f;
+#endif
+
+        IKManager.Initialize();
+    }
+
+    private void InitializeFace(bool force = false)
+    {
+        if (force || faceNeedsInitialization)
+        {
+            DefaultEyeRotL = Body.quaDefEyeL;
+            DefaultEyeRotR = Body.quaDefEyeR;
+
+            faceNeedsInitialization = false;
+        }
+
+        BackupBlendSetValues();
+
+        SetFaceBlendSet(DefaultFaceBlendSet, false);
+    }
+
     private void InitializeGravityControls()
     {
-        HairGravityControl = MakeGravityControl(skirt: false);
-        SkirtGravityControl = MakeGravityControl(skirt: true);
+        if (!HairGravityControl)
+            HairGravityControl = MakeGravityControl(skirt: false);
+
+        if (!SkirtGravityControl)
+            SkirtGravityControl = MakeGravityControl(skirt: true);
+
+        HairGravityControl.Move += OnGravityEvent;
+        SkirtGravityControl.Move += OnGravityEvent;
     }
 
     private DragPointGravity MakeGravityControl(bool skirt = false)
@@ -1001,6 +1077,15 @@ public class Meido
         return gravityDragpoint;
     }
 
+    private void DestroyGravityControl(DragPointGravity control)
+    {
+        if (!control)
+            return;
+
+        control.Move -= OnGravityEvent;
+        Object.Destroy(control.gameObject);
+    }
+
     private void OnUpdateMeido(MeidoUpdateEventArgs args = null) =>
         UpdateMeido?.Invoke(this, args ?? MeidoUpdateEventArgs.Empty);
 

+ 25 - 5
src/MeidoPhotoStudio.Plugin/Patchers/AllProcPropSeqStartPatcher.cs

@@ -5,14 +5,34 @@ using HarmonyLib;
 
 namespace MeidoPhotoStudio.Plugin;
 
-// TODO: Extend this further to potentially reduce the need for coroutines that wait for maid proc state
 public static class AllProcPropSeqStartPatcher
 {
-    public static event EventHandler<ProcStartEventArgs> SequenceStart;
+    public static event EventHandler<ProcStartEventArgs> SequenceStarting;
 
-    [HarmonyPatch(typeof(Maid), nameof(Maid.AllProcPropSeqStart))]
+    public static event EventHandler<ProcStartEventArgs> SequenceEnded;
+
+    [HarmonyPatch(typeof(Maid), "AllProcPropSeq")]
+    [HarmonyPrefix]
+    [SuppressMessage("StyleCop.Analyzers.NamingRules", "SA1313", Justification = "Harmony parameter")]
+    private static void NotifyAllProcPropStarting(Maid __instance)
+    {
+        // TODO: Consider sending a patch to EditBodyLoadFix rather than relying on this brittle hack.
+        // The check for boModelChg is needed because AllProcProp2Cnt gets reset to 0 before the next phase which causes
+        // a second false start
+        if (__instance.AllProcProp2Fase == 0 && __instance.AllProcProp2Cnt == 0 && !__instance.boModelChg)
+        {
+            SequenceStarting?.Invoke(null, new(__instance));
+            if (__instance.GetProp(MPN.head).boDut)
+                Utility.LogDebug("Face is being initialized");
+        }
+    }
+
+    [HarmonyPatch(typeof(Maid), "AllProcPropSeq")]
     [HarmonyPostfix]
     [SuppressMessage("StyleCop.Analyzers.NamingRules", "SA1313", Justification = "Harmony parameter")]
-    private static void NotifyProcStart(Maid __instance) =>
-        SequenceStart?.Invoke(null, new(__instance));
+    private static void NotifyAllProcPropEnded(Maid __instance)
+    {
+        if (__instance.AllProcProp2Fase == 5 && !__instance.IsAllProcPropBusy)
+            SequenceEnded?.Invoke(null, new(__instance));
+    }
 }

+ 24 - 0
src/MeidoPhotoStudio.Plugin/Patchers/SwapNewMaidPropPatcher.cs

@@ -0,0 +1,24 @@
+#if COM25
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+using HarmonyLib;
+
+namespace MeidoPhotoStudio.Plugin;
+
+public static class SwapNewMaidPropPatcher
+{
+    public static event EventHandler<ProcStartEventArgs> NewMaidPropSwapping;
+
+    [HarmonyPatch(typeof(Maid), nameof(Maid.SwapNewMaidProp))]
+    [HarmonyPrefix]
+    [SuppressMessage("StyleCop.Analyzers.NamingRules", "SA1313", Justification = "Harmony parameter")]
+    private static void NotifyNewMaidPropSwapping(Maid __instance, bool toNewBody)
+    {
+        if (__instance.IsCrcBody == toNewBody)
+            return;
+
+        NewMaidPropSwapping?.Invoke(null, new(__instance));
+    }
+}
+#endif