联机bug

This commit is contained in:
daixiawu 2026-05-28 19:56:45 +08:00
parent 6f46f28d0e
commit b7de699820
11 changed files with 5541 additions and 5416 deletions

View File

@ -3136,7 +3136,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2439448078848170519}
- {fileID: 6639640428677976138}
- {fileID: 2289778642835842229}
- {fileID: 2823495548145270491}
- {fileID: 7559943356464538090}
@ -3146,6 +3145,7 @@ RectTransform:
- {fileID: 287912720796096540}
- {fileID: 5266944002495899053}
- {fileID: 748938986039668253}
- {fileID: 6639640428677976138}
m_Father: {fileID: 1937094061063432054}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
@ -3926,7 +3926,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:

View File

@ -140,7 +140,8 @@ namespace TH1_Core.Events
OutsideMultiplayRoomPasswordWrong,
OutsideMultiplayOpenHint,
OutsideMultiplayCantStartCount,
OutsideMultiplayRoomNotReady
OutsideMultiplayRoomNotReady,
OutsideMultiplayRoomAutoCancelReady
}
public struct ShowUINotifyCommon
{
@ -163,6 +164,10 @@ namespace TH1_Core.Events
public struct UpdateUIOutsideMultiplayRoomSetting { }
public struct UpdateUIOutsideMultiplayLobbyList { }
public struct ShowUIOutsideMultiplayLobbyNotify
{
public UINotifyCommonType UINotifyCommonType;
}
public struct ShowUIOutsideSelect { }
public struct HideUIOutsideSelect { }

View File

@ -864,9 +864,12 @@ namespace RuntimeData
}
[MemoryPackable]
public partial class MemberCiv
[MemoryPackable(GenerateType.NoGenerate)]
public partial class MemberCiv : IMemoryPackable<MemberCiv>
{
private const byte CurrentSerializedMemberCount = 9;
private const byte LegacySerializedMemberCountWithoutTeam = 8;
public ulong MemberId;
public uint PlayerId;
public uint CivId;
@ -876,6 +879,82 @@ namespace RuntimeData
public int TeamId;
public bool IsAI;
public bool IsCivFixed;
public static void RegisterFormatter()
{
MemoryPackFormatterProvider.Register(new MemberCivFormatter());
}
public static void Serialize(ref MemoryPackWriter writer, ref MemberCiv value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WriteObjectHeader(CurrentSerializedMemberCount);
writer.WriteUnmanaged(value.MemberId);
writer.WriteUnmanaged(value.PlayerId);
writer.WriteUnmanaged(value.CivId);
writer.WriteUnmanaged(value.ForceId);
writer.WriteUnmanaged(value.IsReady);
writer.WriteUnmanaged(value.Index);
writer.WriteUnmanaged(value.TeamId);
writer.WriteUnmanaged(value.IsAI);
writer.WriteUnmanaged(value.IsCivFixed);
}
public static void Deserialize(ref MemoryPackReader reader, ref MemberCiv value)
{
if (!reader.TryReadObjectHeader(out var memberCount))
{
value = null;
return;
}
if (memberCount != CurrentSerializedMemberCount
&& memberCount != LegacySerializedMemberCountWithoutTeam)
{
MemoryPackSerializationException.ThrowInvalidPropertyCount(
typeof(MemberCiv),
CurrentSerializedMemberCount,
memberCount);
}
value ??= new MemberCiv();
value.MemberId = reader.ReadUnmanaged<ulong>();
value.PlayerId = reader.ReadUnmanaged<uint>();
value.CivId = reader.ReadUnmanaged<uint>();
value.ForceId = reader.ReadUnmanaged<uint>();
value.IsReady = reader.ReadUnmanaged<bool>();
value.Index = reader.ReadUnmanaged<int>();
if (memberCount == LegacySerializedMemberCountWithoutTeam)
{
value.TeamId = MapConfig.NoTeamId;
value.IsAI = reader.ReadUnmanaged<bool>();
value.IsCivFixed = reader.ReadUnmanaged<bool>();
}
else
{
value.TeamId = Math.Max(MapConfig.NoTeamId, reader.ReadUnmanaged<int>());
value.IsAI = reader.ReadUnmanaged<bool>();
value.IsCivFixed = reader.ReadUnmanaged<bool>();
}
}
private sealed class MemberCivFormatter : MemoryPackFormatter<MemberCiv>
{
public override void Serialize(ref MemoryPackWriter writer, ref MemberCiv value)
{
MemberCiv.Serialize(ref writer, ref value);
}
public override void Deserialize(ref MemoryPackReader reader, ref MemberCiv value)
{
MemberCiv.Deserialize(ref reader, ref value);
}
}
}

View File

