Files
test/Assets/PerfectWorld/Scripts/Task/TaskTempl.Struct.cs
2025-12-18 22:27:05 +07:00

696 lines
21 KiB
C#

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<task_notify_base>() + 8 + sub_tags.get_size(); }
public bool valid_size(int sz)
{
int base_sz = Marshal.SizeOf<task_notify_base>() + 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<task_notify_base>() + 4 + sub_tags.get_size(); }
public bool valid_size(int sz)
{
int base_sz = Marshal.SizeOf<task_notify_base>() + 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<FnshedTaskEntry>();
int startIndex = i * size + 4; // 4 bytes for m_FnshHeader
int endIndex = startIndex + size;
taskList[i] = GPDataTypeHelper.FromBytes<FnshedTaskEntry>(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<FnshedTaskEntry>(); // 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<FnshedTaskEntry>();
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<FnshedTaskEntry>();
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<FnshedTaskEntry>();
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<FnshedTaskEntryOld>();
int startIndex = i * size + 4; // 4 bytes for m_FnshHeader
int endIndex = startIndex + size;
taskList[i] = GPDataTypeHelper.FromBytes<FnshedTaskEntryOld>(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<FnshedTaskEntryOld>();
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;
}
}