增加相关协议

This commit is contained in:
2025-07-31 23:57:32 +08:00
parent 6b113cf32b
commit 96f22a3e48
27 changed files with 770 additions and 63 deletions

View File

@@ -48,4 +48,27 @@ message Game2C_GetRoleInfoResponse // ICustomRouteResponse
{
string Name;
string RoleId;
}
///请求邮件列表
message C2Game_GetMailsRequest // ICustomRouteRequest,Game2C_GetMailsResponse,GameRoute
{
}
///获取邮件列表响应
message Game2C_GetMailsResponse // ICustomRouteResponse
{
repeated MailInfo Mail = 1;
}
///新邮件推送
message Game2C_HaveMail // ICustomRouteMessage,GameRoute
{
MailInfo Mail = 1;
}
message Game2C_MailState // ICustomRouteMessage,GameRoute
{
int32 MailState = 1;
int64 MailId = 2;
}

View File

@@ -50,6 +50,7 @@ message VipInfo
int64 ExpirationTime = 2; //到期时间
}
///奖励信息
message AwardInfo
{
@@ -65,7 +66,7 @@ message ItemInfo
int32 Count = 3; //数量
int64 ExpirationTime = 4; //失效时间
int64 GetTime = 5; //获得时间
int64 Abrasion = 6; //耐久度
int32 Abrasion = 6; //耐久度
}
///fish信息

View File

@@ -3,11 +3,12 @@ package Fantasy.Network.Message;
message MailInfo
{
int64 Id = 1; //邮件id
string Title = 2; //标题
string Content = 3; //内容
int64 SendTime = 4; //发送时间
int32 Type = 5; //邮件类型
repeated AwardInfo Items = 6; //附件列表
bool IsRead = 7; //是否已读
int64 Id = 1; //邮件id
string Title = 2; //标题
string Content = 3; //内容
int64 CreateTime = 4; //发送时间
int64 ExpireTime = 4; //发送时间
int32 MailType = 5; //邮件类型
int32 MailState = 6; //邮件状态
repeated AwardInfo Items = 7; //附件列表
}

View File

@@ -29,7 +29,6 @@
<ItemGroup>
<Folder Include="Game\Activity\" />
<Folder Include="Game\Mail\" />
<Folder Include="Game\Skill\" />
<Folder Include="Map\" />
</ItemGroup>

View File

@@ -0,0 +1,17 @@
using Fantasy.Entitas;
using MongoDB.Bson.Serialization.Attributes;
namespace NB.Game;
public class AwardItem : Entity
{
/// <summary>
/// 拥有的数量
/// </summary>
[BsonElement("c")] public int Count;
/// <summary>
/// 配置id
/// </summary>
[BsonElement("cid")] public int ConfigId;
}

View File

@@ -11,6 +11,8 @@ public enum ItemType
Fish,
}
public class Item : Entity
{
/// <summary>
@@ -27,4 +29,19 @@ public class Item : Entity
/// 是否绑定
/// </summary>
[BsonElement("ib")] public bool IsBind;
/// <summary>
/// 失效时间
/// </summary>
[BsonElement("et")] public long ExpirationTime;
/// <summary>
/// 获得时间
/// </summary>
[BsonElement("gt")] public long GetTime;
/// <summary>
/// 耐久度
/// </summary>
[BsonElement("a")] public int Abrasion;
}

View File

@@ -0,0 +1,34 @@
using Fantasy.DataStructure.Collection;
using Fantasy.Entitas;
using Fantasy.Helper;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Options;
namespace NB.Game;
/// <summary>
/// 玩家邮件组件
/// </summary>
public class MailComponent : Entity
{
/// <summary>
/// 最大邮件数据
/// </summary>
public const int MaxMailCount = 50;
/// <summary>
/// 邮件最大保留时间
/// </summary>
public const long MaxExpireTime = TimeHelper.OneDay * 365;
/// <summary>
/// 邮件列表
/// </summary>
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
public Dictionary<long, Mail> Mails = new Dictionary<long, Mail>();
/// <summary>
/// 按照时间进行排序
/// </summary>
[BsonIgnore] public readonly SortedOneToManyListPool<long, long> Timer = new SortedOneToManyListPool<long, long>();
}

