using System; using System.IO; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; using BepInEx.Configuration; namespace COM3D2.MeidoPhotoStudio.Plugin { using static MenuFileUtility; internal class PropManager : IManager, ISerializable { public const string header = "PROP"; public const int propDataVersion = 1000; private static readonly ConfigEntry modItemsOnly; public static bool ModItemsOnly => modItemsOnly.Value; private MeidoManager meidoManager; private static bool cubeActive = true; public static bool CubeActive { get => cubeActive; set { if (value != cubeActive) { cubeActive = value; CubeActiveChange?.Invoke(null, EventArgs.Empty); } } } private static bool cubeSmall; public static bool CubeSmall { get => cubeSmall; set { if (value != cubeSmall) { cubeSmall = value; CubeSmallChange?.Invoke(null, EventArgs.Empty); } } } private static event EventHandler CubeActiveChange; private static event EventHandler CubeSmallChange; private List doguList = new List(); public int DoguCount => doguList.Count; public event EventHandler DoguListChange; public event EventHandler DoguSelectChange; public string[] PropNameList { get { return doguList.Count == 0 ? new[] { Translation.Get("systemMessage", "noProps") } : doguList.Select(dogu => dogu.Name).ToArray(); } } public int CurrentDoguIndex { get; private set; } = 0; public DragPointDogu CurrentDogu => DoguCount == 0 ? null : doguList[CurrentDoguIndex]; static PropManager() { modItemsOnly = Configuration.Config.Bind( "Prop", "ModItemsOnly", false, "Disable waiting for and loading base game clothing" ); } public PropManager(MeidoManager meidoManager) { this.meidoManager = meidoManager; this.meidoManager.BeginCallMeidos += DetachProps; this.meidoManager.EndCallMeidos += OnEndCall; } public void Serialize(BinaryWriter binaryWriter) { binaryWriter.Write(header); binaryWriter.Write(propDataVersion); binaryWriter.Write(doguList.Count); foreach (DragPointDogu dogu in doguList) { binaryWriter.WriteVector3(dogu.MyObject.position); binaryWriter.WriteQuaternion(dogu.MyObject.rotation); binaryWriter.WriteVector3(dogu.MyObject.localScale); dogu.attachPointInfo.Serialize(binaryWriter); binaryWriter.Write(dogu.ShadowCasting); binaryWriter.Write(dogu.assetName); } } public void Deserialize(BinaryReader binaryReader) { Dictionary modToModPath = null; ClearDogu(); int version = binaryReader.ReadInt32(); int numberOfProps = binaryReader.ReadInt32(); int doguIndex = 0; for (int i = 0; i < numberOfProps; i++) { Vector3 position = binaryReader.ReadVector3(); Quaternion rotation = binaryReader.ReadQuaternion(); Vector3 scale = binaryReader.ReadVector3(); AttachPointInfo info = AttachPointInfo.Deserialize(binaryReader); bool shadowCasting = binaryReader.ReadBoolean(); string assetName = binaryReader.ReadString(); if (assetName.EndsWith(".menu") && assetName.Contains('#') && modToModPath == null) { modToModPath = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (string mod in Menu.GetModFiles()) modToModPath.Add(Path.GetFileName(mod), mod); } if (SpawnFromAssetString(assetName, modToModPath)) { DragPointDogu dogu = doguList[doguIndex++]; Transform obj = dogu.MyObject; obj.position = position; obj.rotation = rotation; obj.localScale = scale; dogu.attachPointInfo = info; dogu.ShadowCasting = shadowCasting; } } CurrentDoguIndex = 0; GameMain.Instance.StartCoroutine(DeserializeAttach()); } private System.Collections.IEnumerator DeserializeAttach() { yield return new WaitForEndOfFrame(); foreach (DragPointDogu dogu in doguList) { AttachPointInfo info = dogu.attachPointInfo; if (info.AttachPoint != AttachPoint.None) { Meido parent = meidoManager.GetMeido(info.MaidIndex); if (parent != null) { Transform obj = dogu.MyObject; Vector3 position = obj.position; Vector3 scale = obj.localScale; Quaternion rotation = obj.rotation; Transform point = parent.IKManager.GetAttachPointTransform(info.AttachPoint); dogu.MyObject.SetParent(point, true); info = new AttachPointInfo( info.AttachPoint, parent.Maid.status.guid, parent.Slot ); dogu.attachPointInfo = info; obj.position = position; obj.localScale = scale; obj.rotation = rotation; } } } } public void Activate() { CubeSmallChange += OnCubeSmall; CubeActiveChange += OnCubeActive; } public void Deactivate() { ClearDogu(); CubeSmallChange -= OnCubeSmall; CubeActiveChange -= OnCubeActive; } public void Update() { } private GameObject GetDeploymentObject() { return GameObject.Find("Deployment Object Parent") ?? new GameObject("Deployment Object Parent"); } public bool SpawnModItemProp(ModItem modItem) { GameObject dogu = MenuFileUtility.LoadModel(modItem); string name = modItem.MenuFile; if (modItem.IsOfficialMod) name = Path.GetFileName(name); if (dogu != null) AttachDragPoint(dogu, modItem.ToString(), name, new Vector3(0f, 0f, 0.5f)); return dogu != null; } public bool SpawnMyRoomProp(MyRoomItem item) { MyRoomCustom.PlacementData.Data data = MyRoomCustom.PlacementData.GetData(item.ID); GameObject dogu = GameObject.Instantiate(data.GetPrefab()); string name = Translation.Get("myRoomPropNames", item.PrefabName); if (dogu != null) { GameObject finalDogu = new GameObject(); dogu.transform.SetParent(finalDogu.transform, true); finalDogu.transform.SetParent(GetDeploymentObject().transform, false); AttachDragPoint(finalDogu, item.ToString(), name, new Vector3(0f, 0f, 0.5f)); } else Utility.LogInfo($"Could not load MyRoomCreative prop '{item.PrefabName}'"); return dogu != null; } public bool SpawnBG(string assetName) { if (assetName.StartsWith("BG_")) assetName = assetName.Substring(3); GameObject obj = GameMain.Instance.BgMgr.CreateAssetBundle(assetName) ?? Resources.Load("BG/" + assetName) ?? Resources.Load("BG/2_0/" + assetName); if (obj != null) { GameObject dogu = GameObject.Instantiate(obj); string name = Translation.Get("bgNames", assetName); dogu.transform.localScale = Vector3.one * 0.1f; AttachDragPoint(dogu, $"BG_{assetName}", name, new Vector3(0f, 0f, 0.5f)); } return obj != null; } public bool SpawnObject(string assetName) { // TODO: Add a couple more things to ignore list GameObject dogu = null; string doguName = Translation.Get("propNames", assetName, false); Vector3 doguPosition = new Vector3(0f, 0f, 0.5f); if (assetName.EndsWith(".menu")) { dogu = MenuFileUtility.LoadModel(assetName); string handItem = Utility.HandItemToOdogu(assetName); if (Translation.Has("propNames", handItem)) doguName = Translation.Get("propNames", handItem); } else if (assetName.StartsWith("mirror")) { Material mirrorMaterial = new Material(Shader.Find("Mirror")); dogu = GameObject.CreatePrimitive(PrimitiveType.Plane); Renderer mirrorRenderer = dogu.GetComponent(); mirrorRenderer.material = mirrorMaterial; mirrorRenderer.enabled = true; MirrorReflection2 mirrorReflection = dogu.AddComponent(); mirrorReflection.m_TextureSize = 2048; Vector3 localPosition = new Vector3(0f, 0.96f, 0f); dogu.transform.Rotate(dogu.transform.right, 90f); switch (assetName) { case "mirror1": dogu.transform.localScale = new Vector3(0.2f, 0.4f, 0.2f); break; case "mirror2": dogu.transform.localScale = new Vector3(0.1f, 0.4f, 0.2f); break; case "mirror3": localPosition.y = 0.85f; dogu.transform.localScale = new Vector3(0.03f, 0.18f, 0.124f); break; } dogu.transform.localPosition = localPosition; } else if (assetName.IndexOf(':') >= 0) { string[] assetParts = assetName.Split(':'); GameObject obj = GameMain.Instance.BgMgr.CreateAssetBundle(assetParts[0]) ?? Resources.Load("BG/" + assetParts[0]); try { GameObject bg = GameObject.Instantiate(obj); int num = int.Parse(assetParts[1]); dogu = bg.transform.GetChild(num).gameObject; dogu.transform.SetParent(null); GameObject.Destroy(bg); } catch { } } else { GameObject obj = GameMain.Instance.BgMgr.CreateAssetBundle(assetName) ?? Resources.Load("Prefab/" + assetName) ?? Resources.Load("BG/" + assetName); try { dogu = GameObject.Instantiate(obj); dogu.transform.localPosition = Vector3.zero; MeshRenderer[] meshRenderers = dogu.GetComponentsInChildren(); for (int i = 0; i < meshRenderers.Length; i++) { if (meshRenderers[i] != null && meshRenderers[i].gameObject.name.ToLower().IndexOf("castshadow") < 0 ) meshRenderers[i].shadowCastingMode = ShadowCastingMode.Off; } Collider collider = dogu.transform.GetComponent(); if (collider != null) collider.enabled = false; foreach (Transform transform in dogu.transform) { collider = transform.GetComponent(); if (collider != null) { collider.enabled = false; } } } catch { } #region particle system experiment // if (asset.StartsWith("Particle/")) // { // ParticleSystem particleSystem = go.GetComponent(); // if (particleSystem != null) // { // ParticleSystem.MainModule main; // main = particleSystem.main; // main.loop = true; // main.duration = Mathf.Infinity; // ParticleSystem[] particleSystems = particleSystem.GetComponents(); // foreach (ParticleSystem part in particleSystems) // { // ParticleSystem.EmissionModule emissionModule = part.emission; // ParticleSystem.Burst[] bursts = new ParticleSystem.Burst[emissionModule.burstCount]; // emissionModule.GetBursts(bursts); // for (int i = 0; i < bursts.Length; i++) // { // bursts[i].cycleCount = Int32.MaxValue; // } // emissionModule.SetBursts(bursts); // main = part.main; // main.loop = true; // main.duration = Mathf.Infinity; // } // } // } #endregion } if (dogu != null) { AttachDragPoint(dogu, assetName, doguName, doguPosition); return true; } else { Utility.LogInfo($"Could not spawn object '{assetName}'"); } return false; } private bool SpawnFromAssetString(string assetName, Dictionary modDict = null) { bool result = false; if (assetName.EndsWith(".menu")) { if (assetName.Contains('#')) { string[] assetParts = assetName.Split('#'); string menuFile = modDict == null ? Menu.GetModPathFileName(assetParts[0]) : modDict[assetParts[0]]; ModItem item = ModItem.OfficialMod(menuFile); item.BaseMenuFile = assetParts[1]; result = SpawnModItemProp(item); } else if (assetName.StartsWith("handitem")) result = SpawnObject(assetName); else result = SpawnModItemProp(ModItem.Mod(assetName)); } else if (assetName.StartsWith("MYR_")) { string[] assetParts = assetName.Split('#'); int id = int.Parse(assetParts[0].Substring(4)); string prefabName; if (assetParts.Length == 2 && !string.IsNullOrEmpty(assetParts[1])) prefabName = assetParts[1]; else { // deserialize modifiedMM and maybe MM 23.0+. MyRoomCustom.PlacementData.Data data = MyRoomCustom.PlacementData.GetData(id); prefabName = !string.IsNullOrEmpty(data.resourceName) ? data.resourceName : data.assetName; } result = SpawnMyRoomProp(new MyRoomItem() { ID = id, PrefabName = prefabName }); } else if (assetName.StartsWith("BG_")) result = SpawnBG(assetName); else result = SpawnObject(assetName); return result; } private void AttachDragPoint(GameObject dogu, string assetName, string name, Vector3 position) { // TODO: Figure out why some props aren't centred properly // Doesn't happen in MM but even after copy pasting the code, it doesn't work :/ dogu.name = name; dogu.transform.position = position; DragPointDogu dragDogu = DragPoint.Make(PrimitiveType.Cube, Vector3.one * 0.12f); dragDogu.Initialize(() => dogu.transform.position, () => Vector3.zero); dragDogu.Set(dogu.transform); dragDogu.AddGizmo(scale: 0.45f, mode: CustomGizmo.GizmoMode.World); dragDogu.ConstantScale = true; dragDogu.Delete += DeleteDogu; dragDogu.Select += SelectDogu; dragDogu.DragPointScale = CubeSmall ? DragPointGeneral.smallCube : 1f; dragDogu.assetName = assetName; doguList.Add(dragDogu); OnDoguListChange(); } public void SetCurrentDogu(int doguIndex) { if (doguIndex >= 0 && doguIndex < DoguCount) { this.CurrentDoguIndex = doguIndex; this.DoguSelectChange?.Invoke(this, EventArgs.Empty); } } public void RemoveDogu(int doguIndex) { if (doguIndex >= 0 && doguIndex < DoguCount) { DestroyDogu(doguList[doguIndex]); doguList.RemoveAt(doguIndex); CurrentDoguIndex = Utility.Bound(CurrentDoguIndex, 0, DoguCount - 1); OnDoguListChange(); } } public void CopyDogu(int doguIndex) { if (doguIndex >= 0 && doguIndex < DoguCount) { SpawnFromAssetString(doguList[doguIndex].assetName); } } public void AttachProp( int doguIndex, AttachPoint attachPoint, Meido meido, bool worldPositionStays = true ) { if (doguList.Count == 0 || doguIndex >= doguList.Count || doguIndex < 0) return; AttachProp(doguList[doguIndex], attachPoint, meido, worldPositionStays); } private void AttachProp( DragPointDogu dragDogu, AttachPoint attachPoint, Meido meido, bool worldPositionStays = true ) { GameObject dogu = dragDogu.MyGameObject; Transform attachPointTransform = meido?.IKManager.GetAttachPointTransform(attachPoint); // ?? GetDeploymentObject().transform; dragDogu.attachPointInfo = new AttachPointInfo( attachPoint: meido == null ? AttachPoint.None : attachPoint, maidGuid: meido == null ? String.Empty : meido.Maid.status.guid, maidIndex: meido == null ? -1 : meido.Slot ); Vector3 position = dogu.transform.position; Quaternion rotation = dogu.transform.rotation; Vector3 scale = dogu.transform.localScale; dogu.transform.SetParent(attachPointTransform, worldPositionStays); if (worldPositionStays) { dogu.transform.position = position; dogu.transform.rotation = rotation; } else { dogu.transform.localPosition = Vector3.zero; dogu.transform.rotation = Quaternion.identity; } dogu.transform.localScale = scale; if (meido == null) Utility.FixGameObjectScale(dogu); } private void DetachProps(object sender, EventArgs args) { foreach (DragPointDogu dogu in doguList) { if (dogu.attachPointInfo.AttachPoint != AttachPoint.None) { dogu.MyObject.SetParent(null, /*GetDeploymentObject().transform*/ true); } } } private void ClearDogu() { for (int i = DoguCount - 1; i >= 0; i--) { DestroyDogu(doguList[i]); } doguList.Clear(); CurrentDoguIndex = 0; } private void OnEndCall(object sender, EventArgs args) => ReattachProps(useGuid: true); private void ReattachProps(bool useGuid, bool forceStay = false) { foreach (DragPointDogu dragDogu in doguList) { AttachPointInfo info = dragDogu.attachPointInfo; Meido meido = useGuid ? this.meidoManager.GetMeido(info.MaidGuid) : this.meidoManager.GetMeido(info.MaidIndex); bool worldPositionStays = forceStay || meido == null; AttachProp(dragDogu, dragDogu.attachPointInfo.AttachPoint, meido, worldPositionStays); } } private void DeleteDogu(object sender, EventArgs args) { DragPointDogu dogu = (DragPointDogu)sender; RemoveDogu(doguList.FindIndex(dragDogu => dragDogu == dogu)); } private void DestroyDogu(DragPointDogu dogu) { if (dogu == null) return; dogu.Delete -= DeleteDogu; dogu.Select -= SelectDogu; GameObject.Destroy(dogu.gameObject); } private void SelectDogu(object sender, EventArgs args) { DragPointDogu dogu = (DragPointDogu)sender; SetCurrentDogu(doguList.IndexOf(dogu)); } private void OnCubeSmall(object sender, EventArgs args) { foreach (DragPointDogu dogu in doguList) { dogu.DragPointScale = CubeSmall ? DragPointGeneral.smallCube : 1f; } } private void OnCubeActive(object sender, EventArgs args) { foreach (DragPointDogu dragPoint in doguList) { dragPoint.gameObject.SetActive(CubeActive); } } private void OnDoguListChange() { this.DoguListChange?.Invoke(this, EventArgs.Empty); } } }