2929 lines
102 KiB
C#
2929 lines
102 KiB
C#
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);
|
||
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[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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
} |