mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交bug 艰难先这样
This commit is contained in:
@@ -24,10 +24,10 @@ public class DApplication : MonoBehaviour
|
||||
//开始运行同步
|
||||
if (App.IsServer())
|
||||
{
|
||||
var tileServer = App.Game.Start<JNGTileServerSystem>();
|
||||
var tileServer = App.Game.StartServer<JNGTileServerSystem>();
|
||||
JNTileServerDebuger.Instance.Add(tileServer);
|
||||
}
|
||||
if (App.IsClient()) App.Game.Start<JNGTileClientSystem>();
|
||||
if (App.IsClient()) App.Game.StartClient<JNGTileClientSystem>();
|
||||
|
||||
|
||||
}
|
||||
@@ -48,10 +48,5 @@ public class DApplication : MonoBehaviour
|
||||
var input = App.Game.GetInput<IDWorld>();
|
||||
input.IsAdd = true;
|
||||
}
|
||||
|
||||
public void OnClickServerCreate()
|
||||
{
|
||||
App.Game.Start<JNGTileServerSystem>();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ namespace AppGame
|
||||
|
||||
public class TileServerInfo
|
||||
{
|
||||
public String id;
|
||||
public String server;
|
||||
public String ip;
|
||||
public int port;
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ namespace AppGame.Sync
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
|
||||
base.Initialize();
|
||||
//默认玩家位置
|
||||
SetPlayerPosition(LVector3.Zero);
|
||||
@@ -174,6 +176,7 @@ namespace AppGame.Sync
|
||||
if (info is not null)
|
||||
{
|
||||
client.SetPoint($"{info.ip}:{info.port}");
|
||||
client.SetTileServer(info.server);
|
||||
|
||||
if (IsTileConnect(index))
|
||||
{
|
||||
@@ -194,8 +197,27 @@ namespace AppGame.Sync
|
||||
{
|
||||
Debug.Log($"[{index}] 卸载 Socket");
|
||||
App.Client.RemoveClient(client);
|
||||
//并且释放数据
|
||||
GetSystems<ISTileDataSystem>().ForEach(data =>
|
||||
{
|
||||
data.ClearTileData(index);
|
||||
});
|
||||
}
|
||||
Sockets.Remove(index);
|
||||
}
|
||||
public void RemoveSocket(string server)
|
||||
{
|
||||
Sockets.ToArray().ForEach(tile =>
|
||||
{
|
||||
if (tile.Value.TileServer != server) return;
|
||||
tile.Value.OnClose();
|
||||
Sockets.Remove(tile.Key);
|
||||
//并且释放数据
|
||||
GetSystems<ISTileDataSystem>().ForEach(data =>
|
||||
{
|
||||
data.ClearTileData(tile.Key);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -39,6 +39,9 @@ namespace AppGame.Sync
|
||||
//区块Socket
|
||||
public Dictionary<int, JNGTileClient> Sockets = new ();
|
||||
|
||||
//是否开始前尝试连接周围区块去恢复历史数据
|
||||
public bool isRecover = true;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化服务器
|
||||
/// </summary>
|
||||
@@ -47,6 +50,48 @@ namespace AppGame.Sync
|
||||
{
|
||||
|
||||
await base.OnInit();
|
||||
|
||||
if (isRecover)
|
||||
{
|
||||
|
||||
List<int> tileIds = GetTileGridIndex(TID);
|
||||
foreach (var tileId in tileIds)
|
||||
{
|
||||
var client = await AddSocket(tileId);
|
||||
if (client is null) continue;
|
||||
|
||||
//连接到附近区块 开始 恢复数据
|
||||
Debug.Log($"[JNGTileServerSystem] 连接到附近区块{tileId} 开始 恢复数据");
|
||||
var tileInfo = await client.NSyncTileGetTileInfo(TID);
|
||||
if (tileInfo is not null)
|
||||
{
|
||||
Debug.Log("[JNGTileServerSystem] 获取到恢复数据成功 正在恢复数据");
|
||||
|
||||
var message = new Dictionary<long, byte[]>();
|
||||
tileInfo.Data.Data.ForEach(frame =>
|
||||
{
|
||||
message.Clear();
|
||||
foreach (var data in frame.Messages)
|
||||
{
|
||||
message.Add(data.Key,data.Value.Data.ToByteArray());
|
||||
}
|
||||
GetSystems<ISStateDataSystem>().ForEach(child =>
|
||||
{
|
||||
if (child.NetID != frame.NetID) return;
|
||||
child.OnInsertUBytes(message);
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[JNGTileServerSystem] 获取到恢复数据失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Debug.Log("[JNGTileServerSystem] 恢复数据结束");
|
||||
|
||||
}
|
||||
|
||||
//添加Tile服务器
|
||||
App.Business.Send((int)NActionEnum.NAddTileServer,new JNAddTileServer()
|
||||
@@ -60,7 +105,7 @@ namespace AppGame.Sync
|
||||
Timers.Instance.SetInterval(1f, UpdateTileSocket);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override SLogicSystem[] NewLogicSystems()
|
||||
{
|
||||
return new SLogicSystem[]
|
||||
@@ -193,9 +238,9 @@ namespace AppGame.Sync
|
||||
return Sockets.Keys.Where(key => Sockets[key].IsOpen).ToArray();
|
||||
}
|
||||
|
||||
protected async Task AddSocket(int index)
|
||||
protected async Task<JNGTileClient> AddSocket(int index)
|
||||
{
|
||||
if (IsTileConnect(index)) return;
|
||||
if (IsTileConnect(index)) return null;
|
||||
|
||||
var client = new JNGTileClient();
|
||||
Sockets.Add(index,client);
|
||||
@@ -207,16 +252,23 @@ namespace AppGame.Sync
|
||||
{
|
||||
Debug.Log($"[{index}] 连接 Socket");
|
||||
client.SetPoint($"{info.ip}:{info.port}");
|
||||
client.SetTileServer(info.server);
|
||||
await client.OnInit();
|
||||
return client;
|
||||
}
|
||||
else
|
||||
{
|
||||
Sockets.Remove(index);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void RemoveSocket(int index)
|
||||
/// <summary>
|
||||
/// 销毁Socket
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void RemoveSocket(int index)
|
||||
{
|
||||
if (Sockets.TryGetValue(index,out var client))
|
||||
{
|
||||
@@ -225,6 +277,15 @@ namespace AppGame.Sync
|
||||
}
|
||||
Sockets.Remove(index);
|
||||
}
|
||||
public void RemoveSocket(string server)
|
||||
{
|
||||
Sockets.ToArray().ForEach(tile =>
|
||||
{
|
||||
if (tile.Value.TileServer != server) return;
|
||||
tile.Value.OnClose();
|
||||
Sockets.Remove(tile.Key);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DotRecast.Core.Collections;
|
||||
using JNGame.Network;
|
||||
using Plugins.JNGame.Network;
|
||||
using Plugins.JNGame.Network.Action;
|
||||
@@ -14,6 +15,12 @@ namespace AppGame.Systems
|
||||
private int _clientId;
|
||||
public int ClientId => _clientId;
|
||||
|
||||
/// <summary>
|
||||
/// 区块服务器标识
|
||||
/// </summary>
|
||||
private string _tileServer;
|
||||
public string TileServer => _tileServer;
|
||||
|
||||
public void BindID(int clientId)
|
||||
{
|
||||
_clientId = clientId;
|
||||
@@ -23,6 +30,10 @@ namespace AppGame.Systems
|
||||
{
|
||||
_point = point;
|
||||
}
|
||||
public void SetTileServer(string tileServer)
|
||||
{
|
||||
_tileServer = tileServer;
|
||||
}
|
||||
|
||||
protected override async UniTask<string> GetEndPoint()
|
||||
{
|
||||
@@ -36,6 +47,7 @@ namespace AppGame.Systems
|
||||
//监听服务端事件
|
||||
AddListener((int)NActionEnum.NSyncStateDataUpdate,OnNSyncStateDataUpdate);
|
||||
AddListener((int)NActionEnum.LocalClientConnect,OnClientConnect);
|
||||
AddListener((int)NActionEnum.NSyncTileAllUpdateBack,OnNSyncTileAllUpdateBack);
|
||||
|
||||
//连接
|
||||
await base.OnInit();
|
||||
@@ -49,6 +61,8 @@ namespace AppGame.Systems
|
||||
{
|
||||
ClientId = ClientId
|
||||
});
|
||||
//向服务器索要全量信息
|
||||
Send((int)NActionEnum.NSyncTileAllUpdate);
|
||||
}
|
||||
|
||||
private void OnNSyncStateDataUpdate(byte[] data)
|
||||
@@ -56,5 +70,26 @@ namespace AppGame.Systems
|
||||
var info = JNStateItemData.Parser.ParseFrom(data);
|
||||
App.Game.AddState(info);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 状态同步全量回调
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
private void OnNSyncTileAllUpdateBack(byte[] data)
|
||||
{
|
||||
|
||||
var allData = JNStateTileAllData.Parser.ParseFrom(data);
|
||||
|
||||
App.Game.ClearTileState(allData.TId);
|
||||
|
||||
//生效全局回调
|
||||
allData.Data.Data.ForEach(child =>
|
||||
{
|
||||
App.Game.AddState(child);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -2,9 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using AppGame.Sync;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DotRecast.Core.Collections;
|
||||
using Google.Protobuf;
|
||||
using JNGame.Network;
|
||||
using JNGame.Sync.System;
|
||||
using JNGame.Sync.System.Data;
|
||||
using Plugins.JNGame.Network;
|
||||
using Plugins.JNGame.Network.Action;
|
||||
|
||||
@@ -27,6 +31,8 @@ namespace AppGame.Systems
|
||||
//监听服务端事件
|
||||
AddListener((int)NActionEnum.NSyncFrameInput,OnNSyncFrameInput);
|
||||
AddListener((int)NActionEnum.NSyncTileInput,OnNSyncTileInput);
|
||||
AddListener((int)NActionEnum.NSyncTileAllUpdate,OnNSyncTileAllUpdate);
|
||||
AddListener((int)NActionEnum.NSyncTileGetTileInfo,OnNSyncTileGetTileInfo);
|
||||
AddListener((int)GActionEnum.BindClientID,OnBindClientID);
|
||||
|
||||
//连接
|
||||
@@ -87,6 +93,72 @@ namespace AppGame.Systems
|
||||
});
|
||||
App.Game.AddTileInput(inputs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回全量信息
|
||||
/// </summary>
|
||||
private void OnNSyncTileAllUpdate(JNServerParam args)
|
||||
{
|
||||
|
||||
if (App.Game.Server is not JNGTileServerSystem tileServer) return;
|
||||
|
||||
var allData = new JNStateTileAllData();
|
||||
|
||||
allData.TId = tileServer.TID;
|
||||
allData.Data = new JNStateAllData();
|
||||
|
||||
tileServer.GetSystems<ISTileDataSystem>().ForEach(data =>
|
||||
{
|
||||
|
||||
var item = new JNStateItemData();
|
||||
allData.Data.Data.Add(item);
|
||||
|
||||
item.NetID = ((ISStateDataSystem)data).NetID;
|
||||
data.GetHostDataBytes().ForEach(keyValue =>
|
||||
{
|
||||
item.Messages[keyValue.Key] = new JNStateData() { Data = ByteString.CopyFrom(keyValue.Value) };
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Send(args.Client, (int)NActionEnum.NSyncTileAllUpdateBack,allData);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定区块的全量信息
|
||||
/// </summary>
|
||||
private void OnNSyncTileGetTileInfo(JNServerParam args)
|
||||
{
|
||||
|
||||
if (App.Game.Server is not JNGTileServerSystem tileServer) return;
|
||||
|
||||
var tileSpecify = NSyncTileGetTileInfoRequest.Parser.ParseFrom(args.Message);
|
||||
|
||||
var allData = new JNStateTileAllData();
|
||||
|
||||
allData.TId = tileServer.TID;
|
||||
allData.Data = new JNStateAllData();
|
||||
|
||||
tileServer.GetSystems<ISTileDataSystem>().ForEach(data =>
|
||||
{
|
||||
|
||||
var item = new JNStateItemData();
|
||||
allData.Data.Data.Add(item);
|
||||
|
||||
item.NetID = ((ISStateDataSystem)data).NetID;
|
||||
data.GetTileDataBytes(tileSpecify.TId).ForEach(keyValue =>
|
||||
{
|
||||
item.Messages[keyValue.Key] = new JNStateData() { Data = ByteString.CopyFrom(keyValue.Value) };
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//返回消息
|
||||
SendCallback(args.Client,args.MessageID,allData);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DotRecast.Core.Collections;
|
||||
using JNGame.Network;
|
||||
using Plugins.JNGame.Network;
|
||||
using Plugins.JNGame.Network.Action;
|
||||
@@ -12,10 +13,20 @@ namespace AppGame.Systems
|
||||
|
||||
private string _point;
|
||||
|
||||
/// <summary>
|
||||
/// 区块服务器标识
|
||||
/// </summary>
|
||||
private string _tileServer;
|
||||
public string TileServer => _tileServer;
|
||||
|
||||
public void SetPoint(string point)
|
||||
{
|
||||
_point = point;
|
||||
}
|
||||
public void SetTileServer(string tileServer)
|
||||
{
|
||||
_tileServer = tileServer;
|
||||
}
|
||||
|
||||
protected override async UniTask<string> GetEndPoint()
|
||||
{
|
||||
@@ -28,11 +39,19 @@ namespace AppGame.Systems
|
||||
|
||||
//监听服务端事件
|
||||
AddListener((int)NActionEnum.NSyncStateDataUpdate,OnNSyncStateDataUpdate);
|
||||
AddListener((int)NActionEnum.LocalClientConnect,OnClientConnect);
|
||||
AddListener((int)NActionEnum.NSyncTileAllUpdateBack,OnNSyncTileAllUpdateBack);
|
||||
|
||||
//连接
|
||||
await base.OnInit();
|
||||
|
||||
}
|
||||
|
||||
private void OnClientConnect(byte[] obj)
|
||||
{
|
||||
//向服务器索要全量信息
|
||||
Send((int)NActionEnum.NSyncTileAllUpdate);
|
||||
}
|
||||
|
||||
private void OnNSyncStateDataUpdate(byte[] data)
|
||||
{
|
||||
@@ -40,5 +59,41 @@ namespace AppGame.Systems
|
||||
App.Game.AddState(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态同步全量回调
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
private void OnNSyncTileAllUpdateBack(byte[] data)
|
||||
{
|
||||
|
||||
var allData = JNStateTileAllData.Parser.ParseFrom(data);
|
||||
|
||||
App.Game.ClearTileState(allData.TId);
|
||||
|
||||
//生效全局状态
|
||||
allData.Data.Data.ForEach(child =>
|
||||
{
|
||||
App.Game.AddState(child);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 区块全量更新指定区块
|
||||
/// </summary>
|
||||
public async Task<JNStateTileAllData> NSyncTileGetTileInfo(int index)
|
||||
{
|
||||
var data = new NSyncTileGetTileInfoRequest()
|
||||
{
|
||||
TId = index
|
||||
};
|
||||
var result = await SendCallback((int)NActionEnum.NSyncTileGetTileInfo, data);
|
||||
|
||||
if (result is null) return null;
|
||||
|
||||
return JNStateTileAllData.Parser.ParseFrom(result);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -12,15 +12,14 @@ namespace AppGame.Systems
|
||||
public class JNGGame : SystemBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 同步组
|
||||
/// </summary>
|
||||
private List<JNSyncDefaultService> syncs = new();
|
||||
|
||||
private JNSyncDefaultService client;
|
||||
private List<JNSStateServerService> servers = new();
|
||||
public JNSyncDefaultService Client => client;
|
||||
|
||||
private JNSStateServerService server;
|
||||
public JNSStateServerService Server => server;
|
||||
|
||||
public bool IsStartGame => syncs.Count > 0 && syncs[0].IsStartGame;
|
||||
public bool IsStartClient => client is not null && client.IsStartGame;
|
||||
public bool IsStartServer => server is not null && server.IsStartGame;
|
||||
|
||||
public override async Task OnInit()
|
||||
{
|
||||
@@ -28,23 +27,19 @@ namespace AppGame.Systems
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行一个同步类
|
||||
/// 运行同步类
|
||||
/// </summary>
|
||||
public T Start<T>() where T : JNSyncDefaultService,new()
|
||||
public T StartClient<T>() where T : JNSyncDefaultService,new()
|
||||
{
|
||||
syncs.Add(new T());
|
||||
syncs[^1].Initialize();
|
||||
|
||||
if (syncs[^1] is JNSStateServerService server)
|
||||
{
|
||||
servers.Add(server);
|
||||
}
|
||||
else
|
||||
{
|
||||
client = syncs[^1];
|
||||
}
|
||||
|
||||
return syncs[^1] as T;
|
||||
client = new T();
|
||||
client.Initialize();
|
||||
return client as T;
|
||||
}
|
||||
public T StartServer<T>() where T : JNSStateServerService,new()
|
||||
{
|
||||
server = new T();
|
||||
server.Initialize();
|
||||
return server as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,10 +47,8 @@ namespace AppGame.Systems
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
foreach (var sync in syncs)
|
||||
{
|
||||
sync.Execute();
|
||||
}
|
||||
client?.Execute();
|
||||
server?.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,39 +74,45 @@ namespace AppGame.Systems
|
||||
return (T)client;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 接收输入数据
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
public void AddInput(JNFrameInfo frame)
|
||||
{
|
||||
foreach (var sync in syncs)
|
||||
{
|
||||
(sync as JNGFrameSystem)?.AddFrame(frame);
|
||||
(sync as JNGStateServerSystem)?.AddInput(frame);
|
||||
}
|
||||
(client as JNGFrameSystem)?.AddFrame(frame);
|
||||
(server as JNGStateServerSystem)?.AddInput(frame);
|
||||
}
|
||||
|
||||
public void AddTileInput(JNStateTileInputs frame)
|
||||
{
|
||||
foreach (var sync in syncs)
|
||||
if (server is JNGTileServerSystem system)
|
||||
{
|
||||
if (sync is JNGTileServerSystem system)
|
||||
if (system.TID == frame.TId || frame.TId == 0)
|
||||
{
|
||||
if (system.TID == frame.TId || frame.TId == 0)
|
||||
{
|
||||
system.AddInput(frame);
|
||||
}
|
||||
system.AddInput(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 清除指定区域的状态
|
||||
/// </summary>
|
||||
public void ClearTileState(int index)
|
||||
{
|
||||
client?.GetSystems<ISTileDataSystem>().ForEach(child =>
|
||||
{
|
||||
child.ClearTileData(index);
|
||||
});
|
||||
server?.GetSystems<ISTileDataSystem>().ForEach(child =>
|
||||
{
|
||||
child.ClearTileData(index);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 接收状态数据
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
public void AddState(JNStateItemData frame)
|
||||
{
|
||||
var message = new Dictionary<long, byte[]>();
|
||||
@@ -121,14 +120,17 @@ namespace AppGame.Systems
|
||||
{
|
||||
message.Add(data.Key,data.Value.Data.ToByteArray());
|
||||
}
|
||||
foreach (var sync in syncs)
|
||||
|
||||
client?.GetSystems<ISStateDataSystem>().ForEach(child =>
|
||||
{
|
||||
sync.GetSystems<ISStateDataSystem>().ForEach(child =>
|
||||
{
|
||||
if (child.NetID != frame.NetID) return;
|
||||
child.OnInsertUBytes(message);
|
||||
});
|
||||
}
|
||||
if (child.NetID != frame.NetID) return;
|
||||
child.OnInsertUBytes(message);
|
||||
});
|
||||
server?.GetSystems<ISStateDataSystem>().ForEach(child =>
|
||||
{
|
||||
if (child.NetID != frame.NetID) return;
|
||||
child.OnInsertUBytes(message);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,12 +1,39 @@
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using AppGame;
|
||||
using AppGame.Sync;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Plugins.JNGame.Network;
|
||||
using Plugins.JNGame.Network.Action;
|
||||
|
||||
public class JNGSocket : JNSocket
|
||||
{
|
||||
public override async Task OnInit()
|
||||
{
|
||||
|
||||
AddListener((int)NActionEnum.ServerClientDisconnect,OnServerClientDisconnect);
|
||||
|
||||
await base.OnInit();
|
||||
}
|
||||
|
||||
protected override async UniTask<string> GetUrl()
|
||||
{
|
||||
await UniTask.NextFrame();
|
||||
return "ws://127.0.0.1:8080/websocket";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 有客户端断开服务器连接
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
private void OnServerClientDisconnect(byte[] data)
|
||||
{
|
||||
|
||||
var disconnect = JNClientDisconnect.Parser.ParseFrom(data);
|
||||
|
||||
//断开Tile服务器连接
|
||||
(App.Game.Client as JNGTileClientSystem)?.RemoveSocket(disconnect.ClientId);
|
||||
(App.Game.Server as JNGTileServerSystem)?.RemoveSocket(disconnect.ClientId);
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user