From 752d4c8070898a310fb5cc7f790a6309369b5120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=AE2003?= <2384017988@qq.com> Date: Wed, 7 Oct 2026 23:05:51 +0800 Subject: [PATCH] 1 --- Assets/MyGame/Scripts/Turret/SitPosData.cs | 41 +++---- .../ChineseChess/ChineseChessMainPanel.cs | 3 + .../aa/UI/Game/GameMainPanel_FishBase.cs | 104 +++++++++--------- .../aa/UI/Home/Sub/Activity/ChouJiang.prefab | 8 +- .../aa/UI/Home/Sub/Activity/ChouJiangPanel.cs | 87 +++++++-------- .../aa/UI/Home/Sub/UIHome_BY_BackPackPanel.cs | 8 ++ .../aa/UI/Home/Sub/UIHome_BY_EmailPanel.cs | 8 +- .../aa/UI/Home/Sub/UIHome_BY_ShopPanel.cs | 7 ++ Assets/MyGame/aa/UI/Home/UIHomeMain.cs | 7 +- 9 files changed, 145 insertions(+), 128 deletions(-) diff --git a/Assets/MyGame/Scripts/Turret/SitPosData.cs b/Assets/MyGame/Scripts/Turret/SitPosData.cs index a1c8a720e..9fb85a2d4 100644 --- a/Assets/MyGame/Scripts/Turret/SitPosData.cs +++ b/Assets/MyGame/Scripts/Turret/SitPosData.cs @@ -236,11 +236,6 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler public List tripleLockTargets = new List(); public List tripleLockPositions = new List(); /// - /// ⭐ 三发锁位是否处于"空置等手动点击"状态(索引与 tripleLockTargets 对齐)。 - /// true = 该锁位打死后自动检测没找到鱼 → 保持空着,不再自动补位,必须玩家手动点击才锁。 - /// - private readonly List tripleSlotIdle = new List(); - /// /// ⭐ 该锁位是否由玩家手动点选(索引与 tripleLockTargets 对齐)。 /// true = 玩家自己点的鱼 → 离场前一直跟,死了才换;手动身份会跟着鱼一起移入子锁位。 /// @@ -2135,8 +2130,7 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler bool has = i < targets.Count; tripleLockTargets[i] = has ? targets[i].monster : null; tripleLockPositions[i] = has ? targets[i].hitPos : null; - // ⭐ 自动建位:允许自动补位(补不到会转成"空置等手动点击"),不算玩家手动 - tripleSlotIdle[i] = false; + // ⭐ 自动建位不算玩家手动;空位由锁定系统每帧继续检测补位 tripleSlotManual[i] = false; } @@ -2164,7 +2158,6 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { tripleLockTargets.Add(null); tripleLockPositions.Add(null); - tripleSlotIdle.Add(false); tripleSlotManual.Add(false); } } @@ -2178,12 +2171,12 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler } tripleLockTargets.Clear(); tripleLockPositions.Clear(); - tripleSlotIdle.Clear(); tripleSlotManual.Clear(); } // ==================== 三发锁位(每个锁位独立维护) ==================== // ⭐ index=0 是主锁位(monCotl_Lock + 主 lockFX),1..2 对应 lockFX_Triple[index-1] + // ⭐ 空锁位由锁定系统每帧独立检测补位(不做"空置等手动"的封存) /// 按每个锁位是否有鱼刷新3个锁定圈显示(有目标才有圈) private void RefreshTripleIconVisibility() @@ -2206,7 +2199,6 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler EnsureTripleLockSlots(index + 1); tripleLockTargets[index] = monster; tripleLockPositions[index] = hitPos; - tripleSlotIdle[index] = false; tripleSlotManual[index] = false; // 自动补的位不算玩家手动 if (index == 0) @@ -2218,16 +2210,12 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler RefreshTripleIconVisibility(); } - /// - /// 清空单个三发锁位:该位打死后自动检测没找到鱼 → 保留占位、隐藏该位图标, - /// 并标记"空置等手动点击"(不再自动补位,必须玩家点击才锁)。 - /// + /// 清空单个三发锁位(没鱼可补):保留占位、隐藏该位图标;空位每帧会继续检测 public void ClearTripleLockSlot(int index) { if (index < 0 || index >= tripleLockTargets.Count) return; tripleLockTargets[index] = null; tripleLockPositions[index] = null; - tripleSlotIdle[index] = true; tripleSlotManual[index] = false; if (index == 0) @@ -2239,10 +2227,19 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler RefreshTripleIconVisibility(); } - /// 该锁位是否处于"空置等手动点击"状态(不再自动补位) - public bool IsTripleSlotIdle(int index) + /// + /// ⭐ 主锁状态复位,但**保留3个锁位**(全部锁位暂时没鱼时用): + /// 本地锁状态清掉 + 炮台回 Idle,锁位留着每帧继续检测,有鱼了自动补上。 + /// (与 ClearLock 的区别:ClearLock 会把三发锁位列表一起清掉) + /// + public void ResetMainLockKeepTripleSlots() { - return index >= 0 && index < tripleSlotIdle.Count && tripleSlotIdle[index]; + monCotl_Lock = null; + monCotl_Lock_Position = null; + isLock = false; + if (lockFX != null) lockFX.SetActive(false); + ChangeState(TurretState.Idle); + RefreshTripleIconVisibility(); } /// @@ -2312,7 +2309,6 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler EnsureTripleLockSlots(3); tripleLockTargets[0] = monster; tripleLockPositions[0] = hitPos; - tripleSlotIdle[0] = false; tripleSlotManual[0] = true; // 玩家手动点的 for (int i = 1; i < 3; i++) @@ -2321,8 +2317,6 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler bool has = k < keepMon.Count; tripleLockTargets[i] = has ? keepMon[k] : null; tripleLockPositions[i] = has ? keepPos[k] : null; - // ⭐ 空位标记"等手动点击":玩家在自己点选,空位不再自动补 - tripleSlotIdle[i] = !has; tripleSlotManual[i] = has && keepManual[k]; } @@ -2348,11 +2342,6 @@ public class SitPosData : MonoBehaviour, IPointerDownHandler, IPointerUpHandler tripleLockPositions[0] = pos; tripleLockTargets[i] = null; tripleLockPositions[i] = null; - if (i < tripleSlotIdle.Count) - { - tripleSlotIdle[0] = tripleSlotIdle[i]; - tripleSlotIdle[i] = false; // 让出的位置允许自动补一次 - } if (i < tripleSlotManual.Count) { tripleSlotManual[0] = tripleSlotManual[i]; // 手动身份跟着鱼提升 diff --git a/Assets/MyGame/aa/UI/Game/ChineseChess/ChineseChessMainPanel.cs b/Assets/MyGame/aa/UI/Game/ChineseChess/ChineseChessMainPanel.cs index ec3dccbb2..dffa5b68e 100644 --- a/Assets/MyGame/aa/UI/Game/ChineseChess/ChineseChessMainPanel.cs +++ b/Assets/MyGame/aa/UI/Game/ChineseChess/ChineseChessMainPanel.cs @@ -294,6 +294,7 @@ public class ChineseChessMainPanel : GameMainPanel_Base roomData = chessGameRoom; // 记录房间数据(开象棋对局时要取阵营) StartHeartbeat(); // 房间心跳(更新活跃时间 + 断线超时检查) UpdateUserData(chessGameRoom); + if (btn_FanHuiDaTing != null) btn_FanHuiDaTing.gameObject.SetActive(true); // 等待房/选择界面:显示返回大厅 switch (chessGameRoom.users.Count) { @@ -461,6 +462,7 @@ public class ChineseChessMainPanel : GameMainPanel_Base GameManager.Instance.OnUserDataChanged -= OnUserDataChangedHandler; GameManager.Instance.OnUserDataChanged += OnUserDataChangedHandler; RefreshKickButtonVisible(); // 进面板默认隐藏踢人按钮(防上次残留) + if (btn_FanHuiDaTing != null) btn_FanHuiDaTing.gameObject.SetActive(true); // 选房/选择界面:显示返回大厅 // 回到选房间界面时显示等待中(连带父对象) SetDengDaiZhongActive(true); subUIManager.ShowAsync(UIPanel.ZGXQDistrict, args); @@ -569,6 +571,7 @@ public class ChineseChessMainPanel : GameMainPanel_Base chessGameManager.OnTurnChanged += OnChessTurnChanged; // 要在 StartGame 前订阅(开局红先会触发一次) chessGameManager.StartGame(myCamp); RefreshKickButtonVisible(); // 对局开始 → 隐藏踢人按钮 + if (btn_FanHuiDaTing != null) btn_FanHuiDaTing.gameObject.SetActive(false); // 开局 → 隐藏返回大厅 if (chessBoard == null) CreateDefaultChessBoard(); chessBoard.Init(chessGameManager, myCamp); diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index 8f45cb27a..f107cea81 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -1021,7 +1021,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base _autoLockActivated = false; // ⭐ 同时退出"图鉴锁定"模式:下次再开锁定开关 = 正常锁定(不受图鉴勾选约束) _suoDingMode = false; - _tripleLockWaitManual = false; // 本地清理锁定 if (currSitData != null) @@ -3491,29 +3490,30 @@ public class GameMainPanel_FishBase : GameMainPanel_Base if (currSitData == null) return; // ⭐ 是否处于"战神+三发+锁定"模式(⭐与有没有锁定目标无关:不再和 isLock 循环依赖, - // 否则三格全空→isLock=false→这里判定退出三发→把玩家"等手动点击"的锁位全清掉) + // 否则三格全空→isLock=false→这里判定退出三发→把锁位全清掉) bool tripleModeOn = IsZhanShenTripleMode(); if (!tripleModeOn) { // 不在三发模式 → 清掉三发锁位,回到单锁逻辑(主锁 monCotl_Lock 保留,继续当单锁用) currSitData.isScatterLaserOn = false; - _tripleLockWaitManual = false; if (currSitData.tripleLockTargets.Count > 0) currSitData.HideTripleLockIcons(); return; } // ⭐ 散射标记:三发模式下只要有锁定目标就算散射中(目标不足也要显示左右起点球) + // ⭐ 特殊炮台期间子锁位不受影响,这里照常算 currSitData.isScatterLaserOn = currSitData.HasAnyTripleTarget || (currSitData.isLock && currSitData.monCotl_Lock != null); - // ⭐ 三发锁位全空后处于"等玩家手动点击"状态 → 不再自动建位 - if (_tripleLockWaitManual) return; - - // 已经有锁位 → 跳过 + // 已经有锁位 → 跳过(空位由 CheckLockTargetValid 每帧独立检测补位) if (currSitData.tripleLockTargets.Count > 0) return; + // ⭐ 特殊炮台(激光蟹/钻头蟹)期间禁止"主锁定"锁鱼 → 不新建三发锁位(含主锁); + // 已有的子锁位不受影响(上面的分支已经保留) + if (currSitData.isDrillActive) return; + var targets = FindFireTargets(3); if (targets.Count > 0) currSitData.SetTripleLockTargets(targets); @@ -3530,13 +3530,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base /// 图鉴锁定面板的自动锁定不受影响(那条路径直接激活)。 protected virtual bool AutoLockOnManualLock => true; - /// - /// ⭐ 三发锁位全空(打死后自动检测不到鱼)→ 进入"等玩家手动点击"状态: - /// 不再自动建位/自动补位,直到玩家手动点鱼或退出三发模式。 - /// ⭐ 只作用于三发流程,不动全局的 _autoLockActivated(否则会污染普通模式的自动锁定)。 - /// - private bool _tripleLockWaitManual = false; - private void HandleLock() { // ⭐ 退出倒计时期间屏蔽本地玩家手动锁定,其他玩家和机器人不受影响 @@ -3612,7 +3605,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base currSitData.PushTripleLockSlot(monster, lockTr); currSitData.isManualLock = true; if (AutoLockOnManualLock) _autoLockActivated = true; // XJMH 覆写 false:手动锁定不自动换目标 - _tripleLockWaitManual = false; // ⭐ 玩家手动点鱼 → 退出"等手动点击"状态 SyncLock(currSitData.GetTripleLockGuids(), colliderIndex); return; } @@ -3639,21 +3631,22 @@ public class GameMainPanel_FishBase : GameMainPanel_Base foreach (var sit in list_Sits) { - if (!sit.isLock || sit.monCotl_Lock == null) continue; - - // ⭐ 切换锁定点(小白龙等切换模型后锁定点失效): - // 当前 monCotl_Lock_Position 失效 → 调 SwitchLockPoint 重新取激活的锚点 - if (sit.monCotl_Lock_Position == null || !sit.monCotl_Lock_Position.gameObject.activeInHierarchy) + // ---- 主锁定图标 ---- + if (sit.isLock && sit.monCotl_Lock != null) { - Transform newLockPos = sit.monCotl_Lock.SwitchLockPoint(); - if (newLockPos != null) - sit.monCotl_Lock_Position = newLockPos; + // ⭐ 切换锁定点(小白龙等切换模型后锁定点失效): + // 当前 monCotl_Lock_Position 失效 → 调 SwitchLockPoint 重新取激活的锚点 + if (sit.monCotl_Lock_Position == null || !sit.monCotl_Lock_Position.gameObject.activeInHierarchy) + { + Transform newLockPos = sit.monCotl_Lock.SwitchLockPoint(); + if (newLockPos != null) + sit.monCotl_Lock_Position = newLockPos; + } + + UpdateSingleLockFX(sit, sit.monCotl_Lock, sit.monCotl_Lock_Position, sit.lockFX); } - // 更新主锁定图标 - UpdateSingleLockFX(sit, sit.monCotl_Lock, sit.monCotl_Lock_Position, sit.lockFX); - - // 更新三发锁定额外图标 + // ---- 三发锁定额外图标(⭐独立于主锁:主锁位空着/主锁被冻结时也要跟着自己的鱼走) ---- // ⭐ tripleLockTargets[0] 是主目标(已在主 lockFX 显示), // lockFX_Triple[0] 应对应 tripleLockTargets[1],以此类推 for (int i = 0; i < sit.lockFX_Triple.Count; i++) @@ -3661,9 +3654,16 @@ public class GameMainPanel_FishBase : GameMainPanel_Base int targetIdx = i + 1; // 跳过索引0(主目标) if (targetIdx >= sit.tripleLockTargets.Count) break; var icon = sit.lockFX_Triple[i]; + if (icon == null) continue; + var monster = sit.tripleLockTargets[targetIdx]; + if (monster == null) + { + if (icon.activeSelf) icon.SetActive(false); + continue; + } + var hitPos = sit.tripleLockPositions[targetIdx]; - if (icon == null || monster == null) continue; // ⭐ 同样为三发目标切换锁定点 if (hitPos == null || !hitPos.gameObject.activeInHierarchy) { @@ -3711,18 +3711,29 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } private void CheckLockTargetValid() { - // ⭐⭐⭐ 三发模式:每个锁位独立维护 —— 鱼死了/路径离场时只补那一个锁位,其他锁位不动。 - // 补位只尝试一次:补到了就锁上;补不到该锁位转"空置等手动点击"(不再自动补)。 + // ⭐⭐⭐ 三发模式:每个锁位完全独立维护 —— + // 有鱼的位:鱼死了/路径离场/被取消勾选 → 只补这一个位,其他位不动 + // 空着的位:**每帧持续检测**(有符合条件(含图鉴勾选)的鱼就补上), + // 不等待主锁位的鱼被打死、也不会被封存成"等手动点击" // (Count 由 SetTripleLockTargets 建立;HideTripleLockIcons 才清成 0) if (currSitData.tripleLockTargets.Count > 0) { // ⭐ 固定3个锁位(三发=最多3个目标) currSitData.EnsureTripleLockSlots(3); + // ⭐ 记录本次维护前的锁状态:下面"全空"时据此判断是否需要向服务器同步一次取消锁定 + // (循环里清理锁位会提前把 isLock/monCotl_Lock 置空,所以必须先取) + bool wasLocked = currSitData.isLock || currSitData.monCotl_Lock != null; + for (int i = currSitData.tripleLockTargets.Count - 1; i >= 0; i--) { var t = currSitData.tripleLockTargets[i]; + // ⭐ 特殊炮台(激光蟹/钻头蟹)期间:只冻结"主锁位"——不让它锁新鱼/换目标。 + // 子锁位(1/2)照常每帧独立检测补位,**不受特殊炮台影响**。 + // 主锁位保持原样(鱼死了也不动),等特殊炮台结束后由下面的逻辑自动补上 + if (i == 0 && currSitData.isDrillActive) continue; + // ---- 该锁位有鱼:判断是否需要更换 ---- if (t != null && t.gameObject.activeInHierarchy) { @@ -3736,29 +3747,24 @@ public class GameMainPanel_FishBase : GameMainPanel_Base continue; } - // ---- 该锁位空着 ---- - // ⭐ "空置等手动点击"状态 → 不再自动补位,必须玩家点击才锁 - if (currSitData.IsTripleSlotIdle(i)) continue; - - // ⭐ 刚打死后重新检测:只尝试这一次,成功就锁上,失败则转成"空置等手动" + // ---- 该锁位空着:每帧独立检测补位(补不到就下一帧继续) ---- RefillTripleLockSlot(i); } - // ⭐ 三个锁位全空 → 暂时不锁定:清锁 + 炮台姿态回 Idle + 同步服务器, - // 并进入"等玩家手动点击"状态(⭐只作用于三发流程,不再动全局的 _autoLockActivated) - if (!currSitData.HasAnyTripleTarget) + // ⭐ 三个锁位全空 → 本地锁状态复位 + 同步一次服务器,但**保留3个锁位继续检测** + // (不清锁位列表,这样场上再出现符合条件的鱼时会自动补上) + if (!currSitData.HasAnyTripleTarget && wasLocked) { - if (!_tripleLockWaitManual) - { - _tripleLockWaitManual = true; - currSitData.ClearLock(); // 清锁位/主锁/图标 + ChangeState(Idle) - SyncTurretState(TurretState.Idle); - SyncLock(null); // 同步服务器:取消锁定(原来三发分支漏了这步) - } + currSitData.ResetMainLockKeepTripleSlots(); + SyncTurretState(TurretState.Idle); + SyncLock(null); // 同步服务器:取消锁定 } return; } + // ⭐ 单锁模式(没有三发锁位时,主锁就是唯一锁定):特殊炮台期间禁止锁定 + if (currSitData.isDrillActive) return; + if (!currSitData.isLock) return; var monster = currSitData.monCotl_Lock; @@ -3917,8 +3923,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // ⭐ 面板选完即应用:自动锁定立即开始找目标 _autoLockActivated = true; - // ⭐ 三发模式:退出"等手动点击"状态,允许重新按勾选建位 - _tripleLockWaitManual = false; // ⭐ 进入图鉴模式后,把当前手动锁定的目标转为自动锁定(交给白名单筛选) if (currSitData != null && currSitData.isManualLock) @@ -3950,11 +3954,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // 激活后,当锁定的鱼死亡,自动转换到下一个目标 if (!_autoLockActivated) return; - // ⭐⭐⭐ 三发模式:每个锁位由 CheckLockTargetValid 逐位独立维护(死一条只补那一条), - // 这里不再插手,否则"死一条鱼会导致三个锁定目标全部重新寻找" + // ⭐⭐⭐ 三发模式:每个锁位由 CheckLockTargetValid 逐位独立维护(死一条只补那一条, + // 空位每帧持续检测),这里不插手 if (currSitData.tripleLockTargets.Count > 0) return; - // ⭐ 三发锁位全空后处于"等玩家手动点击"状态 → 不自动接管(否则会和"等手动点击"的语义打架) - if (_tripleLockWaitManual && IsZhanShenTripleMode()) return; // ⭐⭐⭐ 换目标规则(当前使用:只有 手动点选 / 走完路线 / 目标死亡 才换): // ① 手动锁定 → 玩家自己点的鱼,离场前一直跟,死了才换 diff --git a/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiang.prefab b/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiang.prefab index 877f800c4..c8c90d43c 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiang.prefab +++ b/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiang.prefab @@ -4250,7 +4250,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 59c98b7739d292e409a9c611858e6ee5, type: 3} m_Name: m_EditorClassIdentifier: - itemId: 3 + itemId: 1 prefab_DrawEffect: {fileID: 0} iconRatio: 0.85 iconOffsetY: 0 @@ -5139,7 +5139,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!224 &4940949284590563909 RectTransform: m_ObjectHideFlags: 0 @@ -11052,7 +11052,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 59c98b7739d292e409a9c611858e6ee5, type: 3} m_Name: m_EditorClassIdentifier: - itemId: 1 + itemId: 2 prefab_DrawEffect: {fileID: 0} iconRatio: 0.85 iconOffsetY: 0 @@ -13182,7 +13182,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 59c98b7739d292e409a9c611858e6ee5, type: 3} m_Name: m_EditorClassIdentifier: - itemId: 2 + itemId: 3 prefab_DrawEffect: {fileID: 0} iconRatio: 0.85 iconOffsetY: 0 diff --git a/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs b/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs index 00d3fcda0..b3816f7b1 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/Activity/ChouJiangPanel.cs @@ -116,13 +116,15 @@ public class ChouJiangPanel : SubUIBase Refresh(); } - /// 拉取活动配置 + 刷新各 toggle 与用户状态 + /// 拉取抽奖配置 + 刷新各 toggle 与用户状态。 + /// 用 /api/Lottery/Config(新版统一接口:config + drawnPrizeIds 一次返回), + /// 不再用 /api/ActivityConfig/lotteryConfig + DrawnPrizes 两个旧请求。 public void Refresh() { - APIRequestList.GetLotteryActivityConfig(cfg => + APIRequestList.GetLotteryConfig(res => { if (this == null) return; - config = cfg; + config = res != null ? res.config : null; // 活动未开启:提示后关闭面板 if (config != null && !config.isOpen) @@ -134,11 +136,15 @@ public class ChouJiangPanel : SubUIBase UpdateActivityUI(); InitToggles(); + + // 本期已获得奖品 → 置灰(以服务端返回为准) + ApplyDrawnIds(res != null ? res.drawnPrizeIds : null); + RefreshUserStatus(); }); } - /// 初始化各 toggle 面板的 item(A/B/C 三个列表),并置灰已抽中的奖项 + /// 初始化各 toggle 面板的 item(A/B/C 三个列表) private void InitToggles() { if (toggles == null || config == null) return; @@ -147,45 +153,37 @@ public class ChouJiangPanel : SubUIBase if (toggles[i] != null) toggles[i].Init(config); } - RefreshDrawnPrizes(); } - /// 拉取用户抽奖状态(已抽次数决定下一次花费与是否到上限) + /// 拉取用户抽奖状态(各池已抽次数决定下一次花费与是否到上限),并刷新置灰 private void RefreshUserStatus() { APIRequestList.GetLotteryUserStatus(status => { if (this == null) return; userStatus = status; - if (status != null && status.drawnPrizeIds != null) - { - for (int i = 0; i < status.drawnPrizeIds.Count; i++) - drawnIds.Add(status.drawnPrizeIds[i]); - } + // UserStatus 里同样带本期 drawnPrizeIds,以服务端最新数据刷新置灰 + ApplyDrawnIds(status != null ? status.drawnPrizeIds : null); UpdateDrawUI(); }); } - /// 拉取已抽奖项列表,置灰各 toggle 的对应 item(奖励不重复) - private void RefreshDrawnPrizes() + /// 以服务端返回的已获得奖品ID列表为准刷新 drawnIds 并置灰各 toggle + private void ApplyDrawnIds(List ids) { - APIRequestList.GetLotteryDrawnPrizes(ids => + drawnIds.Clear(); + if (ids != null) { - if (this == null || toggles == null) return; + for (int i = 0; i < ids.Count; i++) + drawnIds.Add(ids[i]); + } - drawnIds.Clear(); - if (ids != null) - { - for (int i = 0; i < ids.Count; i++) - drawnIds.Add(ids[i]); - } - - for (int i = 0; i < toggles.Count; i++) - { - if (toggles[i] != null) - toggles[i].RefreshDrawnState(ids); - } - }); + if (toggles == null) return; + for (int i = 0; i < toggles.Count; i++) + { + if (toggles[i] != null) + toggles[i].RefreshDrawnState(ids); + } } /// 切到指定分组的 toggle 面板(抽奖过程中禁止切换:跑马灯期间切池会看不到结果) @@ -283,28 +281,31 @@ public class ChouJiangPanel : SubUIBase btn_Draw.interactable = CanDraw() && !isDrawing; } - /// 下一次抽奖花费:当前分组池的价格阶梯第 N+1 次价格,超出长度取最后一个(独立计费非累加);当前池未配置时取第一个池兜底 + /// 下一次抽奖花费(文档规则): + /// 价格数组 = costsByPool[String(poolType)],当前次数 = drawCountByPool[key](不存在按 0), + /// 价格下标 = min(次数, 数组长度-1)。 + /// 当前奖池没有价格数组或为空 → 该奖池不可抽奖,不回退其他奖池价格(返回 0)。 private long GetNextCost() { - if (config == null || config.costsByPool == null || config.costsByPool.Count == 0) return 0; + if (config == null || config.costsByPool == null) return 0; - // poolType:"1"=炮台,"2"=战神,"3"=翅膀;按当前展示的分组取该池的价格阶梯 string key = ((int)currPoolType).ToString(); if (!config.costsByPool.TryGetValue(key, out var costs) || costs == null || costs.Count == 0) - { - foreach (var kv in config.costsByPool) - { - costs = kv.Value; - break; - } - if (costs == null || costs.Count == 0) return 0; - } + return 0; int drawn = GetPoolDrawCount(key); int idx = Mathf.Min(drawn, costs.Count - 1); return costs[idx]; } + /// 当前池是否有价格配置(没有 = 该池不可抽奖,文档明确不允许回退其他池价格) + private bool HasPoolCost() + { + if (config == null || config.costsByPool == null) return false; + string key = ((int)currPoolType).ToString(); + return config.costsByPool.TryGetValue(key, out var costs) && costs != null && costs.Count > 0; + } + /// 当前池已抽次数(价格阶梯索引:少一个奖励价格升一档): /// drawCountByPool[key](该池独立计数;字典里没有该 key = 该池还没抽过 = 0, /// 不回退总次数,否则在别的池抽的次数会污染本池价格); @@ -316,10 +317,11 @@ public class ChouJiangPanel : SubUIBase return userStatus.drawCountByPool.TryGetValue(key, out var c) ? c : 0; } - /// 能否再抽:活动开启、在时间内、未抽完、未达次数上限 + /// 能否再抽:活动开启、在时间内、当前池有价格配置、未抽完、未达次数上限 private bool CanDraw() { if (config == null || !config.isOpen) return false; + if (!HasPoolCost()) return false; // 当前池无价格 → 该池不可抽(不回退其他池) if (userStatus != null && userStatus.isFinished) return false; long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); @@ -377,8 +379,8 @@ public class ChouJiangPanel : SubUIBase if (this == null) return; effectDone = true; isDrawing = false; - RefreshUserStatus(); // 花费随次数增长 - RefreshDrawnPrizes(); // 已抽奖项置灰 + // 文档第7-8步:重新请求 UserStatus,刷新次数/价格/置灰(drawnPrizeIds 一并返回) + RefreshUserStatus(); UpdateDrawButtonState(); ShowReward(res); // 领取奖励:弹获得面板 }); @@ -389,7 +391,6 @@ public class ChouJiangPanel : SubUIBase { isDrawing = false; RefreshUserStatus(); - RefreshDrawnPrizes(); UpdateDrawButtonState(); ShowReward(res); } diff --git a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_BackPackPanel.cs b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_BackPackPanel.cs index 467939d24..949a01173 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_BackPackPanel.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_BackPackPanel.cs @@ -190,6 +190,14 @@ public class UIHome_BY_BackPackPanel : SubUIBase }); } + private void OnDestroy() + { + // 兜底:防止面板被销毁后仍留在事件里(否则之后任何 UpdataUserData 都会调到已销毁的面板, + // 抛 MissingReferenceException 并中断整个事件链,导致其他面板的用户数据无法刷新) + if (GameManager.Instance != null) + GameManager.Instance.OnUserDataChanged -= OnUserDataChanged; + } + private void OnUserDataChanged() { // 刷新前保存状态 diff --git a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_EmailPanel.cs b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_EmailPanel.cs index 972404a97..09b420876 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_EmailPanel.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_EmailPanel.cs @@ -161,11 +161,17 @@ public class UIHome_BY_EmailPanel : SubUIBase obj_ShowEmail.SetActive(true); } }); - }); } + private void OnDestroy() + { + // 防止面板被销毁后仍留在事件里(否则 UpdataUserData 会调到已销毁的面板并中断事件链) + if (GameManager.Instance != null) + GameManager.Instance.OnUserDataChanged -= RefreshUI; + } + private void RewardEmail(string key) { diff --git a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ShopPanel.cs b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ShopPanel.cs index a873c51ab..6e77d9599 100644 --- a/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ShopPanel.cs +++ b/Assets/MyGame/aa/UI/Home/Sub/UIHome_BY_ShopPanel.cs @@ -422,6 +422,13 @@ public class UIHome_BY_ShopPanel : SubUIBase }); } + private void OnDestroy() + { + // 防止面板被销毁后仍留在事件里(否则 UpdataUserData 会调到已销毁的面板并中断事件链) + if (GameManager.Instance != null) + GameManager.Instance.OnUserDataChanged -= RefreshUI; + } + protected override void OnShow(object args = null) { base.OnShow(args); diff --git a/Assets/MyGame/aa/UI/Home/UIHomeMain.cs b/Assets/MyGame/aa/UI/Home/UIHomeMain.cs index 4321e958a..8b44e1e1c 100644 --- a/Assets/MyGame/aa/UI/Home/UIHomeMain.cs +++ b/Assets/MyGame/aa/UI/Home/UIHomeMain.cs @@ -142,10 +142,11 @@ public class UIHomeMain : UIBase }); btn_ChouJiang.onClick.AddListener(() => { - // 接收配置信息时直接判断:未开启不打开面板,避免面板先弹出又关掉的闪烁 - APIRequestList.GetLotteryActivityConfig(cfg => + // 接收配置信息时直接判断(/api/Lottery/Config 新版统一接口): + // 未开启不打开面板,避免面板先弹出又关掉的闪烁 + APIRequestList.GetLotteryConfig(res => { - if (cfg != null && !cfg.isOpen) + if (res != null && res.config != null && !res.config.isOpen) { UIManager.Instance.ShowTiShi("活动未开启"); return;