View File

@@ -0,0 +1,46 @@
using Fantasy.Entitas;
namespace NB.Game;
public sealed class Mail : Entity
{
/// <summary>
/// 邮件拥有者
/// </summary>
public long OwnerId;
/// <summary>
/// 邮件状态
/// </summary>
public MailState State = MailState.None;
/// <summary>
/// 邮件状态
/// </summary>
public MailType MailType = MailType.None;
/// <summary>
/// 邮件标题
/// </summary>
public string Title;
/// <summary>
/// 邮件内容
/// </summary>
public string Content;
/// <summary>
/// 创建时间
/// </summary>
public long CreateTime;
/// <summary>
/// 过期时间
/// </summary>
public long ExpireTime;
/// <summary>
/// 邮件的附件内容
/// </summary>
public List<AwardItem> Items = new List<AwardItem>();
}

View File

@@ -0,0 +1,41 @@
using Fantasy.Entitas;
namespace NB.Game;
public class MailBox : Entity
{
/// <summary>
/// 邮件
/// </summary>
public Mail Mail;
/// <summary>
/// 创建时间
/// </summary>
public long CreateTime;
/// <summary>
/// 失效时间
/// </summary>
public long ExpireTime;
/// <summary>
/// 邮箱类型
/// </summary>
public MailBoxType BoxType;
/// <summary>
/// 发送人
/// </summary>
public long SendAccountId = 0;
/// <summary>
/// 收件人
/// </summary>
public HashSet<long> AccountId = new HashSet<long>();
/// <summary>
/// 领取过的人
/// </summary>
public HashSet<long> Received = new HashSet<long>();
}

View File

@@ -0,0 +1,54 @@
namespace NB.Game;
public enum MailType
{
None = 0,
System = 1, //系统邮件
Rewards = 2, //奖励邮件
User = 3, //个人邮件
}
public enum MailState
{
None = 0,
/// <summary>
/// 未读
/// </summary>
Unread = 1,
/// <summary>
/// 已读
/// </summary>
HaveRead = 2,
/// <summary>
/// 未领取
/// </summary>
NotClaimed = 3,
/// <summary>
/// 已领取
/// </summary>
Received = 4,
/// <summary>
/// 已删除
/// </summary>
Deleted = 5,
}
public enum MailBoxType
{
None = 0,
/// <summary>
/// 指定目标
/// </summary>
Specify = 1,
/// <summary>
/// 全部人
/// </summary>
All = 2
}

View File

@@ -1,34 +0,0 @@
using Fantasy.Entitas;
namespace NB;
/// <summary>
/// 角色基础数据
/// </summary>
public class PlayerBasic
{
/// <summary>
/// 昵称
/// </summary>
public string NickName;
/// <summary>
/// 头像
/// </summary>
public string Head;
/// <summary>
/// 国家
/// </summary>
public string Country;
/// <summary>
/// 等级
/// </summary>
public int Level;
/// <summary>
/// 当前经验
/// </summary>
public int Exp;
}

View File

