提交示例代码

This commit is contained in:
Bob.Song
2026-03-05 11:39:06 +08:00
commit 25958f58c3
2534 changed files with 209593 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
using Fantasy.Entitas;
using Fantasy.Helper;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
namespace Fantasy;
public static class MailBoxFactory
{
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="mailBoxType"></param>
/// <param name="sendAccountId"></param>
/// <param name="mail"></param>
/// <param name="expireTime"></param>
/// <param name="accountId"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, MailBoxType mailBoxType, long sendAccountId, Mail mail, int expireTime, List<long> accountId = null)
{
var mailBox = Entity.Create<MailBox>(scene, true, true);
mailBox.SendAccountId = sendAccountId;
mailBox.Mail = mail;
mailBox.MailBoxType = mailBoxType;
mailBox.CreateTime = TimeHelper.Now;
mailBox.ExpireTime = mailBox.CreateTime + expireTime;
if (accountId == null || accountId.Count <= 0)
{
return mailBox;
}
foreach (var raId in accountId)
{
mailBox.AccountId.Add(raId);
}
return mailBox;
}
/// <summary>
/// 创建一个邮件箱
/// </summary>
/// <param name="scene"></param>
/// <param name="mailType"></param>
/// <param name="mailBoxType"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="money"></param>
/// <param name="items"></param>
/// <param name="sendAccountId"></param>
/// <param name="expireTime"></param>
/// <param name="accountId"></param>
/// <returns></returns>
public static MailBox Create(Scene scene, MailType mailType, MailBoxType mailBoxType, string title, string content, int money, List<Item> items,
long sendAccountId, int expireTime, List<long> accountId = null)
{
var mail = MailFactory.Create(scene, mailType,title, content, money, items);
return Create(scene, mailBoxType, sendAccountId, mail, expireTime, accountId);
}
}

View File

@@ -0,0 +1,47 @@
using Fantasy.Entitas;
using Fantasy.Serialize;
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
namespace Fantasy;
public static class MailFactory
{
public static readonly ISerialize Serializer = SerializerManager.GetSerializer(FantasySerializerType.Bson);
/// <summary>
/// 创建一个基础的邮件
/// </summary>
/// <param name="scene"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="money"></param>
/// <param name="items"></param>
/// <param name="mailType"></param>
/// <returns></returns>
public static Mail Create(Scene scene, MailType mailType, string title, string content, int money = 0, List<Item> items = null)
{
var mail = Entity.Create<Mail>(scene, true, true);
mail.Title = title;
mail.Content = content;
mail.Money = money;
mail.MailType = mailType;
mail.MailState = MailState.Unread;
// if (items is not { Count: > 0 })
// {
//
// }
if (items != null && items.Count > 0)
{
foreach (var item in items)
{
// 最好的是要个这个Item给克隆出一份来。
// 这样就可以保证,无论外面怎么改变也不会影响这个邮件的东西了。
var cloneItem = Serializer.Clone(item);
mail.Items.Add(cloneItem);
}
}
return mail;
}
}

View File

