using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using UnityEngine; namespace COM3D2.MeidoPhotoStudio.Plugin { public class MeidoManager : IManager, ISerializable { public const string header = "MEIDO"; private static readonly CharacterMgr characterMgr = GameMain.Instance.CharacterMgr; private int undress; private int numberOfMeidos; private int tempEditMaidIndex = -1; public Meido[] Meidos { get; private set; } public HashSet SelectedMeidoSet { get; } = new HashSet(); public List SelectMeidoList { get; } = new List(); public List ActiveMeidoList { get; } = new List(); public Meido ActiveMeido => ActiveMeidoList.Count > 0 ? ActiveMeidoList[SelectedMeido] : null; public Meido EditMeido => tempEditMaidIndex >= 0 ? Meidos[tempEditMaidIndex] : Meidos[EditMaidIndex]; public bool HasActiveMeido => ActiveMeido != null; public event EventHandler UpdateMeido; public event EventHandler EndCallMeidos; public event EventHandler BeginCallMeidos; private int selectedMeido; public int SelectedMeido { get => selectedMeido; private set => selectedMeido = Utility.Bound(value, 0, ActiveMeidoList.Count - 1); } public int EditMaidIndex { get; private set; } public bool Busy => ActiveMeidoList.Any(meido => meido.Busy); private bool globalGravity; public bool GlobalGravity { get => globalGravity; set { globalGravity = value; if (!HasActiveMeido) return; Meido activeMeido = ActiveMeido; int activeMeidoSlot = activeMeido.Slot; foreach (Meido meido in ActiveMeidoList) { if (meido.Slot != activeMeidoSlot) { meido.HairGravityActive = value && activeMeido.HairGravityActive; meido.SkirtGravityActive = value && activeMeido.SkirtGravityActive; } } } } static MeidoManager() => InputManager.Register(MpsKey.MeidoUndressing, KeyCode.H, "All maid undressing"); public MeidoManager() => Activate(); public void ChangeMaid(int index) => OnUpdateMeido(null, new MeidoUpdateEventArgs(index)); public void Activate() { GameMain.Instance.CharacterMgr.ResetCharaPosAll(); numberOfMeidos = characterMgr.GetStockMaidCount(); Meidos = new Meido[numberOfMeidos]; tempEditMaidIndex = -1; for (int stockMaidIndex = 0; stockMaidIndex < numberOfMeidos; stockMaidIndex++) { Meidos[stockMaidIndex] = new Meido(stockMaidIndex); } if (MeidoPhotoStudio.EditMode) { Maid editMaid = GameMain.Instance.CharacterMgr.GetMaid(0); EditMaidIndex = Array.FindIndex(Meidos, meido => meido.Maid.status.guid == editMaid.status.guid); EditMeido.IsEditMaid = true; var editOkCancel = UTY.GetChildObject(GameObject.Find("UI Root"), "OkCancel") .GetComponent(); // Ensure MPS resets editor state before setting maid EditOkCancel.OnClick newEditOnClick = () => SetEditMaid(Meidos[EditMaidIndex]); newEditOnClick += OkCancelDelegate(); Utility.SetFieldValue(editOkCancel, "m_dgOnClickOk", newEditOnClick); // Only for setting custom parts placement animation just in case body was changed before activating MPS SetEditMaid(Meidos[EditMaidIndex]); } ClearSelectList(); } public void Deactivate() { foreach (Meido meido in Meidos) { meido.UpdateMeido -= OnUpdateMeido; meido.GravityMove -= OnGravityMove; meido.Deactivate(); } ActiveMeidoList.Clear(); if (MeidoPhotoStudio.EditMode && !GameMain.Instance.MainCamera.IsFadeOut()) { Meido meido = Meidos[EditMaidIndex]; meido.Maid.Visible = true; meido.Stop = false; meido.EyeToCam = true; SetEditMaid(meido); // Restore original OK button functionality GameObject okButton = UTY.GetChildObjectNoError(GameObject.Find("UI Root"), "OkCancel"); if (okButton) { EditOkCancel editOkCancel = okButton.GetComponent(); Utility.SetFieldValue(editOkCancel, "m_dgOnClickOk", OkCancelDelegate()); } } } private EditOkCancel.OnClick OkCancelDelegate() { return (EditOkCancel.OnClick)Delegate .CreateDelegate(typeof(EditOkCancel.OnClick), SceneEdit.Instance, "OnEditOk"); } public void Update() { if (InputManager.GetKeyDown(MpsKey.MeidoUndressing)) UndressAll(); } public void Serialize(System.IO.BinaryWriter binaryWriter) { binaryWriter.Write(header); // Only true for MM scenes converted to MPS scenes binaryWriter.Write(false); binaryWriter.Write(Meido.meidoDataVersion); binaryWriter.Write(ActiveMeidoList.Count); foreach (Meido meido in ActiveMeidoList) { meido.Serialize(binaryWriter); } // Global hair/skirt gravity binaryWriter.Write(GlobalGravity); } public void Deserialize(System.IO.BinaryReader binaryReader) { bool isMMScene = binaryReader.ReadBoolean(); int dataVersion = binaryReader.ReadInt32(); int numberOfMaids = binaryReader.ReadInt32(); for (int i = 0; i < numberOfMaids; i++) { if (i >= ActiveMeidoList.Count) { long skip = binaryReader.ReadInt64(); // meido buffer length binaryReader.BaseStream.Seek(skip, System.IO.SeekOrigin.Current); continue; } Meido meido = ActiveMeidoList[i]; meido.Deserialize(binaryReader, dataVersion, isMMScene); } // Global hair/skirt gravity GlobalGravity = binaryReader.ReadBoolean(); } private void UnloadMeidos() { SelectedMeido = 0; foreach (Meido meido in ActiveMeidoList) { meido.UpdateMeido -= OnUpdateMeido; meido.GravityMove -= OnGravityMove; meido.Unload(); } ActiveMeidoList.Clear(); } public void CallMeidos() { BeginCallMeidos?.Invoke(this, EventArgs.Empty); bool moreThanEditMaid = ActiveMeidoList.Count > 1; UnloadMeidos(); if (SelectMeidoList.Count == 0) { OnEndCallMeidos(this, EventArgs.Empty); return; } void callMeidos() => GameMain.Instance.StartCoroutine(LoadMeidos()); if (MeidoPhotoStudio.EditMode && !moreThanEditMaid && SelectMeidoList.Count == 1) callMeidos(); else GameMain.Instance.MainCamera.FadeOut(0.01f, f_bSkipable: false, f_dg: callMeidos); } private System.Collections.IEnumerator LoadMeidos() { foreach (int slot in SelectMeidoList) ActiveMeidoList.Add(Meidos[slot]); for (int i = 0; i < ActiveMeidoList.Count; i++) ActiveMeidoList[i].Load(i); while (Busy) yield return null; yield return new WaitForEndOfFrame(); OnEndCallMeidos(this, EventArgs.Empty); } public void SelectMeido(int index) { if (SelectedMeidoSet.Contains(index)) { if (!MeidoPhotoStudio.EditMode || index != EditMaidIndex) { SelectedMeidoSet.Remove(index); SelectMeidoList.Remove(index); } } else { SelectedMeidoSet.Add(index); SelectMeidoList.Add(index); } } public void ClearSelectList() { SelectedMeidoSet.Clear(); SelectMeidoList.Clear(); if (MeidoPhotoStudio.EditMode) { SelectedMeidoSet.Add(EditMaidIndex); SelectMeidoList.Add(EditMaidIndex); } } public void SetEditMaid(Meido meido) { if (!MeidoPhotoStudio.EditMode) return; EditMeido.IsEditMaid = false; tempEditMaidIndex = meido.Maid.status.guid == Meidos[EditMaidIndex].Maid.status.guid ? -1 : Array.FindIndex(Meidos, maid => maid.Maid.status.guid == meido.Maid.status.guid); EditMeido.IsEditMaid = true; Maid newEditMaid = EditMeido.Maid; GameObject uiRoot = GameObject.Find("UI Root"); var presetCtrl = UTY.GetChildObjectNoError(uiRoot, "PresetPanel")?.GetComponent(); var presetButton = UTY.GetChildObjectNoError(uiRoot, "PresetButtonPanel")?.GetComponent(); var profileCtrl = UTY.GetChildObjectNoError(uiRoot, "ProfilePanel")?.GetComponent(); var customPartsWindow = UTY.GetChildObjectNoError(uiRoot, "Window/CustomPartsWindow") ?.GetComponent(); if (!(presetCtrl || presetButton || profileCtrl || customPartsWindow)) return; // Preset application Utility.SetFieldValue(presetCtrl, "m_maid", newEditMaid); // Preset saving Utility.SetFieldValue(presetButton, "m_maid", newEditMaid); // Maid profile (name, description, experience etc) Utility.SetFieldValue(profileCtrl, "m_maidStatus", newEditMaid.status); // Accessory/Parts placement Utility.SetFieldValue(customPartsWindow, "maid", newEditMaid); // Stopping maid animation and head movement when customizing parts placement Utility.SetFieldValue(customPartsWindow, "animation", newEditMaid.GetAnimation()); // Clothing/body in general and maybe other things Utility.SetFieldValue(SceneEdit.Instance, "m_maid", newEditMaid); // Body status, parts colours and maybe more Utility.GetFieldValue( GameMain.Instance.CharacterMgr, "m_gcActiveMaid" )[0] = newEditMaid; } public Meido GetMeido(string guid) { return string.IsNullOrEmpty(guid) ? null : ActiveMeidoList.Find(meido => meido.Maid.status.guid == guid); } public Meido GetMeido(int activeIndex) { return activeIndex >= 0 && activeIndex < ActiveMeidoList.Count ? ActiveMeidoList[activeIndex] : null; } public void PlaceMeidos(string placementType) { MaidPlacementUtility.ApplyPlacement(placementType, ActiveMeidoList); } private void UndressAll() { if (!HasActiveMeido) return; undress = Utility.Wrap(undress + 1, 0, 3); TBody.MaskMode maskMode = TBody.MaskMode.None; switch (undress) { case 0: maskMode = TBody.MaskMode.None; break; case 1: maskMode = TBody.MaskMode.Underwear; break; case 2: maskMode = TBody.MaskMode.Nude; break; } foreach (Meido activeMeido in ActiveMeidoList) { activeMeido.SetMaskMode(maskMode); } UpdateMeido?.Invoke(ActiveMeido, new MeidoUpdateEventArgs(SelectedMeido)); } private void OnUpdateMeido(object sender, MeidoUpdateEventArgs args) { if (!args.IsEmpty) SelectedMeido = args.SelectedMeido; UpdateMeido?.Invoke(ActiveMeido, args); } private void OnEndCallMeidos(object sender, EventArgs args) { GameMain.Instance.MainCamera.FadeIn(1f); EndCallMeidos?.Invoke(this, EventArgs.Empty); foreach (Meido meido in ActiveMeidoList) { meido.UpdateMeido += OnUpdateMeido; meido.GravityMove += OnGravityMove; } if (MeidoPhotoStudio.EditMode && tempEditMaidIndex >= 0 && !SelectedMeidoSet.Contains(tempEditMaidIndex)) { SetEditMaid(Meidos[EditMaidIndex]); } } private void OnGravityMove(object sender, GravityEventArgs args) { if (!GlobalGravity) return; foreach (Meido meido in ActiveMeidoList) { meido.ApplyGravity(args.LocalPosition, args.IsSkirt); } } } public class MeidoUpdateEventArgs : EventArgs { public static new MeidoUpdateEventArgs Empty { get; } = new MeidoUpdateEventArgs(-1); public bool IsEmpty => (this == Empty) || (SelectedMeido == -1 && !FromMeido && IsBody); public int SelectedMeido { get; } public bool IsBody { get; } public bool FromMeido { get; } public MeidoUpdateEventArgs(int meidoIndex = -1, bool fromMaid = false, bool isBody = true) { SelectedMeido = meidoIndex; IsBody = isBody; FromMeido = fromMaid; } } }