@ -50,6 +50,7 @@ public class TextDataAssets : ScriptableObject
[MultilingualField]public string OutsideMultiplayRoomMuteSuccess;
[MultilingualField]public string OutsideMultiplayRoomPasswordWrong;
[MultilingualField]public string OutsideMultiplayRoomNotReady;
[MultilingualField]public string OutsideMultiplayRoomAutoCancelReady;
[MultilingualField]public string OutsideHistoryDropListNoLimitP;
[MultilingualField]public string OutsideHistoryDropList2P;

View File

@ -420,7 +420,13 @@ namespace TH1_Logic.Steam
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMembersNotSynced);
return;
}
var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
var wasReady = Main.Instance.MapConfig?.IsMemberReady(selfMemberId) ?? false;
Main.Instance.MapConfig = message.Config;
var isReady = Main.Instance.MapConfig.IsMemberReady(selfMemberId);
if (wasReady && !isReady)
EventManager.Publish(new ShowUIOutsideMultiplayLobbyNotify { UINotifyCommonType = UINotifyCommonType.OutsideMultiplayRoomAutoCancelReady });
if (Main.Instance.MapConfig.HasSameLobbyMembers(LobbyManager.Instance.Lobby.GetAllMemberInfo()))
{
GameNetSender.Instance.MarkLobbyDataSyncedFromHost();

View File

@ -55,6 +55,8 @@ namespace TH1_Logic.Steam
// 连接超时追踪
private readonly Dictionary<CSteamID, float> _connectionTimeouts = new Dictionary<CSteamID, float>();
private const float ConnectionTimeout = 30f; // 30秒超时
private const float ExpectedDisconnectSuppressSeconds = 15f;
private readonly Dictionary<CSteamID, float> _expectedDisconnectPeers = new Dictionary<CSteamID, float>();
// 重连尝试计数
private readonly Dictionary<CSteamID, int> _retryCount = new Dictionary<CSteamID, int>();
@ -166,6 +168,7 @@ namespace TH1_Logic.Steam
public bool ConnectToPeer(CSteamID steamID)
{
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
_expectedDisconnectPeers.Remove(steamID);
if (_connections.TryGetValue(steamID, out var existingConnection))
{
if (TryGetConnectionState(existingConnection, out var state) && IsActiveConnectionState(state))
@ -333,6 +336,7 @@ namespace TH1_Logic.Steam
// 断开与指定玩家的连接
public void DisconnectFromPeer(CSteamID steamID)
{
MarkExpectedDisconnect(steamID);
if (!_connections.TryGetValue(steamID, out var connection))
return;
@ -346,6 +350,12 @@ namespace TH1_Logic.Steam
LogSystem.LogInfo($"Disconnected from peer: {steamID}");
}
public void MarkExpectedDisconnect(CSteamID steamID)
{
if (IsValidRemotePeer(steamID))
_expectedDisconnectPeers[steamID] = Time.time + ExpectedDisconnectSuppressSeconds;
}
// 断开所有连接
public void DisconnectAll()
{
@ -361,6 +371,7 @@ namespace TH1_Logic.Steam
_outgoingPeerQueues.Clear();
_outgoingPeerOrder.Clear();
_outgoingSequences.Clear();
_expectedDisconnectPeers.Clear();
_outgoingQueuedBytes = 0;
_outgoingPeerRoundRobinIndex = 0;
LogSystem.LogInfo("Disconnected from all peers");
@ -458,6 +469,13 @@ namespace TH1_Logic.Steam
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_None:
// 检查具体的失败原因
var endReason = info.m_info.m_eEndReason;
if (TryConsumeExpectedDisconnect(remote))
{
LogSystem.LogInfo($"Expected P2P disconnect completed: {remote}, reason: {endReason}");
HandleDisconnection(remote, info.m_hConn);
break;
}
LogSystem.LogError($"Connection failed - Reason: {endReason}");
var failureReason = $"Connection failed: {remote}, reason: {endReason}";
MarkConnectionFailure(failureReason);
@ -509,6 +527,15 @@ namespace TH1_Logic.Steam
return isTimeout ? NetworkPlayerTipType.P2PConnectionTimeout : NetworkPlayerTipType.P2PConnectionFailed;
}
private bool TryConsumeExpectedDisconnect(CSteamID remote)
{
if (!IsValidRemotePeer(remote)) return false;
if (!_expectedDisconnectPeers.TryGetValue(remote, out var suppressUntil)) return false;
_expectedDisconnectPeers.Remove(remote);
return Time.time <= suppressUntil;
}
private void MarkConnectionFailure(string reason)
{
_lastConnectionFailureTime = Time.time;

View File

@ -787,6 +787,7 @@ namespace TH1_Logic.Steam
}
LogSystem.LogInfo($"Kicking member: {memberId}");
SimpleP2P.Instance.MarkExpectedDisconnect(new CSteamID(memberId));
// 房主写 LobbyData只有房主有权限设 LobbyData所有客户端都能 GetLobbyData 读到
// 之前用的是 SetLobbyMemberData那个 API 只能设自己的成员数据,写出去对方根本读不到 —— 协议不匹配
@ -1615,6 +1616,14 @@ namespace TH1_Logic.Steam
// 房主切换时内部处理
private void OnHostChangedInternal(CSteamID oldOwner, CSteamID newOwner)
{
if (Main.Instance?.GameLogic != null && Main.Instance.GameLogic.GetCurState() == GameState.Menu)
{
LogSystem.LogInfo($"Lobby host transferred in menu(oldHost={oldOwner}, newHost={newOwner})");
CheckConnectionStatus();
ReconcilePreGameLobbyMembers();
return;
}
if (oldOwner.m_SteamID == GetSelfMemberId())
{
LogSystem.LogWarning($"Local host ownership changed to {newOwner}, waiting local disconnect handling");
@ -1626,6 +1635,28 @@ namespace TH1_Logic.Steam
HandleLocalSteamSessionLost($"Host changed from {oldOwner} to {newOwner}");
}
private void ReconcilePreGameLobbyMembers()
{
if (!IsInLobby()) return;
if (Main.Instance?.GameLogic == null || Main.Instance.GameLogic.GetCurState() != GameState.Menu) return;
if (Main.Instance.MapConfig == null) return;
if (IsLobbyOwner())
{
if (Main.Instance.MapConfig.UpdateLobbyMember(GetAllMemberInfo()))
Main.Instance.MapConfig.CheckMapConfigChanged();
else
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
GameNetSender.Instance.ClearLobbyDataSyncState();
return;
}
GameNetSender.Instance.MarkLobbyDataSyncRequired();
GameNetSender.Instance.RequestLobbyData(true);
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
}
// 房间准备完毕时内部处理
private void OnLobbyReadyInternal()
{
@ -1649,6 +1680,7 @@ namespace TH1_Logic.Steam
var members = EnumerateMembers().ToList();
LogSystem.LogInfo($"Lobby members changed. Count: {members.Count}");
UpdateMemberInfo();
ReconcilePreGameLobbyMembers();
OnMembersChangedEvent?.Invoke(members);
}

View File

@ -328,6 +328,8 @@ namespace TH1_UI.View.Outside
//Step #5 订阅大厅列表更新事件
EventManager.Subscribe<UpdateUIOutsideMultiplayLobbyList>(OnLobbyListUpdated);
EventManager.Unsubscribe<ShowUIOutsideMultiplayLobbyNotify>(OnLobbyNotify);
EventManager.Subscribe<ShowUIOutsideMultiplayLobbyNotify>(OnLobbyNotify);
}
@ -456,8 +458,8 @@ namespace TH1_UI.View.Outside
CreateRoomButton.gameObject.SetActive(false);
NoRoomHint.gameObject.SetActive(false);
var memberList = _lobby.GetAllMemberInfo();
ReconcileRoomMembers();
var memberList = _lobby.GetAllMemberInfo();
var multiCivs = Main.Instance.MapConfig.MultiCivs;
BuildRoomMemberRows(memberList, multiCivs);
RenderRoomMemberRows(multiCivs);
@ -846,7 +848,6 @@ namespace TH1_UI.View.Outside
}
if (slotLayoutChanged)
{
Main.Instance.MapConfig.ResetGuestReadyStates();
Main.Instance.MapConfig.CheckMapConfigChanged();
}
}
@ -1894,6 +1895,7 @@ namespace TH1_UI.View.Outside
//取消订阅大厅列表更新事件
EventManager.Unsubscribe<UpdateUIOutsideMultiplayLobbyList>(OnLobbyListUpdated);
EventManager.Unsubscribe<ShowUIOutsideMultiplayLobbyNotify>(OnLobbyNotify);
}
@ -1918,6 +1920,11 @@ namespace TH1_UI.View.Outside
}
}
private void OnLobbyNotify(ShowUIOutsideMultiplayLobbyNotify evt)
{
ShowLobbyNotify(evt.UINotifyCommonType);
}
/// <summary>
/// 点击刷新大厅按钮
/// </summary>
@ -2080,6 +2087,9 @@ namespace TH1_UI.View.Outside
case UINotifyCommonType.OutsideMultiplayRoomNotReady:
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomNotReady);
break;
case UINotifyCommonType.OutsideMultiplayRoomAutoCancelReady:
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomAutoCancelReady);
break;
default:
EventManager.Publish(new ShowUINotifyCommon { UINotifyCommonType = type });
break;

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff