队伍系统

This commit is contained in:
wuwenbo 2026-05-20 00:05:13 +08:00
parent 2952effbe2
commit a3e26c3af3
8 changed files with 435 additions and 71 deletions

View File

@ -48,6 +48,8 @@ namespace RuntimeData
[MemoryPackable]
public partial class MapConfig
{
public const int NoTeamId = 0;
public uint Id;
public uint Width;
public uint Height;
@ -67,7 +69,7 @@ namespace RuntimeData
public uint selfCivId;
public uint selfForceId;
// 联机用
// 玩家位置槽位。旧名保留用于兼容现有序列化/联机消息。
public List<MemberCiv> MultiCivs;
private Dictionary<ulong, MemberCiv> _memberCivs;
@ -104,6 +106,9 @@ namespace RuntimeData
selfForceId = forceId;
MultiCivs = new List<MemberCiv>();
_memberCivs = new Dictionary<ulong, MemberCiv>();
PlayerSettlements = new List<PlayerSettlementInfo>();
MatchLimits = new List<MatchLimitType>();
EnsurePlayerSlots(NetMode.Single);
}
// MemoryPack 反序列化之后的后处理
@ -117,30 +122,128 @@ namespace RuntimeData
MultiCivs ??= new List<MemberCiv>();
PlayerSettlements ??= new List<PlayerSettlementInfo>();
MatchLimits ??= new List<MatchLimitType>();
EnsurePlayerSlots(NetMode.Multi);
RefreshMultiCivsDict();
}
public void EnsurePlayerSlots(NetMode netMode = NetMode.Multi)
{
MultiCivs ??= new List<MemberCiv>();
var targetCount = (int)PlayerCount;
for (int i = 0; i < MultiCivs.Count; i++)
{
MultiCivs[i] ??= CreateDefaultPlayerSlot(i, netMode);
}
while (MultiCivs.Count < targetCount)
{
MultiCivs.Add(CreateDefaultPlayerSlot(MultiCivs.Count, netMode));
}
if (MultiCivs.Count > targetCount)
{
MultiCivs.RemoveRange(targetCount, MultiCivs.Count - targetCount);
}
for (int i = 0; i < MultiCivs.Count; i++)
{
NormalizePlayerSlot(MultiCivs[i], i);
}
if (netMode == NetMode.Single) ApplySinglePlayerSlotDefaults();
RefreshMultiCivsDict();
}
private MemberCiv CreateDefaultPlayerSlot(int index, NetMode netMode)
{
var slot = new MemberCiv
{
Index = index,
MemberId = 0,
PlayerId = 0,
CivId = 0,
ForceId = 0,
TeamId = NoTeamId,
IsAI = true,
IsReady = false,
IsCivFixed = false
};
if (netMode == NetMode.Single)
{
ApplySinglePlayerSlotDefault(slot, index);
}
return slot;
}
private void ApplySinglePlayerSlotDefaults()
{
for (int i = 0; i < MultiCivs.Count; i++)
{
ClearPlayerSlotMember(MultiCivs[i], makeAi: i != 0);
ApplySinglePlayerSlotDefault(MultiCivs[i], i);
}
}
private void ApplySinglePlayerSlotDefault(MemberCiv slot, int index)
{
slot.Index = index;
slot.IsAI = index != 0;
slot.IsReady = false;
slot.PlayerId = 0;
slot.TeamId = NoTeamId;
slot.IsCivFixed = true;
if (index == 0)
{
slot.CivId = selfCivId;
slot.ForceId = selfForceId;
return;
}
slot.CivId = selfCivId == index ? 0u : (uint)index;
slot.ForceId = selfCivId == index ? 0u : (uint)index;
}
private void NormalizePlayerSlot(MemberCiv slot, int index)
{
slot.Index = index;
if (slot.TeamId < NoTeamId) slot.TeamId = NoTeamId;
if (slot.MemberId != 0) slot.IsAI = false;
if (slot.MemberId == 0) slot.PlayerId = 0;
}
// 根据房间成员信息更新 mapconfig 信息
public bool UpdateLobbyMember(Dictionary<ulong, MemberInfo> memberInfos)
{
if (memberInfos == null) return false;
MultiCivs ??= new List<MemberCiv>();
EnsurePlayerSlots(NetMode.Multi);
var changed = false;
// 先剔除已离开 lobby 的成员,避免 MultiCivs 留下幽灵占位:
// 旧版只 Add 不 Remove会导致大厅 nP 跳号(1P/4P/5P/6P)
// 以及开战时给离线幽灵创 PlayerData、占用 PlayerCount 名额、AI 补位变少。
changed |= MultiCivs.RemoveAll(mc => mc == null || !memberInfos.ContainsKey(mc.MemberId)) > 0;
// 先解绑已离开 lobby 的成员,避免槽位留下幽灵真人。
foreach (var slot in MultiCivs)
{
if (slot == null || slot.MemberId == 0 || memberInfos.ContainsKey(slot.MemberId)) continue;
ClearPlayerSlotMember(slot, makeAi: true, clearCiv: true);
changed = true;
}
RefreshMultiCivsDict();
foreach (var kv in memberInfos)
{
if (_memberCivs.ContainsKey(kv.Key)) continue;
var civ = new MemberCiv();
civ.MemberId = kv.Key;
civ.CivId = 0;
civ.ForceId = 0;
civ.IsReady = LobbyManager.Instance.Lobby.IsInLobby()
&& kv.Key == LobbyManager.Instance.Lobby.GetLobbyOwnerId();
MultiCivs.Add(civ);
var slot = GetFirstAssignableSlot();
if (slot == null)
{
LogSystem.LogError($"房间人数超过玩家槽位数量: member={kv.Key}, playerCount={PlayerCount}");
continue;
}
slot.MemberId = kv.Key;
slot.PlayerId = 0;
slot.IsAI = false;
slot.IsReady = LobbyManager.Instance.Lobby.IsInLobby()
&& kv.Key == LobbyManager.Instance.Lobby.GetLobbyOwnerId();
changed = true;
}
changed |= EnsureLobbyOwnerReady();
@ -148,6 +251,29 @@ namespace RuntimeData
return changed;
}
private MemberCiv GetFirstAssignableSlot()
{
foreach (var slot in MultiCivs)
{
if (slot == null || slot.MemberId != 0) continue;
return slot;
}
return null;
}
private static void ClearPlayerSlotMember(MemberCiv slot, bool makeAi, bool clearCiv = false)
{
slot.MemberId = 0;
slot.PlayerId = 0;
slot.IsReady = false;
slot.IsAI = makeAi;
if (!clearCiv) return;
slot.CivId = 0;
slot.ForceId = 0;
slot.IsCivFixed = false;
}
private bool EnsureLobbyOwnerReady()
{
if (!LobbyManager.Instance.Lobby.IsInLobby()) return false;
@ -174,6 +300,7 @@ namespace RuntimeData
foreach (var memberCiv in MultiCivs)
{
if (memberCiv == null) continue;
if (memberCiv.MemberId == 0) continue;
_memberCivs[memberCiv.MemberId] = memberCiv;
}
}
@ -189,6 +316,135 @@ namespace RuntimeData
return null;
}
public MemberCiv GetPlayerSlot(int index)
{
EnsurePlayerSlots(NetMode.Multi);
if (index < 0 || index >= MultiCivs.Count) return null;
return MultiCivs[index];
}
public bool SetPlayerSlotTeam(int index, int teamId)
{
var slot = GetPlayerSlot(index);
if (slot == null) return false;
teamId = Math.Max(NoTeamId, teamId);
if (slot.TeamId == teamId) return false;
slot.TeamId = teamId;
return true;
}
public bool SetPlayerSlotAI(int index, bool isAI)
{
var slot = GetPlayerSlot(index);
if (slot == null) return false;
if (isAI)
{
var changed = slot.MemberId != 0 || !slot.IsAI;
ClearPlayerSlotMember(slot, makeAi: true);
return changed;
}
if (!slot.IsAI) return false;
slot.IsAI = false;
return true;
}
public bool SetPlayerSlotCiv(int index, uint civId, uint forceId)
{
var slot = GetPlayerSlot(index);
if (slot == null) return false;
if (slot.CivId == civId && slot.ForceId == forceId && slot.IsCivFixed) return false;
slot.CivId = civId;
slot.ForceId = forceId;
slot.IsCivFixed = true;
return true;
}
public bool SetMemberSlot(ulong memberId, int index)
{
if (memberId == 0) return false;
EnsurePlayerSlots(NetMode.Multi);
if (index < 0 || index >= MultiCivs.Count) return false;
var current = GetMemberCiv(memberId);
var target = MultiCivs[index];
if (target.MemberId != 0 && target.MemberId != memberId) return false;
if (current == target) return false;
if (current != null) ClearPlayerSlotMember(current, makeAi: true, clearCiv: true);
target.MemberId = memberId;
target.PlayerId = 0;
target.IsAI = false;
target.IsReady = false;
EnsureLobbyOwnerReady();
RefreshMultiCivsDict();
return true;
}
public bool TryGetTeamIdByPlayerId(uint playerId, out int teamId)
{
teamId = NoTeamId;
if (playerId == 0 || MultiCivs == null) return false;
foreach (var slot in MultiCivs)
{
if (slot == null || slot.PlayerId != playerId) continue;
teamId = slot.TeamId;
return teamId != NoTeamId;
}
return false;
}
public bool ArePlayersInSameTeam(uint playerIdA, uint playerIdB)
{
if (playerIdA == 0 || playerIdB == 0 || playerIdA == playerIdB) return false;
return TryGetTeamIdByPlayerId(playerIdA, out var teamA)
&& TryGetTeamIdByPlayerId(playerIdB, out var teamB)
&& teamA != NoTeamId
&& teamA == teamB;
}
public void ApplyTeamDiplomacy(MapData mapData)
{
if (mapData?.PlayerMap?.PlayerDataList == null) return;
EnsurePlayerSlots(mapData.Net?.Mode ?? NetMode.Multi);
BindSlotPlayerIdsByIndex(mapData);
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
if (player == null) continue;
foreach (var target in mapData.PlayerMap.PlayerDataList)
{
if (target == null || player.Id == target.Id) continue;
if (!ArePlayersInSameTeam(player.Id, target.Id)) continue;
if (player.GetCountryDiplomacyInfo(target.Id, out var info))
{
ApplyTeammateDiplomacy(info);
}
}
}
}
private void BindSlotPlayerIdsByIndex(MapData mapData)
{
if (mapData?.PlayerMap?.PlayerDataList == null || MultiCivs == null) return;
for (int i = 0; i < MultiCivs.Count; i++)
{
if (i >= mapData.PlayerMap.PlayerDataList.Count) break;
if (MultiCivs[i] == null) continue;
MultiCivs[i].PlayerId = mapData.PlayerMap.PlayerDataList[i].Id;
}
}
private static void ApplyTeammateDiplomacy(CountryDiplomacyInfo info)
{
if (info == null) return;
info.IsTeammate = true;
info.DiplomacyState = DiplomacyState.League;
info.IsLeagueRequest = false;
info.IsLeagueRupture = false;
}
// 主从端一致的更新某一个成员信息
public bool UpdateMemberCiv(MemberCiv civ)
@ -230,10 +486,14 @@ namespace RuntimeData
var ownerId = lobby.IsInLobby() ? lobby.GetLobbyOwnerId() : 0;
var next = new MemberCiv
{
Index = memberCiv.Index,
MemberId = memberCiv.MemberId,
PlayerId = memberCiv.PlayerId,
CivId = memberCiv.CivId,
ForceId = memberCiv.ForceId,
TeamId = memberCiv.TeamId,
IsAI = memberCiv.IsAI,
IsCivFixed = memberCiv.IsCivFixed,
IsReady = memberId == ownerId || isReady
};
return UpdateMemberCiv(next);
@ -268,6 +528,11 @@ namespace RuntimeData
if (!LobbyManager.Instance.Lobby.IsInLobby()) return false;
var memberInfos = LobbyManager.Instance.Lobby.GetAllMemberInfo();
if (!HasSameLobbyMembers(memberInfos)) return false;
foreach (var slot in MultiCivs)
{
if (slot == null) return false;
if (slot.MemberId == 0 && !slot.IsAI) return false;
}
var ownerId = LobbyManager.Instance.Lobby.GetLobbyOwnerId();
foreach (var memberId in memberInfos.Keys)
@ -299,31 +564,42 @@ namespace RuntimeData
private bool ApplyMemberCivLocal(MemberCiv civ)
{
MultiCivs ??= new List<MemberCiv>();
foreach (var memberCiv in MultiCivs)
EnsurePlayerSlots(NetMode.Multi);
var memberCiv = civ.MemberId != 0 ? GetMemberCiv(civ.MemberId) : null;
var targetIndex = civ.Index;
var targetSlot = targetIndex >= 0 && targetIndex < MultiCivs.Count ? MultiCivs[targetIndex] : null;
if (memberCiv != null && targetSlot != null && memberCiv != targetSlot && targetSlot.MemberId == 0)
{
if (memberCiv == null) continue;
if (memberCiv.MemberId != civ.MemberId) continue;
if (memberCiv.CivId == civ.CivId
&& memberCiv.ForceId == civ.ForceId
&& memberCiv.PlayerId == civ.PlayerId
&& memberCiv.IsReady == civ.IsReady) return false;
memberCiv.CivId = civ.CivId;
memberCiv.ForceId = civ.ForceId;
memberCiv.PlayerId = civ.PlayerId;
memberCiv.IsReady = civ.IsReady;
RefreshMultiCivsDict();
return true;
ClearPlayerSlotMember(memberCiv, makeAi: true, clearCiv: true);
memberCiv = targetSlot;
}
MultiCivs.Add(new MemberCiv
{
MemberId = civ.MemberId,
CivId = civ.CivId,
ForceId = civ.ForceId,
PlayerId = civ.PlayerId,
IsReady = civ.IsReady
});
memberCiv ??= targetSlot;
if (memberCiv == null) return false;
var nextMemberId = civ.MemberId;
var nextIsAI = nextMemberId == 0 && civ.IsAI;
if (nextMemberId != 0) nextIsAI = false;
if (memberCiv.MemberId == nextMemberId
&& memberCiv.CivId == civ.CivId
&& memberCiv.ForceId == civ.ForceId
&& memberCiv.PlayerId == civ.PlayerId
&& memberCiv.TeamId == civ.TeamId
&& memberCiv.IsAI == nextIsAI
&& memberCiv.IsCivFixed == civ.IsCivFixed
&& memberCiv.IsReady == civ.IsReady) return false;
memberCiv.MemberId = nextMemberId;
memberCiv.CivId = civ.CivId;
memberCiv.ForceId = civ.ForceId;
memberCiv.PlayerId = civ.PlayerId;
memberCiv.TeamId = civ.TeamId;
memberCiv.IsAI = nextIsAI;
memberCiv.IsCivFixed = civ.IsCivFixed;
memberCiv.IsReady = civ.IsReady;
NormalizePlayerSlot(memberCiv, memberCiv.Index);
RefreshMultiCivsDict();
return true;
}
@ -331,13 +607,13 @@ namespace RuntimeData
public bool HasSameLobbyMembers(Dictionary<ulong, MemberInfo> memberInfos)
{
if (memberInfos == null) return false;
MultiCivs ??= new List<MemberCiv>();
if (MultiCivs.Count != memberInfos.Count) return false;
EnsurePlayerSlots(NetMode.Multi);
var seen = new HashSet<ulong>();
foreach (var memberCiv in MultiCivs)
{
if (memberCiv == null) return false;
if (memberCiv.MemberId == 0) continue;
if (!memberInfos.ContainsKey(memberCiv.MemberId)) return false;
if (!seen.Add(memberCiv.MemberId)) return false;
}
@ -364,6 +640,7 @@ namespace RuntimeData
// 从 MapData 中重绑定 MemberCiv 的 Player 信息
public bool ReBindPlayerInfoFromMapData(MapData mapData)
{
EnsurePlayerSlots(mapData.Net.Mode);
RefreshMultiCivsDict();
if (mapData.Net.Mode == NetMode.Single)
{
@ -384,13 +661,21 @@ namespace RuntimeData
{
foreach (var member in MultiCivs)
{
foreach (var player in mapData.PlayerMap.PlayerDataList)
if (member.Index >= 0 && member.Index < mapData.PlayerMap.PlayerDataList.Count)
{
if (player.PlayerCivId != member.CivId || player.PlayerForceId != member.ForceId) continue;
member.PlayerId = player.Id;
member.PlayerId = mapData.PlayerMap.PlayerDataList[member.Index].Id;
}
else
{
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
if (player.PlayerCivId != member.CivId || player.PlayerForceId != member.ForceId) continue;
member.PlayerId = player.Id;
break;
}
}
if (member.PlayerId == 0)
if (member.MemberId != 0 && member.PlayerId == 0)
{
LogSystem.LogError($"联机模式指定地图中找不到指定的阵营信息, 启动游戏失败, " +
$"地图 ID : {Id} 文明 ID : {member.CivId} 势力 ID : {member.ForceId}");
@ -398,12 +683,20 @@ namespace RuntimeData
}
}
}
ApplyTeamDiplomacy(mapData);
return true;
}
public void ClearMultiCivs()
{
MultiCivs?.Clear();
if (MultiCivs != null)
{
foreach (var slot in MultiCivs)
{
ClearPlayerSlotMember(slot, makeAi: true, clearCiv: true);
slot.TeamId = NoTeamId;
}
}
_memberCivs?.Clear();
}
@ -449,10 +742,14 @@ namespace RuntimeData
[MemoryPackable]
public partial class MemberCiv
{
public int Index;
public ulong MemberId;
public uint PlayerId;
public uint CivId;
public uint ForceId;
public int TeamId;
public bool IsAI;
public bool IsCivFixed;
public bool IsReady;
}
@ -1009,7 +1306,7 @@ namespace RuntimeData
if (pidA == pidB) return true;
if (!PlayerMap.GetPlayerDataByPlayerID(pidA, out var pA)) return false;
pA.GetCountryDiplomacyInfo(pidB,out var dipInfo);
return dipInfo.DiplomacyState == DiplomacyState.League;
return dipInfo != null && (dipInfo.IsTeammate || dipInfo.DiplomacyState == DiplomacyState.League);
}
public bool SameUnionByUnitId(uint uidA, uint uidB)
@ -1025,7 +1322,9 @@ namespace RuntimeData
if (pidA == pidB) return true;
if (!PlayerMap.GetPlayerDataByPlayerID(pidA, out var pA)) return false;
pA.GetCountryDiplomacyInfo(pidB,out var dipInfo);
return dipInfo.DiplomacyState is DiplomacyState.League or DiplomacyState.LeagueRupture ;
return dipInfo != null
&& (dipInfo.IsTeammate
|| dipInfo.DiplomacyState is DiplomacyState.League or DiplomacyState.LeagueRupture);
}
//-------------- 查询方法方法 -------------------//
@ -1558,6 +1857,7 @@ namespace RuntimeData
action.Param.MapData = this;
action.Param.RefreshParams();
}
MapConfig?.ApplyTeamDiplomacy(this);
}
// 当场上有小兵受伤前
@ -2304,10 +2604,26 @@ namespace RuntimeData
// 游戏是否结束
public bool CheckIfGameEnd(out bool isWin)
{
isWin = MatchSettlement.IsWin(PlayerMap.SelfPlayerId);
isWin = IsPlayerOrTeammateWin(PlayerMap.SelfPlayerId);
return MatchSettlement.IsFinished;
}
public bool IsPlayerOrTeammateWin(uint playerId)
{
if (MatchSettlement == null) return false;
if (MatchSettlement.IsWin(playerId)) return true;
if (MapConfig == null || PlayerMap?.PlayerDataList == null) return false;
foreach (var player in PlayerMap.PlayerDataList)
{
if (player == null || player.Id == playerId) continue;
if (!MapConfig.ArePlayersInSameTeam(playerId, player.Id)) continue;
if (MatchSettlement.IsWin(player.Id)) return true;
}
return false;
}
// // 游戏是否结束
// public bool CheckIfGameEnd(out bool isWin)
// {

