diff --git a/Assets/MyGame/Scripts/Net/WebResponses/SyncGameData.cs b/Assets/MyGame/Scripts/Net/WebResponses/SyncGameData.cs index e4ee8e6c6..2f2ba4240 100644 --- a/Assets/MyGame/Scripts/Net/WebResponses/SyncGameData.cs +++ b/Assets/MyGame/Scripts/Net/WebResponses/SyncGameData.cs @@ -66,6 +66,19 @@ namespace Assets.MyGame.Scripts.Net.WebResponses public List bulletIds; public SyncGameData extra; public bool isBot; + /// 服务端权威:每发子弹扣费明细(bulletId + gold) + public List bullets; + /// 服务端时间戳 + public long timestamp; + } + /// + /// 单发子弹扣费明细:服务端在 FireCannon 广播里返回的权威扣费金额 + /// + [Serializable] + public class BulletGold + { + public string bulletId; + public long gold; } /// /// 同步游戏数据 diff --git a/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs b/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs index 27ebf8742..e7d2c965c 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketManager_Message.cs @@ -445,6 +445,9 @@ namespace Assets.MyGame.Scripts.Net case "SyncPlayerGold": WebSocketReponseList.SyncPlayerGold(msg); break; + case "ChangeGunMultipleNotice": + WebSocketReponseList.ChangeGunMultipleNotice(msg); + break; case "GetRoomInfoResult": WebSocketReponseList.GetRoomInfoResult(msg); @@ -579,14 +582,9 @@ namespace Assets.MyGame.Scripts.Net } break; - case "ChangeGunMultipleNotice": - { - var data = JsonConvert.DeserializeObject>>(msg).arguments[0]; - // { userId, multiple } - if (data != null) - Debug.Log($"[FishGameHub] 炮倍变更: userId={data["userId"]}, multiple={data["multiple"]}"); - } - break; + //case "ChangeGunMultipleNotice": + // WebSocketReponseList.ChangeGunMultipleNotice(msg); + // break; case "UsePropNotice": { diff --git a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs index f03962fd9..4a93194a0 100644 --- a/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs +++ b/Assets/MyGame/Scripts/Net/WebSocketReponseList.cs @@ -220,6 +220,22 @@ namespace Assets.MyGame.Scripts.Net } } + /// + /// 炮倍变更通知:服务端确认玩家炮倍切换后广播给房间内所有玩家。 + /// 客户端收到后才应用新炮倍到 consumeGoldNum 和 UI,避免开火扣费不一致。 + /// + public static void ChangeGunMultipleNotice(string msg) + { + var data = JsonConvert.DeserializeObject>(msg).arguments[0]; + var panel = UIManager.Instance.Get(UIPanel.MainGamePanelDefault) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelFLZY) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelYYSC) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelXJMH) + ?? UIManager.Instance.Get(UIPanel.MainGamePanelHHWK); + if (panel == null) return; + panel.ApplyCannonMultiple(data.userId, data.multiple); + } + /// /// 后端推送 SyncPlayerGold 时同步本地玩家金币(底层值 + UI 显示)。 /// 之前只更新 goldNum 不刷新 tMP_GoldNum.text,导致 UI 显示和后端 authoritative 值不同步, diff --git a/Assets/MyGame/Scripts/Turret/SitPosData.cs b/Assets/MyGame/Scripts/Turret/SitPosData.cs index 4cda75211..bd4bb2158 100644 --- a/Assets/MyGame/Scripts/Turret/SitPosData.cs +++ b/Assets/MyGame/Scripts/Turret/SitPosData.cs @@ -597,15 +597,18 @@ public class SitPosData : MonoBehaviour // ==================== 金币消耗 ==================== - public bool ConsumeGold(int mul = 1) + public bool ConsumeGold(int mul = 1, long fireConsumeGold = 0) { - long temp = goldNum - consumeGoldNum * mul; + // fireConsumeGold>0: 用服务端返回的该发子弹权威扣费金额 + // fireConsumeGold=0: 回退到本地 consumeGoldNum * mul + long useConsume = fireConsumeGold > 0 ? fireConsumeGold : consumeGoldNum * mul; + long temp = goldNum - useConsume; if (temp < 0) return false; long oldGold = goldNum; goldNum = temp; tMP_GoldNum.text = goldNum.ToString(); // ⭐ 记录金币流水:已扣钱(开火消耗,本地已执行) - GameManager.LogGoldFlowAppliedCost("Fire", $"开火消耗 原有金币={oldGold} 消耗金币=-{consumeGoldNum * mul} 剩余金币={goldNum} 座位={posID} 房间={GameManager.Instance.gameData?.currRoomData?.roomId}"); + GameManager.LogGoldFlowAppliedCost("Fire", $"开火消耗 原有金币={oldGold} 消耗金币=-{useConsume} 剩余金币={goldNum} 座位={posID} 房间={GameManager.Instance.gameData?.currRoomData?.roomId}"); return true; } @@ -631,9 +634,12 @@ public class SitPosData : MonoBehaviour indexConsume = 0; AudioManager.Instance.PlayerFX(62011); - consumeGoldNum = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume]; - tMP_TurretConsumeNum.text = consumeGoldNum.ToString(); - CreateFishManager.Instance.currGameType?.SyncCannonMultiple((int)consumeGoldNum); + // ⭐ 不立即修改 consumeGoldNum 和 UI,等服务端 ChangeGunMultipleNotice 确认后再应用 + // 避免:客户端先切换炮倍 → 开火按新炮倍扣费 → 服务端还没确认按旧炮倍扣费 → 差值 + int newConsume = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume]; + string roomId = GameManager.Instance.gameData?.currRoomData?.roomId; + if (!string.IsNullOrEmpty(roomId)) + WebSocketSendList.ChangeGunMultiple(roomId, user, newConsume); } public void ChangeTurretMinusConsumeNum() @@ -644,9 +650,12 @@ public class SitPosData : MonoBehaviour indexConsume = CreateFishManager.Instance.currGameType.paoBeiList.Count - 1; AudioManager.Instance.PlayerFX(62010); - consumeGoldNum = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume]; - tMP_TurretConsumeNum.text = consumeGoldNum.ToString(); - CreateFishManager.Instance.currGameType?.SyncCannonMultiple((int)consumeGoldNum); + // ⭐ 不立即修改 consumeGoldNum 和 UI,等服务端 ChangeGunMultipleNotice 确认后再应用 + // 避免:客户端先切换炮倍 → 开火按新炮倍扣费 → 服务端还没确认按旧炮倍扣费 → 差值 + int newConsume = CreateFishManager.Instance.currGameType.paoBeiList[indexConsume]; + string roomId = GameManager.Instance.gameData?.currRoomData?.roomId; + if (!string.IsNullOrEmpty(roomId)) + WebSocketSendList.ChangeGunMultiple(roomId, user, newConsume); } // ==================== 锁定系统 ==================== diff --git a/Assets/MyGame/aa/Game/HHWK/MonsterTSY/SDY/GameObject.prefab b/Assets/MyGame/aa/Game/HHWK/MonsterTSY/SDY/GameObject.prefab index 0ef0c03dc..1c191f3a6 100644 --- a/Assets/MyGame/aa/Game/HHWK/MonsterTSY/SDY/GameObject.prefab +++ b/Assets/MyGame/aa/Game/HHWK/MonsterTSY/SDY/GameObject.prefab @@ -44,23 +44,50 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 38ccfa89023796c4a94893b7e9b4914c, type: 3} m_Name: m_EditorClassIdentifier: - tMP_CoinCount: {fileID: 0} - gold: {fileID: 0} - silver: {fileID: 0} - endPoint: {x: 0, y: 0, z: 0} - isBoss: 0 - coinCount: 0 - userID: - sitID: 0 - startPoint: {x: 0, y: 0, z: 0} - radius: 0 - count: 0 + bjEndUI: {fileID: 0} + useScorePanel: 1 + tMP_Score: {fileID: 0} + scorePanelPrefab: {fileID: 0} + currScore: 0 + maxScore: 0 + scoreCount: 0 + posID: 0 paoBei: 0 - time: 0.5 - baoJiangEffectPrefab: {fileID: 0} + fishScore: 0 + bJbeishu: 0 + showDelay: 3 + moveDuration: 0.5 + moveEase: 6 + startOffset: {x: 0, y: 0, z: -100} + bjEndUIOffsetY: 150 + coinFlyDuration: 0.5 + coinArrivalInterval: 0.1 + isSelf: 0 + autoFinishOnScoreFull: 1 + totalBJCount: 0 + currBJCount: 0 + isEffEnd: 0 + isScoreStart: 0 + isScoreEnd: 0 + isGoldEffectStart: 0 + baoJiangMode: 0 + endUIType: 1 + endUIEntries: [] + monster: {fileID: 0} + startPoint: {x: 0, y: 0, z: 0} + coinCount: 0 + isLocalCoin: 0 + baoJiangEffectPrefab: {fileID: 136624701376015062, guid: 8d6e13708cafa5d448d52c8fd7f00f1a, + type: 3} parentTransform: {fileID: 0} offsetPosition: {x: 0, y: 2, z: 0} effectType: 5 + onTimeObjs: [] + hitTimes: [] + time: 0.5 + dieEffectTesyNum: 0 + minBJCount: 10 + maxBJCount: 15 lightningMaterial: {fileID: 2100000, guid: a023113e8eb8bf245b8ba10c3e9788d1, type: 2} lightningColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} lightningWidth: 0.08 @@ -69,7 +96,6 @@ MonoBehaviour: explosionPrefab: {fileID: 1709254077376921, guid: 08be47e4b1d3e1c4a895269c2c08653c, type: 3} explosionRadius: 10 - startPoint: {x: 0, y: 0, z: 0} lightningCount: 5 lightningRange: 5 lightningWait: 0.5 diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs index 9e86f058e..51acd3a2b 100644 --- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs +++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs @@ -1371,10 +1371,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base SyncFire(angle, isSanShe, isDouble, isLock); } private void FireBullet(SitPosData sit, float angle, bool isSanShe, bool isDouble, bool isLock, List bullitIDs, - List fireTargetMonsterIDs = null) + List fireTargetMonsterIDs = null, List bulletGolds = null) { Vector2 startPos = sit.GetBulletStartPos(); + // ⭐ 按索引取该发子弹的服务端权威扣费金额(bullets[i].gold),没有则回退到0(ConsumeGold内部用consumeGoldNum) + long GetBulletGold(int i) + { + if (bulletGolds != null && i < bulletGolds.Count) + return bulletGolds[i]; + return 0; + } + // ⭐ 三发 + 锁定:每发子弹优先瞄准对应目标,目标不足或失效则按默认角度发射 // 必须保证三发子弹都创建,否则金币消耗和后端对不上 if (isSanShe && isLock) @@ -1406,24 +1414,24 @@ public class GameMainPanel_FishBase : GameMainPanel_Base Vector3 targetWorldPos = targetHitPos.position; Vector2 dir = ((Vector2)targetWorldPos - startPos).normalized; float targetAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; - CreateBullet(sit, startPos, targetAngle, isDouble, true, bulletId, targetMonster, targetHitPos); + CreateBullet(sit, startPos, targetAngle, isDouble, true, bulletId, targetMonster, targetHitPos, GetBulletGold(i)); } else { // 目标不足或失效 → 按默认角度发射(不锁定) - CreateBullet(sit, startPos, defaultAngles[i], isDouble, isLock, bulletId); + CreateBullet(sit, startPos, defaultAngles[i], isDouble, isLock, bulletId, null, null, GetBulletGold(i)); } } } else if (isSanShe) { - CreateBullet(sit, startPos, angle - 20, isDouble, isLock, bullitIDs[0]); - CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[1]); - CreateBullet(sit, startPos, angle + 20, isDouble, isLock, bullitIDs[2]); + CreateBullet(sit, startPos, angle - 20, isDouble, isLock, bullitIDs[0], null, null, GetBulletGold(0)); + CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[1], null, null, GetBulletGold(1)); + CreateBullet(sit, startPos, angle + 20, isDouble, isLock, bullitIDs[2], null, null, GetBulletGold(2)); } else { - CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[0]); + CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[0], null, null, GetBulletGold(0)); } if (sit.posID==currSitData.posID) { @@ -2001,7 +2009,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base } private BullitMove CreateBullet(SitPosData sit, Vector2 pos, float angle, bool isDouble, bool isLock, string id, - MonsterContral lockTarget = null, Transform lockHitPos = null) + MonsterContral lockTarget = null, Transform lockHitPos = null, long fireConsumeGold = 0) { // ⭐ 远程座位炮台未加载时补加载 if (sit.currentTurret == null) @@ -2025,7 +2033,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base return null; } - if (!sit.ConsumeGold(isDouble ? 2 : 1)) + if (!sit.ConsumeGold(isDouble ? 2 : 1, fireConsumeGold)) return null; // ⭐ 从对象池取子弹(池空时内部会 Instantiate) @@ -2211,6 +2219,24 @@ public class GameMainPanel_FishBase : GameMainPanel_Base // 但如果服务器不回广播给开火者,本地玩家需要在 SyncFire 里直接 FireBullet) // 实际设计:本地玩家也通过广播回来创建子弹,所以不跳过 + // ⭐ 从服务端权威数据提取每发子弹扣费金额 + // 1. 优先用 bullets 字段(type:3 调用返回值,包含每发子弹的扣费金额) + // 2. fallback 用 sync.gold(RoomFireNotice 广播里的炮倍,服务端权威) + List bulletGolds = null; + if (sync.bullets != null && sync.bullets.Count > 0) + { + bulletGolds = new List(sync.bullets.Count); + foreach (var bg in sync.bullets) + bulletGolds.Add(bg.gold); + } + else if (sync.gold > 0 && sync.bulletIds != null && sync.bulletIds.Count > 0) + { + // RoomFireNotice 没有 bullets 字段,用 sync.gold(服务端权威炮倍)作为每发扣费金额 + bulletGolds = new List(sync.bulletIds.Count); + for (int i = 0; i < sync.bulletIds.Count; i++) + bulletGolds.Add(sync.gold); + } + // extra 为空:从 botLastFireData 或默认参数还原(bot 或真人玩家都可能走到这里) if (sync.extra == null || sync.extra.frameData == null) { @@ -2226,17 +2252,18 @@ public class GameMainPanel_FishBase : GameMainPanel_Base if (isBot && botLastFireData.TryGetValue(sync.userId, out var fireData)) { FireBullet(sit, angle, fireData.isSanShe, fireData.twoRTruuect, - fireData.isSuoDing, sync.bulletIds, fireData.monsterIDs); + fireData.isSuoDing, sync.bulletIds, fireData.monsterIDs, bulletGolds); } else { - FireBullet(sit, angle, false, false, false, sync.bulletIds, null); + FireBullet(sit, angle, false, false, false, sync.bulletIds, null, bulletGolds); } return; } var data = FrameDataParser.Parse(sync.extra.frameData); - FireBullet(list_Sits[sync.extra.sitPos], data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs); + var targetSit = list_Sits[sync.extra.sitPos]; + FireBullet(targetSit, data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs, bulletGolds); } /// @@ -2333,6 +2360,22 @@ public class GameMainPanel_FishBase : GameMainPanel_Base sit.tMP_TurretConsumeNum.text = data.cannonMultiple.ToString(); } + /// + /// 应用服务端确认的炮倍变更(ChangeGunMultipleNotice)。 + /// 本地玩家切换炮倍后,等服务端广播此通知才更新 consumeGoldNum 和 UI, + /// 确保开火扣费时本地炮倍与服务端一致。 + /// + public void ApplyCannonMultiple(string userId, int multiple) + { + var sit = list_Sits.Find(s => s.user == userId); + if (sit == null) return; + + sit.consumeGoldNum = multiple; + sit.SyncIndexByConsume(multiple); + if (sit.tMP_TurretConsumeNum != null) + sit.tMP_TurretConsumeNum.text = multiple.ToString(); + } + /// /// 爆奖结束帧同步(baoJiangEnd): /// 其他玩家爆奖流程完成,从 pending 取出对应 not-self 爆奖,