using System; using System.Reflection; using System.Runtime.InteropServices; using BrewMonster.Network; using BrewMonster.Scripts.Task; using CSNetwork.GPDataType; using PerfectWorld.Scripts.Task; using UnityEngine; namespace BrewMonster.Scripts.Task { // provide some global methods public class TaskClient { #if _TASK_CLIENT private const uint FINISH_DLG_SHOWN_TIME = 3000; // TODO: Confirm correct value private static uint s_finishDlgShownTime = 0; public static void OnTaskCheckStatus(TaskInterface pTask) { // 版本与交付合法性检查 // Version and deliver legality check // CheckVersion not exposed on TaskInterface; skipping version check if (pTask == null || !pTask.CheckVersion() || !pTask.IsDeliverLegal()) return; // 读取激活任务列表 // Read active task list ActiveTaskList pLst = TryGetActiveList(pTask); if (pLst == null) return; ActiveTaskEntry[] aEntries = pLst.m_TaskEntries; uint ulCurTime = GetCurTime(); // 遍历所有激活任务 // Iterate active tasks for (int i = 0; i < pLst.m_uTaskCount; i++) { ActiveTaskEntry CurEntry = aEntries[i]; if (CurEntry == null) continue; if (CurEntry.m_ulTemplAddr == 0) { // assert(false) // English: unexpected empty template continue; } ATaskTempl pTempl = CurEntry.GetTempl(); if (pTempl == null) continue; // IsValidState from C++ not found in managed port; skip validity-state check if (!pTempl.IsValidState()) continue; // PQ子任务 // PQ subtask if (pTempl.m_FixedData.m_bPQSubTask) { // CheckGlobalPQKeyValue(true) not ported; if implemented and returns 0, notify server then continue if(pTempl.CheckGlobalPQKeyValue(true) == 0) { pTempl.IncValidCount(); _notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, CurEntry.m_ID); continue; } } // 超时判断 // Timeout check if (pTempl.m_FixedData.m_ulTimeLimit != 0 && CurEntry.m_ulTaskTime + pTempl.m_FixedData.m_ulTimeLimit < ulCurTime) { pTempl.IncValidCount(); _notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, CurEntry.m_ID); continue; } // 绝对失效时间判断 // Absolute fail time check if (pTempl.m_FixedData.m_bAbsFail) { // TODO: Time zone bias and 'task_tm.before' not ported; skipping precise comparison } // 进入或离开区域导致失败 // Entering or leaving region causes failure { float[] pos = new float[3]; uint ulWorldId = (uint)pTask.GetPos(pos); // 进入区域失败 // Enter region fail if (pTempl.m_FixedData.m_bEnterRegionFail && ulWorldId == pTempl.m_FixedData.m_ulEnterRegionWorld) { for (uint iRegion = 0; iRegion < pTempl.m_FixedData.m_ulEnterRegionCnt; iRegion++) { Task_Region t = pTempl.m_FixedData.m_pEnterRegion[(int)iRegion]; if (IsInZone(t.zvMin, t.zvMax, pos)) { pTempl.IncValidCount(); _notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, CurEntry.m_ID); break; } } } // 离开区域失败 // Leave region fail if (pTempl.m_FixedData.m_bLeaveRegionFail) { bool bLeaveRegion = false; if (ulWorldId != pTempl.m_FixedData.m_ulLeaveRegionWorld) bLeaveRegion = true; else { uint iRegion = 0; for (; iRegion < pTempl.m_FixedData.m_ulLeaveRegionCnt; iRegion++) { Task_Region t = pTempl.m_FixedData.m_pLeaveRegion[(int)iRegion]; if (IsInZone(t.zvMin, t.zvMax, pos)) break; } if (iRegion >= pTempl.m_FixedData.m_ulLeaveRegionCnt) bLeaveRegion = true; } if (bLeaveRegion) { pTempl.IncValidCount(); _notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, CurEntry.m_ID); } } } // 离开家族失败 // Leave faction fail if (!pTask.IsAtCrossServer() && pTempl.m_FixedData.m_bLeaveFactionFail && !pTask.IsInFaction(1)) { pTempl.IncValidCount(); _notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, CurEntry.m_ID); continue; } // 对话/NPC完成类任务跳过本轮 // Skip talk-to-NPC or NPC-finish tasks here if ((TaskCompletionMethod)pTempl.m_FixedData.m_enumMethod == TaskCompletionMethod.enumTMTalkToNPC || pTempl.m_FixedData.m_bMarriage || (TaskFinishType)pTempl.m_FixedData.m_enumFinishType == TaskFinishType.enumTFTNPC) continue; // 判断未完成的直接完成判定 // Check direct-finish for unfinished tasks if (!CurEntry.IsFinished()) { // 到达地点直接完成 // Reach-site direct finish if ((TaskCompletionMethod)pTempl.m_FixedData.m_enumMethod == TaskCompletionMethod.enumTMReachSite && (TaskFinishType)pTempl.m_FixedData.m_enumFinishType == TaskFinishType.enumTFTDirect) { if (ulCurTime - s_finishDlgShownTime < FINISH_DLG_SHOWN_TIME) continue; float[] pos = new float[3]; uint ulWorldId = (uint)pTask.GetPos(pos); if (ulWorldId == pTempl.m_FixedData.m_ulReachSiteId) { for (uint iRegion = 0; iRegion < pTempl.m_FixedData.m_ulReachSiteCnt; iRegion++) { Task_Region t = pTempl.m_FixedData.m_pReachSite[(int)iRegion]; if (IsInZone(t.zvMin, t.zvMax, pos)) { var pTalk = pTempl.m_AwardTalk; if (pTalk.num_window == 0 || (pTalk.num_window == 1 && pTalk.windows != null && pTalk.windows.Length > 0 && pTalk.windows[0].num_option == 0)) { pTempl.IncValidCount(); _notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_REACH_SITE, (ushort)pTempl.GetID()); } else { // TODO: PopupTaskFinishDialog not exposed; implement UI as needed s_finishDlgShownTime = ulCurTime; } break; } } } continue; } // 离开地点直接完成 // Leave-site direct finish if ((TaskCompletionMethod)pTempl.m_FixedData.m_enumMethod == TaskCompletionMethod.enumTMLeaveSite && (TaskFinishType)pTempl.m_FixedData.m_enumFinishType == TaskFinishType.enumTFTDirect) { if (ulCurTime - s_finishDlgShownTime < FINISH_DLG_SHOWN_TIME) continue; float[] pos = new float[3]; uint ulWorldId = (uint)pTask.GetPos(pos); bool regRet = false; if (ulWorldId == pTempl.m_FixedData.m_ulLeaveSiteId) { for (uint iRegion = 0; iRegion < pTempl.m_FixedData.m_ulLeaveSiteCnt; iRegion++) { Task_Region t = pTempl.m_FixedData.m_pLeaveSite[(int)iRegion]; if (IsInZone(t.zvMin, t.zvMax, pos)) { regRet = true; break; } } } if (!regRet) { var pTalk = pTempl.m_AwardTalk; if (pTalk.num_window == 0 || (pTalk.num_window == 1 && pTalk.windows != null && pTalk.windows.Length > 0 && pTalk.windows[0].num_option == 0)) { pTempl.IncValidCount(); _notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_LEAVE_SITE, (ushort)pTempl.GetID()); } else { // TODO: PopupTaskFinishDialog not exposed; implement UI as needed s_finishDlgShownTime = ulCurTime; } } continue; } } // 非子任务:检查奖励条件并按需标记/通知 // If no children, check award conditions and update if (pTempl != null && pTempl.m_pFirstChild == null) { bool bNeedServerCheck = pTempl.RecursiveCheckAward(pTask, pLst, CurEntry, ulCurTime, -1) == 0 && pTempl.CanFinishTask(pTask, CurEntry, ulCurTime); if (pTempl.m_FixedData.m_bDisplayInExclusiveUI && pTempl.m_FixedData.m_bAutoDeliver && (TaskFinishType)pTempl.m_FixedData.m_enumFinishType == TaskFinishType.enumTFTDirect) { // TODO: Hook game UI and update auto-deliver countdown; no UI manager available here uint ulRemainTime = 0; if ((TaskCompletionMethod)pTempl.m_FixedData.m_enumMethod == TaskCompletionMethod.enumTMWaitTime) { uint ultime = CurEntry.m_ulTaskTime + pTempl.m_FixedData.m_ulWaitTime; if (ultime > ulCurTime) ulRemainTime = ultime - ulCurTime; } // TODO: pTempl.m_bReadyToNotifyServer/ResetAutoDelTask workflow may need UI support if (pTempl.m_FixedData.m_bReadyToNotifyServer && bNeedServerCheck) { pTempl.IncValidCount(); // TODO: pTempl.ResetAutoDelTask() not exposed; skip _notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, (ushort)CurEntry.m_ID); } } else { // TODO: UpdateTaskToConfirm not ported; implement confirmation UI/state if needed UpdateTaskToConfirm(pTask, pTempl, bNeedServerCheck); } } } // ATaskTemplMan.UpdateStatus(pTask) not found in C# port; skipping GetTaskTemplMan().UpdateStatus(pTask); } // ===== Helpers ===== // 取当前时间(服务器绝对时间) // Get current time (server absolute) private static uint GetCurTime() { return (uint)EC_Game.GetServerAbsTime(); } // 反射读取激活任务列表 // Read active task list via reflection private static ActiveTaskList TryGetActiveList(TaskInterface pTask) { // Try to get private method GetActiveTaskList on CECTaskInterface MethodInfo mi = pTask.GetType().GetMethod("GetActiveTaskList", BindingFlags.Instance | BindingFlags.NonPublic); if (mi != null) { try { return mi.Invoke(pTask, null) as ActiveTaskList; } catch { } } // Fallback to private field m_pActiveListBuf FieldInfo fi = pTask.GetType().GetField("m_pActiveListBuf", BindingFlags.Instance | BindingFlags.NonPublic); if (fi != null) { try { return fi.GetValue(pTask) as ActiveTaskList; } catch { } } return null; } // 区域内检测(AABB) // In-zone check (AABB) private static bool IsInZone(ZONE_VERT min, ZONE_VERT max, float[] pos) { if (pos == null || pos.Length < 3) return false; return pos[0] >= min.x && pos[0] <= max.x && pos[1] >= min.y && pos[1] <= max.y && pos[2] >= min.z && pos[2] <= max.z; } public static void _notify_svr(TaskInterface pTask, byte uReason, ushort uTaskID) { ATaskTempl._notify_svr(pTask, uReason, uTaskID); } // 更新“待确认任务” // Update task to confirm private static void UpdateTaskToConfirm(TaskInterface pTask, ATaskTempl pTempl, bool needServerCheck) { // TODO: Implement confirmation queue/UI if required by design } // Handle server notification for task updates public static void OnServerNotify(TaskInterface pTask, byte[] pBuf, uint sz) { // Check version validity // CheckVersion not exposed on TaskInterface; skipping version check if (!pTask.CheckVersion()) return; // Validate buffer size for base notification structure if (sz < (uint)Marshal.SizeOf()) return; // Marshal base notification structure from buffer task_notify_base pNotify = GPDataTypeHelper.FromBytes(pBuf); BMLogger.Log($"[TaskClient] OnServerNotify: reason={pNotify.reason}, task={pNotify.task}"); ATaskTempl pTempl = null; ActiveTaskEntry pEntry = null; // Handle error code notification if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_ERROR_CODE) { // TODO: svr_task_err_code struct not defined; need to define or use alternative approach // if (sz != Marshal.SizeOf()) return; #if _ELEMENTCLIENT // TODO: GetEntry method not implemented in ActiveTaskList; need to implement or use alternative // ActiveTaskList pLst = TryGetActiveList(pTask); // if (pLst != null) // { // pEntry = GetEntry(pLst, pNotify.task); // if (pEntry != null) pEntry.SetErrReported(); // } // TODO: TaskShowErrMessage not found; implement error message display // TaskShowErrMessage(...); #endif return; } // Handle forget skill notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_FORGET_SKILL) { // OnForgetLivingSkill method not found in ATaskTemplMan; implement if needed ATaskTemplMan pMan = GetTaskTemplMan(); if (pMan != null) pMan.OnForgetLivingSkill(pTask); return; } // Handle new task notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_NEW) { ATaskTemplMan pMan = GetTaskTemplMan(); if (pMan != null) pTempl = pMan.GetTopTaskByID(pNotify.task); } // Handle dynamic task time mark notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_DYN_TIME_MARK) { // TODO: svr_task_dyn_time_mark struct not defined; need to define or use alternative if (sz != Marshal.SizeOf()) return; // TODO: OnDynTasksTimeMark method not found in ATaskTemplMan; implement if needed ATaskTemplMan pMan = GetTaskTemplMan(); if (pMan != null) { svr_task_dyn_time_mark dynMark = GPDataTypeHelper.FromBytes(pBuf); pMan.OnDynTasksTimeMark(pTask, dynMark.time_mark, dynMark.version); } return; } // Handle dynamic task data notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_DYN_DATA) { if (sz <= (uint)Marshal.SizeOf()) return; // TODO: OnDynTasksData method not found in ATaskTemplMan; implement if needed ATaskTemplMan pMan = GetTaskTemplMan(); if (pMan != null) { byte[] dynData = new byte[sz - Marshal.SizeOf()]; Array.Copy(pBuf, Marshal.SizeOf(), dynData, 0, dynData.Length); pMan.OnDynTasksData(pTask, dynData, dynData.Length, pNotify.task != 0); } return; } // Handle storage data notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_STORAGE) { // TODO: StorageTaskList struct not defined; need to define or use alternative // if (sz != Marshal.SizeOf() + Marshal.SizeOf()) return; // TODO: OnStorageData method not found in ATaskTemplMan; implement if needed // ATaskTemplMan pMan = GetTaskTemplMan(pTask); // if (pMan != null) // { // byte[] storageData = new byte[Marshal.SizeOf()]; // Array.Copy(pBuf, Marshal.SizeOf(), storageData, 0, storageData.Length); // pMan.OnStorageData(pTask, storageData); // } // TODO: UpdateTaskUI static method not found; implement UI update if needed // TaskInterface.UpdateTaskUI(pNotify.task, pNotify.reason); return; } // Handle special award notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_SPECIAL_AWARD) { // Tsvr_task_special_award and special_award structs not defined; need to define if (sz != Marshal.SizeOf()) return; ATaskTemplMan pMan = GetTaskTemplMan(); if (pMan != null) { svr_task_special_award awardNotify = GPDataTypeHelper.FromBytes(pBuf); pMan.OnSpecialAward(awardNotify.sa, pTask); if (awardNotify.sa.id1 == 0) { // ID is 0 means no storage space, show newbie gift reminder // TODO: CECGameUIMan and PopupNewbieGiftRemind not found; implement UI if needed // CECGameUIMan* pGameUI = g_pGame->GetGameRun()->GetUIManager()->GetInGameUIMan(); // pGameUI->PopupNewbieGiftRemind(); } } return; } // Handle task limit increase notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_SET_TASK_LIMIT) { ActiveTaskList pLst = TryGetActiveList(pTask); if (pLst != null) { // ExpandMaxSimultaneousCount method not implemented; implement if needed pLst.ExpandMaxSimultaneousCount(); } // PopChatMessage static method and FIXMSG_TASK_LIMIT_INCREASED constant not found pTask.PopChatMessage((int)FixedMsg.FIXMSG_TASK_LIMIT_INCREASED); return; } // Search for task entry in active task list else { ActiveTaskList pLst = TryGetActiveList(pTask); if (pLst != null) { for (byte i = 0; i < pLst.m_uTaskCount; i++) { ActiveTaskEntry CurEntry = pLst.m_TaskEntries[i]; if (CurEntry == null) continue; if (CurEntry.m_ID != pNotify.task || CurEntry.m_ulTemplAddr == 0) continue; pTempl = CurEntry.GetTempl(); pEntry = CurEntry; break; } } } // Handle player killed notification if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_PLAYER_KILLED) { // TODO: CECUIHelper.OnTaskProcessUpdated not found; implement UI update if needed // CECUIHelper.OnTaskProcessUpdated(pNotify.task); } // Handle monster killed notification if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_MONSTER_KILLED) { // Monster kill count >= 2 triggers auto team // TODO: svr_monster_killed struct not defined; need to define or use alternative // if (sz == Marshal.SizeOf()) // { // svr_monster_killed pKilled = Marshal.PtrToStructure(handle.AddrOfPinnedObject()); // if (pKilled.monster_num >= 2) // { // // TODO: CECAutoTeam and DoAutoTeam not found; implement auto team if needed // // CECAutoTeam pAutoTeam = g_pGame.GetGameRun().GetHostPlayer().GetAutoTeam(); // // pAutoTeam.DoAutoTeam(CECAutoTeam.TYPE_TASK, pNotify.task); // } // } // TODO: CECUIHelper.OnTaskProcessUpdated not found; implement UI update if needed // CECUIHelper.OnTaskProcessUpdated(pNotify.task); } // Handle task completion or give up notification else if (pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_COMPLETE || pNotify.reason == TaskTemplConstants.TASK_SVR_NOTIFY_GIVE_UP) { // TODO: CECUIHelper.OnTaskCompleted not found; implement UI update if needed // CECUIHelper.OnTaskCompleted(pNotify.task); } // Validate template was found if (pTempl == null) { // TODO: Replace assert with appropriate error handling Debug.Assert(false, "Task template not found"); return; } // Clear valid count and process server notification pTempl.ClearValidCount(); // OnServerNotify method signature may need adjustment for C# (ref/out parameters) pTempl.OnServerNotify(pTask, pEntry, pNotify, sz, pBuf); } // Helper method to get task template manager private static ATaskTemplMan GetTaskTemplMan() { return EC_Game.GetTaskTemplateMan(); } #endif } }