View File

@ -305,7 +305,11 @@ namespace RuntimeData
{
if (memberCiv == null || memberCiv.PlayerId == 0) continue;
if (!lobbyMemberSet.Contains(memberCiv.MemberId)) continue;
if (Players.ContainsKey(memberCiv.MemberId)) continue;
if (Players.TryGetValue(memberCiv.MemberId, out var mappedPlayerId))
{
if (mappedPlayerId != memberCiv.PlayerId) Players[memberCiv.MemberId] = memberCiv.PlayerId;
continue;
}
if (Players.ContainsValue(memberCiv.PlayerId)) continue;
Players[memberCiv.MemberId] = memberCiv.PlayerId;
}

View File

@ -118,47 +118,44 @@ namespace RuntimeData
if (netMode == NetMode.Single)
{
for (int i = 0; i < map.MapConfig.PlayerCount; i++)
map.MapConfig.EnsurePlayerSlots(NetMode.Single);
for (int i = 0; i < map.MapConfig.MultiCivs.Count; i++)
{
PlayerData player;
//处理player到底是谁的问题。目前临时使用的方案今后要改。目前civ一定=force,所以可以这么做
if (i == 0)
player = new PlayerData(map.MapConfig.selfCivId,map.MapConfig.selfForceId,idGenerator);
else if (map.MapConfig.selfCivId == i)
player = new PlayerData((uint)0,(uint)0,idGenerator);
else
player = new PlayerData((uint)i,(uint)i,idGenerator);
var slot = map.MapConfig.MultiCivs[i];
var player = new PlayerData(slot.CivId, slot.ForceId, idGenerator);
PlayerDataList.Add(player);
_playerDataDict[player.Id] = player;
slot.PlayerId = player.Id;
}
}
if (netMode == NetMode.Multi)
{
map.MapConfig.EnsurePlayerSlots(NetMode.Multi);
// 这里 civList 要包含所有 civId (civId = civEnum - 1目前 17 个 civ → 0..16)
var civList = new List<uint>();
for (int i = 0; i < 17; i++) civList.Add((uint)i);
foreach (var multiCiv in map.MapConfig.MultiCivs)
foreach (var slot in map.MapConfig.MultiCivs)
{
var player = new PlayerData(multiCiv.CivId, multiCiv.CivId, idGenerator);
var civId = slot.CivId;
var forceId = slot.ForceId;
if (slot.MemberId == 0 && slot.IsAI && !slot.IsCivFixed)
{
var idx = civList.Count > 0 ? UnityEngine.Random.Range(0, civList.Count) : 0;
civId = civList.Count > 0 ? civList[idx] : 0u;
forceId = civId;
slot.CivId = civId;
slot.ForceId = forceId;
}
civList.Remove(civId);
var player = new PlayerData(civId, forceId, idGenerator);
PlayerDataList.Add(player);
_playerDataDict[player.Id] = player;
civList.Remove(multiCiv.CivId);
multiCiv.PlayerId = player.Id;
}
for (int i = map.MapConfig.MultiCivs.Count; i < map.MapConfig.PlayerCount; i++)
{
int idx = UnityEngine.Random.Range(0, civList.Count);//map.Net.GetRandom().Next
var civId = civList[idx];
var player = new PlayerData(civId, civId, idGenerator);
PlayerDataList.Add(player);
_playerDataDict[player.Id] = player;
civList.RemoveAt(idx);
slot.PlayerId = player.Id;
}
}
SelfPlayerId = PlayerDataList[0].Id;
if (PlayerDataList.Count > 0) SelfPlayerId = PlayerDataList[0].Id;
foreach (var self in PlayerDataList)
{
foreach (var target in PlayerDataList)
@ -167,6 +164,7 @@ namespace RuntimeData
self.DiplomacyData.AddCountryDiplomacyInfo(target.Id);
}
}
map.MapConfig.ApplyTeamDiplomacy(map);
}
public PlayerMapData(PlayerMapData copyData)
@ -520,6 +518,13 @@ namespace RuntimeData
{
if (player.Id == Id) continue;
this.GetCountryDiplomacyInfo(player.Id, out var selfToPlayer);
if (selfToPlayer.IsTeammate)
{
selfToPlayer.DiplomacyState = DiplomacyState.League;
selfToPlayer.IsLeagueRequest = false;
selfToPlayer.IsLeagueRupture = false;
continue;
}
// 回合结束处理联盟破裂标记
if (selfToPlayer.DiplomacyState == DiplomacyState.LeagueRupture) selfToPlayer.IsLeagueRupture = true;
// 回合结束处理AI 冷静期计数
@ -916,6 +921,18 @@ namespace RuntimeData
if (player.Id == Id) continue;
this.GetCountryDiplomacyInfo(player.Id, out var selfToPlayer);
player.GetCountryDiplomacyInfo(Id, out var playerToSelf);
if (selfToPlayer.IsTeammate || playerToSelf.IsTeammate)
{
selfToPlayer.IsTeammate = true;
playerToSelf.IsTeammate = true;
selfToPlayer.DiplomacyState = DiplomacyState.League;
playerToSelf.DiplomacyState = DiplomacyState.League;
selfToPlayer.IsLeagueRequest = false;
playerToSelf.IsLeagueRequest = false;
selfToPlayer.IsLeagueRupture = false;
playerToSelf.IsLeagueRupture = false;
continue;
}
// 如果没有外交关系,且没有见过面,则设置为无外交关系
if (!MeetPlayers.Contains(player.Id)) selfToPlayer.DiplomacyState = DiplomacyState.NoDiplomacy;
// 如果没有外交关系,且见过面,则设置为中立
@ -1029,6 +1046,7 @@ namespace RuntimeData
{
if (otherPlayer.Id == Id) continue;
if (!this.GetCountryDiplomacyInfo(otherPlayer.Id, out var selfToOther)) continue;
if (selfToOther.IsTeammate) continue;
if (selfToOther.DiplomacyState != DiplomacyState.League
&& selfToOther.DiplomacyState != DiplomacyState.LeagueRupture) continue;

View File

@ -224,6 +224,7 @@ namespace TH1_Logic.Action
//Step #6 处理break结盟
if (_actionId.PlayerActionType == PlayerActionType.BreakAlly)
{
if (dipInfo.IsTeammate || dipInfo2.IsTeammate) return false;
//Step #1 设置双方的外交状态为背盟
dipInfo.DiplomacyState = DiplomacyState.LeagueRupture;
dipInfo2.DiplomacyState = DiplomacyState.LeagueRupture;
@ -352,10 +353,12 @@ namespace TH1_Logic.Action
if (_actionId.PlayerActionType == PlayerActionType.BreakAlly)
{
actionParams.PlayerData.GetCountryDiplomacyInfo(actionParams.TargetPlayerData.Id, out var dipInfo);
if (dipInfo.IsTeammate) return false;
//如果没有结盟 return
if (dipInfo.DiplomacyState != DiplomacyState.League) return false;
//如果对方率先breakreturn
actionParams.TargetPlayerData.GetCountryDiplomacyInfo(actionParams.PlayerData.Id,out var dipInfo2);
if (dipInfo2.IsTeammate) return false;
if (dipInfo2.DiplomacyState == DiplomacyState.LeagueRupture) return false;
return true;
}
@ -446,10 +449,12 @@ namespace TH1_Logic.Action
//Step #8 判断能否break结盟
{
actionParams.PlayerData.GetCountryDiplomacyInfo(actionParams.TargetPlayerData.Id, out var dipInfo);
if (dipInfo.IsTeammate) return false;
//如果没有结盟 return
if (dipInfo.DiplomacyState != DiplomacyState.League) return false;
//如果对方率先breakreturn
actionParams.TargetPlayerData.GetCountryDiplomacyInfo(actionParams.PlayerData.Id,out var dipInfo2);
if (dipInfo2.IsTeammate) return false;
if (dipInfo2.DiplomacyState == DiplomacyState.LeagueRupture) return false;
return true;
}
@ -488,6 +493,7 @@ namespace TH1_Logic.Action
//Step #3 计算breakAlly按钮的情况
if (_actionId.PlayerActionType == PlayerActionType.BreakAlly)
{
if (dipInfo.IsTeammate || dipInfo2.IsTeammate) return ActionShowState.Unavailable;
return ActionShowState.Available;
}
@ -597,4 +603,4 @@ namespace TH1_Logic.Action
return true;
}
}
}
}

