using System.Text.Json; using AutoMapper; using Common.Utils; using CommonService; using FishGameService.GameSend; using FishGameService.Hubs; using FishGameService.Services; using Microsoft.Extensions.Logging; using RedisService.Param; using StackExchange.Redis; namespace Service.Fishing { public class FishingSendService : IFishingSendService { public readonly IHubSend _hubSend; public readonly IMapper _mapper; private readonly ISubscriber _subscriber; private readonly ILogger _logger; public FishingSendService(IHubSend hubSend, IMapper mapper, IConnectionMultiplexer redis, ILogger logger) { _hubSend = hubSend; _mapper = mapper; _subscriber = redis.GetSubscriber(); _logger = logger; } public void BindSuccess(string cid) { _hubSend.SendUserCId(cid, "BindSuccess", "绑定成功"); } public void ErrorMessage(string cid, int code, string msg) { _hubSend.SendUserCId(cid, "Exception", new { code, message = msg }); } public void KillNotice(string FishId, string userName, long gold = 0) { var msg = JsonSerializer.Serialize(new { Target = "All", Event = "KillNotice", Data = new { FishId, userName, gold } }); _subscriber.Publish("fishgame:global", msg); } public void KillNotice(string FishId, string userName, long gold, string fishName, long multiplier, string gameTypeName, string roomName, long timestamp) { var text = $"恭喜玩家{userName}在{gameTypeName}{roomName}捕获 {fishName}*{multiplier}炮倍 {gold}大奖"; var data = new { Text = text, UserName = userName, GameTypeName = gameTypeName, RoomName = roomName, FishName = fishName, Multiple = multiplier, Gold = gold, Time = timestamp, FishId }; var msg = JsonSerializer.Serialize(new { Target = "All", Event = "KillNotice", Data = data }); _subscriber.Publish("fishgame:global", msg); } public async Task SendRoomInfo(string cid, RedisGameRoom roomInfo, List activeFishes = null, bool sendInitialFish = true) { object payload; List initialFishPayload = null; if (activeFishes != null && activeFishes.Count > 0) { initialFishPayload = BuildActiveFishPayload(activeFishes); payload = new { roomInfo.RoomId, roomInfo.RoomName, roomInfo.GameType, roomInfo.RoomLevel, roomInfo.RoomPartition, roomInfo.RoomNumber, roomInfo.MaxPlayerCount, roomInfo.CurrentOnlinePlayerCount, roomInfo.RobotCount, roomInfo.Players, roomInfo.CreateUserId, roomInfo.SystemFactor, activeFishes = initialFishPayload }; } else { payload = roomInfo; } await _hubSend.SendUserCIdAsync(cid, "RoomInfo", payload); if (sendInitialFish && initialFishPayload != null && initialFishPayload.Count > 0) { _ = SendInitialFishAfterSceneReady(cid, roomInfo.RoomId, initialFishPayload); } } /// /// 补发场上活鱼(挂起恢复 / 重连专用独立协议): /// 发到独立事件 OnFishResync,客户端按"全量替换"处理(先清本地旧鱼,再按本列表重建)。 /// fish 列表口径与 RoomInfo.activeFishes / 首屏 init 完全一致,都带服务端原始的 createdAt。 /// public async Task FishResyncNoticeAsync(string cid, string roomId, List activeFishes) { var fish = activeFishes == null || activeFishes.Count == 0 ? new List() : BuildActiveFishPayload(activeFishes); var now = DateTimeOffset.UtcNow; await _hubSend.SendUserCIdAsync(cid, "OnFishResync", new { roomId, timestamp = now.ToUnixTimeMilliseconds(), serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), count = fish.Count, fish }); } /// /// 把场上活鱼转成下发 payload:单体鱼逐条、鱼阵按 FishArrayNumber 分组(鱼阵用首条 fishId 作主键)。 /// createdAt / expireAt 一律取服务端原始值,客户端据此按 (当前时间 - createdAt) / 路径总时长 推算位置。 /// private static List BuildActiveFishPayload(List activeFishes) { var now = DateTimeOffset.UtcNow; return activeFishes .Where(f => f != null && string.IsNullOrWhiteSpace(f.FishArrayNumber)) .Select(f => (object)new { fishId = f.FishId, fishIds = new[] { f.FishId }, fishNumber = f.FishNumber, routeId = f.ClientRouteId, 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) }) .Concat( activeFishes .Where(f => f != null && !string.IsNullOrWhiteSpace(f.FishArrayNumber)) .GroupBy(f => f.FishArrayNumber) .Select(group => { var first = group.First(); return (object)new { fishId = first.FishId, fishIds = group.Select(f => f.FishId).ToList(), fishNumber = string.Empty, routeId = first.ClientRouteId, fishArrayNumber = first.FishArrayNumber, createdAt = new DateTimeOffset(first.CreatedAt).ToUnixTimeMilliseconds(), createdTime = first.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss.fff"), expireAt = new DateTimeOffset(group.Min(f => f.ExpireAt)).ToUnixTimeMilliseconds(), expireTime = group.Min(f => f.ExpireAt).ToString("yyyy-MM-dd HH:mm:ss.fff"), remainingSeconds = (int)Math.Max(0, (new DateTimeOffset(group.Min(f => f.ExpireAt)) - now).TotalSeconds) }; })) .ToList(); } private async Task SendInitialFishAfterSceneReady(string cid, string roomId, List initialFishPayload) { try { await Task.Delay(300); var syncNow = DateTimeOffset.UtcNow; await _hubSend.SendUserCIdAsync(cid, "OnFishSpawn", new { type = "init", timestamp = syncNow.ToUnixTimeMilliseconds(), serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), fish = initialFishPayload }); } catch (Exception ex) { _logger.LogWarning(ex, "[Socket][FishSpawnData] 首屏鱼延迟补发失败 roomId={RoomId} targetCid={Cid} fishCount={FishCount}", roomId, cid, initialFishPayload.Count); } } public void RoomKillNotice(string roomId, string fishNumber, string userId, long gold, string fishId, long timespan, double beishu = 1,long skillsGold=0,int fishScore=1, object param = null, object drop = null, long gainExp = 0, long exp = 0, int level = 0, long expMax = 0) { var killtime=DateTime.Now.ToTimeStamp(); _hubSend.SendGroup(roomId, "RoomKillNotice", new { fishNumber, userId, gold, fishId, timespan, killtime, msg = $"时间:{killtime - timespan}秒", beishu, skillsGold, fishScore, param, drop, gainExp, exp, level, expMax }); } public void RoomDisconnectNotice(string roomId, string userId) { _hubSend.SendGroup(roomId, "RoomDisconnectNotice", userId); } public void RoomJoinNotice(string roomId, RedisGamePlayer player) { _hubSend.SendGroup(roomId, "RoomJoinNotice", player); } public void ChangeGunMultipleNotice(string roomId, string userId, int multiple) { _hubSend.SendGroup(roomId, "ChangeGunMultipleNotice", new { userId, multiple }); } public void RoomFireNotice(string roomId, string userId, List bulletIds, long gold, int count, object extra = null, bool isBot = false) { _hubSend.SendGroup(roomId, "RoomFireNotice", new { userId, bulletIds, gold, count, extra, isBot }); } public Task RoomFireNoticeAsync(string roomId, string callerCid, string userId, List bulletIds, long gold, int count, object extra = null, bool isBot = false) { var payload = new { userId, bulletIds, gold, count, extra, isBot }; return Task.WhenAll( _hubSend.SendUserCIdAsync(callerCid, "RoomFireNotice", payload), _hubSend.GroupExceptAsync(callerCid, roomId, "RoomFireNotice", payload)); } public void SyncPlayerGold(string cid, long gold) { _hubSend.SendUserCId(cid, "SyncPlayerGold", gold); } public void SendPlayerInfo(string cid, RedisGamePlayer player) { _hubSend.SendUserCId(cid, "SendPlayerInfoResult", player?.Gold); } public void RoomLeaveNotice(string roomId, string userId) { _hubSend.SendGroup(roomId, "RoomLeaveNotice", userId); } public void BotLeaveRoomNotice(string roomId, string userId) { _hubSend.SendGroup(roomId, "BotLeaveRoomNotice", new { userId }); } public void ChangeControllerUser(string roomId, string userId) { _hubSend.SendGroup(roomId, "ChangeControllerUser", userId); } public void HostDisconnectNotice(string roomId) { _hubSend.SendGroup(roomId, "HostDisconnect", null); } public void SyncGameData(string roomId, object data) { _hubSend.SendGroup(roomId, "SyncGameData", data); } public void UsePropNotice(string roomId, string userId, string propNumber) { _hubSend.SendGroup(roomId, "UsePropNotice", new { userId, propNumber }); } public void TorpedoResultNotice(string roomId, string userId, string propNumber, long gold, string color) { _hubSend.SendGroup(roomId, "TorpedoResultNotice", new { userId, propNumber, gold, color }); } public void PlayerAfkNotice(string roomId, string userId) { _hubSend.SendGroup(roomId, "PlayerAfkNotice", new { userId }); } public void PlayerCancelAfkNotice(string roomId, string userId) { _hubSend.SendGroup(roomId, "PlayerCancelAfkNotice", new { userId }); } } }