MeidoPhotoStudio.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. using System.IO;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.SceneManagement;
  6. using Ionic.Zlib;
  7. using BepInEx;
  8. namespace COM3D2.MeidoPhotoStudio.Plugin
  9. {
  10. [BepInPlugin(pluginGuid, pluginName, pluginVersion)]
  11. public class MeidoPhotoStudio : BaseUnityPlugin
  12. {
  13. private static CameraMain mainCamera = GameMain.Instance.MainCamera;
  14. private static event EventHandler<ScreenshotEventArgs> ScreenshotEvent;
  15. private const string pluginGuid = "com.habeebweeb.com3d2.meidophotostudio";
  16. public const string pluginName = "MeidoPhotoStudio";
  17. public const string pluginVersion = "0.0.0";
  18. public const int sceneVersion = 1100;
  19. public const int kankyoMagic = -765;
  20. public static string pluginString = $"{pluginName} {pluginVersion}";
  21. private WindowManager windowManager;
  22. private MeidoManager meidoManager;
  23. private EnvironmentManager environmentManager;
  24. private MessageWindowManager messageWindowManager;
  25. private LightManager lightManager;
  26. private PropManager propManager;
  27. private EffectManager effectManager;
  28. private Constants.Scene currentScene;
  29. private bool initialized = false;
  30. private bool active = false;
  31. private bool uiActive = false;
  32. private void Awake()
  33. {
  34. ScreenshotEvent += OnScreenshotEvent;
  35. DontDestroyOnLoad(this);
  36. }
  37. private void Start()
  38. {
  39. Constants.Initialize();
  40. Translation.Initialize(Configuration.CurrentLanguage);
  41. SceneManager.sceneLoaded += OnSceneLoaded;
  42. }
  43. public byte[] SerializeScene(bool kankyo = false)
  44. {
  45. if (meidoManager.Busy) return null;
  46. byte[] compressedData;
  47. using (MemoryStream memoryStream = new MemoryStream())
  48. using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
  49. using (BinaryWriter binaryWriter = new BinaryWriter(deflateStream, System.Text.Encoding.UTF8))
  50. {
  51. binaryWriter.Write("MPS_SCENE");
  52. binaryWriter.Write(sceneVersion);
  53. binaryWriter.Write(kankyo ? kankyoMagic : meidoManager.ActiveMeidoList.Count);
  54. effectManager.Serialize(binaryWriter);
  55. environmentManager.Serialize(binaryWriter, kankyo);
  56. lightManager.Serialize(binaryWriter);
  57. if (!kankyo)
  58. {
  59. messageWindowManager.Serialize(binaryWriter);
  60. // meidomanager has to be serialized before propmanager because reattached props will be in the
  61. // wrong place after a maid's pose is deserialized.
  62. meidoManager.Serialize(binaryWriter);
  63. }
  64. propManager.Serialize(binaryWriter);
  65. binaryWriter.Write("END");
  66. deflateStream.Close();
  67. compressedData = memoryStream.ToArray();
  68. }
  69. return compressedData;
  70. }
  71. public static byte[] DecompressScene(string filePath)
  72. {
  73. if (!File.Exists(filePath))
  74. {
  75. Utility.LogWarning($"Scene file '{filePath}' does not exist.");
  76. return null;
  77. }
  78. byte[] compressedData;
  79. using (FileStream fileStream = File.OpenRead(filePath))
  80. {
  81. if (Utility.IsPngFile(fileStream))
  82. {
  83. if (!Utility.SeekPngEnd(fileStream))
  84. {
  85. Utility.LogWarning($"'{filePath}' is not a PNG file");
  86. return null;
  87. }
  88. if (fileStream.Position == fileStream.Length)
  89. {
  90. Utility.LogWarning($"'{filePath}' contains no scene data");
  91. return null;
  92. }
  93. int dataLength = (int)(fileStream.Length - fileStream.Position);
  94. compressedData = new byte[dataLength];
  95. fileStream.Read(compressedData, 0, dataLength);
  96. }
  97. else
  98. {
  99. compressedData = new byte[fileStream.Length];
  100. fileStream.Read(compressedData, 0, compressedData.Length);
  101. }
  102. }
  103. return DeflateStream.UncompressBuffer(compressedData);
  104. }
  105. public void ApplyScene(string filePath)
  106. {
  107. if (meidoManager.Busy) return;
  108. byte[] sceneBinary = DecompressScene(filePath);
  109. if (sceneBinary == null) return;
  110. using (MemoryStream memoryStream = new MemoryStream(sceneBinary))
  111. using (BinaryReader binaryReader = new BinaryReader(memoryStream, System.Text.Encoding.UTF8))
  112. {
  113. try
  114. {
  115. if (binaryReader.ReadString() != "MPS_SCENE")
  116. {
  117. Utility.LogWarning($"'{filePath}' is not a {pluginName} scene");
  118. return;
  119. }
  120. if (binaryReader.ReadInt32() > sceneVersion)
  121. {
  122. Utility.LogWarning($"'{filePath}' is made in a newer version of {pluginName}");
  123. return;
  124. }
  125. binaryReader.ReadInt32(); // Number of Maids
  126. string previousHeader = string.Empty;
  127. string header;
  128. while ((header = binaryReader.ReadString()) != "END")
  129. {
  130. switch (header)
  131. {
  132. case MessageWindowManager.header:
  133. messageWindowManager.Deserialize(binaryReader);
  134. break;
  135. case EnvironmentManager.header:
  136. environmentManager.Deserialize(binaryReader);
  137. break;
  138. case MeidoManager.header:
  139. meidoManager.Deserialize(binaryReader);
  140. break;
  141. case PropManager.header:
  142. propManager.Deserialize(binaryReader);
  143. break;
  144. case LightManager.header:
  145. lightManager.Deserialize(binaryReader);
  146. break;
  147. case EffectManager.header:
  148. effectManager.Deserialize(binaryReader);
  149. break;
  150. default: throw new Exception($"Unknown header '{header}'. Last {previousHeader}");
  151. }
  152. previousHeader = header;
  153. }
  154. }
  155. catch (Exception e)
  156. {
  157. Utility.LogError($"Failed to deserialize scene '{filePath}' because {e.Message}");
  158. return;
  159. }
  160. }
  161. }
  162. public static void TakeScreenshot(ScreenshotEventArgs args) => ScreenshotEvent?.Invoke(null, args);
  163. public static void TakeScreenshot(string path = "", int superSize = -1, bool hideMaids = false)
  164. {
  165. TakeScreenshot(new ScreenshotEventArgs() { Path = path, SuperSize = superSize, HideMaids = hideMaids });
  166. }
  167. private void OnScreenshotEvent(object sender, ScreenshotEventArgs args)
  168. {
  169. StartCoroutine(Screenshot(args));
  170. }
  171. private void Update()
  172. {
  173. if (currentScene == Constants.Scene.Daily)
  174. {
  175. if (Input.GetKeyDown(KeyCode.F6))
  176. {
  177. if (active) Deactivate();
  178. else Activate();
  179. }
  180. if (active)
  181. {
  182. if (!Input.GetKey(KeyCode.Q) && !Utility.GetModKey(Utility.ModKey.Control)
  183. && Input.GetKeyDown(KeyCode.S)
  184. ) TakeScreenshot();
  185. meidoManager.Update();
  186. environmentManager.Update();
  187. windowManager.Update();
  188. effectManager.Update();
  189. }
  190. }
  191. }
  192. private IEnumerator Screenshot(ScreenshotEventArgs args)
  193. {
  194. // Hide UI and dragpoints
  195. GameObject editUI = GameObject.Find("/UI Root/Camera");
  196. GameObject fpsViewer =
  197. UTY.GetChildObject(GameMain.Instance.gameObject, "SystemUI Root/FpsCounter", false);
  198. GameObject sysDialog =
  199. UTY.GetChildObject(GameMain.Instance.gameObject, "SystemUI Root/SystemDialog", false);
  200. GameObject sysShortcut =
  201. UTY.GetChildObject(GameMain.Instance.gameObject, "SystemUI Root/SystemShortcut", false);
  202. editUI.SetActive(false);
  203. fpsViewer.SetActive(false);
  204. sysDialog.SetActive(false);
  205. sysShortcut.SetActive(false);
  206. uiActive = false;
  207. // TODO: Hide cubes for bg, maid, lights etc.
  208. List<Meido> activeMeidoList = this.meidoManager.ActiveMeidoList;
  209. bool[] isIK = new bool[activeMeidoList.Count];
  210. bool[] isVisible = new bool[activeMeidoList.Count];
  211. for (int i = 0; i < activeMeidoList.Count; i++)
  212. {
  213. Meido meido = activeMeidoList[i];
  214. isIK[i] = meido.IK;
  215. isVisible[i] = meido.Maid.Visible;
  216. if (meido.IK) meido.IK = false;
  217. if (args.HideMaids) meido.Maid.Visible = false;
  218. }
  219. GizmoRender.UIVisible = false;
  220. yield return new WaitForEndOfFrame();
  221. // Take Screenshot
  222. int[] defaultSuperSize = new[] { 1, 2, 4 };
  223. int selectedSuperSize = args.SuperSize < 1
  224. ? defaultSuperSize[(int)GameMain.Instance.CMSystem.ScreenShotSuperSize]
  225. : args.SuperSize;
  226. string path = string.IsNullOrEmpty(args.Path)
  227. ? Utility.ScreenshotFilename()
  228. : args.Path;
  229. Application.CaptureScreenshot(path, selectedSuperSize);
  230. GameMain.Instance.SoundMgr.PlaySe("se022.ogg", false);
  231. yield return new WaitForEndOfFrame();
  232. // Show UI and dragpoints
  233. uiActive = true;
  234. editUI.SetActive(true);
  235. fpsViewer.SetActive(GameMain.Instance.CMSystem.ViewFps);
  236. sysDialog.SetActive(true);
  237. sysShortcut.SetActive(true);
  238. for (int i = 0; i < activeMeidoList.Count; i++)
  239. {
  240. Meido meido = activeMeidoList[i];
  241. if (isIK[i]) meido.IK = true;
  242. if (args.HideMaids && isVisible[i]) meido.Maid.Visible = true;
  243. }
  244. GizmoRender.UIVisible = true;
  245. }
  246. private void OnGUI()
  247. {
  248. if (uiActive)
  249. {
  250. windowManager.DrawWindows();
  251. if (DropdownHelper.Visible) DropdownHelper.HandleDropdown();
  252. }
  253. }
  254. private void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
  255. {
  256. currentScene = (Constants.Scene)scene.buildIndex;
  257. if (active) Deactivate();
  258. ResetCalcNearClip();
  259. }
  260. private void Initialize()
  261. {
  262. if (initialized) return;
  263. initialized = true;
  264. meidoManager = new MeidoManager();
  265. environmentManager = new EnvironmentManager(meidoManager);
  266. messageWindowManager = new MessageWindowManager();
  267. lightManager = new LightManager();
  268. propManager = new PropManager(meidoManager);
  269. effectManager = new EffectManager();
  270. MaidSwitcherPane maidSwitcherPane = new MaidSwitcherPane(meidoManager);
  271. windowManager = new WindowManager()
  272. {
  273. [Constants.Window.Main] = new MainWindow(meidoManager)
  274. {
  275. [Constants.Window.Call] = new CallWindowPane(meidoManager),
  276. [Constants.Window.Pose] = new PoseWindowPane(meidoManager, maidSwitcherPane),
  277. [Constants.Window.Face] = new FaceWindowPane(meidoManager, maidSwitcherPane),
  278. [Constants.Window.BG] = new BGWindowPane(environmentManager, lightManager, effectManager),
  279. [Constants.Window.BG2] = new BG2WindowPane(meidoManager, propManager)
  280. },
  281. [Constants.Window.Message] = new MessageWindow(messageWindowManager)
  282. };
  283. meidoManager.BeginCallMeidos += (s, a) => uiActive = false;
  284. meidoManager.EndCallMeidos += (s, a) => uiActive = true;
  285. }
  286. private void Activate()
  287. {
  288. if (!initialized) Initialize();
  289. SetNearClipPlane();
  290. uiActive = true;
  291. active = true;
  292. meidoManager.Activate();
  293. environmentManager.Activate();
  294. propManager.Activate();
  295. lightManager.Activate();
  296. effectManager.Activate();
  297. windowManager.Activate();
  298. messageWindowManager.Activate();
  299. GameObject dailyPanel = GameObject.Find("UI Root").transform.Find("DailyPanel").gameObject;
  300. dailyPanel.SetActive(false);
  301. }
  302. private void Deactivate()
  303. {
  304. if (meidoManager.Busy) return;
  305. ResetCalcNearClip();
  306. uiActive = false;
  307. active = false;
  308. meidoManager.Deactivate();
  309. environmentManager.Deactivate();
  310. propManager.Deactivate();
  311. lightManager.Deactivate();
  312. effectManager.Deactivate();
  313. messageWindowManager.Deactivate();
  314. windowManager.Deactivate();
  315. GameObject dailyPanel = GameObject.Find("UI Root")?.transform.Find("DailyPanel")?.gameObject;
  316. dailyPanel?.SetActive(true);
  317. }
  318. private void SetNearClipPlane()
  319. {
  320. mainCamera.StopAllCoroutines();
  321. mainCamera.m_bCalcNearClip = false;
  322. mainCamera.camera.nearClipPlane = 0.01f;
  323. }
  324. private void ResetCalcNearClip()
  325. {
  326. if (mainCamera.m_bCalcNearClip) return;
  327. mainCamera.StopAllCoroutines();
  328. mainCamera.m_bCalcNearClip = true;
  329. mainCamera.Start();
  330. }
  331. }
  332. public class ScreenshotEventArgs : EventArgs
  333. {
  334. public string Path { get; set; } = string.Empty;
  335. public int SuperSize { get; set; } = -1;
  336. public bool HideMaids { get; set; } = false;
  337. }
  338. }