View File

@ -1702,6 +1702,18 @@ namespace Logic
if (!originPlayer.GetCountryDiplomacyInfo(targetPlayer.Id, out var player1ToPlayer2)) return;
if (!targetPlayer.GetCountryDiplomacyInfo(originPlayer.Id, out var player2ToPlayer1)) return;
if ((player1ToPlayer2.IsTeammate || player2ToPlayer1.IsTeammate) && state != DiplomacyState.League)
{
player1ToPlayer2.IsTeammate = true;
player2ToPlayer1.IsTeammate = true;
player1ToPlayer2.DiplomacyState = DiplomacyState.League;
player2ToPlayer1.DiplomacyState = DiplomacyState.League;
player1ToPlayer2.IsLeagueRequest = false;
player2ToPlayer1.IsLeagueRequest = false;
player1ToPlayer2.IsLeagueRupture = false;
player2ToPlayer1.IsLeagueRupture = false;
return;
}
player1ToPlayer2.DiplomacyState = state;
player2ToPlayer1.DiplomacyState = state;
if (state == DiplomacyState.War)

View File

@ -122,10 +122,14 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
var next = new MemberCiv
{
Index = t.Index,
MemberId = selfMemberId,
PlayerId = t.PlayerId,
CivId = Table.Instance.TransCivEnumToCivId(civ),
ForceId = Table.Instance.TransForceEnumToForceId(force),
TeamId = t.TeamId,
IsAI = false,
IsCivFixed = true,
IsReady = false
};

