using System; using System.Linq; using System.Runtime.InteropServices; using CSNetwork.GPDataType; namespace BrewMonster.Scripts.Task { [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct task_sub_tags { // private const int MAX_SUB_TAGS = 32; // union // { // unsigned short sub_task; // unsigned char state; // }; // IMPORTANT: union public ushort sub_task; public byte state { get { return (byte)(sub_task & 0xFF); } set { sub_task = (ushort)((sub_task & 0xFF00) | value); } } public byte sz; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskTemplConstants.MAX_SUB_TAGS)] public byte[] tags; public byte cur_index; // for temporary use, dont take into account public int get_size() { return sz + 3; } public bool valid_size(int _sz) { if (_sz < 3) return false; return get_size() == _sz; } }; [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct task_notify_base { public byte reason; public ushort task; }; [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_monster_killed { public task_notify_base baseObj; public uint monster_id; public ushort monster_num; public int dps; public int dph; } [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_player_killed { public task_notify_base baseObj; public ushort index; public ushort player_num; } [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_task_err_code { public task_notify_base baseObj; public uint err_code; }; [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct StorageTaskList { // unsigned short m_Storages[TASK_STORAGE_COUNT][TASK_STORAGE_LEN]; // NOTE: 2D array flattened [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskTemplConstants.TASK_STORAGE_COUNT * TaskTemplConstants.TASK_STORAGE_LEN)] public ushort[] m_Storages; // union { // unsigned short m_StoragesTaskSetCount[TASK_STORAGE_COUNT]; // unsigned short m_StoragesRefreshCount[TASK_STORAGE_COUNT]; // }; // NOTE: union [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskTemplConstants.TASK_STORAGE_COUNT)] public ushort[] m_StoragesTaskSetCount; // [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskTemplConstants.TASK_STORAGE_COUNT)] public ushort[] m_StoragesRefreshCount { get => m_StoragesTaskSetCount; set { m_StoragesTaskSetCount = value; } } [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskTemplConstants.TASK_STORAGE_COUNT)] public uint[] m_StoragesRefreshTime; [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskTemplConstants.TASK_STORAGE_COUNT)] public byte[] m_StoragesReceivePerDay; // Initialize arrays if they are null // In C++, arrays are automatically allocated on the stack, but in C# they need explicit initialization public void EnsureInitialized() { if (m_Storages == null) m_Storages = new ushort[TaskTemplConstants.TASK_STORAGE_COUNT * TaskTemplConstants.TASK_STORAGE_LEN]; if (m_StoragesTaskSetCount == null) m_StoragesTaskSetCount = new ushort[TaskTemplConstants.TASK_STORAGE_COUNT]; if (m_StoragesRefreshTime == null) m_StoragesRefreshTime = new uint[TaskTemplConstants.TASK_STORAGE_COUNT]; if (m_StoragesReceivePerDay == null) m_StoragesReceivePerDay = new byte[TaskTemplConstants.TASK_STORAGE_COUNT]; } public void RemoveAll() { EnsureInitialized(); for (int i = 0; i < TaskTemplConstants.TASK_STORAGE_COUNT; i++) { for (int j = 0; j < TaskTemplConstants.TASK_STORAGE_LEN; j++) { m_Storages[i * TaskTemplConstants.TASK_STORAGE_LEN + j] = 0; } m_StoragesTaskSetCount[i] = 0; m_StoragesRefreshCount[i] = 0; m_StoragesRefreshTime[i] = 0; m_StoragesReceivePerDay[i] = 0; } } public void ReadByte(byte[] data) { if (data == null) return; EnsureInitialized(); int offset = 0; for (int i=0; i < TaskTemplConstants.TASK_STORAGE_COUNT; i++) { for (int j=0; j < TaskTemplConstants.TASK_STORAGE_LEN; j++) { m_Storages[i * TaskTemplConstants.TASK_STORAGE_LEN + j] = BitConverter.ToUInt16(data, (i * TaskTemplConstants.TASK_STORAGE_LEN + j) * 2); } } offset += TaskTemplConstants.TASK_STORAGE_COUNT * TaskTemplConstants.TASK_STORAGE_LEN * 2; // union for (int i=0; i < TaskTemplConstants.TASK_STORAGE_COUNT; i++) { m_StoragesTaskSetCount[i] = BitConverter.ToUInt16(data, offset + i * 2); // m_StoragesRefreshCount[i] = BitConverter.ToUInt16(data, offset + i * 2); } offset += TaskTemplConstants.TASK_STORAGE_COUNT * 2; for (int i=0; i < TaskTemplConstants.TASK_STORAGE_COUNT; i++) { m_StoragesRefreshTime[i] = BitConverter.ToUInt32(data, offset + i * 4); } offset += TaskTemplConstants.TASK_STORAGE_COUNT * 4; for (int i=0; i < TaskTemplConstants.TASK_STORAGE_COUNT; i++) { m_StoragesReceivePerDay[i] = data[offset + i]; } } } [Serializable] [ StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_new_task { public task_notify_base baseObj; public uint cur_time; public uint cap_task; // In C++ use to store ActiveTaskEntry pointer's address -> In C#, we just store the task ID public task_sub_tags sub_tags; // public ActiveTaskEntry cap_task_entry; // public void set_data( uint _cur_time, uint _cap_task, task_sub_tags _sub_tags) { cur_time = _cur_time; cap_task = _cap_task; // memcpy(&sub_tags, &_sub_tags, _sub_tags.get_size()); sub_tags = _sub_tags; } public void get_data( ref uint _cur_time, ref uint _cap_task, ref task_sub_tags _sub_tags) { _cur_time = cur_time; _cap_task = cap_task; // memcpy(&_sub_tags, &sub_tags, sub_tags.get_size()); _sub_tags = sub_tags; } // inline size_t get_size() const { return sizeof(task_notify_base) + 8 + sub_tags.get_size(); } public int get_size() { return Marshal.SizeOf() + 8 + sub_tags.get_size(); } public bool valid_size(int sz) { int base_sz = Marshal.SizeOf() + 8; if (sz <= base_sz) return false; return sub_tags.valid_size(sz - base_sz); } } [Serializable] [ StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_treasure_map { public task_notify_base baseObj; public int treasure_index; } [Serializable] public struct tm { public int tm_sec; /* seconds after the minute [0-60] */ public int tm_min; /* minutes after the hour [0-59] */ public int tm_hour; /* hours since midnight [0-23] */ public int tm_mday; /* day of the month [1-31] */ public int tm_mon; /* months since January [0-11] */ public int tm_year; /* years since 1900 */ public int tm_wday; /* days since Sunday [0-6] */ public int tm_yday; /* days since January 1 [0-365] */ public int tm_isdst; /* Daylight Savings Time flag */ public long tm_gmtoff; /* offset from UTC in seconds */ public byte tm_zone; /* timezone abbreviation */ }; [Serializable] [StructLayout(LayoutKind.Explicit)] public struct TaskGlobalData { [FieldOffset(0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskInterfaceConstants.TASK_GLOBAL_DATA_SIZE)] public byte[] buf; [FieldOffset(0)] public ulong m_ulReceiverNum; [FieldOffset(8)] public ulong m_ulRcvUpdateTime; public void AddRevNum() { m_ulReceiverNum++; } public void CheckRcvUpdateTime(uint ulCurTime, int nFrequency) { // 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; } } } [Serializable] [ StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_task_complete { public task_notify_base baseObj; public uint cur_time; public task_sub_tags sub_tags; public void set_data( uint _cur_time, task_sub_tags _sub_tags ) { cur_time = _cur_time; // memcpy(&sub_tags, &_sub_tags, _sub_tags.get_size()); sub_tags = _sub_tags; } public void get_data( ref uint _cur_time, ref task_sub_tags _sub_tags) { _cur_time = cur_time; // memcpy(&_sub_tags, &sub_tags, sub_tags.get_size()); _sub_tags = sub_tags; } public int get_size() { return Marshal.SizeOf() + 4 + sub_tags.get_size(); } public bool valid_size(int sz) { int base_sz = Marshal.SizeOf() + 4; if (sz <= base_sz) return false; return sub_tags.valid_size(sz - base_sz); } } [Serializable] [StructLayout( LayoutKind.Sequential, Pack = 1)] public struct special_award { public uint id1; public uint id2; public uint special_mask; }; [Serializable] [StructLayout( LayoutKind.Sequential, Pack = 1)] public struct svr_task_special_award { public svr_task_complete baseObj; public special_award sa; }; [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FnshedTaskListHeader { public ushort m_uTaskCount; public byte m_Version; public byte m_Reserved; } [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FnshedTaskEntry { public ushort m_uTaskId; public byte m_Buf; // public byte m_Mask; // 1-bit mask: 0 = success, 1 = failure // public byte m_Reserved; // Remaining 7 bits reserved public byte m_FnshedCount; // Number of times the task has been completed public byte m_Mask { get { return (byte)(m_Buf & 0x1); } set { m_Buf = (byte)((m_Buf & 0xFE) | (value & 0x1)); } } public byte m_Reserved { get { return (byte)(m_Buf & 0x3); } set { m_Buf = (byte)((m_Buf & 0xFE) | (value & 0x3)); } } } [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FinishedTaskList { // --- Layout 1: Header + List --- public FnshedTaskListHeader m_FnshHeader { get { FnshedTaskListHeader header = new FnshedTaskListHeader(); header.m_uTaskCount = BitConverter.ToUInt16(m_Buf, 0); header.m_Version = m_Buf[2]; header.m_Reserved = m_Buf[3]; return header; } set { Array.Copy(BitConverter.GetBytes(value.m_uTaskCount), 0, m_Buf, 0, 2); m_Buf[2] = value.m_Version; m_Buf[3] = value.m_Reserved; } } // [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN)] public FnshedTaskEntry[] m_aTaskList { get { FnshedTaskEntry[] taskList = new FnshedTaskEntry[TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN]; for (int i=0; i < m_FnshHeader.m_uTaskCount; i++) { int size = Marshal.SizeOf(); int startIndex = i * size + 4; // 4 bytes for m_FnshHeader int endIndex = startIndex + size; taskList[i] = GPDataTypeHelper.FromBytes(m_Buf[startIndex..endIndex]); } return taskList; } } [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE)] public byte[] m_Buf; public bool ReadFromBytes(byte[] data) { if (data.Length != TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE) return false; m_Buf = new byte[TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE]; Array.Copy(data, m_Buf, TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE); return true; } public int GetTaskPos(uint ulID) { ushort uTaskCount = m_FnshHeader.m_uTaskCount; if (uTaskCount == 0) return -1; FnshedTaskEntry[] taskList = m_aTaskList; if (uTaskCount == 1) { if (ulID == taskList[0].m_uTaskId) return 0; else return -1; } uint ulStart = 0; uint ulEnd = (uint)(uTaskCount - 1); while (ulStart + 1 < ulEnd) { uint ulMid = (ulStart + ulEnd) >> 1; if (ulID == taskList[ulMid].m_uTaskId) return (int)ulMid; else if (ulID < taskList[ulMid].m_uTaskId) ulEnd = ulMid; else ulStart = ulMid; } if (taskList[ulStart].m_uTaskId == ulID) return (int)ulStart; if (taskList[ulEnd].m_uTaskId == ulID) return (int)ulEnd; return -1; } public void AddOneTask(uint ulID, bool bSuccess) { // 将任务写入已完成列表(按任务ID有序) // English: Insert/update into finished list (sorted by task id) if (m_Buf == null || m_Buf.Length != TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE) { m_Buf = new byte[TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE]; } var header = m_FnshHeader; ushort count = header.m_uTaskCount; if (count >= TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN) return; int entrySize = Marshal.SizeOf(); // should be 4 int pos = GetTaskPos(ulID); byte mask = (byte)(bSuccess ? 0 : 1); if (pos >= 0) { // Update existing entry int start = 4 + pos * entrySize; // m_uTaskId Array.Copy(BitConverter.GetBytes((ushort)ulID), 0, m_Buf, start, 2); // m_Buf (mask + reserved bits) m_Buf[start + 2] = (byte)((m_Buf[start + 2] & 0xFE) | (mask & 0x1)); // m_FnshedCount: keep at least 1 if (m_Buf[start + 3] == 0) m_Buf[start + 3] = 1; return; } // Find insertion index to keep sorted order int insert = 0; for (; insert < count; insert++) { ushort existingId = BitConverter.ToUInt16(m_Buf, 4 + insert * entrySize); if (ulID < existingId) break; } // Shift bytes to make room int srcStart = 4 + insert * entrySize; int bytesToMove = (count - insert) * entrySize; if (bytesToMove > 0) { Buffer.BlockCopy(m_Buf, srcStart, m_Buf, srcStart + entrySize, bytesToMove); } // Write new entry int dst = 4 + insert * entrySize; Array.Copy(BitConverter.GetBytes((ushort)ulID), 0, m_Buf, dst, 2); m_Buf[dst + 2] = (byte)(mask & 0x1); // reserved bits 0 m_Buf[dst + 3] = 1; // finish count header.m_uTaskCount = (ushort)(count + 1); m_FnshHeader = header; } public void RemoveTask(uint ulID) { // TODO: Implement logic to remove a task (for future use) //throw new NotImplementedException(); } // SearchTask returns: // -1 = task not found (never completed) // 0 = task successfully completed // 1 = task failed public int SearchTask(uint ulID) { int nPos = GetTaskPos(ulID); if (nPos < 0) return -1; FnshedTaskEntry[] taskList = m_aTaskList; return taskList[nPos].m_Mask; } public byte SearchTaskFinishCount(ulong ulID) { int pos = GetTaskPos((uint)ulID); if (pos < 0) return 0; int entrySize = Marshal.SizeOf(); int start = 4 + pos * entrySize; return m_Buf[start + 3]; // m_FnshedCount } public void ResetFinishCount(ulong ulID) { int pos = GetTaskPos((uint)ulID); if (pos < 0) return; int entrySize = Marshal.SizeOf(); int start = 4 + pos * entrySize; m_Buf[start + 3] = 0; } public void AddForFinishCount(ulong ulID, bool bSuccess) { // 只用于计数:如果不存在则插入;如果存在则递增 m_FnshedCount // English: Finish-count bookkeeping: insert if missing; otherwise increment m_FnshedCount. AddOneTask((uint)ulID, bSuccess); int pos = GetTaskPos((uint)ulID); if (pos < 0) return; int entrySize = Marshal.SizeOf(); int start = 4 + pos * entrySize; byte cur = m_Buf[start + 3]; if (cur < byte.MaxValue) m_Buf[start + 3] = (byte)(cur + 1); } public void RemoveAll() { Array.Clear(m_aTaskList, 0, m_aTaskList.Length); Array.Clear(m_Buf, 0, m_Buf.Length); } public bool IsValid() { return m_FnshHeader.m_uTaskCount <= TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN; } } [Serializable] public struct FnshedTaskEntryOld { public ushort m_uTaskId; }; [Serializable] [ StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FnshedTaskListOld { // union // { // struct // { // FnshedTaskListHeader m_FnshHeader; // FnshedTaskEntryOld m_aTaskList[TASK_FINISHED_LIST_MAX_LEN]; // }; // unsigned char m_Buf[TASK_FINISHED_LIST_BUF_SIZE_OLD]; // }; public FnshedTaskListHeader m_FnshHeader { get { var FnshHeader = new FnshedTaskListHeader { m_uTaskCount = BitConverter.ToUInt16(m_Buf, 0), m_Version = m_Buf[2], m_Reserved = m_Buf[3] }; return FnshHeader; } set { Array.Copy(BitConverter.GetBytes(value.m_uTaskCount), 0, m_Buf, 0, 2); m_Buf[2] = value.m_Version; m_Buf[3] = value.m_Reserved; } } // [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN)] public FnshedTaskEntryOld[] m_aTaskList { get { FnshedTaskEntryOld[] taskList = new FnshedTaskEntryOld[TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN]; for (int i=0; i < m_FnshHeader.m_uTaskCount; i++) { int size = Marshal.SizeOf(); int startIndex = i * size + 4; // 4 bytes for m_FnshHeader int endIndex = startIndex + size; taskList[i] = GPDataTypeHelper.FromBytes(m_Buf[startIndex..endIndex]); } return taskList; } set { for (int i=0; i < value.Length && i < TaskInterfaceConstants.TASK_FINISHED_LIST_MAX_LEN; i++) { int size = Marshal.SizeOf(); int startIndex = i * size + 4; // 4 bytes for m_FnshHeader byte[] entryBytes = GPDataTypeHelper.ToBytes(value[i]); Array.Copy(entryBytes, 0, m_Buf, startIndex, size); } } } [MarshalAs(UnmanagedType.ByValArray, SizeConst = TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE_OLD)] public byte[] m_Buf; public FnshedTaskListOld(byte[] data) { m_Buf = new byte[TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE]; Array.Copy(data, m_Buf, TaskInterfaceConstants.TASK_FINISHED_LIST_BUF_SIZE_OLD); } }; [Serializable] [ StructLayout(LayoutKind.Sequential, Pack = 1)] public struct svr_task_dyn_time_mark { public task_notify_base baseObj; public uint time_mark; public ushort version; } }