266 lines
11 KiB
C#
266 lines
11 KiB
C#
using Assets.MyGame.Scripts.Net.WebResponses;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
/// <summary>
|
||
/// 其他玩家爆奖(not-self)流程管理器。
|
||
///
|
||
/// 流程:
|
||
/// 1. 服务端下发 RoomKillNotice(鱼死亡),客户端在 Ws_DieFish 判断 killFish.userId != 本地 user
|
||
/// 且 effect_DieAward 上挂的是 DieEffect_TSY(Boss爆奖模式)→ 调用 <see cref="ShowInProgress"/>:
|
||
/// a. 不克隆 effect_DieAward,只读取预制体上的 BJEndUI(<see cref="BJEventBase.bjEndUI"/>);
|
||
/// b. 把 BJEndUI 实例化到 killer 座位的 <see cref="SitPosData.obj_BaoJiangZhongPanelPosition"/>;
|
||
/// c. <see cref="BJEndUI.ShowInProgress"/> 关闭 tMP_beishu / 激活 img_BaoJiangZhong;
|
||
/// d. 标记 <see cref="SitPosData.IsBaoJiangingActive"/> = true。
|
||
/// 2. 等待服务端帧同步 <see cref="FrameDataType.baoJiangEnd"/>(爆奖玩家爆完奖励):
|
||
/// 在 <see cref="OnBaoJiangEnd"/> 中取出对应 pending,调用 <see cref="BJEndUI.Finalize"/> 显示最终 BJEndUI,
|
||
/// 执行 onScoreAdd 加分,清除 IsBaoJiangingActive。
|
||
///
|
||
/// 机器人杀 Boss 特殊处理:
|
||
/// 机器人不会主动发 baoJiangEnd(机器人客户端无人触发)。
|
||
/// 由房主客户端在进入 not-self 在途状态后,延迟 5~10 秒代发 baoJiangEnd 帧同步给服务端,
|
||
/// 服务端广播给所有玩家 → 各客户端走 OnBaoJiangEnd 完成最终加分。
|
||
///
|
||
/// 帧同步数据由 <see cref="FrameData_BaoJiangEnd"/> 携带(userId/fishId/beishu/score)。
|
||
/// pending 用 "userId|fishId" 做 key,同一玩家同帧杀多条特殊鱼也能区分。
|
||
/// </summary>
|
||
public static class BaoJiangNotSelfHandler
|
||
{
|
||
private class PendingEntry
|
||
{
|
||
public BJEndUI endUI;
|
||
public SitPosData killerSit;
|
||
public Action<int> onScoreAdd;
|
||
public long gold;
|
||
public int beishu;
|
||
public string userId;
|
||
public string fishId;
|
||
public bool isBot;
|
||
}
|
||
|
||
// key: userId|fishId
|
||
private static readonly Dictionary<string, PendingEntry> _pending = new Dictionary<string, PendingEntry>();
|
||
|
||
/// <summary>机器人爆奖模拟延迟下限(秒)</summary>
|
||
public const float BotSendDelayMin = 5f;
|
||
/// <summary>机器人爆奖模拟延迟上限(秒)</summary>
|
||
public const float BotSendDelayMax = 10f;
|
||
|
||
/// <summary>
|
||
/// 其他玩家爆奖进入"爆奖中"状态。
|
||
/// 不克隆 effect_DieAward 预制体,只读取其上的 BJEndUI 引用并实例化到座位。
|
||
/// </summary>
|
||
/// <param name="killerSit">击杀者座位(用于显示位置 + IsBaoJiangingActive 标记)</param>
|
||
/// <param name="effect_DieAwardPrefab">鱼身上配置的 effect_DieAward 预制体(不克隆,仅读取 BJEndUI)</param>
|
||
/// <param name="fishId">被击杀的鱼 ID(用于帧同步匹配)</param>
|
||
/// <param name="killerUserId">击杀者 userId</param>
|
||
/// <param name="beishu">倍数(用于 Finalize 显示)</param>
|
||
/// <param name="gold">金币数(用于 Finalize 时加分)</param>
|
||
/// <param name="onScoreAdd">加分回调(来自 Ws_DieFish 的 onScoreAdd)</param>
|
||
public static bool ShowInProgress(
|
||
SitPosData killerSit,
|
||
GameObject effect_DieAwardPrefab,
|
||
string fishId,
|
||
string killerUserId,
|
||
int beishu,
|
||
long gold,
|
||
Action<int> onScoreAdd)
|
||
{
|
||
if (killerSit == null)
|
||
{
|
||
Debug.LogWarning("[BaoJiangNotSelfHandler] killerSit 为空,跳过 not-self 爆奖");
|
||
return false;
|
||
}
|
||
|
||
// effect_DieAward 不克隆,只读取预制体上的 BJEventBase.bjEndUI
|
||
BJEndUI bjEndUIPrefab = ResolveBJEndUIPrefab(effect_DieAwardPrefab);
|
||
if (bjEndUIPrefab == null)
|
||
{
|
||
Debug.LogWarning("[BaoJiangNotSelfHandler] effect_DieAward 预制体上未找到 BJEndUI,跳过 not-self 爆奖");
|
||
return false;
|
||
}
|
||
|
||
// 实例化 BJEndUI 到座位 obj_BaoJiangZhongPanelPosition
|
||
Transform parent = killerSit.obj_BaoJiangZhongPanelPosition != null
|
||
? killerSit.obj_BaoJiangZhongPanelPosition
|
||
: killerSit.transform;
|
||
|
||
var endUI = UnityEngine.Object.Instantiate(bjEndUIPrefab, parent);
|
||
endUI.transform.localPosition = Vector3.zero;
|
||
endUI.transform.localRotation = Quaternion.identity;
|
||
endUI.transform.localScale = Vector3.one;
|
||
|
||
// 进入"爆奖中"状态:关 tMP_beishu / 开 img_BaoJiangZhong
|
||
endUI.ShowInProgress(isMine: false);
|
||
|
||
// 标记座位正在爆奖
|
||
killerSit.IsBaoJiangingActive = true;
|
||
|
||
// 机器人识别:优先用 SitPosData.isBot(服务端 RoomPlayerInfo 下发),回退 userId.StartsWith("bot_")
|
||
bool isBot = killerSit.isBot
|
||
|| (!string.IsNullOrEmpty(killerUserId) && killerUserId.StartsWith("bot_"));
|
||
|
||
string key = MakeKey(killerUserId, fishId);
|
||
_pending[key] = new PendingEntry
|
||
{
|
||
endUI = endUI,
|
||
killerSit = killerSit,
|
||
onScoreAdd = onScoreAdd,
|
||
gold = gold,
|
||
beishu = beishu,
|
||
userId = killerUserId,
|
||
fishId = fishId,
|
||
isBot = isBot,
|
||
};
|
||
|
||
Debug.Log($"[BaoJiangNotSelfHandler] not-self 爆奖进入在途状态:key={key} gold={gold} beishu={beishu} isBot={isBot}");
|
||
|
||
// 机器人杀 Boss 特殊处理:机器人不会主动发 baoJiangEnd,由房主客户端延迟 5~10 秒代发帧同步
|
||
// 服务端广播给所有玩家 → 各客户端走 OnBaoJiangEnd 完成最终加分(保证多人游戏一致性)
|
||
if (isBot && IsLocalRoomOwner())
|
||
{
|
||
GameManager.Instance.StartCoroutine(DelayedSendBaoJiangEndForBot(killerUserId, fishId, beishu, gold));
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收到帧同步 baoJiangEnd 时调用:
|
||
/// 显示最终 BJEndUI(开 tMP_beishu / 关 img_BaoJiangZhong)→ 加分 → 清除 IsBaoJiangingActive。
|
||
/// </summary>
|
||
public static void OnBaoJiangEnd(string userId, string fishId, int beishu, long score)
|
||
{
|
||
string key = MakeKey(userId, fishId);
|
||
if (!_pending.TryGetValue(key, out var entry))
|
||
{
|
||
Debug.LogWarning($"[BaoJiangNotSelfHandler] 未找到 pending not-self 爆奖:key={key}(可能已被安全超时清理或重复触发)");
|
||
return;
|
||
}
|
||
|
||
_pending.Remove(key);
|
||
|
||
if (entry.endUI != null)
|
||
{
|
||
entry.endUI.Finalize(beishu, score, isMine: false);
|
||
}
|
||
|
||
// 加分(与 self 流程一致,在最终效果展示后回加金币)
|
||
try
|
||
{
|
||
entry.onScoreAdd?.Invoke(0);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogError($"[BaoJiangNotSelfHandler] onScoreAdd 异常:{e}");
|
||
}
|
||
|
||
if (entry.killerSit != null)
|
||
{
|
||
entry.killerSit.IsBaoJiangingActive = false;
|
||
}
|
||
|
||
Debug.Log($"[BaoJiangNotSelfHandler] not-self 爆奖完成:key={key} beishu={beishu} score={score}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 机器人杀 Boss 时由房主客户端延迟 5~10 秒后代发 baoJiangEnd 帧同步给服务端,
|
||
/// 服务端广播给所有玩家(含房主自己)→ 各客户端走 OnBaoJiangEnd 完成最终加分。
|
||
/// 这样可保证所有客户端同一时刻切换到最终 BJEndUI 效果(多人游戏一致性)。
|
||
/// </summary>
|
||
private static IEnumerator DelayedSendBaoJiangEndForBot(string userId, string fishId, int beishu, long score)
|
||
{
|
||
float delay = Random.Range(BotSendDelayMin, BotSendDelayMax);
|
||
Debug.Log($"[BaoJiangNotSelfHandler] 房主代发机器人爆奖结束帧同步:延迟 {delay:F2}s key={MakeKey(userId, fishId)}");
|
||
yield return new WaitForSeconds(delay);
|
||
|
||
// 防御:发送时 pending 可能已被 ClearAll 清掉(如玩家中途退房),跳过发送
|
||
string key = MakeKey(userId, fishId);
|
||
if (!_pending.ContainsKey(key))
|
||
{
|
||
Debug.LogWarning($"[BaoJiangNotSelfHandler] 延迟到点但 pending 已不存在,放弃代发:key={key}");
|
||
yield break;
|
||
}
|
||
|
||
SendBaoJiangEnd(userId, fishId, beishu, score);
|
||
|
||
Debug.Log($"[BaoJiangNotSelfHandler] 房主已代发 baoJiangEnd 帧同步:key={key} beishu={beishu} score={score}");
|
||
}
|
||
|
||
/// <summary>判断当前本地客户端是否为房主(用于决定是否代发机器人帧同步)</summary>
|
||
private static bool IsLocalRoomOwner()
|
||
{
|
||
return SitPosData.roomOwnerSit != null
|
||
&& GameManager.Instance != null
|
||
&& GameManager.Instance.user != null
|
||
&& SitPosData.roomOwnerSit.user == GameManager.Instance.user.id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 effect_DieAward 预制体上读取 BJEndUI(不克隆 effect_DieAward)。
|
||
/// 优先走 BJEventBase.bjEndUI 字段,回退 GetComponentInChildren<BJEndUI>。
|
||
/// </summary>
|
||
private static BJEndUI ResolveBJEndUIPrefab(GameObject effect_DieAwardPrefab)
|
||
{
|
||
if (effect_DieAwardPrefab == null) return null;
|
||
|
||
// 优先:BJEventBase(DieEffect_TSY 继承自它)的 bjEndUI 字段
|
||
var bjEventBase = effect_DieAwardPrefab.GetComponent<BJEventBase>();
|
||
if (bjEventBase != null && bjEventBase.bjEndUI != null)
|
||
return bjEventBase.bjEndUI;
|
||
|
||
// 回退:effect_DieAward 自身或子节点上的 BJEndUI 组件
|
||
var onSelf = effect_DieAwardPrefab.GetComponent<BJEndUI>();
|
||
if (onSelf != null) return onSelf;
|
||
|
||
return effect_DieAwardPrefab.GetComponentInChildren<BJEndUI>(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送 baoJiangEnd 帧同步给服务端(服务端广播给所有玩家)。
|
||
/// 用于:① self 流程结束时由击杀者客户端发送(真人玩家击杀);
|
||
/// ② 机器人击杀时由房主延迟代发(见 DelayedSendBaoJiangEndForBot)。
|
||
/// </summary>
|
||
public static void SendBaoJiangEnd(string userId, string fishId, int beishu, long score, int posID = 0)
|
||
{
|
||
SyncGameData sgd = new SyncGameData
|
||
{
|
||
sitPos = 0,
|
||
frameType = FrameDataType.baoJiangEnd,
|
||
time = BeijingTimeManager.Instance.CurrentTimestampMs,
|
||
frameData = new FrameData_BaoJiangEnd
|
||
{
|
||
userId = userId,
|
||
posID = posID,
|
||
fishId = fishId,
|
||
beishu = beishu,
|
||
score = score,
|
||
}
|
||
};
|
||
WebSocketSendList.SyncGameData(sgd);
|
||
|
||
Debug.Log($"[BaoJiangNotSelfHandler] 已发送 baoJiangEnd 帧同步:userId={userId} fishId={fishId} beishu={beishu} score={score} posID={posID}");
|
||
}
|
||
|
||
/// <summary>清空所有 pending(场景切换/退出房间时调用,避免悬空引用)</summary>
|
||
public static void ClearAll()
|
||
{
|
||
foreach (var kv in _pending)
|
||
{
|
||
if (kv.Value?.killerSit != null)
|
||
kv.Value.killerSit.IsBaoJiangingActive = false;
|
||
if (kv.Value?.endUI != null)
|
||
{
|
||
try { UnityEngine.Object.Destroy(kv.Value.endUI.gameObject); }
|
||
catch { /* 忽略 */ }
|
||
}
|
||
}
|
||
_pending.Clear();
|
||
}
|
||
|
||
private static string MakeKey(string userId, string fishId) => $"{userId}|{fishId}";
|
||
}
|