This commit is contained in:
2025-07-27 12:34:04 +08:00
parent 6311c7cb12
commit 743c1d2baa
194 changed files with 81685 additions and 696 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<LangVersion>default</LangVersion>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

View File

@@ -1,22 +0,0 @@
using System;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.Network.Route;
namespace Fantasy;
public sealed class G2M_RequestAddressableIdHandler : RouteRPC<Scene, G2M_RequestAddressableId, M2G_ResponseAddressableId>
{
protected override async FTask Run(Scene scene, G2M_RequestAddressableId request, M2G_ResponseAddressableId response, Action reply)
{
// 1、因为是测试代码所以默认每次请求这个协议我都创建一个新的Unit来做Addressable。
var unit = Entity.Create<Unit>(scene, false, true);
// 2、给Unit添加AddressableMessageComponent组件并执行Register()向AddressableScene注册自己当前的位置。
await unit.AddComponent<AddressableMessageComponent>().Register();
// 3、返回给Gate服务器AddressableId
response.AddressableId = unit.Id;
await FTask.CompletedTask;
}
}

View File

@@ -1,17 +0,0 @@
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Network.Interface;
namespace Fantasy;
public class G2M_CreateSubSceneRequestHandler : RouteRPC<Scene, G2M_CreateSubSceneRequest, M2G_CreateSubSceneResponse>
{
protected override async FTask Run(Scene scene, G2M_CreateSubSceneRequest request, M2G_CreateSubSceneResponse response, Action reply)
{
// 下面的SceneType传的是666其实并没有这个类型这个是我随便写的。
var subScene = Scene.CreateSubScene(scene, 6666);
// 返回subScene的运行时id
response.SubSceneRouteId = subScene.RouteId;
await FTask.CompletedTask;
}
}

View File

@@ -1,21 +0,0 @@
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Network.Interface;
using Fantasy.Network.Route;
namespace Fantasy;
public sealed class G2SubScene_AddressableIdRequestHandler : RouteRPC<SubScene, G2SubScene_AddressableIdRequest, SubScene2G_AddressableIdResponse>
{
protected override async FTask Run(SubScene subScene, G2SubScene_AddressableIdRequest request, SubScene2G_AddressableIdResponse response, Action reply)
{
Log.Debug($"G2SubScene_AddressableIdRequestHandler {subScene.SceneType}");
// 1、因为是测试代码所以默认每次请求这个协议我都创建一个新的Unit来做Addressable。
var unit = Entity.Create<Unit>(subScene, false, true);
// 2、给Unit添加AddressableMessageComponent组件并执行Register()向AddressableScene注册自己当前的位置。
await unit.AddComponent<AddressableMessageComponent>().Register();
// 3、返回给Gate服务器AddressableId
response.AddressableId = unit.Id;
await FTask.CompletedTask;
}
}

View File

@@ -1,13 +0,0 @@
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace Fantasy;
public class G2SubScene_SentMessageHandler : Route<Scene, G2SubScene_SentMessage>
{
protected override async FTask Run(Scene scene, G2SubScene_SentMessage message)
{
Log.Debug($"接受到来自Gate的消息 SceneType:{scene.SceneType} Message:{message.Tag}");
await FTask.CompletedTask;
}
}

View File

@@ -27,7 +27,7 @@ public class OnSceneCreate_Init : AsyncEventSystem<OnCreateScene>
// 用于验证JWT是否合法的组件
scene.AddComponent<GateJWTComponent>();
// 用于管理GameAccount的组件
scene.AddComponent<GameAccountManageComponent>();
scene.AddComponent<PlayerManageComponent>();
break;
}
}

View File

@@ -21,6 +21,7 @@ public class C2A_LoginRequestHandler : MessageRPC<C2A_LoginRequest, A2C_LoginRes
session.SetTimeout(3000);
var scene = session.Scene;
Log.Info($"登录服场景 {scene.Id} {scene.RouteId} {scene.SceneConfigId}");
var result = await AuthenticationHelper.Login(scene, request.Username, request.Password);
if (result.ErrorCode != ErrorCode.Successful && result.AccountId == -1)

View File

