Meido.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Xml.Linq;
  8. using UnityEngine;
  9. using static TBody;
  10. using Object = UnityEngine.Object;
  11. namespace MeidoPhotoStudio.Plugin;
  12. public class Meido
  13. {
  14. public static readonly string DefaultFaceBlendSet = "通常";
  15. public static readonly string[] FaceKeys =
  16. new string[24]
  17. {
  18. "eyeclose", "eyeclose2", "eyeclose3", "eyebig", "eyeclose6", "eyeclose5", "hitomih",
  19. "hitomis", "mayuha", "mayuw", "mayuup", "mayuv", "mayuvhalf", "moutha", "mouths",
  20. "mouthc", "mouthi", "mouthup", "mouthdw", "mouthhe", "mouthuphalf", "tangout",
  21. "tangup", "tangopen",
  22. };
  23. public static readonly string[] FaceToggleKeys =
  24. new string[12]
  25. {
  26. // blush, shade, nose up, tears, drool, teeth
  27. "hoho2", "shock", "nosefook", "namida", "yodare", "toothoff",
  28. // cry 1, cry 2, cry 3, blush 1, blush 2, blush 3
  29. "tear1", "tear2", "tear3", "hohos", "hoho", "hohol",
  30. };
  31. #pragma warning disable SA1308
  32. // TODO: Refactor reflection to using private members directly
  33. private readonly FieldInfo m_eMaskMode = Utility.GetFieldInfo<TBody>("m_eMaskMode");
  34. #pragma warning restore SA1308
  35. private bool initialized;
  36. private float[] blendSetValueBackup;
  37. private bool freeLook;
  38. public Meido(int stockMaidIndex)
  39. {
  40. StockNo = stockMaidIndex;
  41. Maid = GameMain.Instance.CharacterMgr.GetStockMaid(stockMaidIndex);
  42. IKManager = new(this);
  43. IKManager.SelectMaid += (_, args) =>
  44. OnUpdateMeido(args);
  45. }
  46. public event EventHandler<GravityEventArgs> GravityMove;
  47. public event EventHandler<MeidoUpdateEventArgs> UpdateMeido;
  48. public enum Curl
  49. {
  50. Front,
  51. Back,
  52. Shift,
  53. }
  54. public enum Mask
  55. {
  56. All,
  57. Underwear,
  58. Nude,
  59. }
  60. public MaskMode CurrentMaskMode =>
  61. !Body.isLoadedBody ? default : (MaskMode)m_eMaskMode.GetValue(Body);
  62. public DragPointGravity HairGravityControl { get; private set; }
  63. public DragPointGravity SkirtGravityControl { get; private set; }
  64. public Quaternion DefaultEyeRotL { get; private set; }
  65. public Quaternion DefaultEyeRotR { get; private set; }
  66. public bool Active { get; private set; }
  67. public PoseInfo CachedPose { get; private set; } = PoseInfo.DefaultPose;
  68. public string CurrentFaceBlendSet { get; private set; } = DefaultFaceBlendSet;
  69. public int Slot { get; private set; }
  70. public bool Loading { get; private set; }
  71. public int StockNo { get; }
  72. public Maid Maid { get; }
  73. public MeidoDragPointManager IKManager { get; }
  74. public TBody Body =>
  75. Maid.body0;
  76. public Texture2D Portrait =>
  77. Maid.GetThumIcon();
  78. public string FirstName =>
  79. Maid.status.firstName;
  80. public string LastName =>
  81. Maid.status.lastName;
  82. public bool Busy =>
  83. Maid.IsBusy || Loading;
  84. public bool CurlingFront =>
  85. Maid.IsItemChange("skirt", "めくれスカート") || Maid.IsItemChange("onepiece", "めくれスカート");
  86. public bool CurlingBack =>
  87. Maid.IsItemChange("skirt", "めくれスカート後ろ") || Maid.IsItemChange("onepiece", "めくれスカート後ろ");
  88. public bool PantsuShift =>
  89. Maid.IsItemChange("panz", "パンツずらし") || Maid.IsItemChange("mizugi", "パンツずらし");
  90. public bool HairGravityActive
  91. {
  92. get => HairGravityControl.Active;
  93. set
  94. {
  95. if (HairGravityControl.Valid)
  96. HairGravityControl.gameObject.SetActive(value);
  97. }
  98. }
  99. public bool SkirtGravityActive
  100. {
  101. get => SkirtGravityControl.Active;
  102. set
  103. {
  104. if (SkirtGravityControl.Valid)
  105. SkirtGravityControl.gameObject.SetActive(value);
  106. }
  107. }
  108. public bool FreeLook
  109. {
  110. get => freeLook;
  111. set
  112. {
  113. if (freeLook == value)
  114. return;
  115. freeLook = value;
  116. Body.trsLookTarget = freeLook ? null : GameMain.Instance.MainCamera.transform;
  117. OnUpdateMeido();
  118. }
  119. }
  120. public bool HeadToCam
  121. {
  122. get => Body.isLoadedBody && Body.boHeadToCam;
  123. set
  124. {
  125. if (!Body.isLoadedBody || HeadToCam == value)
  126. return;
  127. Body.HeadToCamPer = 0f;
  128. Body.boHeadToCam = value;
  129. if (!HeadToCam && !EyeToCam)
  130. FreeLook = false;
  131. OnUpdateMeido();
  132. }
  133. }
  134. public bool EyeToCam
  135. {
  136. get => Body.isLoadedBody && Body.boEyeToCam;
  137. set
  138. {
  139. if (!Body.isLoadedBody || EyeToCam == value)
  140. return;
  141. Body.boEyeToCam = value;
  142. if (!HeadToCam && !EyeToCam)
  143. FreeLook = false;
  144. OnUpdateMeido();
  145. }
  146. }
  147. public bool Stop
  148. {
  149. get => !Body.isLoadedBody || !Maid.GetAnimation().isPlaying;
  150. set
  151. {
  152. if (!Body.isLoadedBody || value == Stop)
  153. return;
  154. if (value)
  155. {
  156. Maid.GetAnimation().Stop();
  157. }
  158. else
  159. {
  160. Body.boEyeToCam = true;
  161. Body.boHeadToCam = true;
  162. SetPose(CachedPose);
  163. }
  164. OnUpdateMeido();
  165. }
  166. }
  167. public bool IK
  168. {
  169. get => IKManager.Active;
  170. set
  171. {
  172. if (value == IKManager.Active)
  173. return;
  174. IKManager.Active = value;
  175. }
  176. }
  177. public bool Bone
  178. {
  179. get => IKManager.IsBone;
  180. set
  181. {
  182. if (value == Bone)
  183. return;
  184. IKManager.IsBone = value;
  185. OnUpdateMeido();
  186. }
  187. }
  188. private bool MuneLEnabled
  189. {
  190. set
  191. {
  192. if (!Body.isLoadedBody)
  193. return;
  194. MonoBehaviour mune = Body.jbMuneL;
  195. #if COM25
  196. if (Body.IsCrcBody)
  197. mune = Body.dbMuneL;
  198. #endif
  199. if (mune)
  200. mune.enabled = value;
  201. }
  202. }
  203. private bool MuneREnabled
  204. {
  205. set
  206. {
  207. if (!Body.isLoadedBody)
  208. return;
  209. MonoBehaviour mune = Body.jbMuneR;
  210. #if COM25
  211. if (Body.IsCrcBody)
  212. mune = Body.dbMuneR;
  213. #endif
  214. if (mune)
  215. mune.enabled = value;
  216. }
  217. }
  218. private bool MuneYureLEnabled
  219. {
  220. set
  221. {
  222. #if COM25
  223. Body.SetMuneYureL(value);
  224. #else
  225. if (Body.jbMuneL)
  226. Body.jbMuneL.BlendValueON = Convert.ToSingle(value);
  227. #endif
  228. }
  229. }
  230. private bool MuneYureREnabled
  231. {
  232. set
  233. {
  234. #if COM25
  235. Body.SetMuneYureR(value);
  236. #else
  237. if (Body.jbMuneR)
  238. Body.jbMuneR.BlendValueON = Convert.ToSingle(value);
  239. #endif
  240. }
  241. }
  242. public void Load(int slot)
  243. {
  244. if (Busy)
  245. return;
  246. Slot = slot;
  247. if (Active)
  248. return;
  249. FreeLook = false;
  250. Maid.Visible = true;
  251. Body.boHeadToCam = true;
  252. Body.boEyeToCam = true;
  253. Body.SetBoneHitHeightY(-1000f);
  254. if (!Body.isLoadedBody)
  255. {
  256. Maid.DutPropAll();
  257. Maid.AllProcPropSeqStart();
  258. }
  259. StartLoad(OnBodyLoad);
  260. }
  261. public void Unload()
  262. {
  263. if (Body.isLoadedBody && Maid.Visible)
  264. {
  265. DetachAllMpnAttach();
  266. MuneLEnabled = true;
  267. MuneREnabled = true;
  268. Body.quaDefEyeL = DefaultEyeRotL;
  269. Body.quaDefEyeR = DefaultEyeRotR;
  270. if (HairGravityControl)
  271. {
  272. HairGravityControl.Move -= OnGravityEvent;
  273. HairGravityActive = false;
  274. }
  275. if (SkirtGravityControl)
  276. {
  277. SkirtGravityControl.Move -= OnGravityEvent;
  278. SkirtGravityActive = false;
  279. }
  280. ApplyGravity(Vector3.zero, skirt: false);
  281. ApplyGravity(Vector3.zero, skirt: true);
  282. SetFaceBlendSet(DefaultFaceBlendSet);
  283. }
  284. AllProcPropSeqStartPatcher.SequenceStart -= ReinitializeBody;
  285. MuneYureLEnabled = true;
  286. MuneYureREnabled = true;
  287. Body.SetMaskMode(MaskMode.None);
  288. Body.SetBoneHitHeightY(0f);
  289. Maid.Visible = false;
  290. IKManager.Destroy();
  291. Active = false;
  292. }
  293. public void Deactivate()
  294. {
  295. Unload();
  296. if (HairGravityControl)
  297. Object.Destroy(HairGravityControl.gameObject);
  298. if (SkirtGravityControl)
  299. Object.Destroy(SkirtGravityControl.gameObject);
  300. Maid.SetPos(Vector3.zero);
  301. Maid.SetRot(Vector3.zero);
  302. Maid.SetPosOffset(Vector3.zero);
  303. Body.transform.localScale = Vector3.one;
  304. Maid.ResetAll();
  305. Maid.MabatakiUpdateStop = false;
  306. Maid.ActiveSlotNo = -1;
  307. }
  308. public void SetPose(PoseInfo poseInfo)
  309. {
  310. if (!Body.isLoadedBody)
  311. return;
  312. var pose = poseInfo.Pose;
  313. var custom = poseInfo.CustomPose;
  314. if (custom)
  315. {
  316. var poseFilename = Path.GetFileNameWithoutExtension(pose);
  317. try
  318. {
  319. var poseBuffer = File.ReadAllBytes(pose);
  320. var hash = Path.GetFileName(pose).GetHashCode().ToString();
  321. Body.CrossFade(hash, poseBuffer, loop: true, fade: 0f);
  322. }
  323. catch (Exception e) when (e is DirectoryNotFoundException or FileNotFoundException)
  324. {
  325. Utility.LogWarning($"Could not open '{poseFilename}' because {e.Message}");
  326. Constants.InitializeCustomPoses();
  327. SetDefaultPose();
  328. OnUpdateMeido();
  329. return;
  330. }
  331. catch (Exception e)
  332. {
  333. Utility.LogWarning($"Could not apply pose '{poseFilename}' because {e.Message}");
  334. SetDefaultPose();
  335. OnUpdateMeido();
  336. return;
  337. }
  338. SetMune(true, left: true);
  339. SetMune(true, left: false);
  340. }
  341. else
  342. {
  343. var poseComponents = pose.Split(',');
  344. var poseFilename = poseComponents[0] + ".anm";
  345. var tag = Maid.CrossFade(poseFilename, loop: true, val: 0f);
  346. if (string.IsNullOrEmpty(tag))
  347. {
  348. Utility.LogWarning($"Pose could not be loaded: {poseFilename}");
  349. SetDefaultPose();
  350. return;
  351. }
  352. Maid.GetAnimation().Play();
  353. if (poseComponents.Length > 1)
  354. {
  355. var animation = Maid.GetAnimation()[poseFilename];
  356. var time = float.Parse(poseComponents[1]);
  357. animation.time = time;
  358. animation.speed = 0f;
  359. }
  360. SetPoseMune(poseFilename);
  361. }
  362. Maid.SetAutoTwistAll(true);
  363. CachedPose = poseInfo;
  364. void SetDefaultPose() =>
  365. SetPose(PoseInfo.DefaultPose);
  366. void SetPoseMune(string pose)
  367. {
  368. var momiOrPaizuri = pose.Contains("_momi") || pose.Contains("paizuri_");
  369. SetMune(!momiOrPaizuri, left: true);
  370. SetMune(!momiOrPaizuri, left: false);
  371. }
  372. }
  373. public KeyValuePair<bool, bool> SetFrameBinary(byte[] poseBuffer) =>
  374. GetCacheBoneData().SetFrameBinary(poseBuffer);
  375. public void CopyPose(Meido fromMeido)
  376. {
  377. Stop = true;
  378. SetFrameBinary(fromMeido.SerializePose(frameBinary: true));
  379. SetMune(fromMeido.Body.GetMuneYureL() is not 0f, left: true);
  380. SetMune(fromMeido.Body.GetMuneYureR() is not 0f, left: false);
  381. }
  382. public void SetMune(bool enabled, bool left = false)
  383. {
  384. if (left)
  385. {
  386. MuneLEnabled = enabled;
  387. MuneYureLEnabled = enabled;
  388. }
  389. else
  390. {
  391. MuneREnabled = enabled;
  392. MuneYureREnabled = enabled;
  393. }
  394. }
  395. public void SetHandPreset(string filename, bool right)
  396. {
  397. var faceFilename = Path.GetFileNameWithoutExtension(filename);
  398. try
  399. {
  400. var handDocument = XDocument.Load(filename);
  401. var handElement = handDocument.Element("FingerData");
  402. if (handElement?.Elements().Any(element => element?.IsEmpty ?? true) ?? true)
  403. {
  404. Utility.LogWarning($"{faceFilename}: Could not apply hand preset because it is invalid.");
  405. return;
  406. }
  407. Stop = true;
  408. var rightData = bool.Parse(handElement.Element("RightData").Value);
  409. var base64Data = handElement.Element("BinaryData").Value;
  410. var handData = Convert.FromBase64String(base64Data);
  411. IKManager.DeserializeHand(handData, right, rightData != right);
  412. }
  413. catch (System.Xml.XmlException e)
  414. {
  415. Utility.LogWarning($"{faceFilename}: Hand preset data is malformed because {e.Message}");
  416. }
  417. catch (Exception e) when (e is DirectoryNotFoundException or FileNotFoundException)
  418. {
  419. Utility.LogWarning($"{faceFilename}: Could not open hand preset because {e.Message}");
  420. Constants.InitializeHandPresets();
  421. }
  422. catch (Exception e)
  423. {
  424. Utility.LogWarning($"{faceFilename}: Could not parse hand preset because {e.Message}");
  425. }
  426. }
  427. public byte[] SerializePose(bool frameBinary = false)
  428. {
  429. var cache = GetCacheBoneData();
  430. var muneL = Body.GetMuneYureL() is 0f;
  431. var muneR = Body.GetMuneYureR() is 0f;
  432. return frameBinary
  433. ? cache.GetFrameBinary(muneL, muneR)
  434. : cache.GetAnmBinary(true, true);
  435. }
  436. public Dictionary<string, float> SerializeFace()
  437. {
  438. var faceData = new Dictionary<string, float>();
  439. foreach (var hash in FaceKeys.Concat(FaceToggleKeys))
  440. {
  441. try
  442. {
  443. var value = GetFaceBlendValue(hash);
  444. faceData.Add(hash, value);
  445. }
  446. catch
  447. {
  448. // Ignored
  449. }
  450. }
  451. return faceData;
  452. }
  453. public void SetFaceBlendSet(string blendSet)
  454. {
  455. if (blendSet.StartsWith(Constants.CustomFacePath))
  456. {
  457. var blendSetFileName = Path.GetFileNameWithoutExtension(blendSet);
  458. try
  459. {
  460. var faceDocument = XDocument.Load(blendSet, LoadOptions.SetLineInfo);
  461. var faceDataElement = faceDocument.Element("FaceData");
  462. if (faceDataElement?.IsEmpty ?? true)
  463. {
  464. Utility.LogWarning($"{blendSetFileName}: Could not apply face preset because it is invalid.");
  465. return;
  466. }
  467. var hashKeys = new HashSet<string>(FaceKeys.Concat(FaceToggleKeys));
  468. foreach (var element in faceDataElement.Elements())
  469. {
  470. System.Xml.IXmlLineInfo info = element;
  471. var line = info.HasLineInfo() ? info.LineNumber : -1;
  472. string key;
  473. if ((key = (string)element.Attribute("name")) is null)
  474. {
  475. Utility.LogWarning($"{blendSetFileName}: Could not read face blend key at line {line}.");
  476. continue;
  477. }
  478. if (!hashKeys.Contains(key))
  479. {
  480. Utility.LogWarning($"{blendSetFileName}: Invalid face blend key '{key}' at line {line}.");
  481. continue;
  482. }
  483. if (float.TryParse(element.Value, out var value))
  484. {
  485. try
  486. {
  487. SetFaceBlendValue(key, value);
  488. }
  489. catch
  490. {
  491. // Ignored.
  492. }
  493. }
  494. else
  495. {
  496. Utility.LogWarning(
  497. $"{blendSetFileName}: Could not parse value '{element.Value}' of '{key}' at line {line}");
  498. }
  499. }
  500. }
  501. catch (System.Xml.XmlException e)
  502. {
  503. Utility.LogWarning($"{blendSetFileName}: Face preset data is malformed because {e.Message}");
  504. return;
  505. }
  506. catch (Exception e) when (e is DirectoryNotFoundException or FileNotFoundException)
  507. {
  508. Utility.LogWarning($"{blendSetFileName}: Could not open face preset because {e.Message}");
  509. Constants.InitializeCustomFaceBlends();
  510. return;
  511. }
  512. catch (Exception e)
  513. {
  514. Utility.LogWarning($"{blendSetFileName}: Could not parse face preset because {e.Message}");
  515. return;
  516. }
  517. }
  518. else
  519. {
  520. ApplyBackupBlendSet();
  521. CurrentFaceBlendSet = blendSet;
  522. BackupBlendSetValues();
  523. Maid.FaceAnime(blendSet, 0f);
  524. var morph = Body.Face.morph;
  525. foreach (var faceKey in FaceKeys)
  526. {
  527. var hash = Utility.GP01FbFaceHash(morph, faceKey);
  528. if (!morph.Contains(hash))
  529. continue;
  530. var blendIndex = (int)morph.hash[hash];
  531. var value = faceKey is "nosefook"
  532. ? Maid.boNoseFook || morph.boNoseFook ? 1f : 0f
  533. : morph.dicBlendSet[CurrentFaceBlendSet][blendIndex];
  534. morph.SetBlendValues(blendIndex, value);
  535. }
  536. morph.FixBlendValues();
  537. }
  538. StopBlink();
  539. OnUpdateMeido();
  540. }
  541. public void SetFaceBlendValue(string faceKey, float value)
  542. {
  543. var morph = Body.Face.morph;
  544. var hash = Utility.GP01FbFaceHash(morph, faceKey);
  545. if (!morph.Contains(hash))
  546. return;
  547. var blendIndex = (int)morph.hash[hash];
  548. if (faceKey is "nosefook")
  549. Maid.boNoseFook = morph.boNoseFook = value > 0f;
  550. else
  551. morph.dicBlendSet[CurrentFaceBlendSet][blendIndex] = value;
  552. morph.SetBlendValues(blendIndex, value);
  553. morph.FixBlendValues();
  554. }
  555. public float GetFaceBlendValue(string hash)
  556. {
  557. var morph = Body.Face.morph;
  558. if (hash is "nosefook")
  559. return (Maid.boNoseFook || morph.boNoseFook) ? 1f : 0f;
  560. hash = Utility.GP01FbFaceHash(morph, hash);
  561. return morph.dicBlendSet[CurrentFaceBlendSet][(int)morph.hash[hash]];
  562. }
  563. public void StopBlink()
  564. {
  565. Maid.MabatakiUpdateStop = true;
  566. Body.Face.morph.EyeMabataki = 0f;
  567. Utility.SetFieldValue(Maid, "MabatakiVal", 0f);
  568. }
  569. public void SetMaskMode(Mask maskMode) =>
  570. SetMaskMode(maskMode is Mask.Nude ? MaskMode.Nude : (MaskMode)maskMode);
  571. public void SetMaskMode(MaskMode maskMode)
  572. {
  573. var invisibleBody = !Body.GetMask(SlotID.body);
  574. Body.SetMaskMode(maskMode);
  575. if (invisibleBody)
  576. SetBodyMask(false);
  577. }
  578. public void SetBodyMask(bool enabled)
  579. {
  580. var table = Utility.GetFieldValue<TBody, Hashtable>(Body, "m_hFoceHide");
  581. foreach (var bodySlot in MaidDressingPane.BodySlots)
  582. table[bodySlot] = enabled;
  583. Body.FixMaskFlag();
  584. Body.FixVisibleFlag(false);
  585. }
  586. public void SetCurling(Curl curling, bool enabled)
  587. {
  588. var name = curling is Curl.Shift
  589. ? new[] { "panz", "mizugi" }
  590. : new[] { "skirt", "onepiece" };
  591. if (enabled)
  592. {
  593. var action = curling switch
  594. {
  595. Curl.Shift => "パンツずらし",
  596. Curl.Front => "めくれスカート",
  597. _ => "めくれスカート後ろ",
  598. };
  599. Maid.ItemChangeTemp(name[0], action);
  600. Maid.ItemChangeTemp(name[1], action);
  601. }
  602. else
  603. {
  604. Maid.ResetProp(name[0]);
  605. Maid.ResetProp(name[1]);
  606. }
  607. Maid.AllProcProp();
  608. HairGravityControl.Control.OnChangeMekure();
  609. SkirtGravityControl.Control.OnChangeMekure();
  610. }
  611. public void SetMpnProp(MpnAttachProp prop, bool detach)
  612. {
  613. if (detach)
  614. Maid.ResetProp(prop.Tag, false);
  615. else
  616. Maid.SetProp(prop.Tag, prop.MenuFile, 0, true);
  617. Maid.AllProcProp();
  618. }
  619. public void DetachAllMpnAttach()
  620. {
  621. Maid.ResetProp(MPN.kousoku_lower, false);
  622. Maid.ResetProp(MPN.kousoku_upper, false);
  623. Maid.AllProcProp();
  624. }
  625. public void ApplyGravity(Vector3 position, bool skirt = false)
  626. {
  627. var dragPoint = skirt ? SkirtGravityControl : HairGravityControl;
  628. if (dragPoint.Valid)
  629. dragPoint.Control.transform.localPosition = position;
  630. }
  631. private void StartLoad(Action callback)
  632. {
  633. if (Loading)
  634. return;
  635. GameMain.Instance.StartCoroutine(Load(callback));
  636. }
  637. private IEnumerator Load(Action callback)
  638. {
  639. Loading = true;
  640. while (Maid.IsBusy)
  641. yield return null;
  642. yield return new WaitForEndOfFrame();
  643. callback();
  644. Loading = false;
  645. }
  646. private void OnBodyLoad()
  647. {
  648. if (!initialized)
  649. {
  650. DefaultEyeRotL = Body.quaDefEyeL;
  651. DefaultEyeRotR = Body.quaDefEyeR;
  652. initialized = true;
  653. }
  654. if (blendSetValueBackup is null)
  655. BackupBlendSetValues();
  656. if (!HairGravityControl)
  657. InitializeGravityControls();
  658. HairGravityControl.Move += OnGravityEvent;
  659. SkirtGravityControl.Move += OnGravityEvent;
  660. if (MeidoPhotoStudio.EditMode)
  661. AllProcPropSeqStartPatcher.SequenceStart += ReinitializeBody;
  662. #if COM25
  663. // NOTE: This is required for IK to work in COM3D2.5
  664. Body.motionBlendTime = 0f;
  665. #endif
  666. IKManager.Initialize();
  667. SetFaceBlendSet(DefaultFaceBlendSet);
  668. IK = true;
  669. Stop = false;
  670. Bone = false;
  671. Active = true;
  672. }
  673. private void ReinitializeBody(object sender, ProcStartEventArgs args)
  674. {
  675. if (Loading || !Body.isLoadedBody)
  676. return;
  677. if (args.Maid.status.guid != Maid.status.guid)
  678. return;
  679. var gravityControlProps =
  680. new[]
  681. {
  682. MPN.skirt, MPN.onepiece, MPN.mizugi, MPN.panz, MPN.set_maidwear, MPN.set_mywear, MPN.set_underwear,
  683. MPN.hairf, MPN.hairr, MPN.hairs, MPN.hairt,
  684. };
  685. Action action = null;
  686. // Change body
  687. if (Maid.GetProp(MPN.body).boDut)
  688. {
  689. IKManager.Destroy();
  690. action += ReinitializeBody;
  691. }
  692. // Change face
  693. if (Maid.GetProp(MPN.head).boDut)
  694. {
  695. SetFaceBlendSet(DefaultFaceBlendSet);
  696. action += ReinitializeFace;
  697. }
  698. // Gravity control clothing/hair change
  699. if (gravityControlProps.Any(prop => Maid.GetProp(prop).boDut))
  700. {
  701. if (HairGravityControl)
  702. Object.Destroy(HairGravityControl.gameObject);
  703. if (SkirtGravityControl)
  704. Object.Destroy(SkirtGravityControl.gameObject);
  705. action += ReinitializeGravity;
  706. }
  707. // Clothing/accessory changes
  708. // Includes null_mpn too but any button click results in null_mpn bodut I think
  709. action ??= Default;
  710. StartLoad(action);
  711. void ReinitializeBody()
  712. {
  713. IKManager.Initialize();
  714. Stop = false;
  715. // Maid animation needs to be set again for custom parts edit
  716. var uiRoot = GameObject.Find("UI Root");
  717. var customPartsWindow = UTY.GetChildObject(uiRoot, "Window/CustomPartsWindow")
  718. .GetComponent<SceneEditWindow.CustomPartsWindow>();
  719. Utility.SetFieldValue(customPartsWindow, "animation", Maid.GetAnimation());
  720. }
  721. void ReinitializeFace()
  722. {
  723. DefaultEyeRotL = Body.quaDefEyeL;
  724. DefaultEyeRotR = Body.quaDefEyeR;
  725. BackupBlendSetValues();
  726. }
  727. void ReinitializeGravity()
  728. {
  729. InitializeGravityControls();
  730. OnUpdateMeido();
  731. }
  732. void Default() =>
  733. OnUpdateMeido();
  734. }
  735. private void BackupBlendSetValues()
  736. {
  737. var values = Body.Face.morph.dicBlendSet[CurrentFaceBlendSet];
  738. blendSetValueBackup = new float[values.Length];
  739. values.CopyTo(blendSetValueBackup, 0);
  740. }
  741. private void ApplyBackupBlendSet()
  742. {
  743. blendSetValueBackup.CopyTo(Body.Face.morph.dicBlendSet[CurrentFaceBlendSet], 0);
  744. Maid.boNoseFook = false;
  745. }
  746. private CacheBoneDataArray GetCacheBoneData()
  747. {
  748. var cache = Maid.gameObject.GetComponent<CacheBoneDataArray>();
  749. if (!cache)
  750. {
  751. cache = Maid.gameObject.AddComponent<CacheBoneDataArray>();
  752. CreateCache();
  753. }
  754. if (!cache.bone_data?.transform)
  755. {
  756. Utility.LogDebug("Cache bone_data is null");
  757. CreateCache();
  758. }
  759. void CreateCache() =>
  760. cache.CreateCache(Body.GetBone("Bip01"));
  761. return cache;
  762. }
  763. private void InitializeGravityControls()
  764. {
  765. HairGravityControl = MakeGravityControl(skirt: false);
  766. SkirtGravityControl = MakeGravityControl(skirt: true);
  767. }
  768. private DragPointGravity MakeGravityControl(bool skirt = false)
  769. {
  770. var gravityDragpoint = DragPoint.Make<DragPointGravity>(PrimitiveType.Cube, Vector3.one * 0.12f);
  771. var control = DragPointGravity.MakeGravityControl(Maid, skirt);
  772. gravityDragpoint.Initialize(() => control.transform.position, () => Vector3.zero);
  773. gravityDragpoint.Set(control.transform);
  774. gravityDragpoint.gameObject.SetActive(false);
  775. return gravityDragpoint;
  776. }
  777. private void OnUpdateMeido(MeidoUpdateEventArgs args = null) =>
  778. UpdateMeido?.Invoke(this, args ?? MeidoUpdateEventArgs.Empty);
  779. private void OnGravityEvent(object sender, EventArgs args) =>
  780. OnGravityChange((DragPointGravity)sender);
  781. private void OnGravityChange(DragPointGravity dragPoint)
  782. {
  783. var args =
  784. new GravityEventArgs(dragPoint == SkirtGravityControl, dragPoint.MyObject.transform.localPosition);
  785. GravityMove?.Invoke(this, args);
  786. }
  787. }