@@ -5,7 +5,7 @@ namespace NB;
/// <summary>
/// 角色统计数据
/// </summary>
public class PlayerStatistics
public class PlayerStatistics : Entity
{
/// <summary>
/// 登录次数

View File

@@ -9,10 +9,30 @@ public sealed class Player : Entity
[BsonElement("ct")] public long CreateTime;
[BsonElement("lt")] public long LoginTime;
public PlayerBasic Basic = new PlayerBasic();
public PlayerStatistics Statistics = new PlayerStatistics();
public PlayerDayFlags DayFlags = new PlayerDayFlags();
public PlayerVip Vip = new PlayerVip();
/// <summary>
/// 昵称
/// </summary>
[BsonElement("name")] public string NickName;
/// <summary>
/// 头像
/// </summary>
public string Head;
/// <summary>
/// 国家
/// </summary>
public string Country;
/// <summary>
/// 等级
/// </summary>
[BsonElement("lv")] public int Level;
/// <summary>
/// 当前经验
/// </summary>
public int Exp;
/// <summary>
/// 余额
@@ -24,6 +44,11 @@ public sealed class Player : Entity
/// </summary>
public int Gold;
// public PlayerStatistics Statistics = new PlayerStatistics();
// public PlayerDayFlags DayFlags = new PlayerDayFlags();
// public PlayerVip Vip = new PlayerVip();
/// <summary>
/// 其他货币
/// </summary>

View File

@@ -229,7 +229,7 @@ namespace Fantasy
[ProtoMember(5)]
public long GetTime { get; set; }
[ProtoMember(6)]
public long Abrasion { get; set; }
public int Abrasion { get; set; }
}
/// <summary>
/// fish信息

View File

@@ -29,10 +29,11 @@ namespace Fantasy
Id = default;
Title = default;
Content = default;
SendTime = default;
Type = default;
CreateTime = default;
ExpireTime = default;
MailType = default;
MailState = default;
Items.Clear();
IsRead = default;
#if FANTASY_NET || FANTASY_UNITY
GetScene().MessagePoolComponent.Return<MailInfo>(this);
#endif
@@ -44,12 +45,14 @@ namespace Fantasy
[ProtoMember(3)]
public string Content { get; set; }
[ProtoMember(4)]
public long SendTime { get; set; }
public long CreateTime { get; set; }
[ProtoMember(5)]
public int Type { get; set; }
public long ExpireTime { get; set; }
[ProtoMember(6)]
public List<AwardInfo> Items = new List<AwardInfo>();
public int MailType { get; set; }
[ProtoMember(7)]
public bool IsRead { get; set; }
public int MailState { get; set; }
[ProtoMember(8)]
public List<AwardInfo> Items = new List<AwardInfo>();
}
}

View File

@@ -192,4 +192,96 @@ namespace Fantasy
[ProtoMember(3)]
public uint ErrorCode { get; set; }
}
/// <summary>
/// 请求邮件列表
/// </summary>
[ProtoContract]
public partial class C2Game_GetMailsRequest : AMessage, ICustomRouteRequest, IProto
{
public static C2Game_GetMailsRequest Create(Scene scene)
{
return scene.MessagePoolComponent.Rent<C2Game_GetMailsRequest>();
}
public override void Dispose()
{
#if FANTASY_NET || FANTASY_UNITY
GetScene().MessagePoolComponent.Return<C2Game_GetMailsRequest>(this);
#endif
}
[ProtoIgnore]
public Game2C_GetMailsResponse ResponseType { get; set; }
public uint OpCode() { return OuterOpcode.C2Game_GetMailsRequest; }
[ProtoIgnore]
public int RouteType => Fantasy.RouteType.GameRoute;
}
/// <summary>
/// 获取邮件列表响应
/// </summary>
[ProtoContract]
public partial class Game2C_GetMailsResponse : AMessage, ICustomRouteResponse, IProto
{
public static Game2C_GetMailsResponse Create(Scene scene)
{
return scene.MessagePoolComponent.Rent<Game2C_GetMailsResponse>();
}
public override void Dispose()
{
ErrorCode = default;
Mail.Clear();
#if FANTASY_NET || FANTASY_UNITY
GetScene().MessagePoolComponent.Return<Game2C_GetMailsResponse>(this);
#endif
}
public uint OpCode() { return OuterOpcode.Game2C_GetMailsResponse; }
[ProtoMember(1)]
public List<MailInfo> Mail = new List<MailInfo>();
[ProtoMember(2)]
public uint ErrorCode { get; set; }
}
/// <summary>
/// 新邮件推送
/// </summary>
[ProtoContract]
public partial class Game2C_HaveMail : AMessage, ICustomRouteMessage, IProto
{
public static Game2C_HaveMail Create(Scene scene)
{
return scene.MessagePoolComponent.Rent<Game2C_HaveMail>();
}
public override void Dispose()
{
Mail = default;
#if FANTASY_NET || FANTASY_UNITY
GetScene().MessagePoolComponent.Return<Game2C_HaveMail>(this);
#endif
}
public uint OpCode() { return OuterOpcode.Game2C_HaveMail; }
[ProtoIgnore]
public int RouteType => Fantasy.RouteType.GameRoute;
[ProtoMember(1)]
public MailInfo Mail { get; set; }
}
[ProtoContract]
public partial class Game2C_MailState : AMessage, ICustomRouteMessage, IProto
{
public static Game2C_MailState Create(Scene scene)
{
return scene.MessagePoolComponent.Rent<Game2C_MailState>();
}
public override void Dispose()
{
MailState = default;
MailId = default;
#if FANTASY_NET || FANTASY_UNITY
GetScene().MessagePoolComponent.Return<Game2C_MailState>(this);
#endif
}
public uint OpCode() { return OuterOpcode.Game2C_MailState; }
[ProtoIgnore]
public int RouteType => Fantasy.RouteType.GameRoute;
[ProtoMember(1)]
public int MailState { get; set; }
[ProtoMember(2)]
public long MailId { get; set; }
}
}

View File

@@ -9,5 +9,9 @@ namespace Fantasy
public const uint G2C_RepeatLogin = 134227729;
public const uint C2Game_GetRoleInfoRequest = 2281711377;
public const uint Game2C_GetRoleInfoResponse = 2415929105;
public const uint C2Game_GetMailsRequest = 2281711378;
public const uint Game2C_GetMailsResponse = 2415929106;
public const uint Game2C_HaveMail = 2147493649;
public const uint Game2C_MailState = 2147493650;
}
}

View File

@@ -33,19 +33,18 @@ public class G2Game_EnterRequestHandler : RouteRPC<Scene, G2Game_EnterRequest, G
// 如果不存在,表示这是一个新的账号,需要创建一下这个账号。
account = await PlayerFactory.Create(scene, accountId);
account.Basic.Level = 99;
account.Basic.NickName = "王麻子";
account.Basic.Country = "cn";
account.Basic.Exp = 999;
account.Basic.Head = "xxx.png";
account.Level = 99;
account.NickName = "王麻子";
account.Country = "cn";
account.Exp = 999;
account.Head = "xxx.png";
for (int i = 0; i < 500; i++)
{
var item = Entity.Create<Item>(scene, true, true);
account.Items.Add(item.Id, item);
}
for (int i = 0; i < 500; i++)
{
var item = Entity.Create<Fish>(scene, true, true);
@@ -86,6 +85,19 @@ public class G2Game_EnterRequestHandler : RouteRPC<Scene, G2Game_EnterRequest, G
account.LoginTime = TimeHelper.Now;
response.RoleRouteId = account.RuntimeId;
if (account.GetComponent<MailComponent>() == null)
{
var mailComponent = await scene.World.DataBase.Query<MailComponent>(account.Id, true);
if (mailComponent == null)
{
//如果没有邮件组件
account.AddComponent<MailComponent>();
}
else
{
account.AddComponent(mailComponent);
}
}
await FTask.CompletedTask;
}

View File

@@ -0,0 +1,36 @@
using Fantasy;
using Fantasy.Entitas.Interface;
namespace NB.Game;
public class AwardItemDestroySystem : DestroySystem<AwardItem>
{
protected override void Destroy(AwardItem self)
{
self.ConfigId = 0;
self.Count = 0;
}
}
public static class AwardItemSystem
{
public static AwardInfo ToAwardInfo(this AwardItem item)
{
return new AwardInfo()
{
ConfigId = item.ConfigId,
Count = item.Count
};
}
public static List<AwardInfo> ToAwardInfo(this List<AwardItem> items)
{
var list = new List<AwardInfo>();
foreach (var item in items)
{
list.Add(item.ToAwardInfo());
}
return list;
}
}

