Meido.cs 27 KB

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