MeidoManager.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Linq;
  5. using UnityEngine;
  6. namespace COM3D2.MeidoPhotoStudio.Plugin
  7. {
  8. public class MeidoManager : IManager, ISerializable
  9. {
  10. public const string header = "MEIDO";
  11. private static readonly CharacterMgr characterMgr = GameMain.Instance.CharacterMgr;
  12. private int undress;
  13. private int numberOfMeidos;
  14. private int tempEditMaidIndex = -1;
  15. public Meido[] Meidos { get; private set; }
  16. public HashSet<int> SelectedMeidoSet { get; } = new HashSet<int>();
  17. public List<int> SelectMeidoList { get; } = new List<int>();
  18. public List<Meido> ActiveMeidoList { get; } = new List<Meido>();
  19. public Meido ActiveMeido => ActiveMeidoList.Count > 0 ? ActiveMeidoList[SelectedMeido] : null;
  20. public Meido EditMeido => tempEditMaidIndex >= 0 ? Meidos[tempEditMaidIndex] : Meidos[EditMaidIndex];
  21. public bool HasActiveMeido => ActiveMeido != null;
  22. public event EventHandler<MeidoUpdateEventArgs> UpdateMeido;
  23. public event EventHandler EndCallMeidos;
  24. public event EventHandler BeginCallMeidos;
  25. private int selectedMeido;
  26. public int SelectedMeido
  27. {
  28. get => selectedMeido;
  29. private set => selectedMeido = Utility.Bound(value, 0, ActiveMeidoList.Count - 1);
  30. }
  31. public int EditMaidIndex { get; private set; }
  32. public bool Busy => ActiveMeidoList.Any(meido => meido.Busy);
  33. private bool globalGravity;
  34. public bool GlobalGravity
  35. {
  36. get => globalGravity;
  37. set
  38. {
  39. globalGravity = value;
  40. if (!HasActiveMeido) return;
  41. Meido activeMeido = ActiveMeido;
  42. int activeMeidoSlot = activeMeido.Slot;
  43. foreach (Meido meido in ActiveMeidoList)
  44. {
  45. if (meido.Slot != activeMeidoSlot)
  46. {
  47. meido.HairGravityActive = value && activeMeido.HairGravityActive;
  48. meido.SkirtGravityActive = value && activeMeido.SkirtGravityActive;
  49. }
  50. }
  51. }
  52. }
  53. static MeidoManager() => InputManager.Register(MpsKey.MeidoUndressing, KeyCode.H, "All maid undressing");
  54. public MeidoManager() => Activate();
  55. public void ChangeMaid(int index) => OnUpdateMeido(null, new MeidoUpdateEventArgs(index));
  56. public void Activate()
  57. {
  58. GameMain.Instance.CharacterMgr.ResetCharaPosAll();
  59. numberOfMeidos = characterMgr.GetStockMaidCount();
  60. Meidos = new Meido[numberOfMeidos];
  61. tempEditMaidIndex = -1;
  62. for (int stockMaidIndex = 0; stockMaidIndex < numberOfMeidos; stockMaidIndex++)
  63. {
  64. Meidos[stockMaidIndex] = new Meido(stockMaidIndex);
  65. }
  66. if (MeidoPhotoStudio.EditMode)
  67. {
  68. Maid editMaid = GameMain.Instance.CharacterMgr.GetMaid(0);
  69. EditMaidIndex = Array.FindIndex(Meidos, meido => meido.Maid.status.guid == editMaid.status.guid);
  70. EditMeido.IsEditMaid = true;
  71. var editOkCancel = UTY.GetChildObject(GameObject.Find("UI Root"), "OkCancel")
  72. .GetComponent<EditOkCancel>();
  73. // Ensure MPS resets editor state before setting maid
  74. EditOkCancel.OnClick newEditOnClick = () => SetEditMaid(Meidos[EditMaidIndex]);
  75. newEditOnClick += OkCancelDelegate();
  76. Utility.SetFieldValue(editOkCancel, "m_dgOnClickOk", newEditOnClick);
  77. // Only for setting custom parts placement animation just in case body was changed before activating MPS
  78. SetEditMaid(Meidos[EditMaidIndex]);
  79. }
  80. ClearSelectList();
  81. }
  82. public void Deactivate()
  83. {
  84. foreach (Meido meido in Meidos)
  85. {
  86. meido.UpdateMeido -= OnUpdateMeido;
  87. meido.GravityMove -= OnGravityMove;
  88. meido.Deactivate();
  89. }
  90. ActiveMeidoList.Clear();
  91. if (MeidoPhotoStudio.EditMode && !GameMain.Instance.MainCamera.IsFadeOut())
  92. {
  93. Meido meido = Meidos[EditMaidIndex];
  94. meido.Maid.Visible = true;
  95. meido.Stop = false;
  96. meido.EyeToCam = true;
  97. SetEditMaid(meido);
  98. // Restore original OK button functionality
  99. GameObject okButton = UTY.GetChildObjectNoError(GameObject.Find("UI Root"), "OkCancel");
  100. if (okButton)
  101. {
  102. EditOkCancel editOkCancel = okButton.GetComponent<EditOkCancel>();
  103. Utility.SetFieldValue(editOkCancel, "m_dgOnClickOk", OkCancelDelegate());
  104. }
  105. }
  106. }
  107. private EditOkCancel.OnClick OkCancelDelegate()
  108. {
  109. return (EditOkCancel.OnClick)Delegate
  110. .CreateDelegate(typeof(EditOkCancel.OnClick), SceneEdit.Instance, "OnEditOk");
  111. }
  112. public void Update()
  113. {
  114. if (InputManager.GetKeyDown(MpsKey.MeidoUndressing)) UndressAll();
  115. }
  116. public void Serialize(System.IO.BinaryWriter binaryWriter)
  117. {
  118. binaryWriter.Write(header);
  119. // Only true for MM scenes converted to MPS scenes
  120. binaryWriter.Write(false);
  121. binaryWriter.Write(Meido.meidoDataVersion);
  122. binaryWriter.Write(ActiveMeidoList.Count);
  123. foreach (Meido meido in ActiveMeidoList)
  124. {
  125. meido.Serialize(binaryWriter);
  126. }
  127. // Global hair/skirt gravity
  128. binaryWriter.Write(GlobalGravity);
  129. }
  130. public void Deserialize(System.IO.BinaryReader binaryReader)
  131. {
  132. bool isMMScene = binaryReader.ReadBoolean();
  133. int dataVersion = binaryReader.ReadInt32();
  134. int numberOfMaids = binaryReader.ReadInt32();
  135. for (int i = 0; i < numberOfMaids; i++)
  136. {
  137. if (i >= ActiveMeidoList.Count)
  138. {
  139. long skip = binaryReader.ReadInt64(); // meido buffer length
  140. binaryReader.BaseStream.Seek(skip, System.IO.SeekOrigin.Current);
  141. continue;
  142. }
  143. Meido meido = ActiveMeidoList[i];
  144. meido.Deserialize(binaryReader, dataVersion, isMMScene);
  145. }
  146. // Global hair/skirt gravity
  147. GlobalGravity = binaryReader.ReadBoolean();
  148. }
  149. private void UnloadMeidos()
  150. {
  151. SelectedMeido = 0;
  152. foreach (Meido meido in ActiveMeidoList)
  153. {
  154. meido.UpdateMeido -= OnUpdateMeido;
  155. meido.GravityMove -= OnGravityMove;
  156. meido.Unload();
  157. }
  158. ActiveMeidoList.Clear();
  159. }
  160. public void CallMeidos()
  161. {
  162. BeginCallMeidos?.Invoke(this, EventArgs.Empty);
  163. bool moreThanEditMaid = ActiveMeidoList.Count > 1;
  164. UnloadMeidos();
  165. if (SelectMeidoList.Count == 0)
  166. {
  167. OnEndCallMeidos(this, EventArgs.Empty);
  168. return;
  169. }
  170. void callMeidos() => GameMain.Instance.StartCoroutine(LoadMeidos());
  171. if (MeidoPhotoStudio.EditMode && !moreThanEditMaid && SelectMeidoList.Count == 1) callMeidos();
  172. else GameMain.Instance.MainCamera.FadeOut(0.01f, f_bSkipable: false, f_dg: callMeidos);
  173. }
  174. private System.Collections.IEnumerator LoadMeidos()
  175. {
  176. foreach (int slot in SelectMeidoList) ActiveMeidoList.Add(Meidos[slot]);
  177. for (int i = 0; i < ActiveMeidoList.Count; i++) ActiveMeidoList[i].Load(i);
  178. while (Busy) yield return null;
  179. yield return new WaitForEndOfFrame();
  180. OnEndCallMeidos(this, EventArgs.Empty);
  181. }
  182. public void SelectMeido(int index)
  183. {
  184. if (SelectedMeidoSet.Contains(index))
  185. {
  186. if (!MeidoPhotoStudio.EditMode || index != EditMaidIndex)
  187. {
  188. SelectedMeidoSet.Remove(index);
  189. SelectMeidoList.Remove(index);
  190. }
  191. }
  192. else
  193. {
  194. SelectedMeidoSet.Add(index);
  195. SelectMeidoList.Add(index);
  196. }
  197. }
  198. public void ClearSelectList()
  199. {
  200. SelectedMeidoSet.Clear();
  201. SelectMeidoList.Clear();
  202. if (MeidoPhotoStudio.EditMode)
  203. {
  204. SelectedMeidoSet.Add(EditMaidIndex);
  205. SelectMeidoList.Add(EditMaidIndex);
  206. }
  207. }
  208. public void SetEditMaid(Meido meido)
  209. {
  210. if (!MeidoPhotoStudio.EditMode) return;
  211. EditMeido.IsEditMaid = false;
  212. tempEditMaidIndex = meido.Maid.status.guid == Meidos[EditMaidIndex].Maid.status.guid
  213. ? -1
  214. : Array.FindIndex(Meidos, maid => maid.Maid.status.guid == meido.Maid.status.guid);
  215. EditMeido.IsEditMaid = true;
  216. Maid newEditMaid = EditMeido.Maid;
  217. GameObject uiRoot = GameObject.Find("UI Root");
  218. var presetCtrl = UTY.GetChildObjectNoError(uiRoot, "PresetPanel")?.GetComponent<PresetCtrl>();
  219. var presetButton = UTY.GetChildObjectNoError(uiRoot, "PresetButtonPanel")?.GetComponent<PresetButtonCtrl>();
  220. var profileCtrl = UTY.GetChildObjectNoError(uiRoot, "ProfilePanel")?.GetComponent<ProfileCtrl>();
  221. var customPartsWindow = UTY.GetChildObjectNoError(uiRoot, "Window/CustomPartsWindow")
  222. ?.GetComponent<SceneEditWindow.CustomPartsWindow>();
  223. if (!(presetCtrl || presetButton || profileCtrl || customPartsWindow)) return;
  224. // Preset application
  225. Utility.SetFieldValue(presetCtrl, "m_maid", newEditMaid);
  226. // Preset saving
  227. Utility.SetFieldValue(presetButton, "m_maid", newEditMaid);
  228. // Maid profile (name, description, experience etc)
  229. Utility.SetFieldValue(profileCtrl, "m_maidStatus", newEditMaid.status);
  230. // Accessory/Parts placement
  231. Utility.SetFieldValue(customPartsWindow, "maid", newEditMaid);
  232. // Stopping maid animation and head movement when customizing parts placement
  233. Utility.SetFieldValue(customPartsWindow, "animation", newEditMaid.GetAnimation());
  234. // Clothing/body in general and maybe other things
  235. Utility.SetFieldValue(SceneEdit.Instance, "m_maid", newEditMaid);
  236. // Body status, parts colours and maybe more
  237. Utility.GetFieldValue<CharacterMgr, Maid[]>(
  238. GameMain.Instance.CharacterMgr, "m_gcActiveMaid"
  239. )[0] = newEditMaid;
  240. }
  241. public Meido GetMeido(string guid)
  242. {
  243. return string.IsNullOrEmpty(guid) ? null : ActiveMeidoList.Find(meido => meido.Maid.status.guid == guid);
  244. }
  245. public Meido GetMeido(int activeIndex)
  246. {
  247. return activeIndex >= 0 && activeIndex < ActiveMeidoList.Count ? ActiveMeidoList[activeIndex] : null;
  248. }
  249. public void PlaceMeidos(string placementType)
  250. {
  251. MaidPlacementUtility.ApplyPlacement(placementType, ActiveMeidoList);
  252. }
  253. private void UndressAll()
  254. {
  255. if (!HasActiveMeido) return;
  256. undress = ++undress % Enum.GetNames(typeof(Meido.Mask)).Length;
  257. foreach (Meido activeMeido in ActiveMeidoList) activeMeido.SetMaskMode((Meido.Mask)undress);
  258. UpdateMeido?.Invoke(ActiveMeido, new MeidoUpdateEventArgs(SelectedMeido));
  259. }
  260. private void OnUpdateMeido(object sender, MeidoUpdateEventArgs args)
  261. {
  262. if (!args.IsEmpty) SelectedMeido = args.SelectedMeido;
  263. UpdateMeido?.Invoke(ActiveMeido, args);
  264. }
  265. private void OnEndCallMeidos(object sender, EventArgs args)
  266. {
  267. GameMain.Instance.MainCamera.FadeIn(1f);
  268. EndCallMeidos?.Invoke(this, EventArgs.Empty);
  269. foreach (Meido meido in ActiveMeidoList)
  270. {
  271. meido.UpdateMeido += OnUpdateMeido;
  272. meido.GravityMove += OnGravityMove;
  273. }
  274. if (MeidoPhotoStudio.EditMode && tempEditMaidIndex >= 0 && !SelectedMeidoSet.Contains(tempEditMaidIndex))
  275. {
  276. SetEditMaid(Meidos[EditMaidIndex]);
  277. }
  278. }
  279. private void OnGravityMove(object sender, GravityEventArgs args)
  280. {
  281. if (!GlobalGravity) return;
  282. foreach (Meido meido in ActiveMeidoList)
  283. {
  284. meido.ApplyGravity(args.LocalPosition, args.IsSkirt);
  285. }
  286. }
  287. }
  288. public class MeidoUpdateEventArgs : EventArgs
  289. {
  290. public static new MeidoUpdateEventArgs Empty { get; } = new MeidoUpdateEventArgs(-1);
  291. public bool IsEmpty => (this == Empty) || (SelectedMeido == -1 && !FromMeido && IsBody);
  292. public int SelectedMeido { get; }
  293. public bool IsBody { get; }
  294. public bool FromMeido { get; }
  295. public MeidoUpdateEventArgs(int meidoIndex = -1, bool fromMaid = false, bool isBody = true)
  296. {
  297. SelectedMeido = meidoIndex;
  298. IsBody = isBody;
  299. FromMeido = fromMaid;
  300. }
  301. }
  302. }