Files
test/Assets/PerfectWorld/Scripts/Managers/EC_ManMatter.cs
T
2026-01-07 16:00:59 +07:00

323 lines
11 KiB
C#

using BrewMonster;
using BrewMonster.Network;
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
{
/// <summary>
/// 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);
/// </summary>
[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<int, info_matter> matterDataStorage = new Dictionary<int, info_matter>();
private Dictionary<int, CECMatter> m_MatterTab = new Dictionary<int, CECMatter>();
/// <summary>
/// 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.
/// </summary>
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;
}
}
}
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<info_matter>(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<info_matter>(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<bool> 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(CECWorld.Instance.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);
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;
}
public CECMatter CreateMatter(info_matter info)
{
return null;
}
}
}