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
{
///
/// contains and manages all task templates
/// init in EC_Game
///
[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 m_TaskTemplMap = new Dictionary();
private Dictionary m_AllTemplMap = new Dictionary();
private Dictionary m_DynTaskMap = new Dictionary();
private Dictionary m_TitleTaskMap = new Dictionary();
private Dictionary m_ExlusiveAwardTaskMap = new Dictionary();
private Dictionary m_ProtectNPCMap = new Dictionary();
private Dictionary m_AutoDelvMap = new Dictionary();
private Dictionary m_DeathTrigMap = new Dictionary();
private Dictionary m_PQTemplMap = new Dictionary();
private Dictionary m_StorageEssenseMap = new Dictionary();
private Dictionary m_WeightEssenseMap = new Dictionary();
private Dictionary m_StorageTaskMap = new Dictionary();
private List m_SkillTaskLst = new List();
private List m_TmLmtChkLst = new List();
private List m_TasksCanSeekOut = new List();
private elementdataman m_pEleDataMan;
// Dictionary m_DynTaskMap = new ();
uint m_ulDynTasksTimeMark;
byte[] m_pDynTasksData;
uint m_ulDynTasksDataSize;
byte[] m_pNPCInfoData;
uint m_ulNPCInfoDataSize;
uint m_ulNPCInfoTimeMark;
private Dictionary m_NPCInfoMap = new();
private bool _wasLoaded = false;
// 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 LoadTasksFromPack(string address, bool bLoadDescript, Action onProgress, CancellationToken token)
{
// If loaded Task Template from last play time, skip loading again
if (_wasLoaded)
{
goto END_PROGRESS;
}
_wasLoaded = await LoadTaskTemplFromSO();
if (_wasLoaded)
{
goto END_PROGRESS;
}
var handle = await AddressableManager.Instance.LoadTextAssetAsync(address);
byte[] data = handle.bytes;
List loadedTasks;
// thread-safe capture
Action 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();
}
END_PROGRESS:
_wasLoaded = true;
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(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(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 LoadTasksFromPack_Internal(byte[] data, Action onProgress, CancellationToken token)
{
long readBytes = 0;
var tasks = new List();
using var fs = new MemoryStream(data);
TASK_PACK_HEADER tph =
AAssit.ReadFromBinaryOf(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(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;
}
public int GetStorageRefreshPerDay(uint storageId)
{
if (storageId == 0 || storageId > TaskInterfaceConstants.TASK_STORAGE_COUNT)
return 0;
if (!m_StorageEssenseMap.TryGetValue(storageId, out uint essenceId))
return 0;
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
var serviceData = m_pEleDataMan.get_data_ptr(
essenceId,
ID_SPACE.ID_SPACE_ESSENCE,
ref dt
);
if (serviceData != null && dt == DATA_TYPE.DT_NPC_TASK_OUT_SERVICE)
{
var outService = (NPC_TASK_OUT_SERVICE)serviceData;
return (int)outService.storage_refresh_per_day;
}
return 0;
}
// General method to read a struct from a FileStream
private T ReadStruct(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 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( 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(
fs,
(int)sz,
ref offset
);
}
public async UniTask 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();
//
// 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(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();
if (sz < headerSize)
return null;
long offset = 0;
return AAssit.ReadArrayFromBinary(
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 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)
{
// id��1��ʼ
// abase::hash_map::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(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(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;
}
///
/// Comparator tương đương C++ version
///
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;
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(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������ƺ����ݣ�����ֱ����ȡ���ƺ�����֮ǰ�����ܵ���������
//TODO:Add -----bool CECHostPlayer::ProcessMessage(const ECMSG& Msg)---- feature to check if title data is ready.
//If enable now it will not create autotask
//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(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());
#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())
{
// TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong size");
BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong size: {data_size} < {Marshal.SizeOf()}");
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(data);
long p = Marshal.SizeOf();
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(data, ref p);
m_NPCInfoMap[info.id] = info;
}
return true;
}
bool UnmarshalDynTasks(byte[] data, int data_size, bool header_only)
{
if (data_size < Marshal.SizeOf())
{
// 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(data);
long p = Marshal.SizeOf();
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()}");
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(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 LoadTaskTemplFromSO()
{
const string addressKey = "Assets/PerfectWorld/SO/TaskTemplContainerSO.asset"; // set this to the real address
AsyncOperationHandle handle = default;
try
{
handle = Addressables.LoadAssetAsync(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);
}
}
public TASK_DICE_BY_WEIGHT_CONFIG GetWeightTasksEssence(uint idStorage, out bool result)
{
result = false;
if (!m_WeightEssenseMap.TryGetValue(idStorage, out uint essenceId))
return default;
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
var temp = m_pEleDataMan.get_data_ptr((uint)essenceId, ID_SPACE.ID_SPACE_CONFIG, ref dt);
if (temp == null && dt != DATA_TYPE.DT_TASK_DICE_BY_WEIGHT_CONFIG)
return default;
result = true;
return (TASK_DICE_BY_WEIGHT_CONFIG)temp;
}
}
[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;
};
}