Merge branch 'implement_task_UI' of https://git.brew.monster/Unity/perfect-world-unity into implement_task_UI
This commit is contained in:
@@ -10,6 +10,7 @@ namespace BrewMonster.Network
|
||||
private static int m_AbsTimeStart;
|
||||
private static int m_iTimeError; // 服务器与本机时间差(秒) // Time error in seconds
|
||||
private static int m_iTimeZoneBias; // 服务器时区偏移(秒) // Server timezone bias in seconds
|
||||
private static bool m_bServerTimeInited;
|
||||
public static int GetTimeZoneBias() { return m_iTimeZoneBias; }
|
||||
// 设置时间误差 // Set time error
|
||||
public static void SetServerTime(int iSevTime, int iTimeZoneBias)
|
||||
@@ -37,11 +38,20 @@ namespace BrewMonster.Network
|
||||
// 初始化绝对时间参考点 // Initialize absolute time reference
|
||||
m_AbsTimeStart = iSevTime;
|
||||
m_AbsTickStart = (uint)(Time.realtimeSinceStartup * 1000.0f);
|
||||
m_bServerTimeInited = true;
|
||||
Debug.Log($"timeGetTime(), TickStart = {m_AbsTickStart}");
|
||||
}
|
||||
|
||||
public static int GetServerAbsTime()
|
||||
{
|
||||
// Fallback: if server time was never initialized (SetServerTime not called),
|
||||
// return local unix time seconds so task timestamps (usually epoch seconds) still work.
|
||||
// This makes wait-time/countdown and timetable logic behave correctly even before server sync.
|
||||
if (!m_bServerTimeInited || m_AbsTimeStart == 0)
|
||||
{
|
||||
return (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() + m_iTimeError;
|
||||
}
|
||||
|
||||
uint curTick = (uint)(Time.realtimeSinceStartup * 1000.0f);
|
||||
|
||||
if (curTick < m_AbsTickStart)
|
||||
|
||||
@@ -479,24 +479,7 @@ namespace BrewMonster.Scripts.Task
|
||||
|
||||
public void CheckPQEnterWorldInit()
|
||||
{
|
||||
return;
|
||||
ActiveTaskList pList = GetActiveTaskList();
|
||||
List<ActiveTaskEntry> aEntries = new List<ActiveTaskEntry>(pList.m_TaskEntries);
|
||||
|
||||
for(var i = 0; i < pList.m_uTaskCount; i++)
|
||||
{
|
||||
var CurEntry = aEntries[i];
|
||||
|
||||
if (CurEntry.m_ulTemplAddr == 0)
|
||||
continue;
|
||||
|
||||
ATaskTempl pTempl = CurEntry.GetTempl();
|
||||
if (pTempl == null || !pTempl.m_FixedData.m_bPQTask)
|
||||
continue;
|
||||
|
||||
pTempl.IncValidCount();
|
||||
// _notify_svr(this, TASK_CLT_NOTIFY_PQ_CHECK_INIT, CurEntry.m_ID);
|
||||
}
|
||||
// TODO: implement PQ enter-world init if needed
|
||||
}
|
||||
|
||||
public static void WriteLog(int nPlayerId, int nTaskId, int nType, string szLog)
|
||||
@@ -507,7 +490,6 @@ namespace BrewMonster.Scripts.Task
|
||||
public bool IsDeliverLegal()
|
||||
{
|
||||
return !m_pHost.IsTrading() && m_pHost.GetBoothState() == 0 && !m_pHost.IsDead();
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetCommonItemCount(uint ulCommonItem)
|
||||
@@ -1249,12 +1231,6 @@ namespace BrewMonster.Scripts.Task
|
||||
|
||||
// return pTempl.CheckPrerequisite(this, static_cast<ActiveTaskList*>(GetActiveTaskList()), GetCurTime(), true, true, false);
|
||||
return pTempl.CheckPrerequisite(this, GetActiveTaskList(), GetCurTime(), true, true, false);
|
||||
|
||||
// if (!pTempl.CheckReachLevel(this)) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_BELOW_LEVEL;
|
||||
// uint keyCheck = pTempl.CheckGlobalKeyValue(this, false);
|
||||
// if (keyCheck != 0u) return keyCheck;
|
||||
|
||||
return 0u;
|
||||
}
|
||||
public bool CanDeliverCommonItem(uint ulTypes)
|
||||
{
|
||||
@@ -1730,6 +1706,24 @@ namespace BrewMonster.Scripts.Task
|
||||
global::System.Buffer.BlockCopy(lst.m_Buf, 0, m_pFinishedListBuf, 0, m_pFinishedListBuf.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// Persist an updated FinishedTaskList back into the underlying buffer.
|
||||
public void WriteFinishedTaskList(FinishedTaskList lst)
|
||||
{
|
||||
if (m_pFinishedListBuf == null) return;
|
||||
if (lst.m_Buf == null || lst.m_Buf.Length != m_pFinishedListBuf.Length) return;
|
||||
global::System.Buffer.BlockCopy(lst.m_Buf, 0, m_pFinishedListBuf, 0, m_pFinishedListBuf.Length);
|
||||
}
|
||||
|
||||
// Reset role-based finish counter for a task when period rolls over (used by CheckDeliverTime).
|
||||
public void ResetRoleFinishCount(uint taskId)
|
||||
{
|
||||
if (m_pFinishedListBuf == null) return;
|
||||
FinishedTaskList lst = new FinishedTaskList();
|
||||
lst.ReadFromBytes(m_pFinishedListBuf);
|
||||
lst.ResetFinishCount(taskId);
|
||||
WriteFinishedTaskList(lst);
|
||||
}
|
||||
|
||||
public int GetPlayerId()
|
||||
{
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace BrewMonster.Scripts.Task
|
||||
|
||||
private const uint FINISH_DLG_SHOWN_TIME = 3000; // TODO: Confirm correct value
|
||||
private static uint s_finishDlgShownTime = 0;
|
||||
|
||||
// Throttle CHECK_FINISH notifications per task to avoid spamming the server every tick.
|
||||
private static readonly System.Collections.Generic.Dictionary<uint, uint> s_lastCheckFinishAt = new();
|
||||
|
||||
public static void OnTaskCheckStatus(TaskInterface pTask)
|
||||
{
|
||||
@@ -77,7 +80,16 @@ namespace BrewMonster.Scripts.Task
|
||||
// 绝对失效时间判断 // Absolute fail time check
|
||||
if (pTempl.m_FixedData.m_bAbsFail)
|
||||
{
|
||||
// TODO: Time zone bias and 'task_tm.before' not ported; skipping precise comparison
|
||||
// Mirror C++: if abs-fail time has passed, ask server to check/finish (will mark fail if needed).
|
||||
long sec = ulCurTime - (long)(TaskInterface.GetTimeZoneBias() * 60);
|
||||
if (sec < 0) sec = 0;
|
||||
DateTime cur = DateTimeOffset.FromUnixTimeSeconds(sec).UtcDateTime;
|
||||
if (pTempl.m_FixedData.m_tmAbsFailTime.before(cur))
|
||||
{
|
||||
pTempl.IncValidCount();
|
||||
_notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, CurEntry.m_ID);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 进入或离开区域导致失败 // Entering or leaving region causes failure
|
||||
@@ -245,8 +257,8 @@ namespace BrewMonster.Scripts.Task
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: UpdateTaskToConfirm not ported; implement confirmation UI/state if needed
|
||||
UpdateTaskToConfirm(pTask, pTempl, bNeedServerCheck);
|
||||
// Minimal behavior: for wait-time tasks, auto request server check when time is up.
|
||||
UpdateTaskToConfirm(pTask, pTempl, CurEntry, bNeedServerCheck, ulCurTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,10 +307,27 @@ namespace BrewMonster.Scripts.Task
|
||||
ATaskTempl._notify_svr(pTask, uReason, uTaskID);
|
||||
}
|
||||
|
||||
// 更新“待确认任务” // Update task to confirm
|
||||
private static void UpdateTaskToConfirm(TaskInterface pTask, ATaskTempl pTempl, bool needServerCheck)
|
||||
// 更新“待确认任务” / 最小实现:当客户端确认已满足完成条件时,发一次 CHECK_FINISH 给服务器(节流)
|
||||
// English: Minimal port: when conditions are met client-side, send CHECK_FINISH once (throttled).
|
||||
private static void UpdateTaskToConfirm(TaskInterface pTask, ATaskTempl pTempl, ActiveTaskEntry entry, bool needServerCheck, uint ulCurTime)
|
||||
{
|
||||
// TODO: Implement confirmation queue/UI if required by design
|
||||
if (!needServerCheck || pTask == null || pTempl == null || entry == null) return;
|
||||
|
||||
// Only auto-check for wait-time tasks (the reported broken case).
|
||||
if ((TaskCompletionMethod)pTempl.m_FixedData.m_enumMethod != TaskCompletionMethod.enumTMWaitTime)
|
||||
return;
|
||||
|
||||
if (entry.IsFinished()) return;
|
||||
|
||||
uint id = entry.m_ID;
|
||||
if (id == 0) return;
|
||||
|
||||
if (s_lastCheckFinishAt.TryGetValue(id, out uint last) && ulCurTime <= last + 1)
|
||||
return;
|
||||
s_lastCheckFinishAt[id] = ulCurTime;
|
||||
|
||||
pTempl.IncValidCount();
|
||||
_notify_svr(pTask, (int)ClientNotificationConstants.TASK_CLT_NOTIFY_CHECK_FINISH, (ushort)id);
|
||||
}
|
||||
|
||||
// Handle server notification for task updates
|
||||
|
||||
@@ -24,20 +24,21 @@ namespace BrewMonster.Scripts.Task
|
||||
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
|
||||
public struct TaskFinishCountList
|
||||
{
|
||||
public static ushort m_uCount;
|
||||
public ushort m_uCount;
|
||||
|
||||
public static TaskFinishCountEntry[] m_aList = new TaskFinishCountEntry[(uint)TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN];
|
||||
public TaskFinishCountEntry[] m_aList;
|
||||
|
||||
|
||||
public uint Search(uint ulID, ref uint ulTime)
|
||||
|
||||
{
|
||||
for (ushort i = 0; i < TaskFinishCountList.m_uCount; i++)
|
||||
if (m_aList == null) return 0u;
|
||||
for (ushort i = 0; i < m_uCount; i++)
|
||||
{
|
||||
if (TaskFinishCountList.m_aList[i].m_uTaskId == (ushort)ulID)
|
||||
if (m_aList[i].m_uTaskId == (ushort)ulID)
|
||||
{
|
||||
ulTime = TaskFinishCountList.m_aList[i].m_ulFinishTime;
|
||||
return TaskFinishCountList.m_aList[i].m_ulFinishCount;
|
||||
ulTime = m_aList[i].m_ulFinishTime;
|
||||
return m_aList[i].m_ulFinishCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,12 +48,17 @@ namespace BrewMonster.Scripts.Task
|
||||
public void ResetAt(uint ulID)
|
||||
{
|
||||
for (ushort i = 0; i < m_uCount; i++)
|
||||
if (m_aList[i].m_uTaskId == (ushort)ulID)
|
||||
m_aList[i].m_ulFinishCount = 0;
|
||||
{
|
||||
if (m_aList != null && m_aList[i].m_uTaskId == (ushort)ulID)
|
||||
m_aList[i].m_ulFinishCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddOrUpdate(uint ulID, uint ulFinishTime)
|
||||
{
|
||||
if (m_aList == null || m_aList.Length != TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN)
|
||||
m_aList = new TaskFinishCountEntry[TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN];
|
||||
|
||||
for (ushort i = 0; i < m_uCount; i++)
|
||||
{
|
||||
if (m_aList[i].m_uTaskId == (ushort)ulID)
|
||||
@@ -74,7 +80,7 @@ namespace BrewMonster.Scripts.Task
|
||||
public void RemoveAll()
|
||||
{
|
||||
m_uCount = 0;
|
||||
m_aList = new TaskFinishCountEntry[(uint)TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN];
|
||||
m_aList = new TaskFinishCountEntry[TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN];
|
||||
}
|
||||
public bool IsValid() { return m_uCount <= TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN; }
|
||||
|
||||
@@ -104,6 +110,31 @@ namespace BrewMonster.Scripts.Task
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Persist list back into a buffer returned by TaskInterface.GetFinishedCntList().
|
||||
// Layout: ushort count + TASK_FINISH_COUNT_MAX_LEN * TaskFinishCountEntry bytes
|
||||
public void WriteToBuffer(byte[] data)
|
||||
{
|
||||
if (data == null) return;
|
||||
int entrySize = Marshal.SizeOf<TaskFinishCountEntry>();
|
||||
int expectedSize = 2 + entrySize * TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN;
|
||||
if (data.Length < expectedSize) return;
|
||||
|
||||
Array.Copy(BitConverter.GetBytes(m_uCount), 0, data, 0, 2);
|
||||
if (m_aList == null || m_aList.Length != TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN)
|
||||
{
|
||||
Array.Clear(data, 2, data.Length - 2);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < TaskInterfaceConstants.TASK_FINISH_COUNT_MAX_LEN; i++)
|
||||
{
|
||||
int offset = 2 + i * entrySize;
|
||||
// TaskFinishCountEntry is blittable with Pack=1, use helper
|
||||
byte[] entryBytes = GPDataTypeHelper.ToBytes(m_aList[i]);
|
||||
Array.Copy(entryBytes, 0, data, offset, Math.Min(entryBytes.Length, entrySize));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
|
||||
@@ -10,6 +10,21 @@ 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;
|
||||
@@ -543,6 +558,9 @@ namespace BrewMonster.Scripts.Task
|
||||
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);
|
||||
@@ -578,6 +596,8 @@ namespace BrewMonster.Scripts.Task
|
||||
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
|
||||
@@ -658,6 +678,9 @@ namespace BrewMonster.Scripts.Task
|
||||
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())
|
||||
@@ -969,14 +992,57 @@ namespace BrewMonster.Scripts.Task
|
||||
// 检查任务可接时间表 // English: Check task timetable window
|
||||
public uint CheckTimetable(uint ulCurTime)
|
||||
{
|
||||
if (m_FixedData.m_ulTimetable == 0) return 0;
|
||||
// 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;
|
||||
|
||||
// TODO: Implement judge_time_date function to properly check time windows
|
||||
// C++ logic: if ANY timetable entry matches current time, return 0; else return TASK_PREREQU_FAIL_WRONG_TIME
|
||||
// For now, since judge_time_date is not implemented, we allow the task to pass
|
||||
// This is a temporary workaround - proper implementation should check each timetable entry
|
||||
// WARNING: This may allow tasks to show when they shouldn't, but prevents blocking valid tasks
|
||||
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
|
||||
@@ -986,37 +1052,99 @@ namespace BrewMonster.Scripts.Task
|
||||
// C++: if (m_lAvailFrequency == enumTAFNormal) return 0;
|
||||
if (m_FixedData.m_lAvailFrequency == (int)TaskAwardFreq.enumTAFNormal)
|
||||
return 0u;
|
||||
|
||||
// Basic implementation: Check if task was never completed
|
||||
// If task was never completed (Search returns 0), allow it
|
||||
// TODO: Full implementation should also check time periods (daily/weekly/monthly/yearly)
|
||||
// and compare last completion time with current time
|
||||
|
||||
|
||||
// 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)
|
||||
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)
|
||||
{
|
||||
// No finished time list, task was never completed, allow it
|
||||
if (isLimitTask)
|
||||
{
|
||||
timeList.AddOrUpdate(m_FixedData.m_ID, ulCurTime);
|
||||
timeList.WriteToBuffer(finishedTimeListBuf);
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
TaskFinishTimeList pTimeList = new TaskFinishTimeList();
|
||||
pTimeList.ReadFromBuffer(finishedTimeListBuf);
|
||||
|
||||
// Check if list is full
|
||||
if (pTimeList.m_uCount >= TaskInterfaceConstants.TASK_FINISH_TIME_MAX_LEN)
|
||||
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_FULL;
|
||||
|
||||
// Search for task completion time
|
||||
uint ulTaskTime = pTimeList.Search(m_FixedData.m_ID);
|
||||
|
||||
// If task was never completed (Search returns 0), allow it
|
||||
if (ulTaskTime == 0)
|
||||
return 0u;
|
||||
|
||||
// Task was completed - TODO: Check if within same period based on frequency
|
||||
// For now, return error to prevent showing tasks that were already completed
|
||||
// This is a temporary fix - proper implementation should check time periods
|
||||
return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_WRONG_TIME;
|
||||
|
||||
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
|
||||
@@ -1913,6 +2041,48 @@ namespace BrewMonster.Scripts.Task
|
||||
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;
|
||||
@@ -2717,28 +2887,41 @@ namespace BrewMonster.Scripts.Task
|
||||
ActiveTaskEntry pEntry,
|
||||
uint ulCurTime)
|
||||
{
|
||||
|
||||
// TODO: implement full logic when ActiveTaskList/ActiveTaskEntry and TaskInterface APIs are available
|
||||
// if (m_ulTimeLimit > 0 && pEntry.m_ulTaskTime + m_ulTimeLimit < ulCurTime) // ��ʱ
|
||||
// pEntry.ClearSuccess();
|
||||
//
|
||||
// // if (m_ulAbsFailTime && m_ulAbsFailTime < ulCurTime) // ��������ʧЧ����
|
||||
// // ��������ʧЧ����
|
||||
// if (m_bAbsFail)
|
||||
// {
|
||||
// tm cur = *localtime((long*)&ulCurTime);
|
||||
//
|
||||
// if(m_tmAbsFailTime.before(&cur))
|
||||
// {
|
||||
// pEntry->ClearSuccess();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (m_pParent && pEntry->m_ParentIndex != 0xff)
|
||||
// {
|
||||
// ActiveTaskEntry& ParentEntry = pList->m_TaskEntries[pEntry->m_ParentIndex];
|
||||
// m_pParent->RecursiveCheckTimeLimit(pTask, pList, &ParentEntry, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -247,37 +247,52 @@ namespace BrewMonster.Scripts.Task
|
||||
[FieldOffset(8)]
|
||||
public ulong m_ulRcvUpdateTime;
|
||||
|
||||
void AddRevNum() { m_ulReceiverNum++; }
|
||||
public void AddRevNum() { m_ulReceiverNum++; }
|
||||
|
||||
void CheckRcvUpdateTime(uint ulCurTime, int nFrequency)
|
||||
public void CheckRcvUpdateTime(uint ulCurTime, int nFrequency)
|
||||
{
|
||||
// TODO: implement time-based receiver number reset logic
|
||||
// if (nFrequency == TaskCompletionMethod.enumTAFNormal || m_ulRcvUpdateTime == 0)
|
||||
// return;
|
||||
//
|
||||
// tm tmCur = *localtime((time_t*)&ulCurTime);
|
||||
// tm tmRcv = *localtime((time_t*)&m_ulRcvUpdateTime);
|
||||
//
|
||||
// if (nFrequency == enumTAFEachDay)
|
||||
// {
|
||||
// if (tmCur.tm_year != tmRcv.tm_year || tmCur.tm_yday != tmRcv.tm_yday)
|
||||
// m_ulReceiverNum = 0;
|
||||
// }
|
||||
// else if (nFrequency == enumTAFEachWeek)
|
||||
// {
|
||||
// if (!_is_same_week(&tmCur, &tmRcv, ulCurTime, m_ulRcvUpdateTime))
|
||||
// m_ulReceiverNum = 0;
|
||||
// }
|
||||
// else if (nFrequency == enumTAFEachMonth)
|
||||
// {
|
||||
// if (tmCur.tm_year != tmRcv.tm_year || tmCur.tm_mon != tmRcv.tm_mon)
|
||||
// m_ulReceiverNum = 0;
|
||||
// }
|
||||
// else if (nFrequency == enumTAFEachYear)
|
||||
// {
|
||||
// if (tmCur.tm_year != tmRcv.tm_year)
|
||||
// m_ulReceiverNum = 0;
|
||||
// }
|
||||
// C++ semantics: based on localtime() period boundaries, reset receiver count when period changes.
|
||||
// Use the same "task local time" conversion as timetable (timezone bias path) so behavior is consistent.
|
||||
if (nFrequency == (int)TaskAwardFreq.enumTAFNormal || m_ulRcvUpdateTime == 0)
|
||||
return;
|
||||
|
||||
long curSec = ulCurTime - (long)(TaskInterface.GetTimeZoneBias() * 60);
|
||||
if (curSec < 0) curSec = 0;
|
||||
DateTime cur = DateTimeOffset.FromUnixTimeSeconds(curSec).UtcDateTime;
|
||||
|
||||
long rcvSec = (long)m_ulRcvUpdateTime - (long)(TaskInterface.GetTimeZoneBias() * 60);
|
||||
if (rcvSec < 0) rcvSec = 0;
|
||||
DateTime rcv = DateTimeOffset.FromUnixTimeSeconds(rcvSec).UtcDateTime;
|
||||
|
||||
bool reset = false;
|
||||
if (nFrequency == (int)TaskAwardFreq.enumTAFEachDay)
|
||||
{
|
||||
reset = cur.Year != rcv.Year || cur.Month != rcv.Month || cur.Day != rcv.Day;
|
||||
}
|
||||
else if (nFrequency == (int)TaskAwardFreq.enumTAFEachWeek)
|
||||
{
|
||||
int curDow = (int)cur.DayOfWeek; // Sunday=0
|
||||
int rcvDow = (int)rcv.DayOfWeek;
|
||||
int curDiff = (curDow == 0) ? 6 : (curDow - 1); // Monday-start week
|
||||
int rcvDiff = (rcvDow == 0) ? 6 : (rcvDow - 1);
|
||||
DateTime curWeekStart = cur.Date.AddDays(-curDiff);
|
||||
DateTime rcvWeekStart = rcv.Date.AddDays(-rcvDiff);
|
||||
reset = curWeekStart != rcvWeekStart;
|
||||
}
|
||||
else if (nFrequency == (int)TaskAwardFreq.enumTAFEachMonth)
|
||||
{
|
||||
reset = cur.Year != rcv.Year || cur.Month != rcv.Month;
|
||||
}
|
||||
else if (nFrequency == (int)TaskAwardFreq.enumTAFEachYear)
|
||||
{
|
||||
reset = cur.Year != rcv.Year;
|
||||
}
|
||||
|
||||
if (reset)
|
||||
{
|
||||
m_ulReceiverNum = 0;
|
||||
m_ulRcvUpdateTime = ulCurTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -873,6 +873,131 @@ namespace BrewMonster.Scripts.Task
|
||||
{
|
||||
return string.Format("{0}-{1}-{2} {3}:{4} (wday:{5})", year, month, day, hour, min, wday);
|
||||
}
|
||||
|
||||
// ===== C++ task_tm time comparison helpers =====
|
||||
// [中文] 对齐 C++ 的 task_tm::after/before 等比较逻辑(含“月/周/日”模式)
|
||||
// [English] Parity with C++ task_tm::after/before and per-month/week/day variants
|
||||
|
||||
// C++: bool after(const tm* _tm) const
|
||||
public bool after(DateTime t)
|
||||
{
|
||||
int ty = t.Year;
|
||||
int tm = t.Month;
|
||||
int td = t.Day;
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
|
||||
if (year < ty) return false;
|
||||
if (year > ty) return true;
|
||||
|
||||
if (month < tm) return false;
|
||||
if (month > tm) return true;
|
||||
|
||||
if (day < td) return false;
|
||||
if (day > td) return true;
|
||||
|
||||
if (hour < th) return false;
|
||||
return hour > th || min > tmin;
|
||||
}
|
||||
|
||||
// C++: bool before(const tm* _tm) const
|
||||
public bool before(DateTime t)
|
||||
{
|
||||
int ty = t.Year;
|
||||
int tm = t.Month;
|
||||
int td = t.Day;
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
|
||||
if (year > ty) return false;
|
||||
if (year < ty) return true;
|
||||
|
||||
if (month > tm) return false;
|
||||
if (month < tm) return true;
|
||||
|
||||
if (day > td) return false;
|
||||
if (day < td) return true;
|
||||
|
||||
if (hour > th) return false;
|
||||
return hour < th || min <= tmin;
|
||||
}
|
||||
|
||||
// C++: bool after_per_month(const tm* _tm, bool bLastDay) const
|
||||
public bool after_per_month(DateTime t, bool bLastDay)
|
||||
{
|
||||
int td = t.Day;
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
|
||||
if (day < td) return false;
|
||||
if (!bLastDay && day > td) return true;
|
||||
|
||||
if (hour < th) return false;
|
||||
return hour > th || min > tmin;
|
||||
}
|
||||
|
||||
// C++: bool before_per_month(const tm* _tm, bool bLastDay) const
|
||||
public bool before_per_month(DateTime t, bool bLastDay)
|
||||
{
|
||||
int td = t.Day;
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
|
||||
if (day < td) return true;
|
||||
if (!bLastDay && day > td) return false;
|
||||
|
||||
if (hour > th) return false;
|
||||
return hour < th || min <= tmin;
|
||||
}
|
||||
|
||||
// task_week_map (C++): { 7,1,2,3,4,5,6 } with tm_wday Sunday=0
|
||||
private static readonly int[] s_task_week_map = { 7, 1, 2, 3, 4, 5, 6 };
|
||||
|
||||
// C++: bool after_per_week(const tm* _tm) const
|
||||
public bool after_per_week(DateTime t)
|
||||
{
|
||||
int w = s_task_week_map[(int)t.DayOfWeek];
|
||||
|
||||
if (wday < w) return false;
|
||||
if (wday > w) return true;
|
||||
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
if (hour < th) return false;
|
||||
return hour > th || min > tmin;
|
||||
}
|
||||
|
||||
// C++: bool before_per_week(const tm* _tm) const
|
||||
public bool before_per_week(DateTime t)
|
||||
{
|
||||
int w = s_task_week_map[(int)t.DayOfWeek];
|
||||
|
||||
if (wday > w) return false;
|
||||
if (wday < w) return true;
|
||||
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
if (hour > th) return false;
|
||||
return hour < th || min <= tmin;
|
||||
}
|
||||
|
||||
// C++: bool after_per_day(const tm* _tm) const
|
||||
public bool after_per_day(DateTime t)
|
||||
{
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
if (hour < th) return false;
|
||||
return hour > th || min > tmin;
|
||||
}
|
||||
|
||||
// C++: bool before_per_day(const tm* _tm) const
|
||||
public bool before_per_day(DateTime t)
|
||||
{
|
||||
int th = t.Hour;
|
||||
int tmin = t.Minute;
|
||||
if (hour > th) return false;
|
||||
return hour < th || min <= tmin;
|
||||
}
|
||||
}
|
||||
|
||||
// Define task_team_member_info struct required by TEAM_MEM_WANTED
|
||||
@@ -1444,6 +1569,35 @@ namespace BrewMonster.Scripts.Task
|
||||
}
|
||||
}
|
||||
public bool IsValid() { return m_uCount <= TaskInterfaceConstants.TASK_FINISH_TIME_MAX_LEN; }
|
||||
|
||||
// Persist this list back into the underlying buffer returned by TaskInterface.GetFinishedTimeList().
|
||||
// Buffer layout: ushort count + TASK_FINISH_TIME_MAX_LEN * (ushort taskId + uint timeMark)
|
||||
public void WriteToBuffer(byte[] data)
|
||||
{
|
||||
if (data == null) return;
|
||||
int entrySize = sizeof(ushort) + sizeof(uint);
|
||||
int expected = sizeof(ushort) + entrySize * TaskInterfaceConstants.TASK_FINISH_TIME_MAX_LEN;
|
||||
if (data.Length < expected) return;
|
||||
|
||||
int offset = 0;
|
||||
Array.Copy(BitConverter.GetBytes(m_uCount), 0, data, offset, sizeof(ushort));
|
||||
offset += sizeof(ushort);
|
||||
|
||||
if (m_aList == null || m_aList.Length != TaskInterfaceConstants.TASK_FINISH_TIME_MAX_LEN)
|
||||
{
|
||||
// Keep buffer consistent even if list isn't initialized.
|
||||
Array.Clear(data, offset, data.Length - offset);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < TaskInterfaceConstants.TASK_FINISH_TIME_MAX_LEN; i++)
|
||||
{
|
||||
Array.Copy(BitConverter.GetBytes(m_aList[i].m_uTaskId), 0, data, offset, sizeof(ushort));
|
||||
offset += sizeof(ushort);
|
||||
Array.Copy(BitConverter.GetBytes(m_aList[i].m_ulTimeMark), 0, data, offset, sizeof(uint));
|
||||
offset += sizeof(uint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1692,6 +1846,8 @@ namespace BrewMonster.Scripts.Task
|
||||
uint ulTopCount = 0;
|
||||
byte uBudget = 0;
|
||||
long lReputation = 0;
|
||||
// Suppress unused warnings until RecursiveCalcAward is fully ported.
|
||||
_ = ulCmnCount; _ = ulTskCount; _ = lReputation;
|
||||
|
||||
// 任务屏蔽检查 // Task forbid check
|
||||
if (pTask.CheckTaskForbid(m_FixedData.m_ID)) return (uint)TaskInterfaceConstants.TASK_PREREQU_FAIL_TASK_FORBID;
|
||||
@@ -4478,7 +4634,7 @@ namespace BrewMonster.Scripts.Task
|
||||
|
||||
return true;
|
||||
}
|
||||
public uint GetType() { return m_FixedData.m_ulType; }
|
||||
public new uint GetType() { return m_FixedData.m_ulType; }
|
||||
|
||||
void Init()
|
||||
{
|
||||
|
||||
@@ -135,6 +135,16 @@ namespace BrewMonster.Scripts.Task.UI
|
||||
// [English] Task trace counter
|
||||
private CECCounter m_TaskTraceCounter = new (); // CECCounter -> object placeholder
|
||||
|
||||
// ===== Time-gated task UI refresh (search list) =====
|
||||
// Timetable/time-window tasks become available/unavailable as server time moves.
|
||||
// The original C++ client periodically re-evaluates prerequisites; in this port we refresh the search list
|
||||
// at a low frequency while the Search view is open so players can see time-gated tasks appear/disappear.
|
||||
private uint _lastSearchRefreshMinuteKey = uint.MaxValue;
|
||||
private uint _pendingReselectTaskId = 0;
|
||||
|
||||
// Active-task timer refresh (wait-time / time-limit / protect-time)
|
||||
private float _nextActiveTimerUiRefreshAt = 0f;
|
||||
|
||||
#region Unity METHODS
|
||||
|
||||
private new void OnEnable()
|
||||
@@ -357,6 +367,63 @@ namespace BrewMonster.Scripts.Task.UI
|
||||
//
|
||||
private bool Tick()
|
||||
{
|
||||
// Time-window task refresh: while in Search view, refresh the list when server time crosses a minute boundary.
|
||||
// This is throttled to avoid rebuilding large task lists every frame.
|
||||
if (m_iType == 1)
|
||||
{
|
||||
var host = GetHostPlayer();
|
||||
var task = host != null ? host.GetTaskInterface() : null;
|
||||
if (task != null)
|
||||
{
|
||||
uint now = task.GetCurTime();
|
||||
uint minuteKey = now / 60u;
|
||||
if (minuteKey != _lastSearchRefreshMinuteKey)
|
||||
{
|
||||
_lastSearchRefreshMinuteKey = minuteKey;
|
||||
|
||||
// Preserve current selection if any, so refreshing doesn't feel disruptive.
|
||||
var curItem = m_pTv_Quest != null ? m_pTv_Quest.GetSelectedItem() : null;
|
||||
_pendingReselectTaskId = (curItem != null) ? m_pTv_Quest.GetItemData(curItem) : 0u;
|
||||
|
||||
// Rebuild available task list according to current time-based prerequisites.
|
||||
SearchForTask(-1);
|
||||
|
||||
// Restore selection best-effort (TaskTreeView selection is driven by EventBus).
|
||||
if (_pendingReselectTaskId != 0u)
|
||||
{
|
||||
EventBus.Publish(new TaskItemClickEvent { Data = _pendingReselectTaskId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active view: refresh selected task detail periodically so countdown UI updates in real time.
|
||||
// (Wait-time tasks depend on m_ulTimePassed which changes with server time; without this, UI looks "stuck".)
|
||||
if (m_iType == 0 && Time.unscaledTime >= _nextActiveTimerUiRefreshAt)
|
||||
{
|
||||
_nextActiveTimerUiRefreshAt = Time.unscaledTime + 0.5f; // 2 Hz is plenty for countdown text
|
||||
|
||||
var pTree = m_pTv_Quest;
|
||||
var pItem = pTree != null ? pTree.GetSelectedItem() : null;
|
||||
if (pItem != null && pTree.transform != pItem.transform.parent)
|
||||
{
|
||||
uint selectedTaskId = pTree.GetItemData(pItem);
|
||||
if (selectedTaskId > 0)
|
||||
{
|
||||
var task = GetHostPlayer()?.GetTaskInterface();
|
||||
if (task != null)
|
||||
{
|
||||
Task_State_info tsi = default;
|
||||
task.GetTaskStateInfo(selectedTaskId, ref tsi, true);
|
||||
if (tsi.m_ulWaitTime > 0 || tsi.m_ulTimeLimit > 0 || tsi.m_ulProtectTime > 0)
|
||||
{
|
||||
UpdateTask((int)selectedTaskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if( m_szName == "Win_Quest" && IsShow() )
|
||||
{
|
||||
var pTree = m_pTv_Quest;
|
||||
|
||||
Reference in New Issue
Block a user