MeidoManager.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using HarmonyLib;
  5. using UnityEngine;
  6. namespace MeidoPhotoStudio.Plugin;
  7. public class MeidoManager : IManager
  8. {
  9. public const string Header = "MEIDO";
  10. private static bool active;
  11. private int selectedMeido;
  12. private bool globalGravity;
  13. private int undress;
  14. private Meido editingMeido;
  15. private Meido temporaryEditingMeido;
  16. static MeidoManager() =>
  17. InputManager.Register(MpsKey.MeidoUndressing, KeyCode.H, "All maid undressing");
  18. public MeidoManager() =>
  19. Activate();
  20. public event EventHandler<MeidoUpdateEventArgs> UpdateMeido;
  21. public event EventHandler EndCallMeidos;
  22. public event EventHandler BeginCallMeidos;
  23. public Meido[] Meidos { get; private set; }
  24. public List<Meido> ActiveMeidoList { get; } = new();
  25. public int SelectedMeido
  26. {
  27. get => selectedMeido;
  28. private set => selectedMeido = Utility.Bound(value, 0, ActiveMeidoList.Count - 1);
  29. }
  30. public bool Busy =>
  31. ActiveMeidoList.Any(meido => meido.Busy);
  32. public Meido ActiveMeido =>
  33. ActiveMeidoList.Count > 0 ? ActiveMeidoList[SelectedMeido] : null;
  34. public Meido EditingMeido
  35. {
  36. get => MeidoPhotoStudio.EditMode ? editingMeido : null;
  37. set
  38. {
  39. if (!MeidoPhotoStudio.EditMode || value is null)
  40. return;
  41. editingMeido = value;
  42. temporaryEditingMeido = editingMeido == OriginalEditingMeido ? null : editingMeido;
  43. SetEditorMaid(editingMeido.Maid);
  44. }
  45. }
  46. public Meido TemporaryEditingMeido =>
  47. MeidoPhotoStudio.EditMode ? temporaryEditingMeido : null;
  48. public Meido OriginalEditingMeido =>
  49. MeidoPhotoStudio.EditMode && OriginalEditingMaidIndex >= 0 ? Meidos[OriginalEditingMaidIndex] : null;
  50. public bool HasActiveMeido =>
  51. ActiveMeido is not null;
  52. public bool GlobalGravity
  53. {
  54. get => globalGravity;
  55. set
  56. {
  57. globalGravity = value;
  58. if (!HasActiveMeido)
  59. return;
  60. var activeMeido = ActiveMeido;
  61. var activeMeidoSlot = activeMeido.Slot;
  62. foreach (var meido in ActiveMeidoList)
  63. {
  64. if (meido.Slot == activeMeidoSlot)
  65. continue;
  66. meido.HairGravityActive = value && activeMeido.HairGravityActive;
  67. meido.SkirtGravityActive = value && activeMeido.SkirtGravityActive;
  68. }
  69. }
  70. }
  71. private static CharacterMgr CharacterMgr =>
  72. GameMain.Instance.CharacterMgr;
  73. private static int OriginalEditingMaidIndex { get; set; }
  74. public void ChangeMaid(int index) =>
  75. OnUpdateMeido(null, new(index));
  76. public void Activate()
  77. {
  78. Meidos = CharacterMgr.GetStockMaidList()
  79. .Select(maid => new Meido(maid))
  80. .ToArray();
  81. CharacterMgr.ResetCharaPosAll();
  82. if (MeidoPhotoStudio.EditMode)
  83. {
  84. temporaryEditingMeido = null;
  85. editingMeido = OriginalEditingMeido;
  86. if (OriginalEditingMeido is not null)
  87. CallMeidos(new List<Meido>() { OriginalEditingMeido });
  88. }
  89. else
  90. {
  91. CharacterMgr.DeactivateMaid(0);
  92. }
  93. active = true;
  94. }
  95. public void Deactivate()
  96. {
  97. foreach (var meido in Meidos)
  98. {
  99. meido.UpdateMeido -= OnUpdateMeido;
  100. meido.GravityMove -= OnGravityMove;
  101. meido.Deactivate();
  102. }
  103. ActiveMeidoList.Clear();
  104. if (MeidoPhotoStudio.EditMode && !GameMain.Instance.MainCamera.IsFadeOut())
  105. {
  106. var meido = OriginalEditingMeido;
  107. meido.Maid.Visible = true;
  108. meido.Stop = false;
  109. meido.EyeToCam = true;
  110. SetEditorMaid(meido.Maid);
  111. }
  112. active = false;
  113. }
  114. public void Update()
  115. {
  116. if (InputManager.GetKeyDown(MpsKey.MeidoUndressing))
  117. UndressAll();
  118. }
  119. public void CallMeidos(IList<Meido> meidoToCall)
  120. {
  121. BeginCallMeidos?.Invoke(this, EventArgs.Empty);
  122. SelectedMeido = 0;
  123. if (MeidoPhotoStudio.EditMode && meidoToCall.Count is 0)
  124. meidoToCall.Add(OriginalEditingMeido);
  125. UnloadMeidos(meidoToCall);
  126. if (meidoToCall.Count is 0)
  127. {
  128. OnEndCallMeidos(this, EventArgs.Empty);
  129. return;
  130. }
  131. ActiveMeidoList.AddRange(meidoToCall);
  132. GameMain.Instance.StartCoroutine(LoadMeidos(meidoToCall));
  133. }
  134. public Meido GetMeido(string guid) =>
  135. string.IsNullOrEmpty(guid) ? null : ActiveMeidoList.Find(meido => meido.Maid.status.guid == guid);
  136. public Meido GetMeido(int activeIndex) =>
  137. activeIndex >= 0 && activeIndex < ActiveMeidoList.Count ? ActiveMeidoList[activeIndex] : null;
  138. public void PlaceMeidos(string placementType) =>
  139. MaidPlacementUtility.ApplyPlacement(placementType, ActiveMeidoList);
  140. private static void SetEditorMaid(Maid maid)
  141. {
  142. if (!maid)
  143. {
  144. Utility.LogWarning("Refusing to change editing maid because the new maid is null!");
  145. return;
  146. }
  147. if (SceneEdit.Instance.maid.status.guid == maid.status.guid)
  148. {
  149. Utility.LogDebug("Editing maid is the same as new maid");
  150. return;
  151. }
  152. var uiRoot = GameObject.Find("UI Root");
  153. if (!TryGetUIControl<PresetCtrl>(uiRoot, "PresetPanel", out var presetCtrl))
  154. return;
  155. if (!TryGetUIControl<PresetButtonCtrl>(uiRoot, "PresetButtonPanel", out var presetButtonCtrl))
  156. return;
  157. if (!TryGetUIControl<ProfileCtrl>(uiRoot, "ProfilePanel", out var profileCtrl))
  158. return;
  159. if (!TryGetUIControl<SceneEditWindow.CustomPartsWindow>(
  160. uiRoot, "Window/CustomPartsWindow", out var sceneEditWindow))
  161. return;
  162. // Preset application
  163. presetCtrl.m_maid = maid;
  164. // Preset saving
  165. presetButtonCtrl.m_maid = maid;
  166. // Maid profile (name, description, experience etc)
  167. profileCtrl.m_maidStatus = maid.status;
  168. // Accessory/Parts placement
  169. sceneEditWindow.maid = maid;
  170. // Stopping maid animation and head movement when customizing parts placement
  171. sceneEditWindow.animation = maid.GetAnimation();
  172. // Clothing/body in general and maybe other things
  173. SceneEdit.Instance.m_maid = maid;
  174. // Body status, parts colours and maybe more
  175. GameMain.Instance.CharacterMgr.m_gcActiveMaid[0] = maid;
  176. static bool TryGetUIControl<T>(GameObject root, string hierarchy, out T uiControl)
  177. where T : MonoBehaviour
  178. {
  179. uiControl = null;
  180. var uiElement = UTY.GetChildObjectNoError(root, hierarchy);
  181. if (!uiElement)
  182. return false;
  183. uiControl = uiElement.GetComponent<T>();
  184. return uiControl;
  185. }
  186. }
  187. [HarmonyPostfix]
  188. [HarmonyPatch(typeof(SceneEdit), nameof(SceneEdit.Start))]
  189. private static void SceneEditStartPostfix()
  190. {
  191. if (!SceneEdit.Instance.maid)
  192. return;
  193. var originalEditingMaid = SceneEdit.Instance.maid;
  194. OriginalEditingMaidIndex = GameMain.Instance.CharacterMgr.GetStockMaidList()
  195. .FindIndex(maid => maid.status.guid == originalEditingMaid.status.guid);
  196. try
  197. {
  198. var editOkCancelButton = UTY.GetChildObject(GameObject.Find("UI Root"), "OkCancel")
  199. .GetComponent<EditOkCancel>();
  200. EditOkCancel.OnClick newEditOkCancelDelegate = RestoreOriginalEditingMaid;
  201. newEditOkCancelDelegate += editOkCancelButton.m_dgOnClickOk;
  202. editOkCancelButton.m_dgOnClickOk = newEditOkCancelDelegate;
  203. void RestoreOriginalEditingMaid()
  204. {
  205. // Only restore original editing maid when active.
  206. if (!active)
  207. return;
  208. Utility.LogDebug($"Setting Editing maid back to '{originalEditingMaid.status.fullNameJpStyle}'");
  209. SetEditorMaid(originalEditingMaid);
  210. // Set SceneEdit's maid regardless of UI integration failing
  211. SceneEdit.Instance.m_maid = originalEditingMaid;
  212. }
  213. }
  214. catch (Exception e)
  215. {
  216. Utility.LogWarning($"Failed to hook onto Edit Mode OK button: {e}");
  217. }
  218. }
  219. private void UnloadMeidos(IList<Meido> meidoToCall)
  220. {
  221. foreach (var meido in ActiveMeidoList)
  222. {
  223. meido.UpdateMeido -= OnUpdateMeido;
  224. meido.GravityMove -= OnGravityMove;
  225. if (!meidoToCall.Contains(meido))
  226. meido.Unload();
  227. }
  228. ActiveMeidoList.Clear();
  229. }
  230. private System.Collections.IEnumerator LoadMeidos(IList<Meido> meidoToLoad)
  231. {
  232. GameMain.Instance.MainCamera.FadeOut(0.01f, f_bSkipable: false);
  233. yield return new WaitForSeconds(0.01f);
  234. for (var meidoSlot = 0; meidoSlot < meidoToLoad.Count; meidoSlot++)
  235. meidoToLoad[meidoSlot].Load(meidoSlot);
  236. yield return new WaitForEndOfFrame();
  237. var waitForSeconds = new WaitForSeconds(0.5f);
  238. while (Busy)
  239. yield return waitForSeconds;
  240. OnEndCallMeidos(this, EventArgs.Empty);
  241. }
  242. private void UndressAll()
  243. {
  244. if (!HasActiveMeido)
  245. return;
  246. undress = ++undress % Enum.GetNames(typeof(Meido.Mask)).Length;
  247. foreach (var activeMeido in ActiveMeidoList)
  248. activeMeido.SetMaskMode((Meido.Mask)undress);
  249. UpdateMeido?.Invoke(ActiveMeido, new(SelectedMeido));
  250. }
  251. private void OnUpdateMeido(object sender, MeidoUpdateEventArgs args)
  252. {
  253. if (!args.IsEmpty)
  254. SelectedMeido = args.SelectedMeido;
  255. UpdateMeido?.Invoke(ActiveMeido, args);
  256. }
  257. private void OnEndCallMeidos(object sender, EventArgs args)
  258. {
  259. GameMain.Instance.MainCamera.FadeIn(1f);
  260. EndCallMeidos?.Invoke(this, EventArgs.Empty);
  261. foreach (var meido in ActiveMeidoList)
  262. {
  263. meido.UpdateMeido += OnUpdateMeido;
  264. meido.GravityMove += OnGravityMove;
  265. }
  266. if (MeidoPhotoStudio.EditMode && !ActiveMeidoList.Contains(TemporaryEditingMeido))
  267. EditingMeido = OriginalEditingMeido;
  268. }
  269. private void OnGravityMove(object sender, GravityEventArgs args)
  270. {
  271. if (!GlobalGravity)
  272. return;
  273. foreach (var meido in ActiveMeidoList)
  274. meido.ApplyGravity(args.LocalPosition, args.IsSkirt);
  275. }
  276. }