This commit is contained in:
2026-07-25 21:04:58 +08:00
parent 7f0b9e4e1a
commit 0c58becf41
28 changed files with 502 additions and 6293 deletions
@@ -183,6 +183,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private Dictionary<int, string> botTargetGuid = new Dictionary<int, string>();
// bot 锁定同步节流:记录上次广播的锁定目标 guid(null 表示未锁定),只有目标变化才广播
private Dictionary<int, string> botLastSyncLockGuid = new Dictionary<int, string>();
// bot 炮台状态同步节流:记录上次广播的 TurretState,只有状态变化才广播
private Dictionary<int, TurretState> botLastSyncTurretState = new Dictionary<int, TurretState>();
// bot 状态计时:开火后持续 Attack 动画,空闲 0.3s 切回 Idle
private Dictionary<int, float> botIdleTimer = new Dictionary<int, float>();
// bot 最近的 FrameData_FireBullet(给其他客户端还原技能状态)
@@ -644,10 +646,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
HandleAutoFire(); // 自动开火真正发射
HandleLock();
UpdateLockFX();
HandleAutoLock(); // ⭐ 先切换锁定目标(切换后图标位置同帧更新)
UpdateLockFX(); // 锁定图标跟随目标
RefreshTripleLockIcons();
HandleAutoLock();
LoopLaser();
SettingClose();
DaoJiShi();
@@ -2116,7 +2118,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{
switch (propNumber)
{
case "40004": // 暴走
case "40004": // 暴走 — 个人效果,只有使用者自己生效
// ⭐ 狂暴是个人增益(攻速翻倍+炮台动画),不是全房间效果。
// 服务器会把 UsePropNotice 广播给房间内所有人,必须判断 userId
// 否则别人用狂暴,本地也开了狂暴
if (userId != GameManager.Instance.user.id) break;
_localBaoZouCount--;
UpdateSkillPropsUI();
CreateFishManager.Instance.currGameType.isFrenzy = true;
@@ -2126,22 +2132,33 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
break;
case "40003": // 冰封
_localBingFengCount--;
UpdateSkillPropsUI();
currBingFengTime = bingFengDuration;
toggle_BingFeng.SetIsOnWithoutNotify(true);
if (bingFengEffect != null)
{
if (_bingFengInstance == null)
_bingFengInstance = Instantiate(bingFengEffect);
_bingFengInstance.SetActive(true);
// ⭐ 个人效果(数量/toggle/倒计时/广播):只有使用者自己执行
// 服务器把 UsePropNotice 广播给房间内所有人,不判断 userId 会导致
// 别人用冰封,本地数量也减了、toggle 也亮了、倒计时也走了
bool isLocalSender = userId == GameManager.Instance.user.id;
if (isLocalSender)
{
_localBingFengCount--;
UpdateSkillPropsUI();
currBingFengTime = bingFengDuration;
toggle_BingFeng.SetIsOnWithoutNotify(true);
// ⭐ 只有发起者广播,避免其他客户端重复广播
SyncFreeze(true);
}
// ⭐ 房间效果(冻结鱼/暂停动画/冰冻特效):所有客户端都执行
// FreezeAllFish/PauseAllFishAnimations 内部有 isFrozen 守卫,
// 重复调用安全(不会覆盖已记录的状态)
if (bingFengEffect != null)
{
if (_bingFengInstance == null)
_bingFengInstance = Instantiate(bingFengEffect);
_bingFengInstance.SetActive(true);
}
CreateFishManager.Instance.FreezeAllFish();
PauseAllFishAnimations();
break;
}
CreateFishManager.Instance.FreezeAllFish();
PauseAllFishAnimations();
// ⭐ 广播冰冻给其他客户端(包括人机房主代发的场景)
// 之前只走 UsePropNotice 本地冻结,没广播 → 多人房间其他人不冻结
SyncFreeze(true);
break;
}
}
@@ -2168,6 +2185,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private void PauseAllFishAnimations()
{
// ⭐ 守卫:已暂停时直接返回,避免重复调用覆盖已记录的速度
// (其他客户端会先后被 OnUsePropNotice 和 SyncGameData_FreezeFish 触发两次,
// 第二次会记录 speed=0,解冻时恢复成 0 → 动画永远暂停)
if (_frozenAnimSpeeds.Count > 0) return;
var root = CreateFishManager.Instance.fishRoot;
if (root == null) return;
_frozenAnimSpeeds.Clear();
@@ -2390,10 +2412,12 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
botIdleTimer[sit.posID] = 0f;
botIdleTimer[sit.posID] += Time.deltaTime;
if (botIdleTimer[sit.posID] > 0.3f)
{
sit.ChangeState(TurretState.Idle);
botIdleTimer[sit.posID] = 0f;
}
{
sit.ChangeState(TurretState.Idle);
// ⭐ 广播炮台闲置状态给其他客户端(节流:状态变化才广播)
SyncBotTurretState(sit, TurretState.Idle);
botIdleTimer[sit.posID] = 0f;
}
}
}
@@ -2676,6 +2700,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// 清理记忆
botTargetGuid.Remove(sit.posID);
botLastSyncLockGuid.Remove(sit.posID);
botLastSyncTurretState.Remove(sit.posID);
botFireTimer.Remove(sit.posID);
botIdleTimer.Remove(sit.posID);
botSkills.Remove(sit.posID);
@@ -2713,6 +2738,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private void BotFire(SitPosData sit, MonsterContral target, BotSkillRuntime skill, BotBehaviorMode mode, BotConfig cfg, BotBehaviorConfig behavior)
{
sit.ChangeState(TurretState.Attack);
// ⭐ 广播炮台攻击状态给其他客户端(节流:状态变化才广播)
SyncBotTurretState(sit, TurretState.Attack);
if (!botIdleTimer.ContainsKey(sit.posID))
botIdleTimer[sit.posID] = 0f;
botIdleTimer[sit.posID] = 0f;
@@ -3058,6 +3085,31 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
WebSocketSendList.SyncGameData(sgd);
}
/// <summary>
/// 机器人炮台状态广播(房主代发,复用 changeTurretState 帧类型)。
/// 用 bot 自己的 sitPos,接收端 SyncGameData_TurretState 会根据 sitPos
/// 调用 sit.ChangeState(state) 切换炮台动画。
/// 内置节流:只有状态变化才广播(避免空闲时每帧广播 Idle)。
/// 走 LV2 队列(0.5s flush 一次),对 bot 攻击动画可接受。
/// </summary>
private void SyncBotTurretState(SitPosData botSit, TurretState state)
{
if (botSit == null) return;
// ⭐ 节流:状态未变化不广播
if (botLastSyncTurretState.TryGetValue(botSit.posID, out var lastSynced) && lastSynced == state)
return;
botLastSyncTurretState[botSit.posID] = state;
SyncGameData sgd = new SyncGameData
{
sitPos = botSit.posID,
frameType = FrameDataType.changeTurretState,
time = BeijingTimeManager.Instance.CurrentTimestampMs,
frameData = new FrameData_TurretState { state = state }
};
WebSocketSendList.SyncGameData(sgd);
}
/// <summary>人机开火同步(房主代发)</summary>
private void SyncFire_ForBot(SitPosData botSit, float angle, long gold,
bool isSanShe, bool isDouble, bool isLock, List<string> monsterIDs)