Files
test/Assets/PerfectWorld/Scripts/Task/ATaskTemplMan.cs
T
2025-12-16 15:39:18 +07:00

1279 lines
45 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 System;
using BrewMonster;
using ModelRenderer.Scripts.GameData;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using CSNetwork.GPDataType;
using Cysharp.Threading.Tasks;
using UnityEngine;
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 ();
#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 szPackPath, bool bLoadDescript, Action<float> onProgress)
{
// //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;
BMLogger.Log("[Dat]- szPackPath: " + szPackPath);
if (!File.Exists(szPackPath))
{
BMLogger.LogError("[Dat]- File not found: " + szPackPath);
return false;
}
List<ATaskTempl> loadedTasks;
// thread-safe capture
Action<float> workerReport = p =>
{
// workerProgress = p;
};
try
{
// background thread
loadedTasks = await UniTask.RunOnThreadPool(() => LoadTasksFromPack_Internal(szPackPath, onProgress)
);
}
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;
}
private static List<ATaskTempl> LoadTasksFromPack_Internal(string szPackPath, Action<float> onProgress)
{
long readBytes = 0;
var tasks = new List<ATaskTempl>();
using (var fs = new FileStream(
szPackPath,
FileMode.Open,
FileAccess.Read,
FileShare.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)
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);
float percent = 80.0f / tph.item_count;
float percentCount = 0;
for (int i = 0; i < tph.item_count; i++)
{
percentCount += percent;
onProgress?.Invoke(percentCount);
// LoadingSceneController.Instance.UpdateUI(percentCount);
// Debug.LogError($"pc: {percentCount}");
fs.Seek(pOffs[i], SeekOrigin.Begin);
ATaskTempl templ = new ATaskTempl();
if (!templ.LoadFromBinFile(fs))
continue;
tasks.Add(templ);
}
}
return tasks;
}
// 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 szPath)
{
// // 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;
if (!File.Exists(szPath))
{
BMLogger.LogError("[ATaskTemplMan] LoadNPCInfoFromPack, no such file");
return false;
}
byte[] buf;
try
{
// 1️⃣ background thread
buf = await UniTask.RunOnThreadPool(
() => LoadNPCInfoFromPack_Internal(szPath)
);
}
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(string szPath)
{
using (var fs = new FileStream(
szPath,
FileMode.Open,
FileAccess.Read,
FileShare.Read))
{
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;
if (!File.Exists(szPath))
return false;
byte[] headerBuf;
try
{
// 1️⃣ background thread
headerBuf = await UniTask.RunOnThreadPool(
() => VerifyDynTasksPack_Internal(szPath)
);
}
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(string szPath)
{
using (var fs = new FileStream(
szPath,
FileMode.Open,
FileAccess.Read,
FileShare.Read))
{
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.:
// Debug.LogWarning($"Duplicate Task Found: {pTempl.m_ID}");
return;
}
m_TaskTemplMap[pTask.m_FixedData.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");
}
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)
{
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;
}
}
// void OnStorageData(TaskInterface* pTask, const void* 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;
}
if (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;
}
}
[ 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;
};
}