View File

@@ -0,0 +1,62 @@
using Fantasy;
using Fantasy.Entitas.Interface;
namespace NB.Game;
public class ItemDestroySystem : DestroySystem<Item>
{
protected override void Destroy(Item self)
{
self.ConfigId = 0;
self.Count = 0;
self.IsBind = false;
self.ExpirationTime = 0;
self.GetTime = 0;
self.Abrasion = 0;
}
}
public static class ItemSystem
{
public static ItemInfo ToItemInfo(this Item item)
{
return new ItemInfo()
{
ConfigId = item.ConfigId,
Id = item.Id,
Count = item.Count,
// ExpirationTime = item.IsBind;
};
}
public static List<ItemInfo> ToItemInfo(this List<Item> items)
{
var list = new List<ItemInfo>();
foreach (var item in items)
{
list.Add(item.ToItemInfo());
}
return list;
}
public static AwardInfo ToAwardInfo(this Item item)
{
return new AwardInfo()
{
ConfigId = item.ConfigId,
Count = item.Count
};
}
public static List<AwardInfo> ToAwardInfo(this List<Item> items)
{
var list = new List<AwardInfo>();
foreach (var item in items)
{
list.Add(item.ToAwardInfo());
}
return list;
}
}

View File

@@ -0,0 +1,79 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
namespace NB.Game;
public class MailComponentDestroySystem : DestroySystem<MailComponent>
{
protected override void Destroy(MailComponent self)
{
foreach (var (_, mail) in self.Mails)
{
mail.Dispose();
}
self.Mails.Clear();
self.Timer.Clear();
}
}
public static class MailComponentSystem
{
public static async FTask Add(this MailComponent self, Mail mail, bool sync)
{
mail.CreateTime = TimeHelper.Now;
mail.ExpireTime = TimeHelper.Now + MailComponent.MaxExpireTime;
if (self.Mails.Count >= MailComponent.MaxMailCount)
{
//删除最老的邮件
var (_, value) = self.Timer.First();
foreach (var removeId in value)
{
await self.Remove(removeId, sync);
}
}
self.Mails.Add(mail.Id, mail);
self.Timer.Add(mail.ExpireTime, mail.Id);
if (sync)
{
//同步客户端
self.Scene.NetworkMessagingComponent.SendInnerRoute(0,new Game2C_HaveMail()
{
Mail = mail.ToMailInfo(),
});
}
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponent Add id:{self.Id} mailId:{mail.Id} count:{self.Mails.Count}");
}
public static async FTask<uint> Remove(this MailComponent self, long mailId, bool sync)
{
if (!self.Mails.Remove(mailId, out var mail))
{
return 1;
}
self.Timer.RemoveValue(mail.ExpireTime, mail.Id);
mail.Dispose();
if (sync)
{
//同步客户端
self.Scene.NetworkMessagingComponent.SendInnerRoute(0,new Game2C_MailState()
{
MailState = (int)MailState.Deleted,
MailId = mailId,
});
}
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponent Remove id:{self.Id} mailId:{mail.Id} count:{self.Mails.Count}");
return 0;
}
}

View File

@@ -0,0 +1,22 @@
using Fantasy.Entitas.Interface;
namespace NB.Game;
public class MailBoxDestroySystem : DestroySystem<MailBox>
{
protected override void Destroy(MailBox self)
{
if (self.Mail != null)
{
self.Mail.Dispose();
self.Mail = null;
}
self.BoxType = MailBoxType.None;
self.CreateTime = 0;
self.ExpireTime = 0;
self.SendAccountId = 0;
self.AccountId.Clear();
self.Received.Clear();
}
}

View File

