Files
test/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs
T
2026-03-10 13:58:03 +07:00

2939 lines
103 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 System.Runtime.InteropServices;
using UnityEngine;
using BrewMonster.Network;
using BrewMonster.Scripts.Task;
using CSNetwork.GPDataType;
using PerfectWorld.Scripts.Task;
namespace BrewMonster.Scripts.Task
{
public partial class ATaskTempl
{
// Some servers/packets may send cur_time in a different unit (e.g. ms tick) or a different epoch (uptime).
// We normalize to the same seconds-based epoch used by EC_Game.GetServerAbsTime() so wait-time/timers work.
private static uint NormalizePacketCurTime(TaskInterface pTask, uint packetTime)
{
if (pTask == null) return packetTime;
uint now = pTask.GetCurTime();
if (packetTime == 0) return now;
// If packet time is wildly different from our seconds-based time, trust our time.
// - ms vs sec: packetTime ~ now*1000
// - uptime vs epoch: packetTime << now (or vice versa)
if (packetTime > now * 10u) return now;
if (now > packetTime * 10u) return now;
return packetTime;
}
public uint GetID()
{
return m_FixedData.m_ID;
}
// Process Part
public uint CheckPrerequisite(
TaskInterface pTask,
ActiveTaskList pList,
uint ulCurTime,
bool bCheckPrevTask = true,
bool bCheckTeam = true,
bool bCheckBudge = true,
bool bCheckLevel = true)
{
uint ulRet = 0u;
// Ǹڵ // English: Not the root node
if (m_pParent != null)
{
ulRet = (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NOT_ROOT;
goto ret_here;
}
if (bCheckBudge)
{
ulRet = CheckBudget(pList);
if (ulRet != 0u) goto ret_here;
}
// else if (pList->GetEntry(m_ID)) // English: Already has the same task
else if (pList.GetEntry(m_FixedData.m_ID) != null)
{
ulRet = (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_SAME_TASK;
goto ret_here;
}
/*else if (pTask.HasTask(m_FixedData.m_ID))
{
ulRet = (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_SAME_TASK;
goto ret_here;
}*/
// Ƿ // English: Is task forbidden
if (pTask.CheckTaskForbid(m_FixedData.m_ID))
{
ulRet = (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TASK_FORBID;
goto ret_here;
}
// ΪPQжǷѾPQ // English: If current is PQ task, ensure no existing PQ task
if (m_FixedData.m_bPQTask)
{
for (byte i = 0; i < pList.m_uTaskCount; i++)
{
ActiveTaskEntry CurEntry = pList.m_TaskEntries[i];
if (CurEntry == null) continue;
ATaskTempl pTempl = CurEntry.GetTempl();
if (pTempl != null && pTempl.m_FixedData.m_bPQTask)
{
ulRet = (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ALREADY_HAS_PQ;
goto ret_here;
}
}
}
ulRet = CheckGivenItems(pTask);
if (ulRet != 0u) goto ret_here;
// Ƿʱ // English: Check if within delivery timetable
ulRet = CheckTimetable(ulCurTime);
if (ulRet != 0u) goto ret_here;
// ʱ // English: Check deliver time
ulRet = CheckDeliverTime(pTask, ulCurTime);
if (ulRet != 0u) goto ret_here;
ulRet = CheckFnshLst(pTask, ulCurTime);
if (ulRet != 0u) goto ret_here;
// Ƿ˺ɴ // English: Check daily deliver count limit
ulRet = CheckDeliverCount(pTask);
if (ulRet != 0u) goto ret_here;
// Ƿֵ // English: Check RMB/account balance
ulRet = CheckAccountRMB(pTask);
if (ulRet != 0u) goto ret_here;
// Ƿɫ¼ʱ // English: Check character time requirements
ulRet = CheckCharTime(pTask);
if (ulRet != 0u) goto ret_here;
// Check Level // English: Level constraints
if (bCheckLevel)
{
ulRet = CheckLevel(pTask);
if (ulRet != 0u) goto ret_here;
}
// ת // English: Reincarnation constraints
ulRet = CheckReincarnation(pTask);
if (ulRet != 0u) goto ret_here;
// ȼ // English: Realm level constraints
ulRet = CheckRealmLevel(pTask);
if (ulRet != 0u) goto ret_here;
// Ƿ // English: Realm exp full check
ulRet = CheckRealmExpFull(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Reputation check
ulRet = CheckRepu(pTask);
if (ulRet != 0u) goto ret_here;
// Ѻ // English: Deposit check
ulRet = CheckDeposit(pTask);
if (ulRet != 0u) goto ret_here;
// Ʒ // English: Items check
ulRet = CheckItems(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Faction check
ulRet = CheckFaction(pTask);
if (ulRet != 0u) goto ret_here;
// Ա // English: Gender check
ulRet = CheckGender(pTask);
if (ulRet != 0u) goto ret_here;
// ְҵ // English: Occupation check
ulRet = CheckOccupation(pTask);
if (ulRet != 0u) goto ret_here;
// ضʱ // English: Specific period check
ulRet = CheckPeriod(pTask);
if (ulRet != 0u) goto ret_here;
// ǷGM // English: GM check
ulRet = CheckGM(pTask);
if (ulRet != 0u) goto ret_here;
// Ƿû // English: Shielded user check
ulRet = CheckShieldUser(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Previous task check
if (bCheckPrevTask)
{
ulRet = CheckPreTask(pTask);
if (ulRet != 0u) goto ret_here;
}
// // English: Mutex task check
ulRet = CheckMutexTask(pTask, ulCurTime);
if (ulRet != 0u) goto ret_here;
// 򴥷 // English: In-zone trigger check
ulRet = CheckInZone(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Team task check
if (bCheckTeam)
{
ulRet = CheckTeamTask(pTask);
if (ulRet != 0u) goto ret_here;
}
// // English: Spouse check
ulRet = CheckSpouse(pTask);
if (ulRet != 0u) goto ret_here;
ulRet = CheckWeddingOwner(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Marriage check
ulRet = CheckMarriage(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Living skill check
ulRet = CheckLivingSkill(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Special award activity check
ulRet = CheckSpecialAward(pTask);
if (ulRet != 0u) goto ret_here;
// ȫkey/value // English: Global key/value
ulRet = CheckGlobalKeyValue(pTask, false);
if (ulRet != 0u) goto ret_here;
if (m_FixedData.m_bCompareItemAndInventory)
{
ulRet = CheckIvtrEmptySlot(pTask);
if (ulRet != 0u) goto ret_here;
}
// ɹ׶ // English: Faction contribution check
ulRet = CheckFactionContrib(pTask);
if (ulRet != 0u) goto ret_here;
// ¼ɽɸ // English: Recorded tasks number check
ulRet = CheckRecordTasksNum(pTask);
if (ulRet != 0u) goto ret_here;
// ״̬ // English: Transform mask check
ulRet = CheckTransform(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Force check
ulRet = CheckForce(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Force reputation check
ulRet = CheckForceReputation(pTask);
if (ulRet != 0u) goto ret_here;
// ս // English: Force contribution check
ulRet = CheckForceContribution(pTask);
if (ulRet != 0u) goto ret_here;
// ֵ // English: EXP check
ulRet = CheckExp(pTask);
if (ulRet != 0u) goto ret_here;
// Ԫֵ // English: SP check
ulRet = CheckSP(pTask);
if (ulRet != 0u) goto ret_here;
// Ծȼ // English: Force activity level check
ulRet = CheckForceActivityLevel(pTask);
if (ulRet != 0u) goto ret_here;
// // English: King check
ulRet = CheckKing(pTask);
if (ulRet != 0u) goto ret_here;
// // English: Not in team check
ulRet = CheckNotInTeam(pTask);
if (ulRet != 0u) goto ret_here;
// ƺ // English: Title check
ulRet = CheckTitle(pTask);
if (ulRet != 0u) goto ret_here;
// ʷƽ׶ // English: History stage check
ulRet = CheckHistoryStage(pTask);
if (ulRet != 0u) goto ret_here;
// ռ // English: Card collection count check
ulRet = CheckCardCollection(pTask);
if (ulRet != 0u) goto ret_here;
// ijƷӵ // English: Specific card rank count check
ulRet = CheckCardRankCount(pTask);
if (ulRet != 0u) goto ret_here;
// 󣬲ܽȡǿƶ // English: No navigate when in transform shape
ulRet = CheckInTransformShape(pTask);
if (ulRet != 0u) goto ret_here;
ret_here:
// TODO: pTask.GetPlayerId() not available in managed interface; use 0
// Only log failures to reduce noise
if (ulRet != 0)
{
string log = $"CheckPrerequisite Task {m_FixedData.m_ID}: FAIL code {ulRet}";
CECTaskInterface.WriteLog(0, (int)m_FixedData.m_ID, 1, log);
}
return ulRet;
}
#if _TASK_CLIENT
// bool CanShowTask (TaskInterface* pTask) const;
// bool HasShowCond() const;
public void GetGlobalTaskChar(TaskInterface pTask, Task_State_info.abase_vector_wchar_t_ptr TaskCharArr)
{
// Build display strings for global expressions
// 生成用于显示的全局表达式字符串
for (int i = 0; i < (int)m_FixedData.m_ulTaskCharCnt; i++)
{
// Read one UTF-16 row from m_pTaskChar and convert to C# string
// 从 m_pTaskChar 读取一行 UTF-16 并转换为 C# 字符串
int colCount = BrewMonster.Scripts.Task.TaskTemplConstants.TASK_AWARD_MAX_DISPLAY_CHAR_LEN;
ushort[] row = new ushort[colCount];
for (int j = 0; j < colCount; j++)
{
row[j] = m_FixedData.m_pTaskChar[i, j];
}
string src = ModelRenderer.Scripts.Common.ByteToStringUtils.UshortArrayToUnicodeString(row);
if (string.IsNullOrEmpty(src))
{
continue;
}
// Parse '%' placeholders like %1, %2... and replace with computed values
// 解析形如 %1, %2 的占位符并替换为计算结果
var sb = new System.Text.StringBuilder();
int idx = 0;
char percent = '%'; // Percent sign
// % (percent sign)
while (idx < src.Length)
{
int pos = src.IndexOf(percent, idx);
if (pos >= 0)
{
// Append text before '%'
// 追加 '%' 之前的文本
sb.Append(src, idx, pos - idx);
int j = pos + 1;
int startDigits = j;
while (j < src.Length && char.IsDigit(src[j])) j++;
string digits = (j > startDigits) ? src.Substring(startDigits, j - startDigits) : string.Empty;
int nVal;
// If there's a valid index after '%', compute the global expression value
// ȡõȫֱʽк (get referenced global expression index)
if (!string.IsNullOrEmpty(digits) && int.TryParse(digits, out nVal) && nVal != 0)
{
// Compute one global expression
// ȫֱʽ
int nRet = (int)CalcOneGlobalExp(pTask, nVal - 1);
// Convert number to string and append
// תַַ
sb.Append(nRet.ToString());
// Skip all digits after '%<digits>'
// 跳过 '%' 后面的所有数字
idx = j;
continue;
}
else
{
// No valid number found; keep the '%'
// 未找到合法数字;保留 '%'
sb.Append(percent);
idx = pos + 1;
}
}
else
{
// No more '%' found; append the rest
// δҵȫֱʽֱʾ
sb.Append(src, idx, src.Length - idx);
break;
}
}
string result = sb.ToString();
if (!string.IsNullOrEmpty(result))
{
// Original: TaskCharArr.push_back(pszNewchar);
// 原逻辑:将结果指针压入向量(此处托管环境不直接推入原生向量)
// NOTE: If needed, map 'result' into UI/managed list here.
}
}
}
private float CalcOneGlobalExp(TaskInterface pTask, int nIndex)
{
// Evaluate one global expression row
// 计算一行全局表达式
if (nIndex < 0 || nIndex >= (int)m_FixedData.m_ulExpCnt)
{
return 0f;
}
try
{
int colCount = BrewMonster.Scripts.Task.TaskTemplConstants.TASK_AWARD_MAX_DISPLAY_CHAR_LEN;
float result = 0f;
for (int i = 0; i < colCount; i++)
{
TASK_EXPRESSION expr = m_FixedData.m_pExpArr[nIndex, i];
if (expr.type == -1) break; // Sentinel terminator // 结束标记
// Fallback evaluation: accumulate values
// 回退求值:累加表达式值
result += expr.value;
}
return result;
}
catch (System.Exception ex)
{
CECTaskInterface.WriteLog(0, (int)m_FixedData.m_ID, 0, $"CalcOneGlobalExp, Expression run err: {ex.Message}");
return 0f;
}
}
// bool CanShowInExclusiveUI (TaskInterface* pTask, unsigned long ulCurTime) const;
#if !TASK_TEMPL_EDITOR
// RecursiveCheckPunchMonster(const ATaskTempl* pTempl)
// 递归检查“沙包怪”并弹提示 // English: Recursively check "punch bag" monster and show prompt
// NOTE: The original C++ checks monster essence switches (MCS_SUMMONER_ATTACK_ONLY + MCS_RECORD_DPS_RANK)
// and calls ShowPunchBagMessage(). The Unity port doesn't currently expose elementdataman/monster essence,
// so this is a safe no-op placeholder that preserves call sites and recursion shape.
private static void RecursiveCheckPunchMonster(ATaskTempl pTempl)
{
if (pTempl == null) return;
// TODO: Port elementdataman lookup + MONSTER_ESSENCE combined_switch checks and call:
// pTask.ShowPunchBagMessage(false,false,monsterId,0,0);
var child = pTempl.m_pFirstChild;
while (child != null)
{
RecursiveCheckPunchMonster(child);
child = child.m_pNextSibling;
}
}
#endif
public void OnServerNotify(
TaskInterface pTask,
ActiveTaskEntry pEntry,
task_notify_base pNotify,
uint sz,
byte[] pBuf) // In C++ pNotify is a pointer to raw data; here we pass the full byte array for parsing
{
uint ulTime = 0, ulCaptainTask = 0;
ActiveTaskList pLst = null;
ATaskTempl pSub = new ATaskTempl();
task_sub_tags sub_tags = new task_sub_tags();
// memset(&sub_tags, 0, sizeof(sub_tags));
uint i=0;
svr_monster_killed pKilled = new svr_monster_killed();
svr_player_killed pKilledPlayer = new svr_player_killed();
StorageTaskList pStorage = pTask.GetStorageTaskList();
svr_treasure_map pTreasure = new svr_treasure_map();
var m_enumMethod = m_FixedData.m_enumMethod;
switch (pNotify.reason)
{
case TaskTemplConstants.TASK_SVR_NOTIFY_PLAYER_KILLED:
{
if (sz != Marshal.SizeOf<svr_player_killed>()) break;
if (m_enumMethod != (uint)TaskCompletionMethod.enumTMKillPlayer) break;
pKilledPlayer = GPDataTypeHelper.FromBytes<svr_player_killed>(pBuf);
int iIndex = pKilledPlayer.index;
if (iIndex < TaskInterfaceConstants.MAX_MONSTER_WANTED)
{
pEntry.SetMonsterNum(iIndex, pKilledPlayer.player_num);
}
}
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_TREASURE_MAP:
if (m_enumMethod == (uint)TaskCompletionMethod.enumTMReachTreasureZone)
{
pTreasure = GPDataTypeHelper.FromBytes<svr_treasure_map>(pBuf);
pEntry.m_iUsefulData1 = pTreasure.treasure_index;
}
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_MONSTER_KILLED:
if (sz != Marshal.SizeOf<svr_monster_killed>()) break;
if (m_enumMethod != (uint)TaskCompletionMethod.enumTMKillNumMonster) break;
pKilled = GPDataTypeHelper.FromBytes<svr_monster_killed>(pBuf);
for (i = 0; i < m_FixedData.m_ulMonsterWanted; i++)
{
MONSTER_WANTED mw = m_FixedData.m_MonsterWanted[i];
if (mw.m_ulMonsterTemplId == pKilled.monster_id)
{
pEntry.SetMonsterNum((int)i, pKilled.monster_num);
if (pKilled.dps > 0 && pKilled.dph > 0)
{
pTask.ShowPunchBagMessage(true,pKilled.dps >= mw.m_iDPS && pKilled.dph >= mw.m_iDPH,pKilled.monster_id,pKilled.dps,pKilled.dph);
}
break;
}
}
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_NEW:
// Follow C++ logic: valid_size checks the buffer size before deserializing
// In C++, pNotify is a pointer to the structure, so valid_size can read sub_tags.sz directly
// In C#, we need to read sub_tags.sz from the buffer first, then validate
// Calculate base size: task_notify_base (3) + cur_time (4) + cap_task (4) = 11
int baseSzNew = Marshal.SizeOf<task_notify_base>() + 8;
if (sz <= (uint)baseSzNew)
{
Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_NEW: buffer too small, need more than {baseSzNew} bytes, got {sz}");
break;
}
// Read sub_tags.sz from buffer to validate size (matching C++ valid_size logic)
// Offset to sub_tags: baseSzNew = 11
// sub_tags layout: sub_task/state (2 bytes) + sz (1 byte) = 3 bytes minimum
if ((int)sz < baseSzNew + 3)
{
Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_NEW: buffer too small to read sub_tags.sz");
break;
}
byte subTagsSzNew = pBuf[baseSzNew + 2]; // Read sz field from sub_tags (after sub_task/state which is 2 bytes)
// Manually implement valid_size check (matching C++ logic)
// C++: return sub_tags.valid_size(sz - base_sz);
// sub_tags.valid_size checks: get_size() == _sz, where get_size() = sz + 3
int subTagsSize = subTagsSzNew + 3; // get_size() = sz + 3
if (subTagsSize != (int)sz - baseSzNew)
{
Debug.Log($" [TASK_SVR_NOTIFY_NEW] the size of byte not meet !!! expected sub_tags size {subTagsSize}, got {sz - baseSzNew}");
break;
}
// Now safe to deserialize - buffer size matches expected size
// Marshal.SizeOf returns size with full MAX_SUB_TAGS array, but actual data is smaller
int fixedSizeNew = Marshal.SizeOf<svr_new_task>();
svr_new_task svr_new_task;
if (sz < (uint)fixedSizeNew)
{
// Buffer is smaller than fixed structure size, create padded buffer for safe deserialization
byte[] paddedBufNew = new byte[fixedSizeNew];
Array.Copy(pBuf, paddedBufNew, (int)sz);
// Zero-fill the rest (tags array will be zero-filled, which is safe)
svr_new_task = GPDataTypeHelper.FromBytes<svr_new_task>(paddedBufNew);
// Restore the original sub_tags.sz value since padding might have corrupted it
svr_new_task.sub_tags.sz = subTagsSzNew;
}
else
{
// Buffer is large enough for fixed structure size
svr_new_task = GPDataTypeHelper.FromBytes<svr_new_task>(pBuf);
}
pLst = pTask.GetActiveTaskList();
svr_new_task.get_data(
ref ulTime,
ref ulCaptainTask,
ref sub_tags
);
// Normalize packet time so active timers (wait-time, time-limit) count down correctly.
ulTime = NormalizePacketCurTime(pTask, ulTime);
// NOTE: Disabled for now due to an ambiguous call error being reported by the Unity compiler in this project setup.
// TODO: Re-enable once the underlying duplicate/assembly ambiguity is resolved.
// GetTaskTemplMan().RemoveActiveStorageTask(pStorage, m_FixedData.m_ID);
if (sub_tags.sub_task > 0)
{
pSub = GetConstSubById(sub_tags.sub_task);
if (pSub == null) break;
}
else
pSub = null;
if (CheckBudget(pLst) > 0) break;
DeliverTask(
pTask,
pLst,
null,
ulCaptainTask,
pTask.GetTaskMask(),
ulTime,
pSub,
ref sub_tags,
new TaskGlobalData(),
0xFF);
if (m_FixedData.m_lAvailFrequency != (int)TaskAwardFreq.enumTAFNormal &&
!m_FixedData.m_bAccountTaskLimit && !m_FixedData.m_bRoleTaskLimit)
{
// static_cast<TaskFinishTimeList*>(pTask->GetFinishedTimeList())->AddOrUpdate(
// m_ID,
// ulTime);
var TaskFinishTimeList = new TaskFinishTimeList();
TaskFinishTimeList.ReadFromBuffer(pTask.GetFinishedTimeList());
TaskFinishTimeList.AddOrUpdate(m_FixedData.m_ID, ulTime);
// Persist to underlying buffer so CheckDeliverTime can see it later.
TaskFinishTimeList.WriteToBuffer(pTask.GetFinishedTimeList());
}
// TODO: Log new task acceptance
// if (CanShowPrompt() && !m_bDisplayInTitleTaskUI) TaskInterface::ShowTaskMessage(m_ID, TASK_MSG_NEW);
// ׷ٷʱ
if (!m_FixedData.m_bHidden && !m_FixedData.m_bDisplayInTitleTaskUI)
{
// TaskInterface::TraceTask(m_ID);
// TODO: Log task trace
// pTask.TraceTask(m_FixedData.m_ID);
CECTaskInterface.TraceTask(m_FixedData.m_ID);
}
if (m_FixedData.m_bDisplayInTitleTaskUI)
{
// TODO: Update title UI
// TaskInterface::UpdateTitleUI(m_ID);
}
if ((m_enumMethod == (uint)TaskCompletionMethod.enumTMSimpleClientTask) && m_FixedData.m_uiEmotion > 0)
{
// TODO: Pop emotion UI
// TaskInterface::PopEmotionUI(m_ID,m_uiEmotion,true);
}
pTask.OnNewTask((int)m_FixedData.m_ID);
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_COMPLETE:
// Follow C++ logic: valid_size checks the buffer size before deserializing
// In C++, pNotify is a pointer to the structure, so valid_size can read sub_tags.sz directly
// In C#, we need to read sub_tags.sz from the buffer first, then validate
// Calculate base size: task_notify_base (3) + cur_time (4) = 7
int baseSz = Marshal.SizeOf<task_notify_base>() + 4;
if (sz <= (uint)baseSz)
{
Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_COMPLETE: buffer too small, need more than {baseSz} bytes, got {sz}");
break;
}
// Read sub_tags.sz from buffer to validate size (matching C++ valid_size logic)
// Offset to sub_tags: baseSz = 7
// sub_tags layout: sub_task/state (2 bytes) + sz (1 byte) = 3 bytes minimum
if ((int)sz < baseSz + 3)
{
Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_COMPLETE: buffer too small to read sub_tags.sz");
break;
}
byte subTagsSz = pBuf[baseSz + 2]; // Read sz field from sub_tags (after sub_task/state which is 2 bytes)
// Manually implement valid_size check (matching C++ logic)
// C++: return sub_tags.valid_size(sz - base_sz);
// sub_tags.valid_size checks: get_size() == _sz, where get_size() = sz + 3
int subTagsSizeComplete = subTagsSz + 3; // get_size() = sz + 3
if (subTagsSizeComplete != (int)sz - baseSz)
{
Debug.Log($" [TASK_SVR_NOTIFY_COMPLETE] the size of byte not meet !!! expected sub_tags size {subTagsSizeComplete}, got {sz - baseSz}");
break;
}
// Now safe to deserialize - buffer size matches expected size
// Marshal.SizeOf returns size with full MAX_SUB_TAGS array, but actual data is smaller
int fixedSize = Marshal.SizeOf<svr_task_complete>();
svr_task_complete svr_task_complete;
if (sz < (uint)fixedSize)
{
// Buffer is smaller than fixed structure size, create padded buffer for safe deserialization
byte[] paddedBuf = new byte[fixedSize];
Array.Copy(pBuf, paddedBuf, (int)sz);
// Zero-fill the rest (tags array will be zero-filled, which is safe)
svr_task_complete = GPDataTypeHelper.FromBytes<svr_task_complete>(paddedBuf);
// Restore the original sub_tags.sz value since padding might have corrupted it
svr_task_complete.sub_tags.sz = subTagsSz;
}
else
{
// Buffer is large enough for fixed structure size
svr_task_complete = GPDataTypeHelper.FromBytes<svr_task_complete>(pBuf);
}
svr_task_complete.get_data(
ref ulTime,
ref sub_tags
);
// Normalize packet time so award bookkeeping and time-based logic uses consistent timebase.
ulTime = NormalizePacketCurTime(pTask, ulTime);
pEntry.m_uState = (char)svr_task_complete.sub_tags.state;
if (!pEntry.IsSuccess())
{
#if !TASK_TEMPL_EDITOR
RecursiveCheckPunchMonster(this);
#endif
}
// TODO: Log task completion
// if (CanShowPrompt() && !m_bDisplayInTitleTaskUI) TaskInterface::ShowTaskMessage(
// m_ID,
// (pEntry->IsSuccess() && !pEntry->IsGiveUp()) ? TASK_MSG_SUCCESS : TASK_MSG_FAIL);
RecursiveAward(
pTask,
pTask.GetActiveTaskList(),
pEntry,
ulTime,
-1,
ref sub_tags);
pTask.UpdateConfirmTasksMap();
// TODO: Show task completion trace
// pTask.UpdateTaskEmotionAction(m_FixedData.m_ID);
if ((m_enumMethod == (uint)TaskCompletionMethod.enumTMSimpleClientTask)
&& m_FixedData.m_uiEmotion > 0)
{
// TODO: Pop emotion UI
// PopEmotionUI(m_ID,m_uiEmotion,false);
}
if (m_FixedData.m_bDisplayInTitleTaskUI)
{
// TODO: Update title UI
// UpdateTitleUI(m_ID);
}
pTask.OnCompleteTask((int)m_FixedData.m_ID);
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_GIVE_UP:
pLst = pTask.GetActiveTaskList();
// Match C++ behavior: ClearTask (and realign), do NOT globally compact or just zero the entry.
pLst.ClearTask(pTask, pEntry, false);
// TODO: Log task give up
// if (m_bDisplayInTitleTaskUI) TaskInterface::UpdateTitleUI(m_ID);
// if ((m_enumMethod == enumTMSimpleClientTask) && m_uiEmotion)
// TaskInterface::PopEmotionUI(m_ID,m_uiEmotion,false);
pTask.OnGiveupTask((int)m_FixedData.m_ID);
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_FINISHED:
pEntry.SetFinished();
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_DIS_GLOBAL_VAL:
// DisplayTaskCharInfo(pTask, pEntry);
break;
default:
// assert(false);
break;
}
// Update task UI after processing server notification
pTask.UpdateTaskUI(pNotify.task, pNotify.reason);
}
public static void _notify_svr(TaskInterface pTask, byte uReason, ushort uTaskID)
{
task_notify_base notify = new task_notify_base();
notify.reason = uReason;
notify.task = uTaskID;
// pTask.NotifyServer( notify, (uint)Marshal.SizeOf<task_notify_base>());
pTask.NotifyServer( GPDataTypeHelper.ToBytes(notify), (uint)Marshal.SizeOf<task_notify_base>());
}
#else
// void NotifyClient (TaskInterface* pTask, const ActiveTaskEntry* pEntry, unsigned char uReason, unsigned long ulCurTime, unsigned long ulParam = 0, int dps = 0, int dph = 0) const;
// bool CheckGlobalRequired (TaskInterface* pTask, unsigned long ulSubTaskId, const TaskPreservedData* pPreserve, const TaskGlobalData* pGlobal, unsigned short reason) const;
// bool CheckKillMonster (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, unsigned long ulTemplId, unsigned long ulLev, bool bTeam, float fRand, int dps, int dph) const;
// bool CheckKillPlayer (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, int iOccupation, int iLevel, bool bGender, int iForce, float fRand) const;
// void CheckCollectItem (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, bool bAtNPC, int nChoice) const;
// void CheckMonsterKilled (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, bool bAtNPC, int nChoice) const;
// void CheckMining (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry) const;
// void CheckWaitTime (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, unsigned long ulCurTime, bool bAtNPC, int nChoice) const;
// void GiveUpOneTask (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, bool bForce) const;
// void OnSetFinished (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, bool bNotifyMem = true) const;
// bool DeliverAward (TaskInterface* pTask, ActiveTaskList* pList, ActiveTaskEntry* pEntry, int nChoice, bool bNotifyTeamMem = true, TaskGlobalData* pGlobal = NULL) const;
// void RemoveAcquiredItem (TaskInterface* pTask, bool bClearTask, bool bSuccess) const;
// void TakeAwayGivenItems (TaskInterface* pTask) const;
// bool OnDeliverTeamMemTask (TaskInterface* pTask, TaskGlobalData* pGlobal) const;
// unsigned long CheckDeliverTask (TaskInterface* pTask, unsigned long ulSubTaskId, TaskGlobalData* pGlobal, bool bNotifyErr = true, bool bMemTask = false, unsigned long ulCapId = 0) const;
// bool HasGlobalData() const;
#endif
#if _TASK_CLIENT
// void SyncTaskType(); // ʹ
// bool GetShowGfxFlag() { return m_bShowGfxFinished;}
// const wchar_t* GetDescription() const { assert(m_pwstrDescript); return (wchar_t*)m_pwstrDescript; }
// const wchar_t* GetOkText() const { assert(m_pwstrOkText); return (wchar_t*)m_pwstrOkText; }
// const wchar_t* GetNoText() const { assert(m_pwstrNoText); return (wchar_t*)m_pwstrNoText; }
public ushort[] GetTribute() { return m_pwstrTribute; }
//
public talk_proc GetDeliverTaskTalk() { return m_DelvTaskTalk; }
public talk_proc GetUnqualifiedTalk() { return m_UnqualifiedTalk; }
public talk_proc GetDeliverItemTalk() { return m_DelvItemTalk; }
public talk_proc GetUnfinishedTalk() { return m_ExeTalk; }
public talk_proc GetAwardTalk() { return m_AwardTalk; }
//
public uint GetDeliverNPC() { return m_FixedData.m_ulDelvNPC; }
public uint GetAwardNPC() { return m_FixedData.m_ulAwardNPC; }
//
// void SaveToTextFile(FILE* fp);
// bool SaveToTextFile(const char* szPath);
// void SaveToBinFile(FILE* fp) { SaveBinary(fp); }
// void SaveDescription(FILE* fp);
// void SaveDescriptionBin(FILE* fp);
// void SaveTribute(FILE* fp);
// void SaveTributeBin(FILE* fp);
// void SaveAllText(FILE* fp);
// int MarshalKillMonster(char* pData);
// int MarshalCollectItems(char* pData);
// int MarshalDynTask(char* pData);
// int MarshalSpecialAwardData(char* pData);
//
// ATaskTempl& operator= (const ATaskTempl& src);
// bool operator == (const ATaskTempl& src) const
// {
// return *(ATaskTemplFixedData*)this == *(const ATaskTemplFixedData*)&src;
// }
#endif
public bool _compare_key_value(TaskInterface pTask, COMPARE_KEY_VALUE CompKeyVal)
{
long lleftValue = CompKeyVal.lLeftNum;
if (CompKeyVal.nLeftType == 0)
{
lleftValue = pTask.GetGlobalValue(CompKeyVal.lLeftNum);
}
long lRightValue = CompKeyVal.lRightNum;
if (CompKeyVal.nRightType == 0)
{
lRightValue = pTask.GetGlobalValue(CompKeyVal.lRightNum);
}
switch(CompKeyVal.nCompOper)
{
case 0:
{
if (lleftValue > lRightValue)
return true;
}
break;
case 1:
{
if (lleftValue == lRightValue)
return true;
}
break;
case 2:
{
if (lleftValue < lRightValue)
return true;
}
break;
default:
break;
}
return false;
}
// Early out: skip if this phase does not require key-value comparison
public uint CheckGlobalKeyValue(TaskInterface pTask, bool bFinCheck)
{
if ((bFinCheck && !m_FixedData.m_bFinNeedComp)
|| (!bFinCheck && !m_FixedData.m_bPremNeedComp))
{
return 0;
}
// Initialize comparison flags
bool bFlag1 = false;
bool bFlag2 = false;
if (bFinCheck)
{
// Finish-conditions branch: evaluate two key-value comparisons
bFlag1 = _compare_key_value(pTask, m_FixedData.m_Fin1KeyValue);
bFlag2 = _compare_key_value(pTask, m_FixedData.m_Fin2KeyValue);
// Evaluate logical mode: 0 = OR, 1 = AND
if ((m_FixedData.m_nFinExp1AndOrExp2 == 0 && (bFlag1 || bFlag2))
|| (m_FixedData.m_nFinExp1AndOrExp2 == 1 && (bFlag1 && bFlag2)))
{
return 0;
}
}
else
{
// Premise-conditions branch: evaluate two key-value comparisons
bFlag1 = _compare_key_value(pTask, m_FixedData.m_Prem1KeyValue);
bFlag2 = _compare_key_value(pTask, m_FixedData.m_Prem2KeyValue);
// Evaluate logical mode: 0 = OR, 1 = AND
if ((m_FixedData.m_nPremExp1AndOrExp2 == 0 && (bFlag1 || bFlag2))
|| (m_FixedData.m_nPremExp1AndOrExp2 == 1 && (bFlag1 && bFlag2)))
{
return 0;
}
}
// Failure: global key-value prerequisite not satisfied
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_GLOBAL_KEYVAL;
}
public bool CheckReachLevel(BrewMonster.Scripts.Task.TaskInterface pTask)
{
bool bLevel = true, bReincarnationCount = true, bRealmLevel = true;
if (m_FixedData.m_ulReachLevel != 0) bLevel = pTask.GetPlayerLevel() >= m_FixedData.m_ulReachLevel;
if (m_FixedData.m_ulReachReincarnationCount != 0) bReincarnationCount = GetReincarnationCount(pTask) >= m_FixedData.m_ulReachReincarnationCount;
if (m_FixedData.m_ulReachRealmLevel != 0) bRealmLevel = GetRealmLevel(pTask) >= m_FixedData.m_ulReachRealmLevel;
return bLevel && bReincarnationCount && bRealmLevel;
}
private static uint GetReincarnationCount(BrewMonster.Scripts.Task.TaskInterface pTask)
{
return 0u;
}
private static uint GetRealmLevel(BrewMonster.Scripts.Task.TaskInterface pTask)
{
var host = BrewMonster.Network.EC_Game.GetGameRun()?.GetHostPlayer();
if (host != null)
{
var bp = host.GetBasicProps();
return (uint)bp.iLevel2;
}
return 0u;
}
public bool IsKeyTask()
{
ATaskTempl m = GetTopTask();
return m.m_FixedData.m_bKeyTask;
}
// ===== Missing methods converted from C++ (TaskTempl.inl) =====
// 保留原中文注释,并在旁加入英文翻译
// inline unsigned long _get_item_count(TaskInterface* pTask, unsigned long ulItemId, bool bCommon)
// 获取道具数量(通用/任务) // English: Get item count (common/task)
// public static uint _get_item_count(TaskInterface pTask, uint ulItemId, bool bCommon)
// {
// return bCommon ? pTask.GetCommonItemCount(ulItemId) : pTask.GetTaskItemCount(ulItemId);
// }
// inline unsigned long ATaskTempl::CheckBudget(ActiveTaskList* pList) const
// 检查任务栏容量与空间 // English: Check task list budget and space
public uint CheckBudget(ActiveTaskList pList)
{
// Convert full logic with TASK_HIDDEN_COUNT/TASK_TITLE_TASK_COUNT/TASK_ACTIVE_LIST_MAX_LEN and list counters when constants and fields are available
// // 占位返回通过 // English: Placeholder pass
// return 0u;
var m_bHidden = m_FixedData.m_bHidden;
var m_bDisplayInTitleTaskUI = m_FixedData.m_bDisplayInTitleTaskUI;
var m_ID = m_FixedData.m_ID;
//
bool bReachLimit = false;
if (m_bHidden)
bReachLimit = pList.m_uTopHideTaskCount >= TASK_.TASK_HIDDEN_COUNT;
else if (m_bDisplayInTitleTaskUI)
bReachLimit = bReachLimit || pList.m_uTitleTaskCount >= TASK_.TASK_TITLE_TASK_COUNT;
else
bReachLimit = bReachLimit || pList.m_uTopShowTaskCount >= pList.GetMaxSimultaneousCount();
if (bReachLimit)
return TaskInterfaceConstants.TASK_PREREQU_FAIL_FULL;
// Check Task List Empty Space
if (pList.m_uUsedCount + m_uDepth > TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
return TaskInterfaceConstants.TASK_PREREQU_FAIL_NO_SPACE;
// Ƿͬ
if (pList.GetEntry(m_ID) != null) return TaskInterfaceConstants.TASK_PREREQU_FAIL_SAME_TASK;
return 0;
}
// inline unsigned long ATaskTempl::CheckGivenItems(TaskInterface* pTask) const
// 检查交付所需道具容量合法性 // English: Check deliverable item capacity
public uint CheckGivenItems(TaskInterface pTask)
{
if (m_FixedData.m_ulGivenItems != 0)
{
if (!pTask.IsDeliverLegal()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_GIVEN_ITEM;
if (m_FixedData.m_ulGivenCmnCount != 0 && !pTask.CanDeliverCommonItem(m_FixedData.m_ulGivenCmnCount)
|| m_FixedData.m_ulGivenTskCount != 0 && !pTask.CanDeliverTaskItem(m_FixedData.m_ulGivenTskCount))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_GIVEN_ITEM;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckTimetable(unsigned long ulCurTime) const
// 检查任务可接时间表 // English: Check task timetable window
public uint CheckTimetable(uint ulCurTime)
{
// C++:
// if (!m_ulTimetable) return 0;
// for (i=0; i<m_ulTimetable; i++)
// if (judge_time_date(&m_tmStart[i], &m_tmEnd[i], ulCurTime, (task_tm_type)m_tmType[i])) return 0;
// return TASK_PREREQU_FAIL_WRONG_TIME;
if (m_FixedData.m_ulTimetable == 0) return 0u;
// Defensive: if data missing, don't hard-block delivering the task (avoids regressions)
if (m_FixedData.m_tmStart == null || m_FixedData.m_tmEnd == null || m_FixedData.m_tmType == null)
return 0u;
int cnt = (int)m_FixedData.m_ulTimetable;
int safeCnt = Math.Min(cnt, Math.Min(m_FixedData.m_tmStart.Length, m_FixedData.m_tmEnd.Length));
safeCnt = Math.Min(safeCnt, m_FixedData.m_tmType.Length);
for (int i = 0; i < safeCnt; i++)
{
task_tm_type type = (task_tm_type)m_FixedData.m_tmType[i];
if (judge_time_date(m_FixedData.m_tmStart[i], m_FixedData.m_tmEnd[i], ulCurTime, type))
return 0u;
}
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_TIME;
}
// inline bool judge_time_date(const task_tm* tmStart, const task_tm* tmEnd, unsigned long ulCurTime, task_tm_type tm_type)
// [中文] 对齐 C++ 的时间窗口判断(包含时区偏移、月/周/日模式)
// [English] C++ parity for timetable window checks (timezone bias + month/week/day modes)
private static bool judge_time_date(task_tm tmStart, task_tm tmEnd, uint ulCurTime, task_tm_type tm_type)
{
// C++ client path uses gmtime after subtracting timezone bias.
long t = ulCurTime - (long)(TaskInterface.GetTimeZoneBias() * 60);
if (t < 0) t = 0;
DateTime cur = DateTimeOffset.FromUnixTimeSeconds(t).UtcDateTime;
DateTime tomorrow = DateTimeOffset.FromUnixTimeSeconds(t + 24 * 3600).UtcDateTime;
bool last_day = (cur.Month != tomorrow.Month);
switch (tm_type)
{
case task_tm_type.enumTaskTimeDate:
return tmStart.before(cur) && tmEnd.after(cur);
case task_tm_type.enumTaskTimeMonth:
return tmStart.before_per_month(cur, last_day) && tmEnd.after_per_month(cur, last_day);
case task_tm_type.enumTaskTimeWeek:
return tmStart.before_per_week(cur) && tmEnd.after_per_week(cur);
case task_tm_type.enumTaskTimeDay:
return tmStart.before_per_day(cur) && tmEnd.after_per_day(cur);
default:
return false;
}
}
// inline unsigned long ATaskTempl::CheckDeliverTime(TaskInterface* pTask, unsigned long ulCurTime) const
// 检查任务发放频率限制(日/周/月/年) // English: Check deliver frequency limits (day/week/month/year)
public uint CheckDeliverTime(TaskInterface pTask, uint ulCurTime)
{
// C++: if (m_lAvailFrequency == enumTAFNormal) return 0;
if (m_FixedData.m_lAvailFrequency == (int)TaskAwardFreq.enumTAFNormal)
return 0u;
// Deliver frequency is tracked via TaskFinishTimeList (time marks). For pure-frequency tasks,
// being in the same period means "can't receive again". For account/role limit tasks, the same
// frequency defines the *counting period*; those tasks should not be blocked by WRONG_TIME, but
// their counters should reset when the period changes (C++ calls CheckDeliverTime during award).
byte[] finishedTimeListBuf = pTask.GetFinishedTimeList();
if (finishedTimeListBuf == null || finishedTimeListBuf.Length == 0) return 0u;
TaskFinishTimeList timeList = new TaskFinishTimeList();
timeList.ReadFromBuffer(finishedTimeListBuf);
if (!timeList.IsValid()) return 0u;
uint lastMark = timeList.Search(m_FixedData.m_ID);
bool isLimitTask = m_FixedData.m_bAccountTaskLimit || m_FixedData.m_bRoleTaskLimit;
// Convert server-abs time into "task local time" (same convention as timetable judge_time_date).
long curSec = ulCurTime - (long)(TaskInterface.GetTimeZoneBias() * 60);
if (curSec < 0) curSec = 0;
DateTime curLocal = DateTimeOffset.FromUnixTimeSeconds(curSec).UtcDateTime;
TaskAwardFreq freq = (TaskAwardFreq)m_FixedData.m_lAvailFrequency;
// No mark yet
if (lastMark == 0)
{
if (isLimitTask)
{
timeList.AddOrUpdate(m_FixedData.m_ID, ulCurTime);
timeList.WriteToBuffer(finishedTimeListBuf);
}
return 0u;
}
long lastSec = lastMark - (long)(TaskInterface.GetTimeZoneBias() * 60);
if (lastSec < 0) lastSec = 0;
DateTime lastLocal = DateTimeOffset.FromUnixTimeSeconds(lastSec).UtcDateTime;
// Same-period checks
bool samePeriod = false;
switch (freq)
{
case TaskAwardFreq.enumTAFEachDay:
samePeriod = curLocal.Year == lastLocal.Year && curLocal.Month == lastLocal.Month && curLocal.Day == lastLocal.Day;
break;
case TaskAwardFreq.enumTAFEachWeek:
{
int curDow = (int)curLocal.DayOfWeek; // Sunday=0
int lastDow = (int)lastLocal.DayOfWeek;
int curDiff = (curDow == 0) ? 6 : (curDow - 1); // Monday-start
int lastDiff = (lastDow == 0) ? 6 : (lastDow - 1);
DateTime curWeekStart = curLocal.Date.AddDays(-curDiff);
DateTime lastWeekStart = lastLocal.Date.AddDays(-lastDiff);
samePeriod = curWeekStart == lastWeekStart;
break;
}
case TaskAwardFreq.enumTAFEachMonth:
samePeriod = curLocal.Year == lastLocal.Year && curLocal.Month == lastLocal.Month;
break;
case TaskAwardFreq.enumTAFEachYear:
samePeriod = curLocal.Year == lastLocal.Year;
break;
}
if (!isLimitTask)
{
return samePeriod ? (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_TIME : 0u;
}
// Period-limit tasks: reset counters when entering a new period.
if (!samePeriod && pTask is CECTaskInterface cec)
{
if (m_FixedData.m_bAccountTaskLimit)
{
byte[] cntBuf = cec.GetFinishedCntList();
if (cntBuf != null && cntBuf.Length > 0)
{
TaskFinishCountList cnt = new TaskFinishCountList();
cnt.ReadFromBytes(cntBuf);
cnt.ResetAt(m_FixedData.m_ID);
cnt.WriteToBuffer(cntBuf);
}
}
else if (m_FixedData.m_bRoleTaskLimit)
{
cec.ResetRoleFinishCount(m_FixedData.m_ID);
}
}
// Maintain/update period mark
timeList.AddOrUpdate(m_FixedData.m_ID, ulCurTime);
timeList.WriteToBuffer(finishedTimeListBuf);
return 0u;
}
// inline unsigned long ATaskTempl::CheckFnshLst(TaskInterface* pTask, unsigned long ulCurTime) const
// 检查完成与失败记录是否允许重复领取 // English: Check finished/failed list for redo permissions
public uint CheckFnshLst(TaskInterface pTask, uint ulCurTime)
{
// Get top task ID - finished task list stores top-level task IDs
// 获取顶层任务ID - 已完成任务列表存储的是顶层任务ID
ATaskTempl pTopTempl = GetTopTask();
uint taskIdToCheck = pTopTempl != null ? pTopTempl.m_FixedData.m_ID : m_FixedData.m_ID;
// Use top task's redo flags
// 使用顶层任务的重做标志
bool bCanRedo = pTopTempl != null ? pTopTempl.m_FixedData.m_bCanRedo : m_FixedData.m_bCanRedo;
bool bCanRedoAfterFailure = pTopTempl != null ? pTopTempl.m_FixedData.m_bCanRedoAfterFailure : m_FixedData.m_bCanRedoAfterFailure;
// If both redo flags are true, task can always be redone, skip check
// 如果两个重做标志都为true,任务总是可以重做,跳过检查
if (bCanRedo && bCanRedoAfterFailure)
return 0u;
FinishedTaskList pFinished = (FinishedTaskList)pTask.GetFinishedTaskList();
int nRet = pFinished.SearchTask(taskIdToCheck);
// nRet: -1 = not found, 0 = successfully completed, 1 = failed
// 成功完成不能接任务,或失败后不能重新接任务
// If successfully completed and can't redo, or failed and can't redo after failure
if ((nRet == 0 && !bCanRedo) || (nRet == 1 && !bCanRedoAfterFailure))
{
// Debug: Log why task is blocked
string taskName = GetTaskName(pTopTempl ?? this);
// BMLogger.Log($"[CheckFnshLst] Task {taskIdToCheck} '{taskName}' blocked: nRet={nRet}, bCanRedo={bCanRedo}, bCanRedoAfterFailure={bCanRedoAfterFailure}");
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CANT_REDO;
}
return 0u;
}
// Helper to get task name for debugging
private string GetTaskName(ATaskTempl pTempl)
{
if (pTempl == null || pTempl.m_FixedData.m_szName == null)
return "Unknown";
try
{
return ModelRenderer.Scripts.Common.ByteToStringUtils.UshortArrayToUnicodeString(pTempl.m_FixedData.m_szName);
}
catch
{
return "Error";
}
}
// inline unsigned long ATaskTempl::CheckDeliverCount(TaskInterface* pTask) const
// 检查周期内角色/账号次数限制 // English: Check period deliver counts for role/account
public uint CheckDeliverCount(TaskInterface pTask)
{
if (m_FixedData.m_bAccountTaskLimit && m_FixedData.m_lPeriodLimit != 0)
{
if (pTask.IsAtCrossServer())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_ACOUNT_LIMIT;
byte[] finishedCntListBuf = pTask.GetFinishedCntList();
TaskFinishCountList pFnshList = new TaskFinishCountList();
pFnshList.ReadFromBytes(finishedCntListBuf);
uint ulTemp = 0;
uint nRet = pFnshList.Search(m_FixedData.m_ID, ref ulTemp);
if ((uint)nRet >= m_FixedData.m_lPeriodLimit)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_MAX_ACC_CNT;
}
else if (m_FixedData.m_bRoleTaskLimit && m_FixedData.m_lPeriodLimit != 0)
{
FinishedTaskList pFnshList = (FinishedTaskList)pTask.GetFinishedTaskList();
long finish_count = pFnshList.SearchTaskFinishCount(m_FixedData.m_ID);
if (finish_count >= m_FixedData.m_lPeriodLimit)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_MAX_ROLE_CNT;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckAccountRMB(TaskInterface* pTask) const
// 检查账号现金(RMB)范围 // English: Check account total cash (RMB) range
public uint CheckAccountRMB(TaskInterface pTask)
{
if (m_FixedData.m_ulPremRMBMin != 0u && m_FixedData.m_ulPremRMBMax != 0u)
{
uint total = pTask.GetAccountTotalCash();
if (total < m_FixedData.m_ulPremRMBMin || total > m_FixedData.m_ulPremRMBMax)
{
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_RMB_NOT_ENOUGH;
}
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckCharTime(TaskInterface* pTask) const
// 检查角色创建/登录时间窗口 // English: Check character time window
public uint CheckCharTime(TaskInterface pTask)
{
// TODO: Implement using m_FixedData.m_bCharTime, m_iCharStartTime/m_iCharEndTime, m_tmCharEndTime and pTask time APIs
return 0u;
}
// inline unsigned long ATaskTempl::CheckLevel(TaskInterface* pTask) const
// 等级上下限 // English: Level min/max
public uint CheckLevel(TaskInterface pTask)
{
uint level = m_FixedData.m_bPremCheckMaxHistoryLevel > 0 ? pTask.GetMaxHistoryLevel() : pTask.GetPlayerLevel();
if (m_FixedData.m_ulPremise_Lev_Min != 0u && level < m_FixedData.m_ulPremise_Lev_Min)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_LEVEL;
if (m_FixedData.m_ulPremise_Lev_Max != 0u && level > m_FixedData.m_ulPremise_Lev_Max)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ABOVE_LEVEL;
return 0u;
}
// inline unsigned long ATaskTempl::CheckReincarnation(TaskInterface* pTask) const
// 转生次数上下限 // English: Reincarnation count min/max
public uint CheckReincarnation(TaskInterface pTask)
{
if (m_FixedData.m_bPremCheckReincarnation)
{
uint count = pTask.GetReincarnationCount();
if (count < m_FixedData.m_ulPremReincarnationMin) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_REINCARNATION;
if (count > m_FixedData.m_ulPremReincarnationMax) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ABOVE_REINCARNATION;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckRealmLevel(TaskInterface* pTask) const
// 修真境界等级 // English: Realm level
public uint CheckRealmLevel(TaskInterface pTask)
{
if (m_FixedData.m_bPremCheckRealmLevel)
{
uint level = GetRealmLevel(pTask);
if (level < m_FixedData.m_ulPremRealmLevelMin) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_REALMLEVEL;
if (level > m_FixedData.m_ulPremRealmLevelMax) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ABOVE_REALMLEVEL;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckRealmExpFull(TaskInterface* pTask) const
// 修真经验是否已满 // English: Realm EXP full check
public uint CheckRealmExpFull(TaskInterface pTask)
{
if (m_FixedData.m_bPremCheckRealmExpFull)
{
if (!pTask.IsRealmExpFull()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_REALM_EXP_FULL;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckRepu(TaskInterface* pTask) const
// 声望上下限 // English: Reputation min/max
public uint CheckRepu(TaskInterface pTask)
{
if (m_FixedData.m_lPremise_Reputation != 0 && pTask.GetReputation() < (uint)m_FixedData.m_lPremise_Reputation)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_REPU;
if (m_FixedData.m_lPremise_RepuMax != 0 && pTask.GetReputation() > (uint)m_FixedData.m_lPremise_RepuMax)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_REPU;
return 0u;
}
// inline unsigned long ATaskTempl::CheckDeposit(TaskInterface* pTask) const
// 寄存金(金币) // English: Deposit (gold)
public uint CheckDeposit(TaskInterface pTask)
{
if (m_FixedData.m_ulPremise_Deposit != 0u && pTask.GetGoldNum() < m_FixedData.m_ulPremise_Deposit)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NO_DEPOSIT;
return 0u;
}
// inline unsigned long ATaskTempl::CheckItems(TaskInterface* pTask) const
// 前置道具检查(任一/全部) // English: Prerequisite item check (any/ALL)
public uint CheckItems(TaskInterface pTask)
{
uint i = 0;
uint ret = m_FixedData.m_bPremItemsAnyOne ? (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NO_ITEM : 0;
for (; i < m_FixedData.m_ulPremItems; i++)
{
ITEM_WANTED wi = m_FixedData.m_PremItems[i];
if (m_FixedData.m_bPremItemsAnyOne)
{
if (_get_item_count(pTask, wi.m_ulItemTemplId, wi.m_bCommonItem) >= wi.m_ulItemNum)
{
ret = 0;
break;
}
}
else
{
if (_get_item_count(pTask, wi.m_ulItemTemplId, wi.m_bCommonItem) < wi.m_ulItemNum)
{
ret = (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NO_ITEM;
break;
}
}
}
return ret;
}
// inline unsigned long ATaskTempl::CheckFaction(TaskInterface* pTask) const
// 家族/帮派及职位 // English: Faction/clan and role
public uint CheckFaction(TaskInterface pTask)
{
int role = pTask.GetFactionRole();
bool roleOk = role <= m_FixedData.m_iPremise_FactionRole;
if (m_FixedData.m_ulPremise_Faction != 0u && !(pTask.IsInFaction(m_FixedData.m_ulPremise_Faction) && roleOk))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CLAN;
return 0u;
}
// inline unsigned long ATaskTempl::CheckGender(TaskInterface* pTask) const
// 性别 // English: Gender
public uint CheckGender(TaskInterface pTask)
{
bool isMale = pTask.IsMale();
if (m_FixedData.m_ulGender == TaskTemplConstants.TASK_GENDER_MALE && !isMale) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_GENDER;
if (m_FixedData.m_ulGender == TaskTemplConstants.TASK_GENDER_FEMALE && isMale) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_GENDER;
return 0u;
}
// inline unsigned long ATaskTempl::CheckOccupation(TaskInterface* pTask) const
// 职业 // English: Occupation
public uint CheckOccupation(TaskInterface pTask)
{
if (m_FixedData.m_ulOccupations == 0u) return 0u;
uint current = pTask.GetPlayerOccupation();
for (uint i = 0; i < m_FixedData.m_ulOccupations; i++)
{
if (m_FixedData.m_Occupations[i] == current)
return 0u;
}
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NOT_IN_OCCU;
}
// inline unsigned long ATaskTempl::CheckPeriod(TaskInterface* pTask) const
// 历练阶段(区间) // English: Period check (interval)
public uint CheckPeriod(TaskInterface pTask)
{
uint cur = pTask.GetCurPeriod();
if (cur < m_FixedData.m_ulPremise_Period) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_PERIOD;
if (m_FixedData.m_ulPremise_Period < 20u) return 0u;
if (m_FixedData.m_ulPremise_Period < 30u) return cur < 30u ? 0u : (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_PERIOD;
if (m_FixedData.m_ulPremise_Period < 40u) return cur < 40u ? 0u : (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_PERIOD;
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_PERIOD;
}
// inline unsigned long ATaskTempl::CheckGM(TaskInterface* pTask) const
// GM 限制 // English: GM check
public uint CheckGM(TaskInterface pTask)
{
return m_FixedData.m_bGM ? (pTask.IsGM() ? 0u : (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_GM) : 0u;
}
// inline unsigned long ATaskTempl::CheckShieldUser(TaskInterface* pTask) const
// 屏蔽用户 // English: Shielded user
public uint CheckShieldUser(TaskInterface pTask)
{
return m_FixedData.m_bShieldUser ? (pTask.IsShieldUser() ? 0u : (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_SHIELD_USER) : 0u;
}
// inline unsigned long ATaskTempl::CheckPreTask(TaskInterface* pTask) const
// 前置任务 // English: Previous tasks
public uint CheckPreTask(TaskInterface pTask)
{
uint i;
FinishedTaskList pFinished = (FinishedTaskList)pTask.GetFinishedTaskList();
uint iPremTaskFinishedCount = 0;
for (i = 0; i < m_FixedData.m_ulPremise_Task_Count; i++)
{
if (m_FixedData.m_ulPremise_Task_Least_Num == 0)
{
if (pFinished.SearchTask(m_FixedData.m_ulPremise_Tasks[i]) != 0)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_PREV_TASK;
}
else if (pFinished.SearchTask(m_FixedData.m_ulPremise_Tasks[i]) == 0)
{
iPremTaskFinishedCount++;
}
}
return m_FixedData.m_ulPremise_Task_Least_Num != 0 ?
(iPremTaskFinishedCount < m_FixedData.m_ulPremise_Task_Least_Num ? (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_PREV_TASK : 0) : 0;
}
// (No inline in provided snippet) 互斥任务 // English: Mutex task
public uint CheckMutexTask(TaskInterface pTask, uint ulCurTime)
{
uint i;
FinishedTaskList pFinished = (FinishedTaskList)pTask.GetFinishedTaskList();
uint iPremTaskFinishedCount = 0;
for (i = 0; i < m_FixedData.m_ulPremise_Task_Count; i++)
{
if (m_FixedData.m_ulPremise_Task_Least_Num == 0)
{
if (pFinished.SearchTask(m_FixedData.m_ulPremise_Tasks[i]) != 0)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_PREV_TASK;
}
else if (pFinished.SearchTask(m_FixedData.m_ulPremise_Tasks[i]) == 0)
{
iPremTaskFinishedCount++;
}
}
return m_FixedData.m_ulPremise_Task_Least_Num != 0 ? (iPremTaskFinishedCount < m_FixedData.m_ulPremise_Task_Least_Num ?
(uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_PREV_TASK : 0) : 0;
}
// inline unsigned long ATaskTempl::CheckInZone(TaskInterface* pTask) const
// 区域检查 // English: In-zone check
public uint CheckInZone(TaskInterface pTask)
{
if (m_FixedData.m_bDelvInZone)
{
float[] pos = new float[3];
uint ulWorldId = (uint)pTask.GetPos(pos);
/* if (ulWorldId != m_ulDelvWorld ||
!is_in_zone(
m_DelvMinVert,
m_DelvMaxVert,
pos))
return TASK_PREREQU_FAIL_NOT_IN_ZONE;*/
if(ulWorldId != m_FixedData.m_ulDelvWorld)
return (uint) TaskInterfaceConstants.TASK_PREREQU_FAIL_NOT_IN_ZONE;
for (uint i=0; i < m_FixedData.m_ulDelvRegionCnt; i++)
{
Task_Region t = m_FixedData.m_pDelvRegion[i];
if(is_in_zone(t.zvMin,t.zvMax,pos))
return 0;
}
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NOT_IN_ZONE;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckTeamTask(TaskInterface* pTask) const
// 组队接任务要求 // English: Team-task requirements
public uint CheckTeamTask(TaskInterface pTask)
{
// if (m_FixedData.m_bTeamwork && m_FixedData.m_bRcvByTeam) // ӽ
// {
// if (!pTask.IsCaptain()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NOT_CAPTAIN;
// return HasAllTeamMemsWanted(pTask, true);
// }
return 0u;
}
// inline unsigned long ATaskTempl::CheckSpouse(TaskInterface* pTask) const
// 配偶要求 // English: Spouse requirement
public uint CheckSpouse(TaskInterface pTask)
{
if (m_FixedData.m_bPremise_Spouse && !pTask.IsMarried())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_INDETERMINATE;
return 0u;
}
// inline unsigned long ATaskTempl::CheckWeddingOwner(TaskInterface* pTask) const
// 婚礼举办者检查 // English: Wedding owner check
public uint CheckWeddingOwner(TaskInterface pTask)
{
if (m_FixedData.m_bPremiseWeddingOwner)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_MARRIAGE;
if (!pTask.IsWeddingOwner()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WEDDING_OWNER;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckMarriage(TaskInterface* pTask) const
// 结婚任务条件 // English: Marriage task requirements
public uint CheckMarriage(TaskInterface pTask)
{
//TODO:
/*if (m_FixedData.m_bMarriage)
{
if (pTask.IsAtCrossServer())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_MARRIAGE;
if (pTask.IsMarried())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ILLEGAL_MEM;
if (!pTask.IsInTeam())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ILLEGAL_MEM;
if (pTask.GetTeamMemberNum() != 2)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ILLEGAL_MEM;
task_team_member_info m1, m2;
pTask.GetTeamMemberInfo(0, &m1);
pTask.GetTeamMemberInfo(1, &m2);
if (m1.m_bMale == m2.m_bMale)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_ILLEGAL_MEM;
}*/
return 0u;
}
// inline unsigned long ATaskTempl::CheckLivingSkill(TaskInterface* pTask) const
// 生活技能等级 // English: Living skill level
public uint CheckLivingSkill(TaskInterface pTask)
{
// TODO: Loop m_FixedData.m_lSkillLev[MAX_LIVING_SKILLS] with skill ids and pTask.HasLivingSkill/GetLivingSkillLevel
// unsigned long i;
//
// for (i = 0; i < MAX_LIVING_SKILLS; i++)
// {
// if (m_lSkillLev[i] == 0)
// continue;
//
// if (!pTask.HasLivingSkill(_living_skill_ids[i])
// || pTask->GetLivingSkillLevel(_living_skill_ids[i]) < m_lSkillLev[i])
// return TASK_PREREQU_FAIL_LIVING_SKILL;
// }
return 0u;
}
// inline unsigned long ATaskTempl::CheckIvtrEmptySlot(TaskInterface* pTask) const
// 背包空位检查 // English: Inventory empty slot check
public uint CheckIvtrEmptySlot(TaskInterface pTask)
{
if (!m_FixedData.m_bCompareItemAndInventory) return 0u;
if (pTask.GetInvEmptySlot() < m_FixedData.m_ulInventorySlotNum)
{
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NOT_IVTRSLOTNUM;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckFactionContrib(TaskInterface* pTask) const
// 家族贡献 // English: Faction contribution
public uint CheckFactionContrib(TaskInterface pTask)
{
int contrib = pTask.GetFactionContrib();
if (m_FixedData.m_iPremiseFactionContrib != 0 && contrib < m_FixedData.m_iPremiseFactionContrib)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_FACTION_CONTRIB;
return 0u;
}
// inline unsigned long ATaskTempl::CheckRecordTasksNum(TaskInterface* pTask) const
// 完成记录任务数量 // English: Finished-record tasks count
public uint CheckRecordTasksNum(TaskInterface pTask)
{
// TODO: Implement using FinishedTaskList scanning for recorded tasks count
return 0u;
}
// inline unsigned long ATaskTempl::CheckTransform(TaskInterface* pTask) const
// 变身形态 // English: Transform mask
public uint CheckTransform(TaskInterface pTask)
{
byte playerShapeType = pTask.GetShapeMask();
// 0xFF 不限制 // English: 0xFF = no restriction
if (m_FixedData.m_ucPremiseTransformedForm == 0xFF) return 0u;
// 职业变身 // English: Occupation transform
if (m_FixedData.m_ucPremiseTransformedForm == 0x80)
{
if ((playerShapeType >> 6) != 2) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TRANSFORM_MASK;
return 0u;
}
// 指定形态 // English: Specific form
if (m_FixedData.m_ucPremiseTransformedForm != playerShapeType)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TRANSFORM_MASK;
return 0u;
}
// inline unsigned long ATaskTempl::CheckForce(TaskInterface* pTask) const
// 势力检查 // English: Force check
public uint CheckForce(TaskInterface pTask)
{
if (m_FixedData.m_bPremCheckForce)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_FORCE;
if (m_FixedData.m_iPremForce == -1)
{
if (pTask.GetForce() == 0) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_FORCE;
}
else if (pTask.GetForce() != m_FixedData.m_iPremForce)
{
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_FORCE;
}
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckForceReputation(TaskInterface* pTask) const
// 势力声望 // English: Force reputation
public uint CheckForceReputation(TaskInterface pTask)
{
if (m_FixedData.m_iPremForceReputation != 0)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_FORCE;
if (pTask.GetForceReputation() < m_FixedData.m_iPremForceReputation)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_FORCE_REPUTATION;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckForceContribution(TaskInterface* pTask) const
// 势力贡献 // English: Force contribution
public uint CheckForceContribution(TaskInterface pTask)
{
if (m_FixedData.m_iPremForceContribution != 0)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_FORCE;
if (pTask.GetForceContribution() < m_FixedData.m_iPremForceContribution)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_FORCE_REPUTATION;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckExp(TaskInterface* pTask) const
// 势力经验 // English: Force EXP
public uint CheckExp(TaskInterface pTask)
{
if (m_FixedData.m_iPremForceExp != 0)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_FORCE;
if (pTask.GetExp() < m_FixedData.m_iPremForceExp) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_EXP;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckSP(TaskInterface* pTask) const
// 势力SP // English: Force SP
public uint CheckSP(TaskInterface pTask)
{
if (m_FixedData.m_iPremForceSP != 0)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_FORCE;
if (pTask.GetSP() < m_FixedData.m_iPremForceSP) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_SP;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckForceActivityLevel(TaskInterface* pTask) const
// 势力活跃等级 // English: Force activity level
public uint CheckForceActivityLevel(TaskInterface pTask)
{
if (m_FixedData.m_iPremForceActivityLevel != -1)
{
if (pTask.IsAtCrossServer()) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CROSSSERVER_NO_FORCE;
if (m_FixedData.m_iPremForceActivityLevel != pTask.GetForceActivityLevel())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_FORCE_AL;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckKing(TaskInterface* pTask) const
// 王检查 // English: King check
public uint CheckKing(TaskInterface pTask)
{
if (m_FixedData.m_bPremIsKing && !pTask.IsKing())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_KING;
return 0u;
}
// inline unsigned long ATaskTempl::CheckNotInTeam(TaskInterface* pTask) const
// 不在队伍 // English: Not in team
public uint CheckNotInTeam(TaskInterface pTask)
{
if (m_FixedData.m_bPremNotInTeam && pTask.IsInTeam())
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_IN_TEAM;
return 0u;
}
// inline unsigned long ATaskTempl::CheckTitle(TaskInterface* pTask) const
// 称号检查 // English: Title check
public uint CheckTitle(TaskInterface pTask)
{
if (m_FixedData.m_iPremTitleNumTotal != 0) {
int iNumRequired = (int)m_FixedData.m_iPremTitleNumTotal;
if (m_FixedData.m_iPremTitleNumRequired > 0 && m_FixedData.m_iPremTitleNumRequired < m_FixedData.m_iPremTitleNumTotal)
iNumRequired = (int)m_FixedData.m_iPremTitleNumRequired;
int iTitleCount = 0;
for (uint i = 0; i < m_FixedData.m_iPremTitleNumTotal; ++i) {
if (pTask.HaveGotTitle((uint)m_FixedData.m_PremTitles[i]))
iTitleCount++;
}
if (iTitleCount < iNumRequired)
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TITLE;
}
if (m_Award_S.m_ulTitleNum != 0) {
for (uint i = 0; i < m_Award_S.m_ulTitleNum; ++i) {
if (pTask.HaveGotTitle(m_Award_S.m_pTitleAward[i].m_ulTitleID))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TITLE;
}
}
if (m_Award_F.m_ulTitleNum != 0) {
for (uint i = 0; i < m_Award_F.m_ulTitleNum; ++i) {
if (pTask.HaveGotTitle(m_Award_F.m_pTitleAward[i].m_ulTitleID))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TITLE;
}
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckHistoryStage(TaskInterface* pTask) const
// 历史阶段 // English: History stage
public uint CheckHistoryStage(TaskInterface pTask)
{
int index = pTask.GetCurHistoryStageIndex();
if (m_FixedData.m_iPremHistoryStageIndex == null) return TaskInterfaceConstants.TASK_PREREQU_FAIL_HISTORYSTAGE;
if (m_FixedData.m_iPremHistoryStageIndex[0] != 0 && (index <=0 || index < m_FixedData.m_iPremHistoryStageIndex[0]))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_HISTORYSTAGE;
if (m_FixedData.m_iPremHistoryStageIndex[1] != 0 && (index <=0 || index > m_FixedData.m_iPremHistoryStageIndex[1]))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_HISTORYSTAGE;
return 0u;
}
// inline unsigned long ATaskTempl::CheckCardCollection(TaskInterface* pTask) const
// 将星卡收集数量 // English: General card collection count
public uint CheckCardCollection(TaskInterface pTask)
{
uint count = pTask.GetObtainedGeneralCardCount();
if (m_FixedData.m_ulPremGeneralCardCount != 0 && (count < m_FixedData.m_ulPremGeneralCardCount))
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CARD_COUNT_COLLECTION;
return 0u;
}
// inline unsigned long ATaskTempl::CheckCardRankCount(TaskInterface* pTask) const
// 指定品级将星卡数量 // English: Specific rank general card count
public uint CheckCardRankCount(TaskInterface pTask)
{
if (m_FixedData.m_iPremGeneralCardRank >= 0 && m_FixedData.m_ulPremGeneralCardRankCount != 0) {
uint count = pTask.GetObtainedGeneralCardCountByRank(m_FixedData.m_iPremGeneralCardRank);
if (count < m_FixedData.m_ulPremGeneralCardRankCount) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_CARD_COUNT_RANK;
}
return 0u;
}
// inline unsigned long ATaskTempl::CheckInTransformShape(TaskInterface* pTask) const
// 变身中禁止导航 // English: No navigate while in shaped
public uint CheckInTransformShape(TaskInterface pTask)
{
if (m_FixedData.m_enumMethod == (uint)TaskCompletionMethod.enumTMSimpleClientTaskForceNavi && pTask.GetShapeMask() != 0)
{
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_NO_NAVIGATE_INSHPAED;
}
return 0u;
}
// 特殊活动奖励检查 // English: Special award activity check
public uint CheckSpecialAward(TaskInterface pTask)
{
// 非“特殊活动奖励”类型则直接通过 // English: Pass if not a special-award dynamic task
if (m_FixedData.m_DynTaskType != (byte)DynTaskType.enumDTTSpecialAward) return 0u;
// 未配置特殊奖励标识则失败 // English: Fail if special award id not configured
if (m_FixedData.m_ulSpecialAward == 0u) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_SPECIAL_AWARD;
// TODO: 获取玩家特殊奖励信息并校验 // English: Fetch player's special-award info and validate against required id
// 受限于当前 TaskInterface 未暴露获取接口,暂时视为通过 // English: Interface lacks API; treat as pass for now
return 0u;
}
public void ClearValidCount() { m_uValidCount = 0; }
public ATaskTemplMan GetTaskTemplMan()
{
return EC_Game.GetTaskTemplateMan();
}
public ATaskTempl GetConstSubById(uint ulId)
{
ATaskTempl pChild = m_pFirstChild;
while (pChild != null)
{
if (pChild.m_FixedData.m_ID == ulId) return pChild;
pChild = pChild.m_pNextSibling;
}
return null;
}
// 通过子任务序号获取子任务模板(0基) // English: Get child template by index (0-based)
public ATaskTempl GetSubByIndex(byte index)
{
ATaskTempl child = m_pFirstChild;
byte i = 0;
while (child != null)
{
if (i == index) return child;
i++;
child = child.m_pNextSibling;
}
return null;
}
public ActiveTaskEntry DeliverTask(
TaskInterface pTask,
ActiveTaskList pList,
ActiveTaskEntry pEntry,
uint ulCaptainTask,
uint ulMask,
uint ulCurTime,
ATaskTempl pSubTempl,
ref task_sub_tags pSubTag,
TaskGlobalData pGlobal,
byte uParentIndex)
{
if (pTask == null || pList == null) return null;
// Avoid duplicates (server shouldn't send duplicates, but protects managed UI)
var existed = pList.GetEntry(m_FixedData.m_ID);
if (existed != null && existed.m_ID != 0) return existed;
int startIndex = (pEntry == null)
? pList.m_uTaskCount
: Array.IndexOf(pList.m_TaskEntries, pEntry);
if (startIndex < 0) startIndex = pList.m_uTaskCount;
uint maskRef = ulMask;
DeliverTask_Internal(
pTask, pList, startIndex, ulCaptainTask, ref maskRef, ulCurTime,
pSubTempl, ref pSubTag, pGlobal, uParentIndex);
return (startIndex >= 0 && startIndex < TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
? pList.m_TaskEntries[startIndex]
: null;
}
// C++ parity helper: returns the next free slot index (like returning `pEntry` pointer).
private int DeliverTask_Internal(
TaskInterface pTask,
ActiveTaskList pList,
int entryIndex,
uint ulCaptainTask,
ref uint ulMask,
uint ulCurTime,
ATaskTempl pSubTempl,
ref task_sub_tags pSubTag,
TaskGlobalData pGlobal,
byte uParentIndex)
{
if (entryIndex < 0 || entryIndex >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
return entryIndex;
// If the slot is occupied, reserve 1 empty slot here (C++: RealignTask(pEntry, 1))
if (pList.m_TaskEntries[entryIndex] != null && pList.m_TaskEntries[entryIndex].m_ID != 0)
pList.RealignTaskAtIndex(entryIndex, 1);
var entry = pList.m_TaskEntries[entryIndex] ?? new ActiveTaskEntry();
pList.m_TaskEntries[entryIndex] = entry;
byte uIndex = (byte)entryIndex;
entry.m_ID = (ushort)m_FixedData.m_ID;
entry.m_ulTemplAddr = m_FixedData.m_ID; // managed marker (resolve by id)
entry.m_ParentIndex = (char)uParentIndex;
entry.m_PrevSblIndex = (char)0xff;
entry.m_NextSblIndex = (char)0xff;
entry.m_ChildIndex = (char)0xff;
entry.m_uState = (char)0;
entry.m_ulTaskTime = ulCurTime;
if (ulCaptainTask != 0)
{
entry.m_uCapTaskId = (ushort)ulCaptainTask;
entry.m_ulCapTemplAddr = ulCaptainTask; // managed marker
}
else
{
entry.m_uCapTaskId = 0;
entry.m_ulCapTemplAddr = 0;
}
entry.SetSuccess(); // Later will check whether truly succeed
if (entry.m_BufData != null) Array.Clear(entry.m_BufData, 0, entry.m_BufData.Length);
// C++ always increments here; deletion paths decrement before reusing slots.
if (pList.m_uTaskCount < byte.MaxValue) pList.m_uTaskCount++;
// Root-node counters (client-side only)
if (m_pParent == null)
{
if (m_FixedData.m_bHidden) pList.m_uTopHideTaskCount++;
else if (m_FixedData.m_bDisplayInTitleTaskUI) pList.m_uTitleTaskCount++;
else pList.m_uTopShowTaskCount++;
int used = pList.m_uUsedCount + m_uDepth;
pList.m_uUsedCount = (byte)Math.Clamp(used, 0, byte.MaxValue);
}
// Attach to parent (append to end of child sibling chain)
if (uParentIndex != 0xff)
{
var parent = pList.m_TaskEntries[uParentIndex];
if (parent != null)
{
if (parent.m_ChildIndex == (char)0xff)
parent.m_ChildIndex = (char)uIndex;
else
{
int uChildEntry = (byte)parent.m_ChildIndex;
while (pList.m_TaskEntries[uChildEntry] != null && pList.m_TaskEntries[uChildEntry].m_NextSblIndex != 0xff)
uChildEntry = (byte)pList.m_TaskEntries[uChildEntry].m_NextSblIndex;
if (pList.m_TaskEntries[uChildEntry] != null)
{
pList.m_TaskEntries[uChildEntry].m_NextSblIndex = (char)uIndex;
entry.m_PrevSblIndex = (char)uChildEntry;
}
}
}
}
int nextIndex = entryIndex + 1;
// Explicit server-specified sub-task comes first (C++ pSubTempl branch)
if (pSubTempl != null)
{
return pSubTempl.DeliverTask_Internal(
pTask, pList, nextIndex, 0, ref ulMask, ulCurTime, null, ref pSubTag, pGlobal, uIndex);
}
// Rand-one: on client, follow preselected tag path one step at a time
else if (m_FixedData.m_bRandOne)
{
if (pSubTag.cur_index < pSubTag.sz)
{
byte sel = pSubTag.tags[pSubTag.cur_index++];
var chosen = GetSubByIndex(sel);
if (chosen != null)
{
return chosen.DeliverTask_Internal(
pTask, pList, nextIndex, 0, ref ulMask, ulCurTime, null, ref pSubTag, pGlobal, uIndex);
}
}
return nextIndex;
}
// Normal: deliver children (all, or first if execute-in-order)
else
{
var child = m_pFirstChild;
while (child != null)
{
nextIndex = child.DeliverTask_Internal(
pTask, pList, nextIndex, 0, ref ulMask, ulCurTime, null, ref pSubTag, pGlobal, uIndex);
if (m_FixedData.m_bExeChildInOrder) return nextIndex;
child = child.m_pNextSibling;
}
return nextIndex;
}
}
void RecursiveAward(
TaskInterface pTask,
ActiveTaskList pList,
ActiveTaskEntry pEntry,
uint ulCurtime,
int nChoice,
ref task_sub_tags pSubTag)
{
// 客户端侧尽量对齐 C++ RecursiveAward
// - 清除子任务
// - 维护父/子/兄弟索引关系
// - ExecuteChildInOrder 时复用 slot 投递下一个兄弟任务
// English: Client-side parity with C++ RecursiveAward:
// - Clear children
// - Maintain parent/child/sibling indices
// - If execute-children-in-order, reuse the freed slot to deliver the next sibling task
if (pTask == null || pList == null || pEntry == null) return;
int entryIndex = Array.IndexOf(pList.m_TaskEntries, pEntry);
if (entryIndex < 0) return;
// 失败时不收取物品的特殊逻辑(仅影响清子任务时是否移除物品)
// English: If failed and flagged, don't remove items when clearing children.
bool bFailedTaskDoNotTakeItem = !pEntry.IsSuccess() && m_FixedData.m_bNotClearItemWhenFailed && m_FixedData.m_bClearAcquired;
// Clear children first (C++: pList->ClearChildrenOf(pTask, pEntry, !bFailedTaskDoNotTakeItem))
pList.ClearChildrenOf(pTask, pEntry, !bFailedTaskDoNotTakeItem);
if (pEntry.m_ulTemplAddr == 0) return; // must check it (matches C++)
bool success = pEntry.IsSuccess() && !pEntry.IsGiveUp();
// Only record on top-level templates (same as C++: !m_pParent && m_bNeedRecord)
if (m_pParent == null && m_FixedData.m_bNeedRecord)
{
if (pTask is CECTaskInterface cec)
cec.RecordFinishedTask(m_FixedData.m_ID, success);
}
// Account/role period-limit bookkeeping (C++ RecursiveAward)
if (m_pParent == null && (m_FixedData.m_bAccountTaskLimit || m_FixedData.m_bRoleTaskLimit))
{
// Check deliver time to reset counters when entering a new period.
CheckDeliverTime(pTask, ulCurtime);
// "NotIncCntWhenFailed" gating: only increment counters when allowed.
if (!m_FixedData.m_bNotIncCntWhenFailed || (m_FixedData.m_bNotIncCntWhenFailed && pEntry.IsSuccess()))
{
if (pTask is CECTaskInterface cec)
{
// Maintain time mark for counting period (C++ also updates TaskFinishTimeList here for role limit).
byte[] timeBuf = cec.GetFinishedTimeList();
if (timeBuf != null && timeBuf.Length > 0)
{
TaskFinishTimeList timeList = new TaskFinishTimeList();
timeList.ReadFromBuffer(timeBuf);
timeList.AddOrUpdate(m_FixedData.m_ID, ulCurtime);
timeList.WriteToBuffer(timeBuf);
}
if (m_FixedData.m_bAccountTaskLimit)
{
byte[] cntBuf = cec.GetFinishedCntList();
if (cntBuf != null && cntBuf.Length > 0)
{
TaskFinishCountList cnt = new TaskFinishCountList();
cnt.ReadFromBytes(cntBuf);
cnt.AddOrUpdate(m_FixedData.m_ID, ulCurtime);
cnt.WriteToBuffer(cntBuf);
}
}
else if (m_FixedData.m_bRoleTaskLimit)
{
FinishedTaskList fnsh = cec.GetFinishedTaskList();
fnsh.AddForFinishCount(m_FixedData.m_ID, pEntry.IsSuccess());
cec.WriteFinishedTaskList(fnsh);
}
}
}
}
// Mark empty + decrement count (C++ does this before realign / reuse)
pEntry.m_ulTemplAddr = 0;
pEntry.m_ID = 0;
if (pList.m_uTaskCount > 0) pList.m_uTaskCount--;
// If has parent, unlink from sibling chain and handle parent propagation
if (pEntry.m_ParentIndex != 0xff)
{
int parentIndex = (byte)pEntry.m_ParentIndex;
if (parentIndex >= 0 && parentIndex < TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
{
var parentEntry = pList.m_TaskEntries[parentIndex];
if (parentEntry != null)
{
// unlink siblings (matches C++)
if (pEntry.m_PrevSblIndex != 0xff)
{
var prev = pList.m_TaskEntries[(byte)pEntry.m_PrevSblIndex];
if (prev != null) prev.m_NextSblIndex = pEntry.m_NextSblIndex;
}
else
{
parentEntry.m_ChildIndex = pEntry.m_NextSblIndex;
}
if (pEntry.m_NextSblIndex != 0xff)
{
var next = pList.m_TaskEntries[(byte)pEntry.m_NextSblIndex];
if (next != null) next.m_PrevSblIndex = pEntry.m_PrevSblIndex;
}
// ParentAlsoFail
if (!pEntry.IsSuccess() && m_FixedData.m_bParentAlsoFail && m_pParent != null)
{
pList.RealignTaskAtIndex(entryIndex, 0);
parentEntry.ClearSuccess();
parentEntry.SetFinished();
m_pParent.RecursiveAward(pTask, pList, parentEntry, ulCurtime, -1, ref pSubTag);
return;
}
// ParentAlsoSuccess
else if (pEntry.IsSuccess() && m_FixedData.m_bParentAlsoSucc && m_pParent != null)
{
pList.RealignTaskAtIndex(entryIndex, 0);
parentEntry.SetFinished();
pList.ClearChildrenOf(pTask, parentEntry, true);
if (m_pParent.m_FixedData.m_enumFinishType == (uint)TaskFinishType.enumTFTDirect)
m_pParent.RecursiveAward(pTask, pList, parentEntry, ulCurtime, -1, ref pSubTag);
return;
}
// Execute children in order: deliver next sibling into the freed slot (C++ reuse pEntry)
else if (m_pParent != null && m_pParent.m_FixedData.m_bExeChildInOrder && m_pNextSibling != null)
{
// if parent still has children OR next sibling already exists, just realign
if (parentEntry.m_ChildIndex != 0xff || pList.GetEntry(m_pNextSibling.m_FixedData.m_ID) != null)
{
pList.RealignTaskAtIndex(entryIndex, 0);
}
else
{
// reuse current slot object; keep it in array at entryIndex
m_pNextSibling.DeliverTask(
pTask,
pList,
pEntry,
0,
pTask.GetTaskMask(),
ulCurtime,
null,
ref pSubTag,
new TaskGlobalData(),
(byte)pEntry.m_ParentIndex);
}
return;
}
// If this was the last child, mark parent finished
else if (parentEntry.m_ChildIndex == 0xff && m_pParent != null)
{
pList.RealignTaskAtIndex(entryIndex, 0);
parentEntry.SetFinished();
if (m_pParent.m_FixedData.m_enumFinishType == (uint)TaskFinishType.enumTFTDirect)
m_pParent.RecursiveAward(pTask, pList, parentEntry, ulCurtime, -1, ref pSubTag);
return;
}
}
}
// Default: just realign this freed slot away
pList.RealignTaskAtIndex(entryIndex, 0);
return;
}
else
{
// Root node: realign and adjust counts similar to C++ (counters will also be recomputed by UI paths)
pList.RealignTaskAtIndex(entryIndex, 0);
pList.RecountTaskCounters();
return;
}
// TODO : implement full logic when ActiveTaskList/ActiveTaskEntry and TaskInterface APIs are available
/*{
char log[1024];
sprintf(log, "RecursiveAward: state = 0x%x", pEntry->m_uState);
TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 1, log);
}
ActiveTaskEntry* aEntries = pList->m_TaskEntries;
// ʧܲȡƷ
bool bFailedTaskDoNotTakeItem = !pEntry->IsSuccess() && m_bNotClearItemWhenFailed && m_bClearAcquired;
//
pList->ClearChildrenOf(pTask, pEntry, !bFailedTaskDoNotTakeItem);
if (!pEntry->m_ulTemplAddr) return; // must check it
if (!m_pParent && m_bNeedRecord)
{
static_cast<FinishedTaskList*>(pTask->GetFinishedTaskList())->AddOneTask(
m_ID,
pEntry->IsSuccess());
}
// ˺޴ɴ
if (!m_pParent && m_bAccountTaskLimit)
{
// ʱжǷɴ
CheckDeliverTime(pTask, ulCurtime);
// ûйѡʧܵʱ򲻼¼ɴ߹ѡʧܵʱ򲻼¼ɴɹʱ
if (!m_bNotIncCntWhenFailed || (m_bNotIncCntWhenFailed && pEntry->IsSuccess()))
static_cast<TaskFinishCountList*>(pTask->GetFinishedCntList())->AddOrUpdate(m_ID,ulCurtime);
}
// ɫ޴ɴ
else if (!m_pParent && m_bRoleTaskLimit)
{
// ʱжǷɴ
CheckDeliverTime(pTask, ulCurtime);
// ûйѡʧܵʱ򲻼¼ɴ߹ѡʧܵʱ򲻼¼ɴɹʱ
if (!m_bNotIncCntWhenFailed || (m_bNotIncCntWhenFailed && pEntry->IsSuccess())) {
// FinishedTaskListɴ
static_cast<FinishedTaskList*>(pTask->GetFinishedTaskList())->AddForFinishCount(m_ID,pEntry->IsSuccess());
// TaskFinishTimeListʱ
static_cast<TaskFinishTimeList*>(pTask->GetFinishedTimeList())->AddOrUpdate(m_ID, ulCurtime);
}
}
#ifndef _TASK_CLIENT
// Ʒ
AWARD_DATA ad;
CalcAwardData(
pTask,
&ad,
pEntry,
pEntry->m_ulTaskTime,
ulCurtime);
unsigned long ulRet = DeliverByAwardData(pTask, pList, pEntry, &ad, ulCurtime, nChoice);
if (ulRet)
{
char log[1024];
sprintf(log, "RecursiveAward, ret = %d", ulRet);
TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 0, log);
}
// ȥõĻԤȸƷڷƷִ
if (m_bClearAcquired)
{
if (!bFailedTaskDoNotTakeItem)
RemoveAcquiredItem(pTask, false, pEntry->IsSuccess());
}
else if (!pEntry->IsSuccess())
TakeAwayGivenItems(pTask);
#endif
pEntry->m_ulTemplAddr = 0;
pEntry->m_ID = 0;
if (pList->m_uTaskCount)
pList->m_uTaskCount--;
else
TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 0, "Award, TaskCount == 0");
if (pEntry->m_ParentIndex != 0xff)
{
ActiveTaskEntry& ParentEntry = aEntries[pEntry->m_ParentIndex];
if (pEntry->m_PrevSblIndex != 0xff)
aEntries[pEntry->m_PrevSblIndex].m_NextSblIndex = pEntry->m_NextSblIndex;
else
ParentEntry.m_ChildIndex = pEntry->m_NextSblIndex;
if (pEntry->m_NextSblIndex != 0xff)
aEntries[pEntry->m_NextSblIndex].m_PrevSblIndex = pEntry->m_PrevSblIndex;
if (!pEntry->IsSuccess() && m_bParentAlsoFail)
{
pList->RealignTask(pEntry, 0);
ParentEntry.ClearSuccess();
ParentEntry.SetFinished();
m_pParent->RecursiveAward(pTask, pList, &ParentEntry, ulCurtime, -1, pSubTag);
}
else if (pEntry->IsSuccess() && m_bParentAlsoSucc)
{
pList->RealignTask(pEntry, 0);
ParentEntry.SetFinished();
pList->ClearChildrenOf(pTask, &ParentEntry);
if (m_pParent->m_enumFinishType == enumTFTDirect)
m_pParent->RecursiveAward(pTask, pList, &ParentEntry, ulCurtime, -1, pSubTag);
#ifdef _TASK_CLIENT
else if (!m_pParent->m_bHidden && !m_pParent->m_bDisplayInTitleTaskUI)
TaskInterface::TraceTask(m_pParent->m_ID);
#endif
}
else if (m_pParent->m_bExeChildInOrder && m_pNextSibling)
{
if (ParentEntry.m_ChildIndex != 0xff || pList->GetEntry(m_pNextSibling->m_ID)) // Ϊ0xff
pList->RealignTask(pEntry, 0);
else
{
m_pNextSibling->DeliverTask(
pTask,
pList,
pEntry,
0,
*pTask->GetTaskMask(),
ulCurtime,
NULL,
pSubTag,
NULL,
pEntry->m_ParentIndex);
#ifdef _TASK_CLIENT
// ѡһһ˳ִ
// ǰ׷ٸʱѾ
// ڵڶԺ˳ִе
if (!m_pParent->m_bHidden && !m_pParent->m_bDisplayInTitleTaskUI)
TaskInterface::TraceTask(m_pNextSibling->m_ID);
#endif
}
}
else if (ParentEntry.m_ChildIndex == 0xff) // ʱΪ
{
pList->RealignTask(pEntry, 0);
ParentEntry.SetFinished();
if (m_pParent->m_enumFinishType == enumTFTDirect)
m_pParent->RecursiveAward(pTask, pList, &ParentEntry, ulCurtime, -1, pSubTag);
#ifdef _TASK_CLIENT
else if (!m_pParent->m_bHidden && !m_pParent->m_bDisplayInTitleTaskUI)
TaskInterface::TraceTask(m_pParent->m_ID);
#endif
}
else
pList->RealignTask(pEntry, 0);
}
else // Root Node
{
pList->RealignTask(pEntry, 0);
if (pList->m_uUsedCount >= m_uDepth)
pList->m_uUsedCount -= m_uDepth;
else
{
char log[1024];
sprintf(log, "Award, Used = %d", pList->m_uUsedCount);
TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 0, log);
pList->m_uUsedCount = 0;
}
if (m_bHidden)
{
if (pList->m_uTopHideTaskCount)
pList->m_uTopHideTaskCount--;
}
else if(m_bDisplayInTitleTaskUI)
{
if (pList->m_uTitleTaskCount)
pList->m_uTitleTaskCount--;
}
else
{
if (pList->m_uTopShowTaskCount)
pList->m_uTopShowTaskCount--;
}
}*/
}
public void TakeAwayGivenItems(TaskInterface pTask)
{
uint ulCount;
for (int i = 0; i < m_FixedData.m_ulGivenItems; i++)
{
ITEM_WANTED wi = m_FixedData.m_GivenItems[i];
if (wi.m_bCommonItem)
{
ulCount = (uint)pTask.GetCommonItemCount(wi.m_ulItemTemplId);
if (ulCount > wi.m_ulItemNum) ulCount = wi.m_ulItemNum;
if (ulCount > 0) pTask.TakeAwayCommonItem(wi.m_ulItemTemplId, ulCount);
}
else
{
ulCount = (uint)pTask.GetTaskItemCount(wi.m_ulItemTemplId);
if (ulCount > 0) pTask.TakeAwayTaskItem(wi.m_ulItemTemplId, ulCount);
}
}
}
public void RemoveAcquiredItem(TaskInterface pTask, bool bClearTask, bool bSuccess)
{
var m_enumMethod = m_FixedData.m_enumMethod;
var m_ulItemsWanted = m_FixedData.m_ulItemsWanted;
var m_ItemsWanted = m_FixedData.m_ItemsWanted;
var m_ulGoldWanted = m_FixedData.m_ulGoldWanted;
var m_iFactionContribWanted = m_FixedData.m_iFactionContribWanted;
var m_iFactionExpContribWanted = m_FixedData.m_iFactionExpContribWanted;
var m_ulMonsterWanted = m_FixedData.m_ulMonsterWanted;
var m_MonsterWanted = m_FixedData.m_MonsterWanted;
var m_ulPlayerWanted = m_FixedData.m_ulPlayerWanted;
var m_PlayerWanted = m_FixedData.m_PlayerWanted;
if (m_enumMethod == (uint)TaskCompletionMethod.enumTMCollectNumArticle)
{
for (int i = 0; i < m_ulItemsWanted; i++)
{
ITEM_WANTED wi = m_ItemsWanted[i];
uint ulCount;
if (wi.m_bCommonItem)
{
if (bClearTask) continue;
ulCount = (uint)pTask.GetCommonItemCount(wi.m_ulItemTemplId);
if (ulCount == 0) continue;
if (wi.m_ulItemNum > 0 && ulCount > wi.m_ulItemNum) ulCount = wi.m_ulItemNum;
pTask.TakeAwayCommonItem(wi.m_ulItemTemplId, ulCount);
}
else
{
ulCount = (uint)pTask.GetTaskItemCount(wi.m_ulItemTemplId);
if (ulCount > 0) pTask.TakeAwayTaskItem(wi.m_ulItemTemplId, ulCount);
}
}
// ɹǮ
if (m_ulGoldWanted > 0 && !bClearTask && bSuccess)
{
uint ulGold = pTask.GetGoldNum();
if (ulGold > m_ulGoldWanted) ulGold = m_ulGoldWanted;
pTask.TakeAwayGold(ulGold);
}
//ɹѰɹ׶
if (m_iFactionContribWanted > 0 && !bClearTask && bSuccess)
{
int iContrib = pTask.GetFactionConsumeContrib();
if (iContrib > m_iFactionContribWanted) iContrib = m_iFactionContribWanted;
pTask.TakeAwayFactionConsumeContrib(iContrib);
}
//ɹѰɾ
if (m_iFactionExpContribWanted > 0 && !bClearTask && bSuccess)
{
int iContrib = pTask.GetFactionExpContrib();
if (iContrib > m_iFactionExpContribWanted) iContrib = m_iFactionExpContribWanted;
pTask.TakeAwayFactionExpContrib(iContrib);
}
}
else if (m_enumMethod == (uint)TaskCompletionMethod.enumTMKillNumMonster)
{
for (int i = 0; i < m_ulMonsterWanted; i++)
{
// const MONSTER_WANTED& mw = m_MonsterWanted[i];
MONSTER_WANTED mw = m_MonsterWanted[i];
if (mw.m_ulDropItemId == 0) continue;
uint ulCount;
if (mw.m_bDropCmnItem)
{
ulCount = (uint)pTask.GetCommonItemCount(mw.m_ulDropItemId);
if (mw.m_ulDropItemCount > 0 && ulCount > mw.m_ulDropItemCount) ulCount = mw.m_ulDropItemCount;
if (ulCount > 0) pTask.TakeAwayCommonItem(mw.m_ulDropItemId, ulCount);
}
else
{
ulCount = (uint)pTask.GetTaskItemCount(mw.m_ulDropItemId);
if (ulCount > 0) pTask.TakeAwayTaskItem(mw.m_ulDropItemId, ulCount);
}
}
}
else if (m_enumMethod == (uint)TaskCompletionMethod.enumTMKillPlayer)
{
for (uint i = 0; i < m_ulPlayerWanted; ++i)
{
// const PLAYER_WANTED& pw = m_PlayerWanted[i];
var pw = m_PlayerWanted[i];
if (pw.m_ulDropItemId == 0) continue;
uint ulCount;
if (pw.m_bDropCmnItem)
{
ulCount = (uint)pTask.GetCommonItemCount(pw.m_ulDropItemId);
if (pw.m_ulDropItemCount > 0 && ulCount > pw.m_ulDropItemCount) ulCount = pw.m_ulDropItemCount;
if (ulCount > 0) pTask.TakeAwayCommonItem(pw.m_ulDropItemId, ulCount);
}
else
{
ulCount = (uint)pTask.GetTaskItemCount(pw.m_ulDropItemId);
if (ulCount>0) pTask.TakeAwayTaskItem(pw.m_ulDropItemId, ulCount);
}
}
}
}
public bool IsValidState() { return m_uValidCount < TaskTemplConstants.TASK_MAX_VALID_COUNT; }
public static bool _compare_pq_key_value( COMPARE_KEY_VALUE CompKeyVal)
{
long lleftValue = CompKeyVal.lLeftNum;
if (CompKeyVal.nLeftType == 0)
{
lleftValue = PublicQuestInterface.QuestGetGlobalValue(CompKeyVal.lLeftNum);
// pTask->GetGlobalValue(CompKeyVal.lLeftNum);
}
long lRightValue = CompKeyVal.lRightNum;
if (CompKeyVal.nRightType == 0)
{
lRightValue = PublicQuestInterface.QuestGetGlobalValue(CompKeyVal.lRightNum);
// pTask->GetGlobalValue(CompKeyVal.lRightNum);
}
switch(CompKeyVal.nCompOper)
{
case 0:
{
if (lleftValue > lRightValue)
return true;
}
break;
case 1:
{
if (lleftValue == lRightValue)
return true;
}
break;
case 2:
{
if (lleftValue < lRightValue)
return true;
}
break;
default:
break;
}
return false;
}
public uint CheckGlobalPQKeyValue(bool bFinCheck)
{
var m_bFinNeedComp = m_FixedData.m_bFinNeedComp;
var m_bPremNeedComp = m_FixedData.m_bPremNeedComp;
var m_Fin1KeyValue = m_FixedData.m_Fin1KeyValue;
var m_Fin2KeyValue = m_FixedData.m_Fin2KeyValue;
var m_nFinExp1AndOrExp2 = m_FixedData.m_nFinExp1AndOrExp2;
var m_nPremExp1AndOrExp2 = m_FixedData.m_nPremExp1AndOrExp2;
var m_Prem1KeyValue = m_FixedData.m_Prem1KeyValue;
var m_Prem2KeyValue = m_FixedData.m_Prem2KeyValue;
if (bFinCheck && !m_bFinNeedComp
||!bFinCheck && !m_bPremNeedComp)
return 0;
bool bFlag1 = false;
bool bFlag2 = false;
if (bFinCheck)
{
bFlag1 = _compare_pq_key_value(m_Fin1KeyValue);
bFlag2 = _compare_pq_key_value(m_Fin2KeyValue);
if (m_nFinExp1AndOrExp2 == 0 && (bFlag1 || bFlag2) //
|| m_nFinExp1AndOrExp2 == 1 && (bFlag1 && bFlag2)) //
return 0;
}
else
{
bFlag1 = _compare_pq_key_value(m_Prem1KeyValue);
bFlag2 = _compare_pq_key_value(m_Prem2KeyValue);
if (m_nPremExp1AndOrExp2 == 0 && (bFlag1 || bFlag2) //
|| m_nPremExp1AndOrExp2 == 1 && (bFlag1 && bFlag2)) //
return 0;
}
return TaskInterfaceConstants.TASK_PREREQU_FAIL_GLOBAL_KEYVAL;
}
public void OnSetFinished(
TaskInterface pTask,
ActiveTaskList pList,
ActiveTaskEntry pEntry,
bool bNotifyMem = true)
{
var m_enumFinishType = m_FixedData.m_enumMethod;
pEntry.SetFinished();
// ֪ͨͻ
NotifyClient(
pTask,
pEntry,
TaskTemplConstants.TASK_SVR_NOTIFY_FINISHED,
0);
if(pEntry.GetTempl().m_FixedData.m_bPQTask)
PublicQuestInterface.QuestRemovePlayer((int)pEntry.GetTempl().m_FixedData.m_ID, pTask.GetPlayerId());
if (m_enumFinishType == (uint)TaskFinishType.enumTFTDirect ||
!pEntry.IsSuccess())
DeliverAward(pTask, pList, pEntry, -1, bNotifyMem);
}
public void GiveUpOneTask(
TaskInterface pTask,
ActiveTaskList pList,
ActiveTaskEntry pEntry,
bool bForce)
{
var m_bCanGiveUp = m_FixedData.m_bCanGiveUp;
if (m_pParent != null || !m_bCanGiveUp) return;
pEntry.ClearSuccess();
pEntry.SetGiveUp();
OnSetFinished(pTask, pList, pEntry);
// TaskInterface::WriteLog(pTask.GetPlayerId(), m_ID, 1, "GiveUpTask");
// TaskInterface::WriteKeyLog(pTask.GetPlayerId(), m_ID, 1, "GiveUpTask");
}
private void NotifyClient(
TaskInterface pTask,
ActiveTaskEntry pEntry,
byte uReason,
uint ulCurTime,
uint ulParam=0,
int dps=0,
int dph=0)
{
var m_ID = m_FixedData.m_ID;
var m_MonsterWanted = m_FixedData.m_MonsterWanted;
string log = new string(new char[1024]);
byte[] buf = new byte[512];
// task_notify_base* pNotify = reinterpret_cast<task_notify_base*>(buf);
var pNotify = new task_notify_base();
int sz = 0;
pNotify.reason = uReason;
pNotify.task = (ushort)(m_ID);
bool bWriteLog = true;
switch (uReason)
{
case TaskTemplConstants.TASK_SVR_NOTIFY_PLAYER_KILLED:
var svr_player_killed = new svr_player_killed();
svr_player_killed.baseObj = pNotify;
svr_player_killed.player_num = pEntry.m_wMonsterNum[ulParam];
svr_player_killed.index = (ushort)ulParam;
sz = Marshal.SizeOf(svr_player_killed);
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_MONSTER_KILLED:
{
var svrMonsterKilled = new svr_monster_killed()
{
baseObj = pNotify,
monster_id = m_MonsterWanted[ulParam].m_ulMonsterTemplId,
monster_num = pEntry.m_wMonsterNum[ulParam],
dps = (dps != 0 && dph != 0) ? dps : 0,
dph = (dps != 0 && dph != 0) ? dph : 0
};
sz = Marshal.SizeOf(svrMonsterKilled);
bWriteLog = false;
}
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_NEW:
{
// static_cast<svr_new_task*>(pNotify)->set_data(
// ulCurTime,
// reinterpret_cast<unsigned long>(pEntry),
// *(reinterpret_cast<const task_sub_tags*>(ulParam))
// );
var svrNewTask = new svr_new_task
{
baseObj = pNotify,
};
svrNewTask.set_data(
ulCurTime,
pEntry.m_ID,
Marshal.PtrToStructure<task_sub_tags>((IntPtr)ulParam)
);
sz = svrNewTask.get_size();
break;
}
case TaskTemplConstants.TASK_SVR_NOTIFY_COMPLETE:
// static_cast<svr_task_complete*>(pNotify)->set_data(
// ulCurTime,
// *(reinterpret_cast<const task_sub_tags*>(ulParam))
// );
// sz = static_cast<svr_task_complete*>(pNotify)->get_size();
var svrTaskComplete = new svr_task_complete
{
baseObj = pNotify,
};
svrTaskComplete.set_data(
ulCurTime,
Marshal.PtrToStructure<task_sub_tags>((IntPtr)ulParam)
);
sz = svrTaskComplete.get_size();
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_GIVE_UP:
case TaskTemplConstants.TASK_SVR_NOTIFY_FINISHED:
case TaskTemplConstants.TASK_SVR_NOTIFY_DIS_GLOBAL_VAL:
sz = Marshal.SizeOf<task_notify_base>();
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_ERROR_CODE:
var svrTaskErrCode = new svr_task_err_code
{
baseObj = pNotify
};
svrTaskErrCode.err_code = ulParam;
sz = Marshal.SizeOf( svrTaskErrCode );
break;
default:
// sprintf(log, "NotifyClient, Unknown Reason = %d", uReason);
// TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 0, log);
return;
}
if (bWriteLog)
{
// sprintf(log, "svr: Reason = %d, Param = 0x%x", uReason, ulParam);
// TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 1, log);
}
// assert(sz <= sizeof(buf));
pTask.NotifyClient(buf, sz);
}
public bool DeliverAward(
TaskInterface pTask,
ActiveTaskList pList,
ActiveTaskEntry pEntry,
int nChoice,
bool bNotifyTeamMem = true, // = true
Nullable<TaskGlobalData> pGlobal = null ) // = null
{
char[] log = new char[1024];
// sprintf(log, "DeliverAward: Choice = %d", nChoice);
// TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 1, log);
// TaskInterface::WriteKeyLog(pTask->GetPlayerId(), m_ID, 1, log);
// ʱ
uint ulCurTime = pTask.GetCurTime();
RecursiveCheckTimeLimit(pTask, pList, pEntry, ulCurTime);
// TODO : implement full logic when ActiveTaskList/ActiveTaskEntry and TaskInterface APIs are available
// if (!RecursiveCheckParent(pTask, pList, pEntry))
// pEntry->ClearSuccess();
//
// // ʧ
// if (!pEntry->IsAwardNotifyTeam() && bNotifyTeamMem && pEntry->GetCapOrSelf()->m_bTeamwork && !pEntry->IsSuccess())
// {
// AwardNotifyTeamMem(pTask, pEntry);
// pEntry->SetAwardNotifyTeam(); // Nofity only once
// }
//
// if (!pTask->IsDeliverLegal()) // ׵״̬
// return false;
//
// if (pEntry->IsGiveUp() && m_bClearAsGiveUp)
// {
// pList->ClearTask(pTask, pEntry, true);
// pList->UpdateTaskMask(*pTask->GetTaskMask());
// NotifyClient(pTask, NULL, TASK_SVR_NOTIFY_GIVE_UP, 0);
//
// // Ѱ
// if (m_bCompareItemAndInventory)
// {
// pTask->LockInventory(false);
// }
//
// return true;
// }
//
// pEntry->SetFinished();
//
// #if _TASK_CLIENT
//
// unsigned long ulRet;
// if ((ulRet = RecursiveCheckAward(
// pTask,
// pList,
// pEntry,
// ulCurTime,
// nChoice
// )) != 0)
// {
// if (m_enumFinishType == enumTFTNPC || !pEntry->IsErrReported())
// {
// NotifyClient(
// pTask,
// NULL,
// TASK_SVR_NOTIFY_ERROR_CODE,
// 0,
// ulRet
// );
//
// pEntry->SetErrReported();
// }
//
// char log[1024];
// sprintf(log, "DeliverAward: ret = %d", ulRet);
// TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 1, log);
// return false;
// }
//
// #endif
//
// // ӳɹ
// if (!pEntry->IsAwardNotifyTeam() && bNotifyTeamMem && pEntry->IsSuccess())
// {
// if(pEntry->GetCapOrSelf()->m_bTeamwork)
// {
// AwardNotifyTeamMem(pTask, pEntry);
// pEntry->SetAwardNotifyTeam(); // Nofity only once
// }
// else
// {
// const ATaskTempl* pParent = pEntry->GetTempl()->GetTopTask();
// ActiveTaskEntry* pParentEntry = static_cast<ActiveTaskList*>(pTask->GetActiveTaskList())->GetEntry(pParent->GetID());
//
// if(pParentEntry->GetCapOrSelf()->m_bTeamwork && pEntry->m_ChildIndex == 0xff)
// {
// bool bIsLastChild = true;
// const ATaskTempl* pTempParent = pEntry->GetTempl();
// while(pTempParent)// && pTempParent->m_pNextSibling == NULL)
// {
// if(pTempParent->m_pNextSibling != NULL)
// {
// bIsLastChild = false;
// break;
// }
//
// pTempParent = pTempParent->m_pParent;
// }
//
// if(bIsLastChild)
// {
// AwardNotifyTeamMem(pTask, pParentEntry);
// pParentEntry->SetAwardNotifyTeam(); // Nofity only once
// }
// }
// }
// }
//
// task_sub_tags sub_tags;
// memset(&sub_tags, 0, sizeof(sub_tags));
// sub_tags.state = pEntry->m_uState;
//
// //
// RecursiveAward(pTask, pList, pEntry, ulCurTime, nChoice, &sub_tags);
//
// // ֪ͨͻ
// NotifyClient(
// pTask,
// NULL,
// TASK_SVR_NOTIFY_COMPLETE,
// ulCurTime,
// reinterpret_cast<unsigned long>(&sub_tags));
//
// // Mask
// pList->UpdateTaskMask(*pTask->GetTaskMask());
//
// // Ѱ
// if (m_bCompareItemAndInventory)
// {
// pTask->LockInventory(false);
// }
return true;
}
private void RecursiveCheckTimeLimit(
TaskInterface pTask,
ActiveTaskList pList,
ActiveTaskEntry pEntry,
uint ulCurTime)
{
if (pTask == null || pList == null || pEntry == null) return;
// Timeout (relative time limit)
if (m_FixedData.m_ulTimeLimit > 0
&& (ulong)pEntry.m_ulTaskTime + (ulong)m_FixedData.m_ulTimeLimit < (ulong)ulCurTime)
{
pEntry.ClearSuccess();
}
// Absolute fail time (task_tm)
if (m_FixedData.m_bAbsFail)
{
long sec = ulCurTime - (long)(TaskInterface.GetTimeZoneBias() * 60);
if (sec < 0) sec = 0;
DateTime cur = DateTimeOffset.FromUnixTimeSeconds(sec).UtcDateTime;
if (m_FixedData.m_tmAbsFailTime.before(cur))
{
pEntry.ClearSuccess();
}
}
// Recurse to parent (template + entry link)
if (m_pParent != null && pEntry.m_ParentIndex != 0xff)
{
int parentIndex = (byte)pEntry.m_ParentIndex;
if (parentIndex >= 0 && parentIndex < pList.m_TaskEntries.Length)
{
ActiveTaskEntry parentEntry = pList.m_TaskEntries[parentIndex];
if (parentEntry != null)
{
m_pParent.RecursiveCheckTimeLimit(pTask, pList, parentEntry, ulCurTime);
}
}
}
}
}
}