MeidoManager.cs 11 KB

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