using BrewMonster; using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.World; using CSNetwork; using CSNetwork.GPDataType; using CSNetwork.Protocols; using CSNetwork.Protocols.RPCData; using ModelRenderer.Scripts.Common; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; namespace PerfectWorld.Scripts.Managers { /// /// Matter Manager - Handles matter data storage and provides access to other classes /// /// Usage Examples: /// // Get matter manager instance /// var matterManager = EC_ManMessageMono.Instance.GetECManMatter; /// /// // Get specific matter data /// var matterData = matterManager.GetMatterData(12345); /// /// // Get individual fields /// int? mid = matterManager.GetMatterId(12345); /// int? tid = matterManager.GetMatterTid(12345); /// A3DVECTOR3? pos = matterManager.GetMatterPosition(12345); /// byte? state = matterManager.GetMatterState(12345); /// /// // Find matters by criteria /// int[] mattersByTid = matterManager.FindMattersByTid(100); /// int[] mattersByState = matterManager.FindMattersByState(1); /// int[] nearbyMatters = matterManager.FindMattersNearPosition(new A3DVECTOR3(0,0,0), 10.0f); /// [Serializable] public class EC_ManMatter : IMsgHandler { public int HandlerId => (int)MANAGER_INDEX.MAN_MATTER; // Storage for matter data that players can access later private Dictionary matterDataStorage = new Dictionary(); private Dictionary m_MatterTab = new Dictionary(); /// /// Unity-only recovery: ensure an existing scene matter is present in the manager table. /// This helps recover after Unity script/domain reload where Dictionaries are not serialized. /// public void RegisterExistingMatter(CECMatter matter) { if (matter == null) return; int mid = matter.GetMatterID(); if (mid == 0) return; m_MatterTab[mid] = matter; } public bool ProcessMessage(ECMSG Msg) { if (Msg.iSubID == 0) { switch ((int)Msg.dwMsg) { case int value when value == EC_MsgDef.MSG_MM_MATTERINFO: { //ENABLE LATER: It fetch all matters in the game world, causing performance issues OnMsgMatterInfo(Msg); break; } case int value when value == EC_MsgDef.MSG_MM_MATTERENTWORLD: { OnMsgMatterEnterWorld(Msg); break; } case int value when value == EC_MsgDef.MSG_MM_MATTERDISAPPEAR: { OnMsgMatterDisappear(Msg); break; } case EC_MsgDef.MSG_MM_MATTEROUTOFVIEW: OnMsgMatterOutOfView(Msg); break; case EC_MsgDef.MSG_MM_INVALIDOBJECT: OnMsgInvalidObject(Msg); break; } } else { } return true; } public async void OnMsgMatterInfo(ECMSG Msg) { byte[] data = (byte[])Msg.dwParam1; try { // Parse the data structure: count + info_matter array int offset = 0; // Read count (ushort) ushort count = BitConverter.ToUInt16(data, offset); offset += sizeof(ushort); // Parse each info_matter entry for (int i = 0; i < count; i++) { int entryOffset = offset; // Parse info_matter structure info_matter info; try { info = CSNetwork.GPDataType.GPDataTypeHelper.FromBytes(data, offset); } catch (Exception ex) { Debug.LogError( $"Failed to parse info_matter entry. index={i}/{count}, offset={entryOffset}, remaining={(data != null ? data.Length - entryOffset : 0)}. ex={ex}"); break; // buffer likely misaligned; stop to avoid cascading errors } offset += Marshal.SizeOf(typeof(info_matter)); // Store the matter data for later player access matterDataStorage[info.mid] = info; try { await MatterEnter(info); } catch (Exception ex) { TryResolveMatterTemplateDebugInfo(info.tid, out string templName, out string filePath, out string normalizedPath, out DATA_TYPE dt); Debug.LogError( $"MatterEnter failed. mid={info.mid}, tid={(info.tid & 0x0000ffff)}, name='{templName}', file='{filePath}', normalized='{normalizedPath}', dataType={dt}, index={i}/{count}, entryOffset={entryOffset}. ex={ex}"); } } } catch (Exception ex) { Debug.LogError($"Failed to parse matter info data: {ex}"); } } private static bool TryResolveMatterTemplateDebugInfo(int tid, out string name, out string filePath, out string normalizedPath, out DATA_TYPE dataType) { name = string.Empty; filePath = string.Empty; normalizedPath = string.Empty; dataType = DATA_TYPE.DT_INVALID; try { var edm = ElementDataManProvider.GetElementDataMan(); if (edm == null) return false; int templTid = tid & 0x0000ffff; object templ = edm.get_data_ptr((uint)templTid, ID_SPACE.ID_SPACE_ESSENCE, ref dataType); if (templ == null) return false; const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; var t = templ.GetType(); // Resolve template name (usually `ushort[] name` in element data) var nameField = t.GetField("name", flags) ?? t.GetField("Name", flags); if (nameField != null) { object v = nameField.GetValue(templ); if (v is ushort[] us) name = ByteToStringUtils.UshortArrayToCP936String(us); else if (v is byte[] bs) name = ByteToStringUtils.ByteArrayToCP936String(bs); else if (v != null) name = v.ToString(); } // Resolve model/matter file path var fileField = t.GetField("file_matter", flags) ?? t.GetField("file_model", flags); if (fileField != null) { object v = fileField.GetValue(templ); if (v is byte[] bs) filePath = ByteToStringUtils.ByteArrayToCP936String(bs); else if (v is ushort[] us) filePath = ByteToStringUtils.UshortArrayToCP936String(us); else if (v != null) filePath = v.ToString(); if (!string.IsNullOrWhiteSpace(filePath)) normalizedPath = AFile.NormalizePath(filePath.ToLower(), true); } return true; } catch { return false; } } public async void OnMsgMatterEnterWorld(ECMSG Msg) { byte[] data = (byte[])Msg.dwParam1; try { // Parse the byte array into info_matter structure info_matter matterInfo = CSNetwork.GPDataType.GPDataTypeHelper.FromBytes(data); await MatterEnter(matterInfo); } catch (Exception ex) { Debug.LogError($"Failed to parse matter data: {ex.Message}"); } } public void OnMsgMatterDisappear(ECMSG Msg) { byte[] data = (byte[])Msg.dwParam1; int matterId = BitConverter.ToInt32(data); MatterLeave(matterId); } private async Task MatterEnter(info_matter info) { CECMatter pMatter = GetMatter(info.mid); if (pMatter != null) return true; // Create a new matter pMatter = await CECMatter.Init(info); if (pMatter == null) { Debug.LogError($"Failed to create matter: {info.mid}"); return false; } pMatter.SetBornStamp(CECGameRun.Instance.GetWorld().GetBornStamp()); m_MatterTab[info.mid] = pMatter; return true; } // Remove a matter public void RemoveMatter(int idMatter) { MatterLeave(idMatter); } public void MatterLeave(int mid) { CECMatter pMatter = GetMatter(mid); if (pMatter != null) { //UnityEngine.Object.Destroy(pMatter.gameObject); PoolManager.Instance.Despawn(pMatter.gameObject); m_MatterTab.Remove(mid); } //TODO: Might need to implement later /*/ else { MatterTable::pair_type Pair = m_DynModelTab.get(mid); if (Pair.second) { pMatter = *Pair.first; ReleaseMatter(pMatter); // Remove it from active matter table m_DynModelTab.erase(mid); QueueMatterUndoLoad(mid); } } CECPlayerWrapper* pWrapper = CECAutoPolicy::GetInstance().GetPlayerWrapper(); if( pWrapper ) pWrapper->OnObjectDisappear(mid); //*/ } // Public methods for players to access matter data public info_matter? GetMatterData(int matterId) { return matterDataStorage.TryGetValue(matterId, out info_matter data) ? data : null; } public bool HasMatterData(int matterId) { return matterDataStorage.ContainsKey(matterId); } public CECMatter GetMatter(int mid, uint dwBornStamp = 0) { if (m_MatterTab.TryGetValue(mid, out CECMatter matter)) { if (dwBornStamp != 0) { if (matter.GetBornStamp() != dwBornStamp) { return null; } } return matter; } return null; } /// /// Nearest ground item or money matter within horizontal of . /// Mines (gather nodes) are excluded — use direct click / gather flow for those. /// public CECMatter GetNearestPickupableMatter(A3DVECTOR3 hostPos, float maxRadiusH) { CECMatter best = null; float bestDist = float.MaxValue; foreach (var kv in m_MatterTab) { CECMatter m = kv.Value; if (m == null || !m) continue; if (m.IsMine()) continue; float d = m.CalcDist(hostPos, false); if (d > maxRadiusH || d >= bestDist) continue; bestDist = d; best = m; } return best; } /// Port of CECMatterMan::FindMatterNearHost — nearest pickupable ground matter. public CECMatter FindMatterNearHost(float fRadius, bool bPickOnly = true) { CECHostPlayer host = CECGameRun.Instance?.GetHostPlayer(); if (host == null) return null; return GetNearestPickupableMatter(host.GetPos(), fRadius); } public CECMatter CreateMatter(info_matter info) { return null; } bool OnMsgMatterOutOfView(ECMSG Msg) { MatterLeave(Convert.ToInt32(Msg.dwParam1)); return true; } bool OnMsgInvalidObject(ECMSG Msg) { cmd_invalid_object pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); MatterLeave(pCmd.id); return true; } } }