@@ -0,0 +1,53 @@
using Fantasy;
using Fantasy.Entitas.Interface;
namespace NB.Game;
public class MailDestroySystem : DestroySystem<Mail>
{
protected override void Destroy(Mail self)
{
self.OwnerId = 0;
self.Title = string.Empty;
self.Content = string.Empty;
self.ExpireTime = 0;
self.CreateTime = 0;
self.State = MailState.None;
self.MailType = MailType.None;
foreach (var item in self.Items)
{
item.Dispose();
}
self.Items.Clear();
}
}
public static class MailSystem
{
public static MailInfo ToMailInfo(this Mail mail)
{
return new MailInfo()
{
Id = mail.Id,
Title = mail.Title,
Content = mail.Content,
ExpireTime = mail.ExpireTime,
CreateTime = mail.CreateTime,
MailState = (int)mail.State,
MailType = (int)mail.MailType,
Items = mail.Items.ToAwardInfo()
};
}
public static List<MailInfo> ToMailInfo(this List<Mail> mails)
{
var list = new List<MailInfo>();
foreach (var mail in mails)
{
list.Add(mail.ToMailInfo());
}
return list;
}
}

View File

@@ -0,0 +1,55 @@
using Fantasy;
using Fantasy.Entitas;
using Fantasy.Helper;
namespace NB.Game;
public static class MailBoxFactory
{
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="sendAccountId"></param>
/// <param name="mail"></param>
/// <param name="expireTime"></param>
/// <param name="accountIds"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, long sendAccountId, Mail mail, int expireTime, List<long> accountIds)
{
var mailBox = Entity.Create<MailBox>(scene, true, true);
mailBox.SendAccountId = sendAccountId;
mailBox.Mail = mail;
mailBox.ExpireTime = TimeHelper.Now + expireTime;
mailBox.CreateTime = TimeHelper.Now;
if (accountIds == null || accountIds.Count <= 0)
{
return mailBox;
}
foreach (var accountId in accountIds)
{
mailBox.AccountId.Add(accountId);
}
return mailBox;
}
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="sendAccountId"></param>
/// <param name="expireTime"></param>
/// <param name="accountIds"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="items"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, long sendAccountId, int expireTime, List<long> accountIds, string title,
string content, List<AwardItem> items = null)
{
var mail = MailFactory.Create(scene, title, content, items);
return Create(scene, sendAccountId, mail, expireTime, accountIds);
}
}

View File

@@ -0,0 +1,30 @@
using Fantasy;
using Fantasy.Entitas;
using Fantasy.Helper;
using Fantasy.Serialize;
namespace NB.Game;
public static class MailFactory
{
private static readonly ISerialize _serializer = SerializerManager.GetSerializer(FantasySerializerType.Bson);
public static Mail Create(Scene scene, string title, string content, List<AwardItem> items = null)
{
var mail = Entity.Create<Mail>(scene, true, true);
mail.Title = title;
mail.Content = content;
mail.State = MailState.Unread;
mail.CreateTime = TimeHelper.Now;
if (items != null && items.Count > 0)
{
foreach (var item in items)
{
mail.Items.Add(_serializer.Clone(item));
}
}
return mail;
}
}

View File

@@ -0,0 +1,31 @@
using Fantasy;
using Fantasy.Async;
namespace NB.Game;
/// <summary>
/// 发送邮件接口
/// </summary>
public static class MailHelper
{
public static async FTask Send(Scene scene, MailBox mailBox)
{
if (mailBox.BoxType == MailBoxType.None)
{
Log.Error("不支持的邮件类型");
return;
}
switch (mailBox.BoxType)
{
case MailBoxType.All:
{
break;
}
case MailBoxType.Specify:
{
break;
}
}
}
}

View File

@@ -12,4 +12,8 @@
<ProjectReference Include="..\Fantasy\Fantasy.Net\Fantasy.Net\Fantasy.Net.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Game\Mail\Handler\" />
</ItemGroup>
</Project>