增加热作逻辑
This commit is contained in:
parent
1363d7c2e0
commit
d79f1f9519
@ -103,6 +103,7 @@ AI 的最终执行仍然走 action 层:
|
||||
|
||||
多人行为不要绕过 `CompleteExecute`:
|
||||
|
||||
- 判断本机是否能执行当前回合 action 时,优先使用 `MapData.CanLocalControlPlayer(playerId)`,不要只比较 `CurPlayer == PlayerMap.SelfPlayerData`。单机/房主可以控制多个真实 player 时,`SelfPlayerId` 会为旧 UI/input 临时切到当前本机可控玩家;非本机可控回合应恢复默认本机 player,避免资源、投降、存活等待等 UI 看错对象。
|
||||
- Host 本地 action:`CompleteExecute` 先 `ActionExecute` 广播,广播失败则不执行本地 mutation。
|
||||
- Client 本地 action:`CompleteExecute` 发送 `ActionConfirm` 给 host;发送失败不执行。本地 `TurnEnd` 发送后直接返回 false,等待 host 广播。
|
||||
- Host 收到 `ActionConfirm`:刷新 params,校验当前玩家,调用 `CompleteExecute`,再广播 `ActionExcuteMessage` 并执行。
|
||||
|
||||
@ -44,6 +44,12 @@ Archive files are grouped by folder:
|
||||
|
||||
`Quick` uses fixed record id and file id `quick`. Manual/end/begin ids are GUID-like strings from `GameArchiveManager`.
|
||||
|
||||
Record persistence:
|
||||
|
||||
- `Manual` and `Quick` records are persistent by default and should stay persistent after old data migration.
|
||||
- `Ended` records are non-persistent by default; only explicit user/UI action should persist them.
|
||||
- Append new `GameRecord` fields at the end only. `IsPersistent` belongs after archive id fields.
|
||||
|
||||
## Write Flow
|
||||
|
||||
- New single-player game: write begin after map generation, before first turn refresh.
|
||||
@ -51,12 +57,27 @@ Archive files are grouped by folder:
|
||||
- Ended game: `FinishState.Enter` calls `GameArchiveManager.SaveEndRecord(Main.MapData)`.
|
||||
- Per-turn quick save: `MapData.RefreshTurn` calls `SaveQuickContinueRecord`.
|
||||
- Manual save: UI or upper layer should call `GameArchiveManager.SaveManualGameRecord(recordName)`.
|
||||
- Manual delete: call `GameArchiveManager.DeleteManualGameRecord(record)`; do not delete quick/end records through this path.
|
||||
- Record delete: call `GameArchiveManager.DeleteGameRecord(record)` for any non-`Quick` record. This must only remove the record index, not archive files.
|
||||
|
||||
Do not save archives directly from surrender action. Surrender should mutate game state through the action flow; `AfterExecute` refreshes settlement, `GameLogic.Update` enters `Finished`, and `FinishState.Enter` writes end and clears quick.
|
||||
|
||||
`SaveEndRecord` writes end, creates an `Ended` record except for Tutor, and deletes the current begin's quick record/file. Manual saves survive game end until the player deletes them.
|
||||
|
||||
## Record Cleanup
|
||||
|
||||
Keep record operations and archive-file operations separate:
|
||||
|
||||
- `GameArchiveManager.SetGameRecordPersistent(record, bool)` may only change `Ended` records.
|
||||
- `GameArchiveManager.DeleteGameRecord(record)` may delete `Manual` or `Ended` records, never `Quick`, and must not delete `MapData` files.
|
||||
- `GameArchiveManager.CleanupNonPersistentGameRecords()` removes non-persistent records only. With defaults, this mostly cleans unpinned `Ended` records.
|
||||
- `GameArchiveManager.CleanupUnlinkedLocalMapData()` removes local `MapData` files not referenced by any remaining record.
|
||||
|
||||
Archive cleanup should cover both current and legacy local data:
|
||||
|
||||
- current `Config/GameArchives/begin`, `quick_continue`, `continue`, and `end`;
|
||||
- legacy `Config/map_archive_begin|continue|end[_multi]_{MapID}.dat` and sidecars;
|
||||
- old `Config/begin`, `continue`, `end`, `begincontinue` / `begin_continue`, and `quick_continue` folders when they contain archive payloads.
|
||||
|
||||
## Resume Flow
|
||||
|
||||
Public resume should go through:
|
||||
|
||||
@ -62,14 +62,18 @@ For the one-click Steam P2P stress tool, report fields, and current healthy base
|
||||
6. Keep lobby `MapConfig` host-authoritative.
|
||||
- Pre-game room settings, `MemberCiv`, player slot/team/AI flags, and ready state live in `Main.Instance.MapConfig`.
|
||||
- `MapConfig.MultiCivs` is now the full player-slot list and should be sized to `PlayerCount`; do not treat it as only current lobby members.
|
||||
- Each `MemberCiv` slot uses `Index` as the stable player position. `MemberId != 0` means a real member is bound, `MemberId == 0 && IsAI` means an AI slot, and `TeamId == 0` means no team.
|
||||
- Each `MemberCiv` slot uses `Index` as the stable player position. `MemberId != 0` means a real member is bound, `MemberId == 0 && IsAI` means an AI slot, `MemberId == 0 && !IsAI && IsHostControlled` means a host/local-controlled real player slot, and `TeamId == 0` means no team.
|
||||
- Host-controlled slots are real players, not AI and not lobby members. Keep `IsReady=false`, do not add them to `Net.Players`, skip them when assigning/moving real lobby members, and allow them in `AreAllLobbyMembersReady()`.
|
||||
- `Net.RefreshPlayerNet(mapData)` maps only current lobby `MemberId` values to `PlayerId`; host-controlled slots must not require a Steam member mapping or duplicate the host's mapping.
|
||||
- Local control should go through `MapData.CanLocalControlPlayer(...)`: in singleplayer all real local slots are controllable; in multiplayer the member's mapped slot is controllable, and only the lobby owner may also control `IsHostControlled` slots. When switching turn identity for UI/input, restore the default local member slot on non-local turns.
|
||||
- Call `MapConfig.EnsurePlayerSlots(NetMode.Multi)` before reading or mutating lobby slots, especially after changing `PlayerCount` or receiving host config.
|
||||
- Clients may optimistically update their own `MemberCiv` only after `ChangeCiv` send succeeds, then still accept host `UpdateLobbyData` as authority.
|
||||
- Clients entering a room should request host lobby data until current lobby members match `MapConfig.MultiCivs`.
|
||||
- Host must refresh lobby members before sending lobby config; new guests default not ready, and the owner is always ready.
|
||||
- Changing civ, team, AI slot ownership, slot assignment, or host room settings should clear guest ready state.
|
||||
- UI room-row reconciliation must count host-controlled slots as occupied seats, not open seats, and must not auto-convert them between open and AI.
|
||||
- Host start/resume must require `AreAllLobbyMembersReady()`.
|
||||
- `Net.RefreshPlayerNet(mapData)` maps real lobby `MemberId` values to the slot-created `PlayerId`; AI slots must not require a lobby member mapping.
|
||||
- `Net.RefreshPlayerNet(mapData)` maps real lobby `MemberId` values to the slot-created `PlayerId`; AI and host-controlled slots must not require a lobby member mapping.
|
||||
- `TeamId` drives in-game teammate diplomacy, so host and clients must agree on the full slot list before `GameStart`.
|
||||
|
||||
7. Roll back failed start/resume.
|
||||
@ -115,7 +119,8 @@ For network-heavy changes, inspect these risks explicitly:
|
||||
- Could a caller ignore a failed send and still mutate game state?
|
||||
- Could a client treat optimistic `MemberCiv` or ready state as authoritative before host `UpdateLobbyData`?
|
||||
- Could the host start/resume while any guest is not ready or current lobby members do not match `MapConfig.MultiCivs`?
|
||||
- Could an empty non-AI slot (`MemberId == 0 && !IsAI`) start the game accidentally?
|
||||
- Could an empty non-AI slot (`MemberId == 0 && !IsAI && !IsHostControlled`) start the game accidentally?
|
||||
- Could a host-controlled slot be counted as an open UI seat, converted to AI/open by reconciliation, or inserted into `Net.Players`?
|
||||
- Could code reintroduce the old assumption that `MultiCivs.Count == current lobby member count`?
|
||||
- Could `MemberCiv.Index`, `PlayerId`, or `TeamId` diverge between host and clients before `GameStart`?
|
||||
- Could `MapData` deserialize with missing core fields and still be used?
|
||||
|
||||
@ -224,7 +224,7 @@ namespace RuntimeData
|
||||
|
||||
private static bool CanRemovePlayerSlot(MemberCiv slot)
|
||||
{
|
||||
return slot == null || slot.MemberId == 0 && slot.IsAI;
|
||||
return slot == null || slot.MemberId == 0 && slot.IsAI && !slot.IsHostControlled;
|
||||
}
|
||||
|
||||
private MemberCiv CreateDefaultPlayerSlot(int index, NetMode netMode)
|
||||
@ -239,7 +239,8 @@ namespace RuntimeData
|
||||
TeamId = NoTeamId,
|
||||
IsAI = true,
|
||||
IsReady = false,
|
||||
IsCivFixed = false
|
||||
IsCivFixed = false,
|
||||
IsHostControlled = false
|
||||
};
|
||||
|
||||
return slot;
|
||||
@ -251,6 +252,7 @@ namespace RuntimeData
|
||||
|
||||
var selfSlot = MultiCivs[0];
|
||||
PrepareSinglePlayerSlot(selfSlot, index: 0, isAI: false);
|
||||
selfSlot.IsHostControlled = true;
|
||||
selfSlot.IsCivFixed = true;
|
||||
|
||||
var usedCivs = new HashSet<uint> { selfSlot.CivId };
|
||||
@ -277,6 +279,7 @@ namespace RuntimeData
|
||||
slot.PlayerId = 0;
|
||||
slot.TeamId = NoTeamId;
|
||||
slot.IsAI = isAI;
|
||||
slot.IsHostControlled = !isAI;
|
||||
}
|
||||
|
||||
private static uint PickDefaultCivId(int preferredIndex, HashSet<uint> usedCivs)
|
||||
@ -295,7 +298,12 @@ namespace RuntimeData
|
||||
{
|
||||
slot.Index = index;
|
||||
if (slot.TeamId < NoTeamId) slot.TeamId = NoTeamId;
|
||||
if (slot.MemberId != 0) slot.IsAI = false;
|
||||
if (slot.MemberId != 0)
|
||||
{
|
||||
slot.IsAI = false;
|
||||
slot.IsHostControlled = false;
|
||||
}
|
||||
if (slot.IsAI) slot.IsHostControlled = false;
|
||||
if (slot.MemberId == 0) slot.PlayerId = 0;
|
||||
}
|
||||
|
||||
@ -303,7 +311,12 @@ namespace RuntimeData
|
||||
{
|
||||
slot.Index = index;
|
||||
if (slot.TeamId < NoTeamId) slot.TeamId = NoTeamId;
|
||||
if (slot.MemberId != 0) slot.IsAI = false;
|
||||
if (slot.MemberId != 0)
|
||||
{
|
||||
slot.IsAI = false;
|
||||
slot.IsHostControlled = false;
|
||||
}
|
||||
if (slot.IsAI) slot.IsHostControlled = false;
|
||||
}
|
||||
|
||||
// 根据房间成员信息更新 mapconfig 信息
|
||||
@ -336,6 +349,7 @@ namespace RuntimeData
|
||||
slot.MemberId = kv.Key;
|
||||
slot.PlayerId = 0;
|
||||
slot.IsAI = false;
|
||||
slot.IsHostControlled = false;
|
||||
slot.IsReady = LobbyManager.Instance.Lobby.IsInLobby()
|
||||
&& kv.Key == LobbyManager.Instance.Lobby.GetLobbyOwnerId();
|
||||
changed = true;
|
||||
@ -350,6 +364,7 @@ namespace RuntimeData
|
||||
foreach (var slot in MultiCivs)
|
||||
{
|
||||
if (slot == null || slot.MemberId != 0) continue;
|
||||
if (slot.IsHostControlled) continue;
|
||||
return slot;
|
||||
}
|
||||
|
||||
@ -362,6 +377,7 @@ namespace RuntimeData
|
||||
slot.PlayerId = 0;
|
||||
slot.IsReady = false;
|
||||
slot.IsAI = makeAi;
|
||||
slot.IsHostControlled = false;
|
||||
if (!clearCiv) return;
|
||||
slot.CivId = 0;
|
||||
slot.ForceId = 0;
|
||||
@ -445,6 +461,31 @@ namespace RuntimeData
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetPlayerSlotHostControlled(int index, bool isHostControlled, NetMode netMode = NetMode.Multi)
|
||||
{
|
||||
if (netMode == NetMode.Multi
|
||||
&& LobbyManager.Instance.Lobby.IsInLobby()
|
||||
&& !LobbyManager.Instance.Lobby.IsLobbyOwner())
|
||||
return false;
|
||||
var slot = GetPlayerSlot(index, netMode);
|
||||
if (slot == null) return false;
|
||||
if (isHostControlled)
|
||||
{
|
||||
if (slot.MemberId != 0) return false;
|
||||
if (slot.IsHostControlled && !slot.IsAI) return false;
|
||||
slot.MemberId = 0;
|
||||
slot.PlayerId = 0;
|
||||
slot.IsAI = false;
|
||||
slot.IsHostControlled = true;
|
||||
slot.IsReady = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!slot.IsHostControlled) return false;
|
||||
slot.IsHostControlled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetPlayerSlotCiv(int index, uint civId, uint forceId, NetMode netMode = NetMode.Multi)
|
||||
{
|
||||
var slot = GetPlayerSlot(index, netMode);
|
||||
@ -493,12 +534,14 @@ namespace RuntimeData
|
||||
var current = GetMemberCiv(memberId);
|
||||
var target = MultiCivs[index];
|
||||
if (target.MemberId != 0 && target.MemberId != memberId) return false;
|
||||
if (target.IsHostControlled) 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.IsHostControlled = false;
|
||||
target.IsReady = false;
|
||||
EnsureLobbyOwnerReady();
|
||||
RefreshMultiCivsDict();
|
||||
@ -519,6 +562,30 @@ namespace RuntimeData
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsHostControlledPlayer(uint playerId)
|
||||
{
|
||||
if (playerId == 0 || MultiCivs == null) return false;
|
||||
foreach (var slot in MultiCivs)
|
||||
{
|
||||
if (slot == null || slot.PlayerId != playerId) continue;
|
||||
return slot.IsHostControlled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsRealPlayerSlot(uint playerId)
|
||||
{
|
||||
if (playerId == 0 || MultiCivs == null) return false;
|
||||
foreach (var slot in MultiCivs)
|
||||
{
|
||||
if (slot == null || slot.PlayerId != playerId) continue;
|
||||
return slot.MemberId != 0 || slot.IsHostControlled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ArePlayersInSameTeam(uint playerIdA, uint playerIdB)
|
||||
{
|
||||
if (playerIdA == 0 || playerIdB == 0 || playerIdA == playerIdB) return false;
|
||||
@ -615,6 +682,7 @@ namespace RuntimeData
|
||||
var targetSlot = GetPlayerSlot(index, NetMode.Multi);
|
||||
if (targetSlot == null) return false;
|
||||
if (targetSlot.MemberId != 0 && targetSlot.MemberId != selfMemberId) return false;
|
||||
if (targetSlot.IsHostControlled) return false;
|
||||
|
||||
var next = CopyMemberCiv(memberCiv);
|
||||
next.Index = index;
|
||||
@ -657,6 +725,7 @@ namespace RuntimeData
|
||||
var targetSlot = GetPlayerSlot(index, NetMode.Multi);
|
||||
if (targetSlot == null) return false;
|
||||
if (targetSlot.MemberId != 0 && targetSlot.MemberId != selfMemberId) return false;
|
||||
if (targetSlot.IsHostControlled) return false;
|
||||
|
||||
var next = CopyMemberCiv(memberCiv);
|
||||
next.Index = index;
|
||||
@ -680,7 +749,8 @@ namespace RuntimeData
|
||||
TeamId = memberCiv.TeamId,
|
||||
IsAI = memberCiv.IsAI,
|
||||
IsCivFixed = memberCiv.IsCivFixed,
|
||||
IsReady = memberCiv.IsReady
|
||||
IsReady = memberCiv.IsReady,
|
||||
IsHostControlled = memberCiv.IsHostControlled
|
||||
};
|
||||
}
|
||||
|
||||
@ -701,7 +771,8 @@ namespace RuntimeData
|
||||
TeamId = memberCiv.TeamId,
|
||||
IsAI = memberCiv.IsAI,
|
||||
IsCivFixed = memberCiv.IsCivFixed,
|
||||
IsReady = memberId == ownerId || isReady
|
||||
IsReady = memberId == ownerId || isReady,
|
||||
IsHostControlled = memberCiv.IsHostControlled
|
||||
};
|
||||
return UpdateMemberCiv(next);
|
||||
}
|
||||
@ -738,7 +809,7 @@ namespace RuntimeData
|
||||
foreach (var slot in MultiCivs)
|
||||
{
|
||||
if (slot == null) return false;
|
||||
if (slot.MemberId == 0 && !slot.IsAI) return false;
|
||||
if (slot.MemberId == 0 && !slot.IsAI && !slot.IsHostControlled) return false;
|
||||
}
|
||||
|
||||
var ownerId = LobbyManager.Instance.Lobby.GetLobbyOwnerId();
|
||||
@ -783,7 +854,7 @@ namespace RuntimeData
|
||||
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 && targetSlot != null && memberCiv != targetSlot && targetSlot.MemberId == 0 && !targetSlot.IsHostControlled)
|
||||
{
|
||||
ClearPlayerSlotMember(memberCiv, makeAi: true, clearCiv: true);
|
||||
memberCiv = targetSlot;
|
||||
@ -795,6 +866,7 @@ namespace RuntimeData
|
||||
var nextMemberId = civ.MemberId;
|
||||
var nextIsAI = nextMemberId == 0 && civ.IsAI;
|
||||
if (nextMemberId != 0) nextIsAI = false;
|
||||
var nextIsHostControlled = nextMemberId == 0 && !nextIsAI && civ.IsHostControlled;
|
||||
|
||||
if (memberCiv.MemberId == nextMemberId
|
||||
&& memberCiv.CivId == civ.CivId
|
||||
@ -803,7 +875,8 @@ namespace RuntimeData
|
||||
&& memberCiv.TeamId == civ.TeamId
|
||||
&& memberCiv.IsAI == nextIsAI
|
||||
&& memberCiv.IsCivFixed == civ.IsCivFixed
|
||||
&& memberCiv.IsReady == civ.IsReady) return false;
|
||||
&& memberCiv.IsReady == civ.IsReady
|
||||
&& memberCiv.IsHostControlled == nextIsHostControlled) return false;
|
||||
|
||||
memberCiv.MemberId = nextMemberId;
|
||||
memberCiv.CivId = civ.CivId;
|
||||
@ -813,6 +886,7 @@ namespace RuntimeData
|
||||
memberCiv.IsAI = nextIsAI;
|
||||
memberCiv.IsCivFixed = civ.IsCivFixed;
|
||||
memberCiv.IsReady = civ.IsReady;
|
||||
memberCiv.IsHostControlled = nextIsHostControlled;
|
||||
NormalizePlayerSlot(memberCiv, memberCiv.Index);
|
||||
RefreshMultiCivsDict();
|
||||
return true;
|
||||
@ -915,6 +989,7 @@ namespace RuntimeData
|
||||
public int TeamId;
|
||||
public bool IsAI;
|
||||
public bool IsCivFixed;
|
||||
public bool IsHostControlled;
|
||||
}
|
||||
|
||||
|
||||
@ -2381,7 +2456,7 @@ namespace RuntimeData
|
||||
LogSystem.LogError($"UpdateNextPlayer Error : nextPlayer is null!!!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (Main.MapData.Net.Mode == NetMode.Spectator)
|
||||
{
|
||||
Main.PlayerLogic.StartPlayerTurn(this, nextPlayer.Id);
|
||||
@ -2396,6 +2471,7 @@ namespace RuntimeData
|
||||
GameArchiveManager.Instance.SaveQuickContinueRecord(Main.MapData);
|
||||
AchievementDataManager.Instance.SaveAchievementData();
|
||||
// 设置当前玩家
|
||||
SyncSelfPlayerToLocalControl(nextPlayer.Id);
|
||||
Main.PlayerLogic.StartPlayerTurn(this, nextPlayer.Id);
|
||||
}
|
||||
|
||||
@ -2559,6 +2635,8 @@ namespace RuntimeData
|
||||
|
||||
public bool CheckIsRealPlayer(uint playerId)
|
||||
{
|
||||
if (playerId == 0) return false;
|
||||
if (MapConfig != null && MapConfig.IsRealPlayerSlot(playerId)) return true;
|
||||
if (Net.Mode == NetMode.Multi)
|
||||
{
|
||||
foreach (var kv in Net.Players)
|
||||
@ -2573,16 +2651,77 @@ namespace RuntimeData
|
||||
|
||||
public bool CheckIsAI(uint playerId)
|
||||
{
|
||||
if (Net.Mode == NetMode.Multi)
|
||||
return !CheckIsRealPlayer(playerId);
|
||||
}
|
||||
|
||||
public bool CanLocalControlPlayer(uint playerId)
|
||||
{
|
||||
if (playerId == 0) return false;
|
||||
if (Net?.Mode == NetMode.Multi)
|
||||
{
|
||||
foreach (var kv in Net.Players)
|
||||
{
|
||||
if (kv.Value == playerId) return false;
|
||||
}
|
||||
var lobby = LobbyManager.Instance.Lobby;
|
||||
if (lobby == null || !lobby.IsInLobby()) return playerId == PlayerMap.SelfPlayerId;
|
||||
var selfMemberId = lobby.GetSelfMemberId();
|
||||
if (Net.Players.TryGetValue(selfMemberId, out var selfPlayerId) && selfPlayerId == playerId)
|
||||
return true;
|
||||
return lobby.IsLobbyOwner() && (MapConfig?.IsHostControlledPlayer(playerId) ?? false);
|
||||
}
|
||||
|
||||
if (Net?.Mode == NetMode.Single)
|
||||
return MapConfig?.IsRealPlayerSlot(playerId) ?? playerId == PlayerMap.SelfPlayerId;
|
||||
|
||||
return playerId == PlayerMap.SelfPlayerId;
|
||||
}
|
||||
|
||||
public bool HasSurvivingLocalControlledPlayer()
|
||||
{
|
||||
if (PlayerMap?.PlayerDataList == null) return false;
|
||||
foreach (var player in PlayerMap.PlayerDataList)
|
||||
{
|
||||
if (player == null || !player.IsSurvival) continue;
|
||||
if (CanLocalControlPlayer(player.Id)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SyncSelfPlayerToLocalControl(uint playerId)
|
||||
{
|
||||
if (!CanLocalControlPlayer(playerId)) return false;
|
||||
if (PlayerMap == null || !PlayerMap.GetPlayerDataByPlayerID(playerId, out _)) return false;
|
||||
if (PlayerMap.SelfPlayerId == playerId) return false;
|
||||
PlayerMap.SelfPlayerId = playerId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SyncSelfPlayerToDefaultLocalControl()
|
||||
{
|
||||
if (PlayerMap?.PlayerDataList == null) return false;
|
||||
if (TryGetLocalControlledPlayerId(preferSurvival: true, out var playerId)
|
||||
|| TryGetLocalControlledPlayerId(preferSurvival: false, out playerId))
|
||||
{
|
||||
if (PlayerMap.SelfPlayerId == playerId) return false;
|
||||
PlayerMap.SelfPlayerId = playerId;
|
||||
return true;
|
||||
}
|
||||
|
||||
return playerId != PlayerMap.SelfPlayerId;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetLocalControlledPlayerId(bool preferSurvival, out uint playerId)
|
||||
{
|
||||
playerId = 0;
|
||||
if (PlayerMap?.PlayerDataList == null) return false;
|
||||
foreach (var player in PlayerMap.PlayerDataList)
|
||||
{
|
||||
if (player == null) continue;
|
||||
if (preferSurvival && !player.IsSurvival) continue;
|
||||
if (!CanLocalControlPlayer(player.Id)) continue;
|
||||
playerId = player.Id;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 用于使用指定地图的地图相关内容重生成
|
||||
|
||||
@ -98,9 +98,12 @@ namespace Logic
|
||||
// 开始通用更新,主从客户端都走这一套
|
||||
Main.MapData.RefreshTurn();
|
||||
if (Main.MapData.CurPlayer == null) return;
|
||||
var isLocalTurn = Main.MapData.CanLocalControlPlayer(Main.MapData.CurPlayer.Id);
|
||||
if (isLocalTurn) Main.MapData.SyncSelfPlayerToLocalControl(Main.MapData.CurPlayer.Id);
|
||||
else Main.MapData.SyncSelfPlayerToDefaultLocalControl();
|
||||
if (_curState == GameState.Spectate) return;
|
||||
else if (!Main.MapData.PlayerMap.SelfPlayerData.IsSurvival) ChangeState(GameState.DieWaiting);
|
||||
else if (Main.MapData.CurPlayer.Id == Main.MapData.PlayerMap.SelfPlayerId) ChangeState(GameState.PlayerRound);
|
||||
else if (!Main.MapData.HasSurvivingLocalControlledPlayer()) ChangeState(GameState.DieWaiting);
|
||||
else if (isLocalTurn) ChangeState(GameState.PlayerRound);
|
||||
else ChangeState(GameState.OtherPlayerRound);
|
||||
UpdateAI();
|
||||
UpdateConfirm();
|
||||
@ -179,7 +182,7 @@ namespace Logic
|
||||
|
||||
private bool NeedAI()
|
||||
{
|
||||
if (Main.MapData.CurPlayer == Main.MapData.PlayerMap.SelfPlayerData) return false;
|
||||
if (Main.MapData.CanLocalControlPlayer(Main.MapData.CurPlayer.Id)) return false;
|
||||
|
||||
var slot = Main.MapData.MapConfig?.MultiCivs?.Find(civ => civ != null && civ.PlayerId == Main.MapData.CurPlayer.Id);
|
||||
if (slot != null && slot.MemberId != 0)
|
||||
|
||||
@ -304,7 +304,7 @@ namespace Logic
|
||||
if ((map.Net.Mode == NetMode.Multi || map.Net.Mode == NetMode.Spectator)
|
||||
&& map.Net.Players.ContainsValue(playerId))
|
||||
return;
|
||||
if (map.PlayerMap.SelfPlayerData.Id == playerId) return;
|
||||
if (!map.CheckIsAI(playerId)) return;
|
||||
AIAddMoney(map, playerId);
|
||||
}
|
||||
|
||||
|
||||
@ -151,6 +151,7 @@ namespace TH1_UI.View.Outside
|
||||
private enum RoomMemberRowType
|
||||
{
|
||||
Human,
|
||||
HostControlled,
|
||||
Open,
|
||||
Line,
|
||||
AI
|
||||
@ -472,8 +473,10 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
_roomMemberRows.Clear();
|
||||
var humanRows = new List<RoomMemberRowData>();
|
||||
var hostControlledRows = new List<RoomMemberRowData>();
|
||||
var openRows = new List<RoomMemberRowData>();
|
||||
var emptyAiRows = new List<RoomMemberRowData>();
|
||||
memberList.TryGetValue(_lobby.GetLobbyOwnerId(), out var ownerInfo);
|
||||
|
||||
for (int i = 0; i < multiCivs.Count; i++)
|
||||
{
|
||||
@ -487,6 +490,10 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
emptyAiRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.AI, MemberCiv = mc, SlotIndex = i });
|
||||
}
|
||||
else if (mc.MemberId == 0 && mc.IsHostControlled)
|
||||
{
|
||||
hostControlledRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.HostControlled, MemberCiv = mc, MemberInfo = ownerInfo, SlotIndex = i });
|
||||
}
|
||||
else if (mc.MemberId == 0)
|
||||
{
|
||||
openRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Open, MemberCiv = mc, SlotIndex = i });
|
||||
@ -494,12 +501,14 @@ namespace TH1_UI.View.Outside
|
||||
}
|
||||
|
||||
int totalPlayerCount = Mathf.Max(0, (int)Main.Instance.MapConfig.PlayerCount);
|
||||
int roomSeatCount = Mathf.Clamp(_roomSeatCount, humanRows.Count, Mathf.Min(totalPlayerCount, MaxRoomSeatCount));
|
||||
_openMemberRowCount = Mathf.Max(0, roomSeatCount - humanRows.Count);
|
||||
int occupiedSeatCount = humanRows.Count + hostControlledRows.Count;
|
||||
int roomSeatCount = Mathf.Clamp(_roomSeatCount, occupiedSeatCount, Mathf.Min(totalPlayerCount, MaxRoomSeatCount));
|
||||
_openMemberRowCount = Mathf.Max(0, roomSeatCount - occupiedSeatCount);
|
||||
int aiCount = Mathf.Max(0, totalPlayerCount - roomSeatCount);
|
||||
bool showAiRows = IsTeamAndAIConfigEnabled();
|
||||
|
||||
_roomMemberRows.AddRange(humanRows);
|
||||
_roomMemberRows.AddRange(hostControlledRows);
|
||||
for (int i = 0; i < _openMemberRowCount; i++)
|
||||
{
|
||||
if (i < openRows.Count)
|
||||
@ -592,6 +601,24 @@ namespace TH1_UI.View.Outside
|
||||
showTeamControls);
|
||||
return;
|
||||
}
|
||||
if (row.Type == RoomMemberRowType.HostControlled)
|
||||
{
|
||||
if (row.MemberInfo != null)
|
||||
{
|
||||
memberRow.SetHumanContent(row.MemberInfo, Table.Instance.TextDataAssets.MultiplayRoomOwnerTitle, civ, force, teamId, maxTeamId, _lobby, _lobby.IsLobbyOwner(), forceNameOverride,
|
||||
direction => OnOpenRowForceChanged(row.SlotIndex, direction),
|
||||
direction => OnOpenRowTeamChanged(row.SlotIndex, direction),
|
||||
showTeamControls);
|
||||
}
|
||||
else
|
||||
{
|
||||
memberRow.SetOpenContent(civ, force, teamId, maxTeamId, _lobby.IsLobbyOwner(), forceNameOverride,
|
||||
direction => OnOpenRowForceChanged(row.SlotIndex, direction),
|
||||
direction => OnOpenRowTeamChanged(row.SlotIndex, direction),
|
||||
showTeamControls);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var status = GetHumanStatus(mc);
|
||||
bool canEditHuman = mc.MemberId == _lobby.GetSelfMemberId();
|
||||
@ -815,7 +842,12 @@ namespace TH1_UI.View.Outside
|
||||
_roomSeatCount = Mathf.Clamp(_roomSeatCount, _lobby.GetMemberCount(), maxRoomSeatCount);
|
||||
int lobbyMemberLimit = _lobby.GetMemberLimit();
|
||||
if (lobbyMemberLimit > 0) _roomSeatCount = Mathf.Clamp(lobbyMemberLimit, _lobby.GetMemberCount(), maxRoomSeatCount);
|
||||
int openSlotBudget = Mathf.Max(0, _roomSeatCount - _lobby.GetMemberCount());
|
||||
int hostControlledSeatCount = 0;
|
||||
foreach (var mc in multiCivs)
|
||||
{
|
||||
if (mc != null && mc.MemberId == 0 && mc.IsHostControlled) hostControlledSeatCount++;
|
||||
}
|
||||
int openSlotBudget = Mathf.Max(0, _roomSeatCount - _lobby.GetMemberCount() - hostControlledSeatCount);
|
||||
bool canReconcileOpenSlots = _lobby.IsLobbyOwner();
|
||||
bool slotLayoutChanged = false;
|
||||
var usedCivs = new HashSet<uint>();
|
||||
@ -825,7 +857,7 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
var mc = multiCivs[i];
|
||||
if (mc == null) continue;
|
||||
if (canReconcileOpenSlots && mc.MemberId == 0)
|
||||
if (canReconcileOpenSlots && mc.MemberId == 0 && !mc.IsHostControlled)
|
||||
{
|
||||
bool shouldBeOpenSlot = openSlotBudget > 0;
|
||||
if (mc.IsAI == shouldBeOpenSlot)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user