Files
5.24BuYu/Assets/MyGame/aa/Game/FLZY/BOSS/BaoJiangNotSelfHandler.cs
T
2026-08-09 17:57:46 +08:00

266 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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&lt;BJEndUI&gt;。
/// </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}";
}