@@ -0,0 +1,193 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Platform.Net;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace Fantasy;
/// <summary>
/// 发送邮件的唯一接口
/// 如果不是通过这个接口发送的邮件、出现任何问题,后果自负
/// </summary>
public static class MailHelper
{
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="scene"></param>
/// <param name="mailBox">发送完成后记着一定要销毁这个MailBox不然会有GC。</param>
public static async FTask Send(Scene scene, MailBox mailBox)
{
if (scene.SceneType == SceneType.Mail)
{
await InnerSend(scene, mailBox);
return;
}
// 如果不在同一个Scene下就需要发送内部的网络消息给这个Scene了
var mailSceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Mail)[0];
await scene.NetworkMessagingComponent.CallInnerRoute(mailSceneConfig.RouteId, new Other2Mail_SendMailRequest()
{
MailBox = mailBox
});
}
/// <summary>
/// 发送邮件
/// </summary>
/// <param name="scene"></param>
/// <param name="mailBox"></param>
/// <returns></returns>
private static async FTask InnerSend(Scene scene, MailBox mailBox)
{
// 现在的情况这个接口只能是在Mail这个服务器下可以操作
// 但是真实的情况,其他同时开发的逻辑,调用这个接口一般都是在其他的服务器。
// 这个问题其实很好解决只需要判定当前Scene不是Mail、那就做一个协议自动发送到这个MailScene就可以了。
if (mailBox.MailBoxType == MailBoxType.None)
{
Log.Error("MailBoxType MailBoxType.None not support!");
return;
}
var mailUnitManageComponent = scene.GetComponent<MailUnitManageComponent>();
var mailBoxManageComponent = scene.GetComponent<MailBoxManageComponent>();
mailBoxManageComponent.MailBoxes.Add(mailBox.Id, mailBox);
switch (mailBox.MailBoxType)
{
case MailBoxType.Specify:
{
if (mailBox.AccountId.Count <= 0)
{
Log.Error($"{mailBox.Id} AccountId is 0!");
return;
}
// 这里可能有几种情况
// 1、AccountId里面可能只有一个人。
// 2、AccountId里面有多个人。
using var sendAccountIds = ListPool<long>.Create();
foreach (var accountId in mailBox.AccountId)
{
if (!mailUnitManageComponent.TryGet(accountId, out var mailUnit))
{
// 如果没有的话,代表这个用户根本不存在。
// 那这样的情况就不需要做任何处理。
continue;
}
// 如果有的话,那么就给这个用户发送邮件。
// 这个玩家是否在线?
var mailComponent = mailUnit.GetComponent<MailComponent>();
// 在线的话,就直接发送邮件给这个玩家就可以了。
if (mailComponent != null)
{
await mailComponent.Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
sendAccountIds.Add(accountId);
}
else
{
// 不在线
// 首先
// 1、如果玩家不在线那就把这个邮件在数据库中拿出来然后把邮件插入到玩家的邮件列表里。然后再保存
// 这样的做法是不推荐的,因为咱们游戏所有的东西都是有状态的,但是你拿出来的邮件是没有状态的。这样可能会导致一些问题的出现。
// 正确的做做法:
// 把这个邮件方法一个地方,比如是一个处理中心,当玩家上线的时候,主动去这个中心领取这个邮件。
// 快递驿站、菜鸟、
mailBoxManageComponent.MailsByAccount.Add(accountId, mailBox);
Log.Debug("发送离线邮件成功,用户上线第一时间会领取这个邮件。");
}
}
// 移除掉发送成功的账号。
foreach (var sendAccountId in sendAccountIds)
{
mailBox.AccountId.Remove(sendAccountId);
}
// 如果没有任何收件人了、就可以把这个邮箱给删除了。
if (mailBox.AccountId.Count <= 0)
{
mailBox.Dispose();
}
else
{
// 当这个邮件箱还有没有接收的玩家时候,要保存到数据库中,方便下次停服维护再重新启动的时候,加载这个邮件箱。
await scene.World.DataBase.Save(mailBox);
Log.Debug("保存离线邮件成功");
}
break;
}
case MailBoxType.Online:
{
// // 这里有个问题,如何知道在线的人呢?
// foreach (var (_, mailUnit) in mailUnitManageComponent.UnitByAccountId)
// {
// var mailComponent = mailUnit.GetComponent<MailComponent>();
// if (mailComponent == null)
// {
// continue;
// }
// // 能指定到这里的都是在线的玩家。
// await mailComponent.Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
// }
try
{
foreach (var (_, mailUnit) in mailUnitManageComponent.Online)
{
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
}
}
finally
{
mailBox.Dispose();
}
break;
}
case MailBoxType.All:
{
// 要保证这个邮件一定要有一个生命周期。并且这个周期一定要短如果是想要实现永久的可以比如30天发送一次。
mailBoxManageComponent.MailsByMailBoxType.Add((int)MailBoxType.All, mailBox);
// 首先给所有在线的玩家发送。
foreach (var (_, mailUnit) in mailUnitManageComponent.Online)
{
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
// 在邮件盒子中记录下玩家领取的记录,避免重复领取。
mailBox.Received.Add(mailUnit.Id);
}
// 保存邮件箱到数据库。
await scene.World.DataBase.Save(mailBox);
break;
}
case MailBoxType.AllToDate:
{
mailBoxManageComponent.MailsByMailBoxType.Add((int)MailBoxType.AllToDate, mailBox);
foreach (var (_, mailUnit) in mailUnitManageComponent.Online)
{
if (mailUnit.CreateTime > mailBox.CreateTime)
{
// 如果执行到这里,表示这里用户是这个邮件创建之后的用户。这个就不要发送了
continue;
}
// 所以这个邮件类型的逻辑就是,给当前邮件创建时间之前的玩家发送。
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
// 在邮件盒子中记录下玩家领取的记录,避免重复领取。
mailBox.Received.Add(mailUnit.Id);
}
// 保存邮件箱到数据库。
await scene.World.DataBase.Save(mailBox);
break;
}
// 根据玩家等级、等等这样的邮件箱类型,都可以自行扩展了
// 课下作业、自己实现一个起来类型的邮箱。
}
}
}