434 lines
16 KiB
C#
434 lines
16 KiB
C#
using CommonDomain;
|
|
using FishGameService.GameSend;
|
|
using FishGameService.GameService;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.Extensions.Logging;
|
|
using RedisService;
|
|
using RedisService.Param;
|
|
using UserService.Hubs;
|
|
using UserService.User;
|
|
|
|
namespace FishGameService.Hubs
|
|
{
|
|
public class FishGameHub : Hub
|
|
{
|
|
private readonly IFishingGameService _fishingService;
|
|
private readonly IFishingSendService _send;
|
|
private readonly IRedisService _redisService;
|
|
private readonly IUserService _userService;
|
|
private readonly ILogger<FishGameHub> _logger;
|
|
private readonly IHubContext<LoginHub> _hubLoginContext;
|
|
|
|
|
|
public FishGameHub(IFishingGameService fishingService, IFishingSendService send, IRedisService redisService, IUserService userService, ILogger<FishGameHub> logger, IHubContext<LoginHub> hubLoginContext)
|
|
{
|
|
_fishingService = fishingService;
|
|
_send = send;
|
|
_redisService = redisService;
|
|
_userService = userService;
|
|
_logger = logger;
|
|
_hubLoginContext = hubLoginContext;
|
|
}
|
|
|
|
public override async Task OnConnectedAsync()
|
|
{
|
|
|
|
try
|
|
{
|
|
await base.OnConnectedAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "FishHub建立连接异常 cid={Cid}", Context.ConnectionId);
|
|
}
|
|
}
|
|
|
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
|
{
|
|
var cid = Context.ConnectionId;
|
|
var userId = _redisService.GetUserId(cid);
|
|
if (exception != null)
|
|
{
|
|
_logger.LogWarning(exception, "FishHub异常断开 cid={Cid} userId={UserId} reason={Reason}",
|
|
cid, userId, exception.Message);
|
|
}
|
|
try
|
|
{
|
|
await _fishingService.HandleDisconnect(Context.ConnectionId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "FishHub断线处理失败 cid={Cid} userId={UserId}", cid, userId);
|
|
}
|
|
|
|
await base.OnDisconnectedAsync(exception);
|
|
}
|
|
|
|
public async Task Reconnect(string userId)
|
|
{
|
|
try
|
|
{
|
|
await ReconnectInternal(userId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "FishHub重连异常 cid={Cid} userId={UserId}", Context.ConnectionId, userId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task ReconnectInternal(string userId)
|
|
{
|
|
var cid = Context.ConnectionId;
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
{
|
|
_send.ErrorMessage(cid, ErrorCodes.用户不存在, "缺少 userId,无法重连");
|
|
return;
|
|
}
|
|
|
|
var userRedis = _redisService.GetRedisUserCId(userId);
|
|
if (userRedis == null)
|
|
{
|
|
userRedis = new RedisUserCid { UserId = userId, CId = cid };
|
|
_redisService.SetRedisUserCId(userRedis);
|
|
_redisService.SetUserId(cid, userId);
|
|
_send.BindSuccess(cid);
|
|
|
|
return;
|
|
}
|
|
|
|
var oldCid = userRedis.CId;
|
|
if (!string.IsNullOrWhiteSpace(oldCid) && oldCid != cid)
|
|
{
|
|
|
|
if (!string.IsNullOrWhiteSpace(userRedis.RoomId))
|
|
{
|
|
|
|
try { _send.RoomLeaveNotice(userRedis.RoomId, userId); } catch (Exception ex) { _logger.LogError(ex, "发送RoomLeaveNotice失败 userId={UserId} roomId={RoomId}", userId, userRedis.RoomId); }
|
|
}
|
|
|
|
_redisService.RemoveUserId(oldCid);
|
|
try
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(userRedis.RoomId))
|
|
await Groups.RemoveFromGroupAsync(oldCid, userRedis.RoomId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "FishHub重连移除旧连接房间组失败 oldCid={OldCid} roomId={RoomId}", oldCid, userRedis.RoomId);
|
|
}
|
|
}
|
|
|
|
userRedis.CId = cid;
|
|
_redisService.SetRedisUserCId(userRedis);
|
|
_redisService.SetUserId(cid, userId);
|
|
|
|
if (!string.IsNullOrWhiteSpace(userRedis.RoomId))
|
|
{
|
|
var room = _redisService.GetGameRoom(userRedis.RoomId);
|
|
if (room != null)
|
|
{
|
|
var player = room.Players.FirstOrDefault(p => p.UserId == userId);
|
|
if (player != null)
|
|
{
|
|
await _fishingService.CancelPendingDisconnect(userId);
|
|
player.CId = cid;
|
|
player.State = 0;
|
|
|
|
bool ownerRestored = false;
|
|
if (!string.IsNullOrWhiteSpace(userId) && room.CreateUserId != userId)
|
|
{
|
|
var currentOwner = room.Players.FirstOrDefault(p => p.UserId == room.CreateUserId && !p.IsBot);
|
|
if (string.IsNullOrWhiteSpace(room.CreateUserId)
|
|
|| currentOwner == null
|
|
|| currentOwner.State != 0)
|
|
{
|
|
room.CreateUserId = userId;
|
|
ownerRestored = true;
|
|
}
|
|
}
|
|
|
|
_redisService.SetGameRoom(room);
|
|
|
|
await Groups.AddToGroupAsync(cid, room.RoomId);
|
|
|
|
var activeFishes = _fishingService.GetActiveFishes(room.RoomId);
|
|
await _send.SendRoomInfo(cid, room, activeFishes);
|
|
_send.RoomJoinNotice(room.RoomId, player);
|
|
await Clients.Caller.SendAsync("OnPlayerInfo", new
|
|
{
|
|
userId = player.UserId,
|
|
userName = player.UserName,
|
|
gold = player.Gold,
|
|
diamond = player.Diamond,
|
|
viplvl = player.VipLevel,
|
|
cannonMultiple = player.CannonMultiple,
|
|
cannonId = player.CannonId,
|
|
wingsId = player.WingsId,
|
|
position = player.Position
|
|
});
|
|
await Clients.Group(room.RoomId).SendAsync("OnSeatChange", new
|
|
{
|
|
type = "join",
|
|
playerId = player.Position + 1,
|
|
position = player.Position,
|
|
playerName = player.UserName,
|
|
nickname = player.UserName,
|
|
gold = player.Gold,
|
|
diamond = player.Diamond,
|
|
vipLevel = player.VipLevel,
|
|
turretId = 1,
|
|
turretRate = 1,
|
|
turretLv = 1,
|
|
headId = 1,
|
|
roomRate = 1,
|
|
bulletPower = 1
|
|
});
|
|
if (ownerRestored)
|
|
_send.ChangeControllerUser(room.RoomId, room.CreateUserId);
|
|
|
|
await _fishingService.SendRoomChange(room);
|
|
|
|
await SendActiveFishSync(room.RoomId);
|
|
|
|
|
|
|
|
try
|
|
{
|
|
var user = await _userService.GetUser(userId);
|
|
if (user != null)
|
|
{
|
|
user.Static.RoomNumber = room.RoomId;
|
|
user.Static.CurrentGameType = (int)room.GameType;
|
|
await _userService.UpdateUser(user);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "FishHub重连后更新用户状态失败 userId={UserId} roomId={RoomId}", userId, room.RoomId);
|
|
}
|
|
|
|
try
|
|
{
|
|
await SendActiveFishSync(room.RoomId);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "FishHub重连同服务端步活跃鱼失败 userId={UserId} roomId={RoomId}", userId, room.RoomId);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
_send.BindSuccess(cid);
|
|
|
|
}
|
|
|
|
public async Task JoinRoom(string roomId, string userId, int posituin)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
try
|
|
{
|
|
var boundUserId = _redisService.GetUserId(cid);
|
|
if (!string.IsNullOrWhiteSpace(boundUserId) && boundUserId != userId)
|
|
{
|
|
|
|
await Clients.Caller.SendAsync("KickToMainPage", new { userId });
|
|
return;
|
|
}
|
|
|
|
await _fishingService.JoinRoom(cid, roomId, userId, posituin);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "FishHub加入房间异常 cid={Cid} roomId={RoomId} userId={UserId} position={Position}", cid, roomId, userId, posituin);
|
|
_send.ErrorMessage(cid, ErrorCodes.未知错误, $"加入房间失败: {ex.Message}");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public async Task GetRoomInfo(string roomId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
if (!IsConnectionInRoom(cid, roomId))
|
|
return;
|
|
var room = _redisService.GetGameRoom(roomId);
|
|
|
|
await Clients.Caller.SendAsync("GetRoomInfoResult", room);
|
|
|
|
}
|
|
|
|
public async Task SyncGameData(string roomId, object data)
|
|
{
|
|
_send.SyncGameData(roomId, data);
|
|
}
|
|
|
|
private bool IsConnectionInRoom(string cid, string roomId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(cid) || string.IsNullOrWhiteSpace(roomId))
|
|
return false;
|
|
|
|
var userId = _redisService.GetUserId(cid);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return false;
|
|
|
|
var userRedis = _redisService.GetRedisUserCId(userId);
|
|
return userRedis != null && userRedis.CId == cid && userRedis.RoomId == roomId;
|
|
}
|
|
public async Task<int> UseProp(string roomId, string userId, string propNumber, int diamond)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
|
|
return await _fishingService.UseProp(cid, roomId, userId, propNumber, diamond);
|
|
}
|
|
|
|
public async Task<long> UseTorpedo(string roomId, string userId, string propNumber)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
return await _fishingService.UseTorpedo(cid, roomId, userId, propNumber);
|
|
}
|
|
|
|
public async Task<object> FireCannon(string roomId, string userId, long gold, int count, long timestamp, string sign, object extra = null)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
var result = extra is string extraSign && (sign == null || sign.Length != 8)
|
|
? await _fishingService.FireCannon(cid, roomId, userId, gold, count, timestamp, extraSign, null)
|
|
: await _fishingService.FireCannon(cid, roomId, userId, gold, count, timestamp, sign, extra);
|
|
return new
|
|
{
|
|
bullets = result.bullets.Select(bulletId => new { bulletId, gold }).ToList(),
|
|
gold = result.gold,
|
|
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
|
};
|
|
}
|
|
|
|
public async Task<object> KillFish(string roomId, string userId, string fishNumber, string fihsId, long gold, long timespan, string bulletId, long timestamp, string sign)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
|
|
var currentGold = await _fishingService.KillFish(cid, roomId, userId, fishNumber, fihsId, gold, timespan, bulletId, timestamp, sign);
|
|
return new { gold = currentGold, timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() };
|
|
}
|
|
|
|
public async Task LeaveRoom(string roomId, string userId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
try
|
|
{
|
|
|
|
|
|
await _fishingService.LeaveRoom(cid, roomId, userId);
|
|
|
|
if (IsConnectionInRoom(cid, roomId))
|
|
{
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "FishHub离开房间异常 cid={Cid} roomId={RoomId} userId={UserId}", cid, roomId, userId);
|
|
throw;
|
|
}
|
|
|
|
}
|
|
|
|
public async Task ChangeGunMultiple(string roomId, string userId, int multiple)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
|
|
await _fishingService.ChangeGunMultiple(cid, roomId, userId, multiple);
|
|
}
|
|
|
|
|
|
public async Task GetPlayerInfo(string roomId, string userId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
await _fishingService.GetPlayerInfo(cid, roomId, userId);
|
|
}
|
|
|
|
public async Task SetPlayerAfk(string roomId, string userId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
await _fishingService.SetPlayerAfk(cid, roomId, userId);
|
|
}
|
|
|
|
public async Task CancelPlayerAfk(string roomId, string userId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
await _fishingService.CancelPlayerAfk(cid, roomId, userId);
|
|
|
|
try
|
|
{
|
|
var room = _redisService.GetGameRoom(roomId);
|
|
if (room != null)
|
|
{
|
|
var activeFishes = _fishingService.GetActiveFishes(roomId);
|
|
await _send.SendRoomInfo(cid, room, activeFishes);
|
|
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "CancelPlayerAfk 同步房间信息失败 userId={UserId} roomId={RoomId}", userId, roomId);
|
|
}
|
|
}
|
|
|
|
private async Task SendActiveFishSync(string roomId)
|
|
{
|
|
var activeFishes = _fishingService.GetActiveFishes(roomId);
|
|
if (activeFishes == null || activeFishes.Count == 0)
|
|
return;
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
var syncFish = activeFishes.Select(f => new
|
|
{
|
|
fishId = f.FishId,
|
|
fishIds = new[] { f.FishId },
|
|
fishNumber = string.IsNullOrWhiteSpace(f.FishArrayNumber) ? f.FishNumber : string.Empty,
|
|
routeId = f.ClientRouteId,
|
|
fishArrayNumber = f.FishArrayNumber ?? string.Empty,
|
|
createdAt = new DateTimeOffset(f.CreatedAt).ToUnixTimeMilliseconds(),
|
|
createdTime = f.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss.fff"),
|
|
expireAt = new DateTimeOffset(f.ExpireAt).ToUnixTimeMilliseconds(),
|
|
expireTime = f.ExpireAt.ToString("yyyy-MM-dd HH:mm:ss.fff"),
|
|
remainingSeconds = (int)Math.Max(0, (new DateTimeOffset(f.ExpireAt) - now).TotalSeconds)
|
|
}).ToList();
|
|
|
|
var payload = new
|
|
{
|
|
type = "sync",
|
|
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
|
serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
|
|
fish = syncFish
|
|
};
|
|
|
|
|
|
await Clients.Caller.SendAsync("OnFishSpawn", payload);
|
|
}
|
|
|
|
|
|
public async Task<List<string>> BotFireCannon(string roomId, string botUserId, long gold, int count)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
return await _fishingService.BotFireCannon(cid, roomId, botUserId, gold, count);
|
|
}
|
|
|
|
public async Task BotKillFish(string roomId, string botUserId, string fishNumber, string fishId, long gold, string bulletId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
await _fishingService.BotKillFish(cid, roomId, botUserId, fishNumber, fishId, gold, bulletId);
|
|
}
|
|
|
|
}
|
|
}
|