View File

@ -307,6 +307,7 @@ namespace TH1_UI.View.Outside
var memberList = _lobby.GetAllMemberInfo();
Main.Instance.MapConfig.EnsurePlayerSlots(NetMode.Multi);
var multiCivs = Main.Instance.MapConfig.MultiCivs;
// 按 MultiCivs 顺序渲染(与游戏内 PlayerDataList 中真人玩家顺序一致),
// 这样大厅显示的全局位置与进入游戏后的 nP 编号对齐。
@ -729,6 +730,7 @@ namespace TH1_UI.View.Outside
Main.Instance.MapConfig.AIDiff = diff;
Main.Instance.MapConfig.GameMode = gameMode;
Main.Instance.MapConfig.WaterType = waterType;
Main.Instance.MapConfig.EnsurePlayerSlots(NetMode.Multi);
if (resetGuestReady) Main.Instance.MapConfig.ResetGuestReadyStates();
// CREATIVE 下持久化 MapSize 选择(独立 key不与单机冲突
@ -790,6 +792,7 @@ namespace TH1_UI.View.Outside
Main.Instance.MapConfig.PlayerCount = playerCount;
Main.Instance.MapConfig.Width = mapSize;
Main.Instance.MapConfig.Height = mapSize;
Main.Instance.MapConfig.EnsurePlayerSlots(NetMode.Multi);
if (_lobby.IsLobbyOwner()) Main.Instance.MapConfig.ResetGuestReadyStates();
// 触发房主→成员的整包广播hash 比对幂等,不会刷屏)
Main.Instance.MapConfig.CheckMapConfigChanged();

View File

@ -294,6 +294,7 @@ namespace TH1_UI.View.Outside
Main.Instance.MapConfig.selfForceId = Table.Instance.TransForceEnumToForceId(_selectEmpire.Force);
Main.Instance.MapConfig.AIDiff = diff;
Main.Instance.MapConfig.WaterType = waterType;
Main.Instance.MapConfig.EnsurePlayerSlots(NetMode.Single);
// Save the selected empire for next time
SaveLastSelectedEmpire(_selectEmpire);