@@ -0,0 +1,86 @@
using Fantasy.Entitas.Interface;
namespace NB.Game;
public sealed class ContainerDestroySystem : DestroySystem<Container>
{
protected override void Destroy(Container self)
{
self.CellCount = 0;
self.CellCountMax = 0;
self.CurrentCellCount = 0;
foreach (var (_, item) in self.Items)
{
item.Dispose();
}
self.Items.Clear();
self.ItemsByCell.Clear();
self.ItemsByConfigId.Clear();
self.ItemsByType.Clear();
}
}
public static class ContainerSystem
{
#region Get
/// <summary>
/// 通过唯一id获取
/// </summary>
/// <param name="self"></param>
/// <param name="id"></param>
/// <param name="item"></param>
/// <returns></returns>
public static bool GetItemById(this Container self, uint id, out Item item)
{
return self.Items.TryGetValue(id, out item);
}
/// <summary>
/// 通过格子位置获取物品
/// </summary>
/// <param name="self"></param>
/// <param name="cell"></param>
/// <param name="item"></param>
/// <returns></returns>
public static bool GetItemByCell(this Container self, uint cell, out Item item)
{
return self.ItemsByCell.TryGetValue(cell, out item);
}
/// <summary>
/// 通过配置id获取物品
/// </summary>
/// <param name="self"></param>
/// <param name="configId"></param>
/// <param name="items"></param>
public static void GetItemByConfigId(this Container self, uint configId, List<Item> items)
{
if (!self.ItemsByConfigId.TryGetValue(configId, out var itemList))
{
return;
}
items.AddRange(itemList);
}
/// <summary>
/// 通过类型获取物品
/// </summary>
/// <param name="self"></param>
/// <param name="type"></param>
/// <param name="items"></param>
public static void GetItemByType(this Container self, ItemType type, List<Item> items)
{
if (!self.ItemsByType.TryGetValue((uint)type, out var itemList))
{
return;
}
items.AddRange(itemList);
}
#endregion
}

View File

