Files
test/Assets/PerfectWorld/Scripts/Task/ATaskTemplMan.cs
T

1467 lines
52 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BrewMonster;
using CSNetwork.GPDataType;
using Cysharp.Threading.Tasks;
using ModelRenderer.Scripts.GameData;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace BrewMonster.Scripts.Task
{
/// <summary>
/// contains and manages all task templates
/// init in EC_Game
/// </summary>
[Serializable]
public class ATaskTemplMan
{
public int TaskLoadedCount => m_TaskTemplMap.Count;
public const ulong TASK_PACK_MAGIC = 0x93858361;
public const ulong _task_templ_cur_version = 121;
private const int DYN_TASK_CUR_VERSION = 10;
private const int DYN_TASK_VERIFY_SVR = 8711;
private const int DYN_TASK_DELIVER_SVR = 8889;
private const int TASK_NPC_INFO_VERSION = 1;
private const int SEEK_SET = 0; /* set file offset to offset */
private const int SEEK_CUR = 1; /* set file offset to current plus offset */
private const int SEEK_END = 2; /* set file offset to EOF plus offset */
private ulong g_ulNewCount = 0;// do we need this? // MH: I think not, it look like a debug counter
private Dictionary<ulong, ATaskTempl> m_TaskTemplMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_AllTemplMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_DynTaskMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_TitleTaskMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_ExlusiveAwardTaskMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_ProtectNPCMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_AutoDelvMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_DeathTrigMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<ulong, ATaskTempl> m_PQTemplMap = new Dictionary<ulong, ATaskTempl>();
private Dictionary<uint, uint> m_StorageEssenseMap = new Dictionary<uint, uint>();
private Dictionary<uint, uint> m_WeightEssenseMap = new Dictionary<uint, uint>();
private Dictionary<int, int> m_StorageTaskMap = new Dictionary<int, int>();
private List<ATaskTempl> m_SkillTaskLst = new List<ATaskTempl>();
private List<ATaskTempl> m_TmLmtChkLst = new List<ATaskTempl>();
private List<ATaskTempl> m_TasksCanSeekOut = new List<ATaskTempl>();
private elementdataman m_pEleDataMan;
// Dictionary<uint, ATaskTempl> m_DynTaskMap = new ();
uint m_ulDynTasksTimeMark;
byte[] m_pDynTasksData;
uint m_ulDynTasksDataSize;
byte[] m_pNPCInfoData;
uint m_ulNPCInfoDataSize;
uint m_ulNPCInfoTimeMark;
private Dictionary<uint, NPC_INFO> m_NPCInfoMap = new();
// Lookup NPC/task object coordinates info by template id (loaded from task_npc pack)
// 通过模板ID查找NPC/任务对象坐标信息(从task_npc包加载)
public bool TryGetTaskNPCInfo(uint id, out NPC_INFO info)
{
return m_NPCInfoMap.TryGetValue(id, out info);
}
private TaskTemplContainerSO _taskTemplContainerSO;
#if _TASK_CLIENT
// char m_szDynPackPath[512];
private string m_szDynPackPath;
bool m_bDynTasksVerified;
protected special_award m_SpecialAward;
#endif
public void Release()
{
}
public void Init(elementdataman pMan)
{
m_pEleDataMan = pMan;
}
public async UniTask<bool> LoadTasksFromPack(string address, bool bLoadDescript, Action<float> onProgress, CancellationToken token)
{
bool wasLoaded = await LoadTaskTemplFromSO();
if (wasLoaded)
{
BMLogger.Log($" [ATaskTemplMan] Loaded task templates from ScriptableObject.");
onProgress?.Invoke(1f);
return true;
}
var handle = await AddressableManager.Instance.LoadTextAssetAsync(address);
byte[] data = handle.bytes;
List<ATaskTempl> loadedTasks;
// thread-safe capture
Action<float> workerReport = p =>
{
// workerProgress = p;
};
try
{
// background thread
loadedTasks = await UniTask.RunOnThreadPool(() => LoadTasksFromPack_Internal(data, onProgress, token), cancellationToken: token);
}
catch (OperationCanceledException)
{
// ❗ Đây là cancel bình thường → KHÔNG log error
Debug.Log("LoadTasksFromPack canceled");
return false;
}
catch (Exception e)
{
Debug.LogException(e);
return false;
}
Debug.Log($" Starting to load {loadedTasks.Count} task templates...");
// int batch = 0;
// foreach (var templ in loadedTasks)
// {
// AddOneTaskTempl(templ);
// g_ulNewCount++;
//
// // avoid frame spike
// if (++batch >= 20)
// {
// batch = 0;
// await UniTask.Yield();
// }
// }
int count = loadedTasks.Count;
for (int i = 0; i < count; i++)
{
AddOneTaskTempl(loadedTasks[i]);
g_ulNewCount++;
onProgress?.Invoke(0.8f + (i + 1) / (float)count * 0.2f);
if (i % 20 == 0)
await UniTask.Yield();
}
onProgress?.Invoke(1f);
Debug.Log($" Finished loading {m_TaskTemplMap.Count} task templates.");
#if !_TASK_CLIENT
UpdateTimeLimitCheckList();
#else
SortTasksCanSeekOut();
#endif
return true;
}
/* public bool LoadTasksFromPackNoAsyn(string szPackPath, bool bLoadDescript)
{
//TaskInterface::WriteLog(0, 0, 2, "LoadPack begin");
BMLogger.Log("[Dat]- szPackPath: " + szPackPath);
if (!File.Exists(szPackPath))
{
BMLogger.LogError("[Dat]- File not found: " + szPackPath);
return false;
}
long readBytes = 0;
FileStream fs = new FileStream(szPackPath, FileMode.Open, FileAccess.Read);
TASK_PACK_HEADER tph = AAssit.ReadFromBinaryOf<TASK_PACK_HEADER>(fs, ref readBytes);
if (tph.magic != TASK_PACK_MAGIC
|| tph.version != _task_templ_cur_version)
return false;
if (tph.item_count == 0) return true;
uint[] pOffs = new uint[tph.item_count];
g_ulNewCount++;
// fread(pOffs, sizeof(long), tph.item_count, fp);
// read File and prepare offset array before loading tasks
pOffs = AAssit.ReadArrayFromBinary<uint>(fs, (int)tph.item_count, ref readBytes);
Debug.Log((int)tph.item_count);
//BMLogger.Log($" [MH] Task File Lenght: {fs.Length}");
// for (int i = 2058; i < 2059; i++) //TODO: tph.item_count
Debug.Log($" Starting to load {tph.item_count} task templates...");
for (int i = 0; i < tph.item_count; i++)
{
// mvoe file pointer to task offset
fs.Seek(pOffs[i], SeekOrigin.Begin);
// BMLogger.Log(" [MH] Loading Task Templ at offset: " + pOffs[i]);
ATaskTempl pTempl = new ATaskTempl();
g_ulNewCount++;
// Debug.Log($"Task Index {i}: Attempting to load task template...");
if (!pTempl.LoadFromBinFile(fs))
{
CECTaskInterface.WriteLog(0, (int)pTempl.m_FixedData.m_ID, 0, "Cant Load Task");
// LOG_DELETE(pTempl);
continue;
}
AddOneTaskTempl(pTempl);
// TaskInterface::WriteLog(0, pTempl->m_ID, 2, "LoadTask");
}
Debug.Log($" Finished loading {m_TaskTemplMap.Count} task templates.");
// // char log[1024];
// // sprintf(log, "LoadTask, Count = %d", m_TaskTemplMap.size());
// // TaskInterface::WriteLog(0, 0, 2, log);
// //todo: check
// // LOG_DELETE_ARR(pOffs);
fs.Close();
#if !_TASK_CLIENT
UpdateTimeLimitCheckList();
#else
SortTasksCanSeekOut();
#endif
#if _ELEMENTCLIENT
// TODO: implement task error logging if needed
// _task_err.Release();
// _task_err.Init("Configs\\task_err.txt", true);
#endif
return true;
}*/
private static List<ATaskTempl> LoadTasksFromPack_Internal(byte[] data, Action<float> onProgress, CancellationToken token)
{
long readBytes = 0;
var tasks = new List<ATaskTempl>();
using var fs = new MemoryStream(data);
TASK_PACK_HEADER tph =
AAssit.ReadFromBinaryOf<TASK_PACK_HEADER>(fs, ref readBytes);
if (tph.magic != TASK_PACK_MAGIC ||
tph.version != _task_templ_cur_version)
throw new Exception("Invalid task pack header");
if (tph.item_count == 0)
return tasks;
uint[] pOffs =
AAssit.ReadArrayFromBinary<uint>(fs, (int)tph.item_count, ref readBytes);
const float TASK_LOAD_WEIGHT = 0.8f;
for (int i = 0; i < tph.item_count; i++)
{
if (token.IsCancellationRequested)
return tasks;
// percentCount += percent;
float progress =
((i + 1) / (float)tph.item_count) * TASK_LOAD_WEIGHT;
// LoadingSceneController.Instance.UpdateUI(percentCount);
// Debug.LogError($"pc: {percentCount}");
fs.Seek(pOffs[i], SeekOrigin.Begin);
ATaskTempl templ = new ATaskTempl();
if (!templ.LoadFromBinFile(fs))
{
onProgress?.Invoke(progress);
continue;
}
onProgress?.Invoke(progress);
tasks.Add(templ);
}
return tasks;
}
// Add this method to ATaskTemplMan class if not exists
public List<ATaskTempl> GetAllTopTasks()
{
List<ATaskTempl> topTasks = new List<ATaskTempl>();
// Iterate through all task templates and collect top-level tasks
// Assuming you have a dictionary or collection of all tasks
foreach (var kvp in m_TaskTemplMap) // Replace with your actual collection name
{
var templ = kvp.Value;
if (templ != null && templ.m_pParent == null) // Top-level task has no parent
{
topTasks.Add(templ);
}
}
return topTasks;
}
// Alternative method if you want to check by NPC ID directly
public List<ATaskTempl> GetAvailableTasksForNPC(int npcID)
{
List<ATaskTempl> availableTasks = new List<ATaskTempl>();
foreach (var kvp in m_TaskTemplMap) // Replace with your actual collection name
{
var templ = kvp.Value;
if (templ != null &&
templ.m_pParent == null &&
templ.m_FixedData.m_ulDelvNPC == npcID)
{
availableTasks.Add(templ);
}
}
return availableTasks;
}
// Add this method to ATaskTemplMan class
/// <summary>
/// Get all task templates (for iterating to find available tasks)
/// Lấy tất cả task templates (để duyệt tìm nhiệm vụ có thể nhận)
/// </summary>
public List<ATaskTempl> GetAllTaskTemplates()
{
List<ATaskTempl> allTasks = new List<ATaskTempl>();
// Assuming you have a collection storing all task templates
// Replace m_TaskMap with your actual collection name
if (m_TaskTemplMap != null)
{
foreach (var kvp in m_TaskTemplMap)
{
if (kvp.Value != null)
{
allTasks.Add(kvp.Value);
}
}
}
return allTasks;
}
/// <summary>
/// Get list of tasks delivered by a specific NPC
/// </summary>
public List<ATaskTempl> GetTasksFromNPC(int npcID)
{
var result = new List<ATaskTempl>();
// Check if NPC info map is available
if (m_NPCInfoMap != null && m_NPCInfoMap.TryGetValue((uint)npcID, out var npcInfo))
{
return new List<ATaskTempl>();
}
else
{
// Fallback
foreach (var kvp in m_TaskTemplMap)
{
var templ = kvp.Value;
if (templ != null && templ.m_FixedData.m_ulDelvNPC == npcID)
{
result.Add(templ);
}
}
}
return result;
}
// General method to read a struct from a FileStream
private T ReadStruct<T>(FileStream stream) where T : struct
{
int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
byte[] buffer = new byte[size];
int bytesRead = stream.Read(buffer, 0, size);
if (bytesRead != size)
throw new EndOfStreamException("Could not read enough bytes for struct");
var handle = System.Runtime.InteropServices.GCHandle.Alloc(buffer, System.Runtime.InteropServices.GCHandleType.Pinned);
try
{
return (T)System.Runtime.InteropServices.Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
}
finally
{
handle.Free();
}
}
public async UniTask<bool> LoadNPCInfoFromPack(string address)
{
// // TODO: Implement NPC info loading if needed
// // FILE* fp = fopen(szPath, "rb");
// FileStream fp = new FileStream(szPath, FileMode.Open, FileAccess.Read);
// // if (fp == null)
// // {
// // TaskInterface::WriteLog(0, 0, 0, "LoadNPCInfoFromPack, no such file");
// // return false;
// // }
//
// // fseek(fp, 0, SEEK_END);
// // size_t sz = ftell(fp);
// // fseek(fp, 0, SEEK_SET);
// long sz = fp.Length;
//
// if (sz == 0)
// {
// BMLogger.LogError("[ATaskTemplMan] LoadNPCInfoFromPack, file size is 0");
// fp.Close();
// return false;
// }
//
// long offset = 0;
// byte[] buf = new Byte[sz];
// g_ulNewCount++;
// // fread(buf, 1, sz, fp);
// buf = AAssit.ReadArrayFromBinary<byte>( fp, (int)sz, ref offset);
// // fclose(fp);
// fp.Close();
//
// if (!UnmarshalNPCInfo(buf, (int)sz, false))
// {
// // LOG_DELETE_ARR(buf);
// return false;
// }
//
// #if _TASK_CLIENT
// // LOG_DELETE_ARR(buf);
// #else
// m_pNPCInfoData = buf;
// m_ulNPCInfoDataSize = (uint)sz;
// #endif
//
// return true;
var textAsset = await AddressableManager.Instance.LoadTextAssetAsync(address);
byte[] data = textAsset.bytes;
byte[] buf;
try
{
// 1️⃣ background thread
buf = await UniTask.RunOnThreadPool(
() => LoadNPCInfoFromPack_Internal(data)
);
}
catch (Exception e)
{
Debug.LogException(e);
return false;
}
// 2️⃣ main thread (Unity-safe)
g_ulNewCount++;
if (!UnmarshalNPCInfo(buf, buf.Length, false))
return false;
#if _TASK_CLIENT
// nothing to store
#else
m_pNPCInfoData = buf;
m_ulNPCInfoDataSize = (uint)buf.Length;
#endif
return true;
}
private static byte[] LoadNPCInfoFromPack_Internal(byte[] data)
{
using var fs = new MemoryStream(data);
long sz = fs.Length;
if (sz == 0)
throw new Exception("NPC info file size is 0");
long offset = 0;
return AAssit.ReadArrayFromBinary<byte>(
fs,
(int)sz,
ref offset
);
}
public async UniTask<bool> VerifyDynTasksPack(string szPath)
{
// // TODO: Implement dynamic task pack verification if needed
// // strcpy(m_szDynPackPath, szPath);
// m_szDynPackPath = szPath;
//
//
// // FILE* fp = fopen(szPath, "rb");
// FileStream fp = new FileStream(szPath, FileMode.Open, FileAccess.Read);
// // if (fp == NULL) return;
//
// // C++
// // fseek(fp, 0, SEEK_END);
// // size_t sz = ftell(fp);
// // fseek(fp, 0, SEEK_SET);
//
// // C#
// long offset = 0;
// long sz = fp.Length;
//
// int header_sz = Marshal.SizeOf<DYN_TASK_PACK_HEADER>();
//
// if (sz < header_sz)
// {
// // fclose(fp);
// fp.Close();
// return;
// }
//
// // C++
// // char* buf = new char[header_sz];
// // g_ulNewCount++;
// // fread(buf, 1, header_sz, fp);
// // fclose(fp);
//
// byte[] buf = new byte[header_sz];
// g_ulNewCount++;
// buf = AAssit.ReadArrayFromBinary<byte>(fp, header_sz, ref offset);
// fp.Close();
//
// UnmarshalDynTasks(buf, header_sz, true);
// // LOG_DELETE_ARR(buf);
// store path on main thread
m_szDynPackPath = szPath;
var textAsset = await AddressableManager.Instance.LoadTextAssetAsync(szPath);
byte[] data = textAsset.bytes;
byte[] headerBuf;
try
{
// 1️⃣ background thread
headerBuf = await UniTask.RunOnThreadPool(
() => VerifyDynTasksPack_Internal(data)
);
}
catch (Exception e)
{
Debug.LogException(e);
return false;
}
if (headerBuf == null)
return false;
// 2️⃣ main thread
g_ulNewCount++;
UnmarshalDynTasks(headerBuf, headerBuf.Length, true);
return true;
}
private static byte[] VerifyDynTasksPack_Internal(byte[] data)
{
using var fs = new MemoryStream(data);
long sz = fs.Length;
int headerSize = Marshal.SizeOf<DYN_TASK_PACK_HEADER>();
if (sz < headerSize)
return null;
long offset = 0;
return AAssit.ReadArrayFromBinary<byte>(
fs,
headerSize,
ref offset
);
}
public ATaskTempl GetTopTaskByID(uint ulID)
{
if (m_TaskTemplMap.TryGetValue(ulID, out ATaskTempl task))
{
return task;
}
return null;
}
public ATaskTempl GetTaskTemplByID(uint ulID)
{
if (m_AllTemplMap.TryGetValue((uint)ulID, out ATaskTempl task))
{
return task;
}
return null;
}
public bool CanGiveUpTask(uint ulTaskId)
{
var pTempl = GetTaskTemplByID(ulTaskId);
if (pTempl == null) return false;
pTempl = pTempl.GetTopTask();
return pTempl.m_FixedData.m_bCanGiveUp;
}
private void AddOneTaskTempl(ATaskTempl pTask)
{
if (m_TaskTemplMap.ContainsKey(pTask.m_FixedData.m_ID))
{
CECTaskInterface.WriteLog(0, (int)pTask.m_FixedData.m_ID, 0, "Dup Task Found");
// Optionally log duplicate task found, e.g.:
BMLogger.LogError($"Duplicate Task Found: {pTask.m_ID}");
return;
}
m_TaskTemplMap[pTask.m_ID] = pTask;
if (pTask.m_FixedData.m_bDeathTrig) m_DeathTrigMap[pTask.m_FixedData.m_ID] = pTask;
else if (pTask.m_FixedData.m_bAutoDeliver) m_AutoDelvMap[pTask.m_FixedData.m_ID] = pTask;
if (pTask.m_FixedData.m_bPQTask) m_PQTemplMap[pTask.m_FixedData.m_ID] = pTask;
if (pTask.m_FixedData.m_bSkillTask) m_SkillTaskLst.Add(pTask);
//todo: recheck m_DynTaskType type
if (pTask.m_FixedData.m_DynTaskType != '\0')
{
if (m_DynTaskMap.TryGetValue(pTask.m_FixedData.m_ID, out ATaskTempl task))
{
CECTaskInterface.WriteLog(0, (int)pTask.m_FixedData.m_ID, 0, "Dup Dyn Task Found");
BMLogger.LogError($"Duplicate Dyn Task Found: {pTask.m_ID}");
}
m_DynTaskMap[pTask.m_FixedData.m_ID] = pTask;
}
if (pTask.m_FixedData.m_bDisplayInTitleTaskUI)
m_TitleTaskMap[pTask.m_FixedData.m_ID] = pTask;
if (pTask.m_FixedData.m_bAutoDeliver && pTask.m_FixedData.m_bDisplayInExclusiveUI)
m_ExlusiveAwardTaskMap[pTask.m_FixedData.m_ID] = pTask;
#if _TASK_CLIENT
if (pTask.m_FixedData.m_ulDelvNPC != 0 && pTask.m_FixedData.m_bCanSeekOut)
m_TasksCanSeekOut.Add(pTask);
#endif
AddTaskToMap(pTask);
}
private void AddTaskToMap(ATaskTempl pTempl)
{
// fill unserialize data when play game
if (_taskTemplContainerSO != null && _taskTemplContainerSO.LoadAllTasksFromSO)
{
pTempl.FillUnserializableDataWhenPlayGame(_taskTemplContainerSO);
}
if (pTempl.m_FixedData.m_enumMethod == (ulong)TaskCompletionMethod.enumTMProtectNPC && pTempl.m_FixedData.m_ulNPCToProtect > 0)
m_ProtectNPCMap[pTempl.m_FixedData.m_ulNPCToProtect] = pTempl;
m_AllTemplMap[pTempl.m_FixedData.m_ID] = pTempl;
ATaskTempl pChild = pTempl.m_pFirstChild;
while (pChild != null)
{
AddTaskToMap(pChild);
pChild = pChild.m_pNextSibling;
}
}
#if !_TASK_CLIENT
void UpdateTimeLimitCheckList()
{
m_TmLmtChkLst.Clear();
foreach (var entry in m_TaskTemplMap)
{
if (entry.Value.m_FixedData.m_ulMaxReceiver != 0)
{
m_TmLmtChkLst.Add(entry.Value);
}
}
}
#endif
public bool InitStorageTask()
{
m_StorageEssenseMap.Clear();
m_WeightEssenseMap.Clear();
DATA_TYPE dt;
//ID_SPACE.ID_SPACE_ESSENCE
foreach (var pair in m_pEleDataMan.essence_id_data_type_map)
{
if (pair.Value == DATA_TYPE.DT_NPC_TASK_OUT_SERVICE)
{
dt = pair.Value;
NPC_TASK_OUT_SERVICE pData = (NPC_TASK_OUT_SERVICE)m_pEleDataMan.get_data_ptr(pair.Key, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
if (pData.storage_id == 0) continue;
if (pData.storage_id > TaskTemplConstants.TASK_STORAGE_COUNT) return false;
if (m_StorageEssenseMap.ContainsKey(pData.storage_id)) return false;
m_StorageEssenseMap[pData.storage_id] = pData.id;
for (var i = 0; i < pData.id_tasks.Length; i++)
{
if (pData.id_tasks[i] > 0)
{
m_StorageTaskMap[(int)pData.id_tasks[i]] = (int)pData.storage_id;
}
}
}
}
// ID_SPACE_CONFIG
foreach (var pair in m_pEleDataMan.config_id_data_type_map)
{
if (pair.Value == DATA_TYPE.DT_NPC_TASK_OUT_SERVICE)
{
dt = pair.Value;
NPC_TASK_OUT_SERVICE pData = (NPC_TASK_OUT_SERVICE)m_pEleDataMan.get_data_ptr(pair.Key, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
if (pData.storage_id == 0) continue;
if (pData.storage_id > TaskTemplConstants.TASK_STORAGE_COUNT) return false;
if (m_StorageEssenseMap.ContainsKey(pData.storage_id)) return false;
m_StorageEssenseMap[pData.storage_id] = pData.id;
for (var i = 0; i < pData.id_tasks.Length; i++)
{
if (pData.id_tasks[i] > 0)
{
m_StorageTaskMap[(int)pData.id_tasks[i]] = (int)pData.storage_id;
}
}
}
}
return true;
}
#if _TASK_CLIENT
public bool IsTaskToPush(int id)
{
// TODO: Implement this method properly
// int count = m_TasksToPush.size();
// for (size_t i = 0; i < count; ++i) {
// ATaskTempl pTempl = m_TasksToPush[i].task;
// if (pTempl && (int)pTempl->m_ID == id) return true;
// }
return false;
}
// 可接任务列表 // Available tasks list
public void GetAvailableTasks(TaskInterface pPlayer, List<ATaskTempl> lst)
{
if (lst == null) return;
if (lst.Capacity < 256) lst.Capacity = 256; // 预留容量 // reserve capacity
string log = "";
int count = m_TasksCanSeekOut.Count;
for (int i = 0; i < count; i++)
{
ATaskTempl pTempl = m_TasksCanSeekOut[i];
if (pTempl == null) continue;
// 如果等级条件不满足则跳过 // Skip if level requirements are not met
if (!pTempl.CheckReachLevel(pPlayer)) continue;
// 玩家可接此任务则加入列表 // If player can accept this task, add to list
var failCode = pPlayer.CanDeliverTask(pTempl.m_FixedData.m_ID);
if (failCode == 0)
{
lst.Add(pTempl);
}
else
{
log += $"Task ID {pTempl.m_FixedData.m_ID} Fail : {failCode} \n";
}
// if (i % 1000 == 0)
// {
// Debug.Log($"--- {i % 1000} Find Available Task --- \n {log}");
// log = "";
// }
}
}
public uint GetTaskStorageId(uint id)
{
// id1ʼ
// abase::hash_map<int, int>::iterator it = m_StorageTaskMap.find(id);
// return it == m_StorageTaskMap.end() ? 0 : it->second;
return m_StorageTaskMap.ContainsKey((int)id) ? (uint)m_StorageTaskMap[(int)id] : 0;
}
public void RemoveActiveStorageTask(StorageTaskList pLst, uint id)
{
// unsigned int set_id = GetTaskTemplMan()->GetTaskStorageId(id);
uint set_id = GetTaskStorageId(id);
if (set_id > 0)
{
int start = (int)set_id - 1;
ushort[] arr = pLst.m_Storages;
for (int i = start; i < TaskTemplConstants.TASK_STORAGE_LEN; i++)
{
if (arr[i] == (ushort)id)
{
arr[i] = 0;
break;
}
}
}
}
// 占位:可接返回0,不可接返回非0 // Placeholder: return 0 if deliverable, non-zero otherwise
// private int CanDeliverTask(TaskInterface pPlayer, uint templId)
// {
// // 后续可替换为正式逻辑 // Replace with real logic later
// var impl = pPlayer;
// return (impl != null && impl.IsDeliverLegal()) ? 0 : 1;
// }
public void OnSpecialAward(special_award p, TaskInterface pTask)
{
m_SpecialAward = p;
CheckSpecialAwardMask(pTask);
}
public void CheckSpecialAwardMask(TaskInterface pTask)
{
ActiveTaskList pLst = pTask.GetActiveTaskList();
uint ulCurTime = pTask.GetCurTime();
for (int i = 0; i < TaskTemplConstants.NUM_SPECIAL_AWARD; i++)
{
if ((m_SpecialAward.special_mask & (1 << i)) != 0)
{
// const ATaskTempl* pTempl = GetTaskTemplByID(static_cast<unsigned short>(TASK_SPECIAL_AWARD[i])); C++
var pTempl = GetTaskTemplByID((uint)TaskTemplConstants.TASK_SPECIAL_AWARD[i]);
if (pTempl != null && pTempl.CheckPrerequisite(pTask, pLst, ulCurTime) == 0)
{
// _notify_svr(pTask, TASK_CLT_NOTIFY_SPECIAL_AWARD_MASK,static_cast<unsigned short>(pTempl->m_ID)); C++
_notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_SPECIAL_AWARD_MASK, (ushort)(pTempl.m_FixedData.m_ID));
}
}
}
}
private void _notify_svr(TaskInterface pTask, byte uReason, ushort uTaskID)
{
ATaskTempl._notify_svr(pTask, uReason, uTaskID);
}
static bool compare_tasks_canseekout(ATaskTempl lhs, ATaskTempl rhs)
{
if (lhs.m_FixedData.m_ulPremItems != 0 && rhs.m_FixedData.m_ulPremItems == 0) return true;
else if (lhs.m_FixedData.m_ulPremItems == 0 && rhs.m_FixedData.m_ulPremItems != 0) return false;
else if (lhs.m_FixedData.m_ulPremItems != 0 && rhs.m_FixedData.m_ulPremItems != 0) return lhs.m_FixedData.m_ID > rhs.m_FixedData.m_ID;
else return lhs.m_FixedData.m_ulPremise_Lev_Min > rhs.m_FixedData.m_ulPremise_Lev_Min;
}
/// <summary>
/// Comparator tương đương C++ version
/// </summary>
private static int CompareTasksCanSeekOut(ATaskTempl lhs, ATaskTempl rhs)
{
// Rule 1: Ưu tiên task có PremItems
bool lhsHasPrem = lhs.m_FixedData.m_ulPremItems != 0;
bool rhsHasPrem = rhs.m_FixedData.m_ulPremItems != 0;
if (lhsHasPrem && !rhsHasPrem) return -1; // lhs lên trước
if (!lhsHasPrem && rhsHasPrem) return 1; // rhs lên trước
// Rule 2: Nếu cả hai có PremItems → sắp theo ID giảm dần
if (lhsHasPrem && rhsHasPrem)
return rhs.m_FixedData.m_ID.CompareTo(lhs.m_FixedData.m_ID); // ID lớn đứng trước
// Rule 3: Nếu không có PremItems → LevelMin giảm dần
return rhs.m_FixedData.m_ulPremise_Lev_Min.CompareTo(lhs.m_FixedData.m_ulPremise_Lev_Min);
}
void SortTasksCanSeekOut()
{
// std::sort(m_TasksCanSeekOut.begin(), m_TasksCanSeekOut.end(), compare_tasks_canseekout);
m_TasksCanSeekOut.Sort(CompareTasksCanSeekOut);
}
public void CheckAutoDelv(TaskInterface pTask)
{
ATaskTempl pTempl = null;
// TaskTemplMap::iterator it = m_AutoDelvMap.begin();
var it = m_AutoDelvMap[0];
uint ulCurTime = pTask.GetCurTime();
ActiveTaskList pLst = pTask.GetActiveTaskList();
#if _ELEMENTLOCALIZE
#if _ELEMENTCLIENT
// if (CECCommandLine::GetBriefConfig(_AL("noautodelivertask"))) return;
#endif
#endif
// C++
// for (; it != m_AutoDelvMap.end(); ++it)
// {
// pTempl = it->second;
//
// if (!pTempl->IsValidState())
// continue;
//
// if (pTempl->CheckPrerequisite(pTask, pLst, ulCurTime) == 0)
// {
// pTempl->IncValidCount();
// _notify_svr(pTask, TASK_CLT_NOTIFY_AUTO_DELV, static_cast<unsigned short>(pTempl->m_ID));
// }
// }
foreach (var key in m_AutoDelvMap.Keys)
{
pTempl = m_AutoDelvMap[key];
if (!pTempl.IsValidState())
continue;
if (pTempl.CheckPrerequisite(pTask, pLst, ulCurTime) == 0)
{
pTempl.IncValidCount();
_notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_AUTO_DELV, (ushort)(pTempl.m_FixedData.m_ID));
}
}
}
public void UpdateStatus(TaskInterface pTask)
{
// ΪCheckTitleƺݣֱȡƺ֮ǰܵ
if (!pTask.IsTitleDataReady()) return;
CheckAutoDelv(pTask);
// TODO: Implement other checks as needed
// if (CECUIConfig::Instance().GetGameUI().bEnableTitle)
// CheckTitleTask(pTask);
// UpdateTasksSeekOutDiff(pTask);
}
// extarn from TaskServer
#endif
#if !_TASK_CLIENT
private void OnTaskGiveUpOneTask(TaskInterface pTask, uint ulTaskId, bool bForce)
{
TaskServer.OnTaskGiveUpOneTask(pTask, ulTaskId, bForce);
}
#endif
public void OnForgetLivingSkill(TaskInterface pTask)
{
// FinishedTaskList* pList = static_cast<FinishedTaskList*>(pTask->GetFinishedTaskList()); // C++
FinishedTaskList pList = pTask.GetFinishedTaskList();
for (int i = 0; i < m_SkillTaskLst.Count; i++)
{
pList.RemoveTask(m_SkillTaskLst[i].GetID());
#if !_TASK_CLIENT
OnTaskGiveUpOneTask(pTask, m_SkillTaskLst[i].GetID(), false);
#endif
}
#if !_TASK_CLIENT
task_notify_base notify = new task_notify_base();
notify.reason = TaskTemplConstants.TASK_SVR_NOTIFY_FORGET_SKILL;
notify.task = 0;
pTask.NotifyClient(notify, Marshal.SizeOf<task_notify_base>());
#endif
}
// process part
#if _TASK_CLIENT
// void GetTitleTasks(TaskInterface pTask, TaskTemplLst lst);
// void GetAvailableTasks(TaskInterface* pPlayer, TaskTemplLst& lst);
// void ManualTrigTask(TaskInterface* pTask, unsigned long ulTask);
// void ForceGiveUpTask(TaskInterface* pTask, unsigned long ulTask);
// void ForceRemoveFinishTask(TaskInterface* pTask, unsigned long ulTask);
public bool IsDynTasksVerified() { return m_bDynTasksVerified; }
void SetDynTasksVerified(bool b) { m_bDynTasksVerified = b; }
public void OnDynTasksTimeMark(TaskInterface pTask, uint ulTimeMark, ushort version)
{
if (version != DYN_TASK_CUR_VERSION)
return;
if (m_ulDynTasksTimeMark == ulTimeMark && LoadDynTasksFromPack(m_szDynPackPath))
{
SetDynTasksVerified(true);
pTask.InitActiveTaskList();
UpdateDynDataNPCService();
}
else
_notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_DYN_DATA, 0);
}
public void OnDynTasksData(TaskInterface pTask, byte[] data, int sz, bool ended)
{
if (m_bDynTasksVerified)
{
// assert(false);
return;
}
uint new_sz = (uint)(sz + m_ulDynTasksDataSize);
var buf = new byte[new_sz];
g_ulNewCount++;
if (m_pDynTasksData != null && m_ulDynTasksDataSize > 0)
{
// memcpy(buf, m_pDynTasksData, m_ulDynTasksDataSize);
Buffer.BlockCopy(
m_pDynTasksData, // src
0, // src offset
buf, // dst
0, // dst offset
(int)m_ulDynTasksDataSize // bytes
);
}
// memcpy(buf + m_ulDynTasksDataSize, data, sz);
Buffer.BlockCopy(
data, // src
0, // src offset
buf, // dst
(int)m_ulDynTasksDataSize, // dst offset
sz // bytes
);
// LOG_DELETE_ARR(m_pDynTasksData);
m_pDynTasksData = buf;
m_ulDynTasksDataSize = new_sz;
if (ended)
{
// a_LogOutput(1, "[Dat Task] OnDynTasksData");
if (UnmarshalDynTasks(m_pDynTasksData, (int)m_ulDynTasksDataSize, false))
{
if (m_pDynTasksData != null && m_pDynTasksData.Length > 0)
{
try
{
using (FileStream fs = new FileStream(m_szDynPackPath, FileMode.Create, FileAccess.Write))
{
fs.Write(m_pDynTasksData, 0, (int)m_ulDynTasksDataSize);
}
}
catch (Exception ex)
{
BMLogger.LogError($"[ATaskTemplMan] Failed to write dyn tasks pack: {ex.Message}");
}
}
SetDynTasksVerified(true);
pTask.InitActiveTaskList();
UpdateDynDataNPCService();
}
// LOG_DELETE_ARR(m_pDynTasksData);
m_pDynTasksData = null;
m_ulDynTasksDataSize = 0;
}
}
// 处理存储任务数据 // Handle storage task data
public void OnStorageData(TaskInterface pTask, byte[] data)
{
// Copy data directly to the storage buffer (equivalent to C++ memcpy operations)
// 直接将数据复制到存储缓冲区(等同于C++的memcpy操作)
if (pTask is CECTaskInterface cecTask)
{
cecTask.SetStorageTaskListBuffer(data);
}
else
{
// Fallback: read into struct (but won't persist without writing back)
// 后备方案:读取到结构体(但不写回则不会持久化)
StorageTaskList pLst = pTask.GetStorageTaskList();
pLst.ReadByte(data);
}
}
// void OnSpecialAward(const special_award* p,TaskInterface* pTask);
// void VerifyDynTasksPack(const char* szPath);
// const special_award* GetSpecialAward() const { return &m_SpecialAward; }
public void ClearSpecailAward()
{
// memset(&m_SpecialAward, 0, sizeof(m_SpecialAward));
m_SpecialAward = new special_award();
}
// void SortTasksCanSeekOut();
// void UpdateTasksSeekOutDiff(TaskInterface* pTask);
// bool IsTaskToPush(int id);
// void ClearTasksToPush() { m_TasksToPush.clear(); }
// bool HasTaskToPush() { return !m_TasksToPush.empty(); }
void UpdateDynDataNPCService()
{
// assert(m_pEleDataMan);
DATA_TYPE dt = default;
NPC_TASK_IN_SERVICE service = (NPC_TASK_IN_SERVICE)m_pEleDataMan.get_data_ptr(
DYN_TASK_VERIFY_SVR,
ID_SPACE.ID_SPACE_ESSENCE,
ref dt
);
if (dt != DATA_TYPE.DT_NPC_TASK_IN_SERVICE)
{
BMLogger.LogError($"UpdateDynDataNPCService, wrong service, dt = {dt}");
return;
}
NPC_TASK_OUT_SERVICE deliver = (NPC_TASK_OUT_SERVICE)m_pEleDataMan.get_data_ptr(
DYN_TASK_DELIVER_SVR,
ID_SPACE.ID_SPACE_ESSENCE,
ref dt
);
if (dt != DATA_TYPE.DT_NPC_TASK_OUT_SERVICE)
{
BMLogger.LogError($"UpdateDynDataNPCService, wrong service, dt = {dt}");
return;
}
deliver.id_tasks ??= new uint[256];
service.id_tasks ??= new uint[256];
deliver.id_tasks.AsSpan().Clear();
service.id_tasks.AsSpan().Clear();
foreach (var kv in m_DynTaskMap)
{
ATaskTempl p = kv.Value;
if (p != null && p.m_FixedData.m_DynTaskType != (byte)DynTaskType.enumDTTGiftCard)
{
mount_task_out_service(p, ref deliver);
mount_task_in_service(p, ref service);
}
}
}
#else
void CheckDeathTrig(TaskInterface* pTask);
void OnTaskCheckAllTimeLimits(unsigned long ulCurTime);
void OnTaskGetDynTasksTimeMark(TaskInterface* pTask);
void OnTaskGetDynTasksData(TaskInterface* pTask);
void OnTaskGetSpecialAward(TaskInterface* pTask);
void OnTaskRemoveFinishTask(TaskInterface* pTask, unsigned long ulTask);
void OnTaskUpdateStorage(TaskInterface* pTask, unsigned long ulCurTime);
bool UpdateStorage(TaskInterface* pTask, StorageTaskList* pLst, unsigned long ulCurTime, unsigned long idStorage);
bool UpdateOneStorageDebug(TaskInterface* pTask, unsigned long ulCurTime, int idStorage, bool bUseDayAsSeed);
#endif
bool UnmarshalNPCInfo(byte[] data, int data_size, bool header_only)
{
if (data_size < Marshal.SizeOf<TASK_NPC_PACK_HEADER>())
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong size");
BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong size: {data_size} < {Marshal.SizeOf<TASK_NPC_PACK_HEADER>()}");
return false;
}
// const char* p = data;
// TASK_NPC_PACK_HEADER* header = (TASK_NPC_PACK_HEADER*)p;
// p += sizeof(TASK_NPC_PACK_HEADER);
TASK_NPC_PACK_HEADER header = GPDataTypeHelper.FromBytes<TASK_NPC_PACK_HEADER>(data);
var p = Marshal.SizeOf<TASK_NPC_PACK_HEADER>();
if (header.version != TASK_NPC_INFO_VERSION)
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong version");
BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong version: {header.version}");
return false;
}
if (header.pack_size != data_size)
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong header");
BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong header: pack_size {header.pack_size} != data_size {data_size}");
return false;
}
m_ulNPCInfoTimeMark = (uint)header.time_mark;
if (header_only)
return true;
// const NPC_INFO* pInfos = (const NPC_INFO*)p;
for (int i = 0; i < header.npc_count; i++)
{
// const NPC_INFO& info = pInfos[i];
// m_NPCInfoMap[info.id] = info;
NPC_INFO info = GPDataTypeHelper.FromBytes<NPC_INFO>(data, p);
p += Marshal.SizeOf<NPC_INFO>();
m_NPCInfoMap[info.id] = info;
}
return true;
}
bool UnmarshalDynTasks(byte[] data, int data_size, bool header_only)
{
if (data_size < Marshal.SizeOf<DYN_TASK_PACK_HEADER>())
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalDynTasks, wrong size");
BMLogger.LogError(" [ATaskTemplMan] UnmarshalDynTasks, wrong size");
return false;
}
// C++
// const char* p = data;
// DYN_TASK_PACK_HEADER* header = (DYN_TASK_PACK_HEADER*)p;
// p += sizeof(DYN_TASK_PACK_HEADER);
DYN_TASK_PACK_HEADER header = GPDataTypeHelper.FromBytes<DYN_TASK_PACK_HEADER>(data);
long p = Marshal.SizeOf<DYN_TASK_PACK_HEADER>();
if (header.version != DYN_TASK_CUR_VERSION)
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalDynTasks, wrong version");
BMLogger.LogError($" [ATaskTemplMan] UnmarshalDynTasks, wrong version: {header.version}");
return false;
}
// Only check pack_size when reading full data, not when header_only is true
// When header_only is true, we only read the header, so pack_size will be larger than data_size
if (!header_only && header.pack_size != data_size)
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalDynTasks, wrong header");
BMLogger.LogError($" [ATaskTemplMan] UnmarshalDynTasks, wrong header: pack_size {header.pack_size} != data_size {data_size}");
return false;
}
m_ulDynTasksTimeMark = (uint)header.time_mark;
if (header_only)
return true;
// BMLogger.Log($" [ATaskTemplMan] UnmarshalDynTasks, loading {header.task_count} tasks, data_size: {data_size}");
for (int i = 0; i < header.task_count; i++)
{
ATaskTempl pTempl = new ATaskTempl();
g_ulNewCount++;
pTempl.UnmarshalDynTask(data, ref p);
// BMLogger.Log($" [MH Task] after load UnmarshalDynTask[{i}] pointer at : {p - Marshal.SizeOf<DYN_TASK_PACK_HEADER>()}");
AddOneTaskTempl(pTempl);
// TaskInterface::WriteLog(0, pTempl->GetID(), 2, "LoadDynTask");
}
#if _TASK_CLIENT
SortTasksCanSeekOut();
#endif
// assert(p == data + data_size);
return true;
}
void mount_task_out_service(ATaskTempl task, ref NPC_TASK_OUT_SERVICE svr)
{
if (task.IsAutoDeliver())
return;
if (task.m_pParent != null && !task.m_pParent.m_FixedData.m_bChooseOne)
return;
uint[] tasks = svr.id_tasks;
for (int i = 0; i < tasks.Length; i++)
{
if (tasks[i] != 0)
continue;
tasks[i] = task.GetID();
break;
}
ATaskTempl child = task.m_pFirstChild;
while (child != null)
{
mount_task_out_service(child, ref svr);
child = child.m_pNextSibling;
}
}
void mount_task_in_service(ATaskTempl task, ref NPC_TASK_IN_SERVICE svr)
{
if ((TaskFinishType)task.m_FixedData.m_enumFinishType == TaskFinishType.enumTFTNPC)
{
uint[] tasks = svr.id_tasks;
for (int i = 0; i < tasks.Length; i++)
{
if (tasks[i] != 0)
continue;
tasks[i] = task.GetID();
break;
}
}
ATaskTempl child = task.m_pFirstChild;
while (child != null)
{
mount_task_in_service(child, ref svr);
child = child.m_pNextSibling;
}
}
bool LoadDynTasksFromPack(string szPath)
{
// TaskInterface::WriteLog(0, 0, 2, "LoadDynPack begin");
// FILE* fp = fopen(szPath, "rb");
FileStream fp = new FileStream(szPath, FileMode.Open, FileAccess.Read);
if (fp == null)
{
// TaskInterface::WriteLog(0, 0, 0, "LoadDynTasksFromPack, no such file");
BMLogger.LogError("[ATaskTemplMan] LoadDynTasksFromPack, no such file");
return false;
}
// fseek(fp, 0, SEEK_END);
// size_t sz = ftell(fp);
// fseek(fp, 0, SEEK_SET);
long sz = fp.Length;
if (sz == 0)
{
fp.Close();
return false;
}
// char* buf = new char[sz];
byte[] buf = new byte[sz];
g_ulNewCount++;
// fread(buf, 1, sz, fp);
long offset = 0;
buf = AAssit.ReadArrayFromBinary<byte>(fp, (int)sz, ref offset);
fp.Close();
if (!UnmarshalDynTasks(buf, (int)sz, false))
{
// LOG_DELETE_ARR(buf);
return false;
}
#if _TASK_CLIENT
// LOG_DELETE_ARR(buf);
#else
m_pDynTasksData = buf;
m_ulDynTasksDataSize = sz;
UpdateDynDataNPCService();
#endif
return true;
}
public async UniTask<bool> LoadTaskTemplFromSO()
{
const string addressKey = "Assets/PerfectWorld/SO/TaskTemplContainerSO.asset"; // set this to the real address
AsyncOperationHandle<TaskTemplContainerSO> handle = default;
try
{
handle = Addressables.LoadAssetAsync<TaskTemplContainerSO>(addressKey);
await handle.Task;
if (handle.Status != AsyncOperationStatus.Succeeded)
{
BMLogger.LogError($"[ATaskTemplMan] LoadTaskTemplFromSO failed: {handle.OperationException}");
return false;
}
var config = handle.Result;
if (config == null)
{
BMLogger.LogError("[ATaskTemplMan] LoadTaskTemplFromSO returned null result");
return false;
}
if (!config.LoadAllTasksFromSO) return false;
_taskTemplContainerSO = config;
config.BuildTaskTemplateMap();
var loadedTasks = config.TopTaskTemplates;
if (loadedTasks == null || loadedTasks.Count == 0)
{
BMLogger.LogError("[ATaskTemplMan] LoadTaskTemplFromSO, no task templates found in SO");
return false;
}
for (int i = 0; i < loadedTasks.Count; i++)
{
AddOneTaskTempl(loadedTasks[i]);
g_ulNewCount++;
}
Debug.Log($"Finished loading {m_TaskTemplMap.Count} task templates.");
return true;
}
catch (Exception e)
{
BMLogger.LogError($"[ATaskTemplMan] LoadTaskTemplFromSO exception: {e}");
return false;
}
finally
{
if (handle.IsValid())
Addressables.Release(handle);
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TASK_NPC_PACK_HEADER
{
public uint pack_size;
public int time_mark;
public short version;
public short npc_count;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DYN_TASK_PACK_HEADER
{
public uint pack_size;
public int time_mark;
public ushort version;
public ushort task_count;
};
}