351 lines
12 KiB
C#
351 lines
12 KiB
C#
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
|
|
{
|
|
/// <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(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Nearest ground item or money matter within horizontal <paramref name="maxRadiusH"/> of <paramref name="hostPos"/>.
|
|
/// Mines (gather nodes) are excluded — use direct click / gather flow for those.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
public CECMatter CreateMatter(info_matter info)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|