@@ -9,7 +9,7 @@ public sealed class C2G_GetAccountInfoRequestHandler : MessageRPC<C2G_GetAccount
{
protected override async FTask Run(Session session, C2G_GetAccountInfoRequest request, G2C_GetAccountInfoResponse response, Action reply)
{
var gameAccountFlagComponent = session.GetComponent<GameAccountFlagComponent>();
var gameAccountFlagComponent = session.GetComponent<PlayerFlagComponent>();
if (gameAccountFlagComponent == null)
{
@@ -19,7 +19,7 @@ public sealed class C2G_GetAccountInfoRequestHandler : MessageRPC<C2G_GetAccount
return;
}
GameAccount account = gameAccountFlagComponent.Account;
Player account = gameAccountFlagComponent.Account;
if (account == null)
{

View File

@@ -4,8 +4,6 @@ using Fantasy.Async;
using Fantasy.Network;
using Fantasy.Network.Interface;
#pragma warning disable CS8604 // Possible null reference argument.
namespace NB.Gate.Handler;
public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_LoginResponse>
@@ -22,7 +20,7 @@ public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_L
}
var scene = session.Scene;
// Log.Info($"网关服场景 {scene.Id} {scene.RouteId} {scene.SceneConfigId} {scene.RouteId} {session.RouteId}");
if (!GateJWTHelper.ValidateToken(scene, request.ToKen, out var accountId))
{
// 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。
@@ -31,19 +29,19 @@ public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_L
}
// 在缓存中检查该账号是否存在
var gameAccountManageComponent = scene.GetComponent<GameAccountManageComponent>();
var gameAccountManageComponent = scene.GetComponent<PlayerManageComponent>();
Log.Debug("检查账号是否在缓存中");
if (!gameAccountManageComponent.TryGet(accountId, out var account))
{
// 首先要先到数据库中查询是否有这个账号
account = await GameAccountHelper.LoadDataBase(scene, accountId);
account = await PlayerHelper.LoadDataBase(scene, accountId);
// 如果有的话,就直接加入在缓存中就可以了
if (account == null)
{
Log.Debug("检查到账号没有在数据库中,需要创建一个新的账号并且保存到数据库中");
// 如果没有,就要创建一个新的并且保存到数据库。
// 如果不存在,表示这是一个新的账号,需要创建一下这个账号。
account = await GameAccountFactory.Create(scene, accountId);
account = await PlayerFactory.Create(scene, accountId);
}
else
{
@@ -79,7 +77,7 @@ public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_L
// 1、客户端断线重连要给这个Session发送一个消息通知它有人登录了。
// 2、其他的客户端登录了这个账号要给这个Session发送一个消息通知它有人登录了。
var gameAccountFlagComponent = oldSession.GetComponent<GameAccountFlagComponent>();
var gameAccountFlagComponent = oldSession.GetComponent<PlayerFlagComponent>();
gameAccountFlagComponent.AccountID = 0;
gameAccountFlagComponent.Account = null;
// 给客户端发送一个重复登录的消息,如果当前客户端是自己上次登录的,发送也不会收到。
@@ -90,7 +88,7 @@ public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_L
}
// 给当前Session添加一个组件当Session销毁的时候会销毁这个组件。
var accountFlagComponent = session.AddComponent<GameAccountFlagComponent>();
var accountFlagComponent = session.AddComponent<PlayerFlagComponent>();
accountFlagComponent.AccountID = accountId;
accountFlagComponent.Account = account;

View File

@@ -1,49 +0,0 @@
using Fantasy.Entitas.Interface;
namespace NB.Gate.System;
public sealed class GameAccountManageComponentDestroySystem : DestroySystem<GameAccountManageComponent>
{
protected override void Destroy(GameAccountManageComponent self)
{
foreach (var (_, gameAccount) in self.Accounts)
{
gameAccount.Dispose();
}
self.Accounts.Clear();
}
}
public static class GameAccountManageComponentSystem
{
public static void Add(this GameAccountManageComponent self, GameAccount account)
{
self.Accounts.Add(account.Id, account);
}
public static GameAccount? Get(this GameAccountManageComponent self, long accountId)
{
return self.Accounts.GetValueOrDefault(accountId);
}
public static bool TryGet(this GameAccountManageComponent self, long accountId, out GameAccount? account)
{
return self.Accounts.TryGetValue(accountId, out account);
}
public static void Remove(this GameAccountManageComponent self, long accountId, bool isDispose = true)
{
if (!self.Accounts.Remove(accountId, out var account))
{
return;
}
if (!isDispose)
{
return;
}
account.Dispose();
}
}

View File

@@ -2,9 +2,9 @@ using Fantasy.Entitas.Interface;
namespace NB.Gate;
public sealed class GameAccountDestroySystem : DestroySystem<GameAccount>
public sealed class PlayerDestroySystem : DestroySystem<Player>
{
protected override void Destroy(GameAccount self)
protected override void Destroy(Player self)
{
self.CreateTime = 0;
self.LoginTime = 0;

View File

@@ -5,20 +5,20 @@ using Fantasy.Helper;
namespace NB.Gate;
public static class GameAccountFactory
public static class PlayerFactory
{
/// <summary>
/// 创建一个新的GameAccount
/// 创建一个新的Player
/// </summary>
/// <param name="scene"></param>
/// <param name="aId">ToKen令牌传递过来的aId</param>
/// <param name="isSaveDataBase">是否在创建的过程中保存到数据库</param>
/// <returns></returns>
public static async FTask<GameAccount> Create(Scene scene, long aId, bool isSaveDataBase = true)
public static async FTask<Player> Create(Scene scene, long aId, bool isSaveDataBase = true)
{
var gameAccount = Entity.Create<GameAccount>(scene, aId, false, false);
var gameAccount = Entity.Create<Player>(scene, aId, false, false);
gameAccount.LoginTime = gameAccount.CreateTime = TimeHelper.Now;
if (isSaveDataBase)
{
await gameAccount.SaveDataBase();

View File

@@ -2,15 +2,15 @@ using Fantasy.Entitas.Interface;
namespace NB.Gate;
public sealed class GameAccountFlagComponentDestroySystem : DestroySystem<GameAccountFlagComponent>
public sealed class PlayerFlagComponentDestroySystem : DestroySystem<PlayerFlagComponent>
{
protected override void Destroy(GameAccountFlagComponent self)
protected override void Destroy(PlayerFlagComponent self)
{
if (self.AccountID != 0)
{
// 执行下线过程、并且要求在5分钟后完成缓存清理。也就是5分钟后会保存数据到数据库。
// 由于5分钟太长了、咱们测试的时候不方便测试也可以把这个时间改短一些比如10秒。
GameAccountHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine();
PlayerHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine();
self.AccountID = 0;
}

View File

@@ -5,7 +5,7 @@ using Fantasy.Network;
namespace NB.Gate;
public static class GameAccountHelper
public static class PlayerHelper
{
/// <summary>
/// 从数据库中读取GameAccount
@@ -13,9 +13,9 @@ public static class GameAccountHelper
/// <param name="scene"></param>
/// <param name="accountId">账号Id</param>
/// <returns></returns>
public static async FTask<GameAccount?> LoadDataBase(Scene scene, long accountId)
public static async FTask<Player?> LoadDataBase(Scene scene, long accountId)
{
var account = await scene.World.DataBase.First<GameAccount>(d => d.Id == accountId);
var account = await scene.World.DataBase.First<Player>(d => d.Id == accountId);
if (account == null)
{
return null;
@@ -29,7 +29,7 @@ public static class GameAccountHelper
/// 保存账号到数据库中
/// </summary>
/// <param name="self"></param>
public static async FTask SaveDataBase(this GameAccount self)
public static async FTask SaveDataBase(this Player self)
{
await self.Scene.World.DataBase.Save(self);
}
@@ -38,12 +38,12 @@ public static class GameAccountHelper
/// 执行该账号的断开逻辑,不要非必要不要使用这个接口,这个接口是内部使用。
/// </summary>
/// <param name="self"></param>
public static async FTask Disconnect(this GameAccount self)
public static async FTask Disconnect(this Player self)
{
// 保存该账号信息到数据库中。
await SaveDataBase(self);
// 在缓存中移除自己并且执行自己的Dispose方法。
self.Scene.GetComponent<GameAccountManageComponent>().Remove(self.Id);
self.Scene.GetComponent<PlayerManageComponent>().Remove(self.Id);
}
/// <summary>
@@ -62,7 +62,7 @@ public static class GameAccountHelper
// 这样的话是不是可以在登录的时候给这个组件把AccountId存到这个组件呢
// 要检查当前缓存中是否存在该账号的数据
var gameAccountManageComponent = scene.GetComponent<GameAccountManageComponent>();
var gameAccountManageComponent = scene.GetComponent<PlayerManageComponent>();
if (!gameAccountManageComponent.TryGet(accountId, out var account))
{
// 如果缓存中没有、那表示已经下线或者根本不存在该账号,应该在打印一个警告,因为正常的情况下是不会出现的。
@@ -98,7 +98,7 @@ public static class GameAccountHelper
/// </summary>
/// <param name="self"></param>
/// <returns></returns>
public static GameAccountInfo GetGameAccountInfo(this GameAccount self)
public static GameAccountInfo GetGameAccountInfo(this Player self)
{
// 其实可以不用每次都NEW一个新的GameAccountInfo
// 可以在当前账号下创建一个GameAccountInfo每次变动会提前通知这个GameAccountInfo

View File

@@ -0,0 +1,49 @@
using Fantasy.Entitas.Interface;
namespace NB.Gate.System;
public sealed class PlayerManageComponentDestroySystem : DestroySystem<PlayerManageComponent>
{
protected override void Destroy(PlayerManageComponent self)
{
foreach (var (_, gameAccount) in self.Players)
{
gameAccount.Dispose();
}
self.Players.Clear();
}
}
public static class PlayerManageComponentSystem
{
public static void Add(this PlayerManageComponent self, Player account)
{
self.Players.Add(account.Id, account);
}
public static Player Get(this PlayerManageComponent self, long accountId)
{
return self.Players.GetValueOrDefault(accountId);
}
public static bool TryGet(this PlayerManageComponent self, long accountId, out Player? account)
{
return self.Players.TryGetValue(accountId, out account);
}
public static void Remove(this PlayerManageComponent self, long accountId, bool isDispose = true)
{
if (!self.Players.Remove(accountId, out var account))
{
return;
}
if (!isDispose)
{
return;
}
account.Dispose();
}
}