MeidoPhotoStudio.cs 18 KB

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