联机功能开发

This commit is contained in:
daixiawu 2026-05-26 16:19:00 +08:00
parent 792081cb82
commit d104b96e00
20 changed files with 964 additions and 224 deletions

View File

@ -1,5 +1,5 @@
{
"nextId": 266,
"nextId": 267,
"bugs": [
{
"id": 2,
@ -2630,6 +2630,16 @@
"module": "",
"createdAt": 1779610731847,
"updatedAt": 1779610731847
},
{
"id": 266,
"title": "遇到個挺神秘的bug在戀戀被擊殺的被動觸發以後我的單位同時被觸發的被動擊殺後她們部隊兵棋還會留在地圖上p1p2 在此之後整個地靈殿陣營的部隊都會隱身直到占領我方地區的時候才會出現有點像瞬移一樣XDbug出現後游戲回合在超過30回合也不會結束(p3p4)",
"description": "",
"status": "open",
"priority": "medium",
"module": "",
"createdAt": 1779776751092,
"updatedAt": 1779776751092
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1526,7 +1526,7 @@ MonoBehaviour:
NoExport: 0
FontBan: 0
Preset: 0
ID: 19877
ID: 20072
FontID: 0
TextCfg: []
--- !u!1 &5647923838447659735
@ -1755,7 +1755,7 @@ MonoBehaviour:
NoExport: 0
FontBan: 0
Preset: 0
ID: 2226
ID: 20071
FontID: 2
TextCfg:
- Type: 1

View File

@ -137,7 +137,7 @@ MonoBehaviour:
DocText: {fileID: 3795164220530813026}
MaxChatCount: 2
MessageLifetime: 10
DocRecentCount: 10
DocRecentCount: 50
--- !u!1 &2477995237858003733
GameObject:
m_ObjectHideFlags: 0
@ -1044,7 +1044,7 @@ MonoBehaviour:
NoExport: 0
FontBan: 0
Preset: 0
ID: 19739
ID: 20076
FontID: 0
TextCfg: []
--- !u!1 &5277186255209122562

View File

@ -762,8 +762,8 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9d7b0b66e4c64b5cabac7c3c0e7f1a29, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Name:
m_EditorClassIdentifier:
Animancer: {fileID: 5660124054416036487}
TitleText: {fileID: 8548412456207606499}
ContentText: {fileID: 1560515396742602137}

View File

@ -4437,7 +4437,7 @@ MonoBehaviour:
NoExport: 0
FontBan: 0
Preset: 0
ID: 19455
ID: 20073
FontID: 2
TextCfg:
- Type: 1

View File

@ -1589,6 +1589,7 @@ MonoBehaviour:
HistoryButton: {fileID: 7456826708375644621}
TutorButton: {fileID: 7185898387603956833}
StoryButton: {fileID: 3611518138806132449}
TransReportButton: {fileID: 0}
ButtonList:
- {fileID: 5519043339116172500}
- {fileID: 7179142844686828325}
@ -3403,6 +3404,7 @@ GameObject:
- component: {fileID: 5814070196398329509}
- component: {fileID: 7963249260858927386}
- component: {fileID: 1157791559205469021}
- component: {fileID: -7562340078456195498}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
@ -3526,6 +3528,25 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &-7562340078456195498
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6691324969803418791}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
m_Name:
m_EditorClassIdentifier:
Ban: 0
NoExport: 0
FontBan: 0
Preset: 0
ID: 20074
FontID: 0
TextCfg: []
--- !u!1 &6763301404642353131
GameObject:
m_ObjectHideFlags: 0

View File

@ -6889,7 +6889,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &2172527432691932578
RectTransform:
m_ObjectHideFlags: 0
@ -7731,7 +7731,7 @@ MonoBehaviour:
NoExport: 0
FontBan: 0
Preset: 0
ID: 99
ID: 19978
FontID: 0
TextCfg: []
--- !u!1 &2262495720347823500
@ -14141,7 +14141,10 @@ MonoBehaviour:
StartGameButton: {fileID: 6399683637336089336}
ReadyButton: {fileID: 7100000000000000005}
CancelReadyButton: {fileID: 7100000000000000105}
AddRoomSeatButton: {fileID: 2431086168572787555}
ReduceRoomSeatButton: {fileID: 7006549652002422834}
ResumeToggle: {fileID: 5089543168947799435}
RoomSeatText: {fileID: 7585458862150312575}
CantStartHint: {fileID: 1209949301985471219}
CantStartHintText: {fileID: 3832249066705421499}
CantStartHintAnimancer: {fileID: 5154294124780001396}
@ -14158,6 +14161,10 @@ MonoBehaviour:
CreateRoomPanelCloseButton: {fileID: 8542356455767446690}
RoomType: {fileID: 1344031542082465645}
CreateRoomPanelMono: {fileID: 0}
RoomNameText: {fileID: 2765877689270703071}
RoomNameInput: {fileID: 7878389199091342944}
RoomNameEditButton: {fileID: 8871656449494329365}
RoomNameCheckButton: {fileID: 7672218077581223274}
FriendRowPrefab: {fileID: 6041009835357861232, guid: 70146c0c4b561f342aa79e9b1d023c0f, type: 3}
FriendList: {fileID: 7803650363429376031}
MemberRowPrefab: {fileID: 7377647467463133788, guid: 2594e36360312bd46901e6c0b34f9814, type: 3}

View File

@ -3021,6 +3021,7 @@ GameObject:
- component: {fileID: 4667728109123456789}
- component: {fileID: 8120436579012345678}
- component: {fileID: 6024178395501234567}
- component: {fileID: 4008369207175740087}
m_Layer: 5
m_Name: OpenHintText
m_TagString: Untagged
@ -3044,8 +3045,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0.5}
m_AnchorMax: {x: 0, y: 0.5}
m_AnchoredPosition: {x: 553.5004, y: -0.35351}
m_SizeDelta: {x: 714.816, y: 60.115}
m_AnchoredPosition: {x: 860.5266, y: 0.70847}
m_SizeDelta: {x: 271.2567, y: 57.991}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8120436579012345678
CanvasRenderer:
@ -3075,7 +3076,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u8BF7\u7B49\u5F85\u5176\u4ED6\u73A9\u5BB6\u52A0\u5165"
m_text: "\u5F85\u52A0\u5165"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: ce4904f8ddac15944907907115531ad5, type: 2}
m_sharedMaterial: {fileID: 1214840240034325189, guid: ce4904f8ddac15944907907115531ad5, type: 2}
@ -3144,6 +3145,25 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &4008369207175740087
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7335209913350123456}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6b27f832d22e4a8d916272b644937774, type: 3}
m_Name:
m_EditorClassIdentifier:
Ban: 0
NoExport: 0
FontBan: 0
Preset: 0
ID: 20075
FontID: 0
TextCfg: []
--- !u!1 &7372916197570849001
GameObject:
m_ObjectHideFlags: 0

View File

@ -4886,7 +4886,7 @@ MonoBehaviour:
NoExport: 0
FontBan: 0
Preset: 0
ID: 19412
ID: 20074
FontID: 1
TextCfg:
- Type: 1

View File

@ -13,6 +13,7 @@ using RuntimeData;
using Steamworks;
using TH1_Core.Events;
using TH1_Core.Managers;
using TH1_Logic.Chat;
using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.Net;
@ -635,7 +636,7 @@ namespace TH1_Logic.Steam
}
_pendingLobbyPassword = password ?? "";
_pendingLobbyRoomName = string.IsNullOrWhiteSpace(roomName) ? GetDefaultRoomName(SelfName) : roomName.Trim();
_pendingLobbyRoomName = FilterRoomName(string.IsNullOrWhiteSpace(roomName) ? GetDefaultRoomName(SelfName) : roomName.Trim());
_pendingLobbyIsPublic = isPublic;
CurrentState = LobbyState.Creating;
LogSystem.LogInfo($"Creating {(isPublic ? "public" : "friends-only")} lobby with max members: {maxMembers}, hasPassword: {!string.IsNullOrEmpty(_pendingLobbyPassword)}");
@ -1121,8 +1122,9 @@ namespace TH1_Logic.Steam
// 达到最大长度时停止
if (result.Length >= maxLength) break;
}
if (string.IsNullOrEmpty(result.ToString())) return "Default";
return result.ToString();
var filteredRoomName = result.ToString();
if (string.IsNullOrEmpty(filteredRoomName)) return "Default";
return BannedWordFilter.Filter(filteredRoomName);
}
public bool IsInitialized()
@ -1214,6 +1216,39 @@ namespace TH1_Logic.Steam
return SteamMatchmaking.GetLobbyData(CurrentLobby, key);
}
public string GetRoomName()
{
var roomName = GetLobbyData("RoomName");
return FilterRoomName(string.IsNullOrWhiteSpace(roomName) ? GetDefaultRoomName(SelfName) : roomName);
}
public bool SetRoomName(string roomName)
{
if (!IsLobbyOwner())
{
LogSystem.LogError("Only lobby owner can set room name");
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed);
return false;
}
if (!CurrentLobby.IsValid()) return false;
roomName = FilterRoomName(roomName);
bool success = SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", roomName);
if (success)
{
EventManager.Publish(new UpdateUIOutsideMultiplayLobbyList());
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
}
else
{
LogSystem.LogError($"Failed to set room name: {roomName}");
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed);
}
return success;
}
// 房间创建回调
private void OnLobbyCreatedCallback(LobbyCreated_t data)
{
@ -1288,6 +1323,12 @@ namespace TH1_Logic.Steam
private void OnLobbyDataUpdateCallback(LobbyDataUpdate_t data)
{
var lobbyId = new CSteamID(data.m_ulSteamIDLobby);
if (data.m_bSuccess != 0 && CurrentLobby.IsValid() && lobbyId == CurrentLobby)
{
EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting());
EventManager.Publish(new UpdateUIOutsideMultiplayLobbyList());
}
if (!_pendingJoinLobby.IsValid() || lobbyId != _pendingJoinLobby) return;
var password = _pendingJoinPassword;

View File

@ -1,3 +1,4 @@
using TH1_Logic.Chat;
using TH1_UI.View.Common;
using TMPro;
using UnityEngine;
@ -37,7 +38,7 @@ namespace TH1_UI.View.Bottom.Chat
}
else
{
SenderText.text = data.SenderName + ":";
SenderText.text = BannedWordFilter.Filter(data.SenderName) + ":";
SenderText.color = data.IsSelf ? SelfNameColor : OtherNameColor;
}
}
@ -45,7 +46,7 @@ namespace TH1_UI.View.Bottom.Chat
// 设置消息内容
if (MessageText != null)
{
MessageText.text = data.Message;
MessageText.text = BannedWordFilter.Filter(data.Message);
}
// 设置时间

View File

@ -92,7 +92,7 @@ namespace TH1_UI.View.Common
[Header("配置")]
public int MaxChatCount = 50;
public float MessageLifetime = 10f;
public int DocRecentCount = 10;
public int DocRecentCount = 50;
// ---- 内部数据 ----
private readonly List<ChatRowEntry> _chatRows = new List<ChatRowEntry>();
@ -342,7 +342,7 @@ namespace TH1_UI.View.Common
senderName = $"玩家{item.SenderId}";
if (sb.Length > 0) sb.Append('\n');
sb.Append(senderName).Append(':').Append(item.Message);
sb.Append(FilterChatText(senderName)).Append(':').Append(FilterChatText(item.Message));
}
DocText.text = sb.ToString();
@ -389,9 +389,11 @@ namespace TH1_UI.View.Common
var go = Instantiate(ChatPrefab, ChatShowArea);
// 拼接显示文本: "发言者:内容" 或系统消息直接显示内容
string displayMessage = FilterChatText(data.Message);
string displaySenderName = FilterChatText(data.SenderName);
string displayText = data.IsSystem
? data.Message
: $"{data.SenderName}:{data.Message}";
? displayMessage
: $"{displaySenderName}:{displayMessage}";
// 优先查找子物体上的 TMP其次根物体
var tmp = go.GetComponentInChildren<TMP_Text>();
@ -402,6 +404,11 @@ namespace TH1_UI.View.Common
_chatRows.Add(new ChatRowEntry { Go = go, SpawnTime = Time.time });
}
private static string FilterChatText(string text)
{
return BannedWordFilter.Filter(text);
}
}
/// <summary>

View File

@ -237,7 +237,7 @@ namespace TH1_UI.View.Outside
{
if (TransReportButton == null)
{
TransReportButton = FindButtonInChildren("TransReport");
TransReportButton = FindOrCreateButtonInChildren("TransReport");
}
if (TransReportButton == null) return;
@ -250,12 +250,26 @@ namespace TH1_UI.View.Outside
EventManager.Publish(new ShowUIGlobalBugReport());
}
private Button FindButtonInChildren(string buttonName)
private Button FindOrCreateButtonInChildren(string buttonName)
{
var children = GetComponentsInChildren<Transform>(true);
foreach (var child in children)
{
if (child.name == buttonName) return child.GetComponent<Button>();
if (child.name != buttonName) continue;
var button = child.GetComponent<Button>();
if (button != null) return button;
button = child.gameObject.AddComponent<Button>();
button.transition = Selectable.Transition.None;
button.targetGraphic = child.GetComponent<Graphic>();
if (button.targetGraphic != null)
{
var color = button.targetGraphic.color;
color.a = 1f;
button.targetGraphic.color = color;
}
return button;
}
return null;

View File

@ -1,4 +1,5 @@
using System;
using TH1_Logic.Steam;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@ -89,6 +90,7 @@ namespace TH1_UI.View.Outside
{
var roomName = RoomNameInput != null ? RoomNameInput.text?.Trim() : string.Empty;
if (string.IsNullOrEmpty(roomName)) roomName = _defaultRoomName;
roomName = SteamLobbyManager.FilterRoomName(roomName);
args = new CreateRoomParams
{
@ -147,6 +149,7 @@ namespace TH1_UI.View.Outside
private void SetRoomNamePlaceholder(string roomName)
{
roomName = SteamLobbyManager.FilterRoomName(roomName);
if (RoomNamePlaceholderText != null) RoomNamePlaceholderText.text = roomName;
else if (RoomNameInput != null && RoomNameInput.placeholder is TextMeshProUGUI placeholder)
placeholder.text = roomName;

View File

@ -1,4 +1,5 @@
using System;
using TH1_Logic.Chat;
using TH1_Logic.Steam;
using TMPro;
using UnityEngine;
@ -34,9 +35,10 @@ public class UIOutsideMultiplayLobbyRowMono : MonoBehaviour
_lobbyInfo = lobbyInfo;
RoomNameText.text = string.IsNullOrEmpty(lobbyInfo.RoomName)
var roomName = string.IsNullOrEmpty(lobbyInfo.RoomName)
? $"Room {lobbyInfo.LobbyId}"
: lobbyInfo.RoomName;
RoomNameText.text = BannedWordFilter.Filter(roomName);
OwnerNameText.text = lobbyInfo.OwnerName ?? "Unknown";
PlayerCountText.text = $"{lobbyInfo.CurrentPlayers}/{lobbyInfo.MaxPlayers}";

View File

@ -29,6 +29,8 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
public Button TeamLeftButton;
public Button TeamRightButton;
private static readonly Color AIBackgroundColor = new Color32(0xDA, 0xDA, 0xDA, 0xFF);
private SteamLobbyManager _lobby;
private CivEnum _civ;
private ForceEnum _force;
@ -38,6 +40,12 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
private Action<int> _onForceChanged;
private Action<int> _onTeamChanged;
private bool _useAllForceOptions;
private bool _shrinkFallbackLeaderAvatar;
private Vector3 _leaderImageDefaultScale = Vector3.one;
private bool _leaderImageScaleCached;
private Image _rootBackgroundImage;
private Color _rootBackgroundDefaultColor;
private bool _rootBackgroundColorCached;
private static readonly (CivEnum Civ, ForceEnum Force)[] ForceOptions =
{
@ -73,6 +81,7 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
{
if (!CheckParam()) return;
ResetButtons();
SetRootBackground(false);
_lobby = lobby;
_forceNameOverride = forceNameOverride;
_teamId = Mathf.Max(1, teamId);
@ -80,6 +89,7 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
_onForceChanged = onForceChanged;
_onTeamChanged = onTeamChanged;
_useAllForceOptions = false;
_shrinkFallbackLeaderAvatar = false;
SetAvatarVisible(true);
SetLeaderVisible(true);
@ -99,7 +109,9 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
{
if (!CheckParam()) return;
ResetButtons();
SetRootBackground(false);
_useAllForceOptions = false;
_shrinkFallbackLeaderAvatar = false;
SetAvatarVisible(false);
SetLeaderVisible(false);
NameText.gameObject.SetActive(false);
@ -111,6 +123,39 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
if (QuitButton != null) QuitButton.gameObject.SetActive(false);
}
public void SetOpenContent(
CivEnum civ,
ForceEnum force,
int teamId,
int maxTeamId,
bool canEdit,
string forceNameOverride,
Action<int> onForceChanged,
Action<int> onTeamChanged)
{
if (!CheckParam()) return;
ResetButtons();
SetRootBackground(false);
_forceNameOverride = forceNameOverride;
_teamId = Mathf.Max(1, teamId);
_maxTeamId = Mathf.Max(1, maxTeamId);
_onForceChanged = onForceChanged;
_onTeamChanged = onTeamChanged;
_useAllForceOptions = false;
_shrinkFallbackLeaderAvatar = false;
SetAvatarVisible(false);
SetLeaderVisible(true);
SetOpenHintVisible(true);
NameText.gameObject.SetActive(false);
StatusText.text = string.Empty;
UpdatePlayerInfoView(civ, force);
SetTeamText(_teamId);
SetEditButtonsVisible(canEdit);
BindEditButtons(canEdit);
if (QuitButton != null) QuitButton.gameObject.SetActive(false);
}
public void SetAIContent(
CivEnum civ,
ForceEnum force,
@ -123,12 +168,14 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
{
if (!CheckParam()) return;
ResetButtons();
SetRootBackground(true);
_forceNameOverride = forceNameOverride;
_teamId = Mathf.Max(1, teamId);
_maxTeamId = Mathf.Max(1, maxTeamId);
_onForceChanged = onForceChanged;
_onTeamChanged = onTeamChanged;
_useAllForceOptions = true;
_shrinkFallbackLeaderAvatar = true;
SetAvatarVisible(false);
SetLeaderVisible(true);
@ -152,7 +199,7 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
ForcesText.text = _forceNameOverride;
else
MultilingualManager.Instance.SetUIText(ForcesText, playerInfo.ForceName);
if (LeaderImage != null) LeaderImage.sprite = playerInfo.LeaderIllustration != null ? playerInfo.LeaderIllustration : playerInfo.LeaderAvatar;
SetLeaderImage(playerInfo.LeaderIllustration, playerInfo.LeaderAvatar, _shrinkFallbackLeaderAvatar);
}
public bool UpdatePlayerInfoData(CivEnum civ, ForceEnum force)
@ -189,6 +236,19 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
AvatarImage.rectTransform.localScale = new Vector3(1, -1, 1);
}
private void SetRootBackground(bool isAI)
{
if (_rootBackgroundImage == null) _rootBackgroundImage = GetComponent<Image>();
if (_rootBackgroundImage == null) return;
if (!_rootBackgroundColorCached)
{
_rootBackgroundDefaultColor = _rootBackgroundImage.color;
_rootBackgroundColorCached = true;
}
_rootBackgroundImage.color = isAI ? AIBackgroundColor : _rootBackgroundDefaultColor;
}
private void SetAvatarVisible(bool visible)
{
if (AvatarRoot != null) AvatarRoot.SetActive(visible);
@ -201,6 +261,24 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
if (LeaderImage != null) LeaderImage.gameObject.SetActive(visible);
}
private void SetLeaderImage(Sprite illustration, Sprite fallbackAvatar, bool shrinkFallback)
{
if (LeaderImage == null) return;
CacheLeaderImageScale();
bool useFallback = illustration == null && fallbackAvatar != null;
LeaderImage.sprite = illustration != null ? illustration : fallbackAvatar;
LeaderImage.rectTransform.localScale = useFallback && shrinkFallback
? _leaderImageDefaultScale * 0.5f
: _leaderImageDefaultScale;
}
private void CacheLeaderImageScale()
{
if (_leaderImageScaleCached || LeaderImage == null) return;
_leaderImageDefaultScale = LeaderImage.rectTransform.localScale;
_leaderImageScaleCached = true;
}
private void SetOpenHintVisible(bool visible)
{
if (OpenHintText == null) return;

View File

@ -35,7 +35,10 @@ namespace TH1_UI.View.Outside
public Button StartGameButton;
public Button ReadyButton;
public Button CancelReadyButton;
public Button AddRoomSeatButton;
public Button ReduceRoomSeatButton;
public Toggle ResumeToggle;
public TextMeshProUGUI RoomSeatText;
[Header("相关提示对象")]
public GameObject CantStartHint;
@ -59,6 +62,10 @@ namespace TH1_UI.View.Outside
public GameObject CreateRoomPanelCloseButton;
public UIOutsideSelectOptionGroupMono RoomType;
public UIOutsideMultiplayCreateRoomPanelMono CreateRoomPanelMono;
public TextMeshProUGUI RoomNameText;
public TMP_InputField RoomNameInput;
public Button RoomNameEditButton;
public Button RoomNameCheckButton;
@ -105,6 +112,7 @@ namespace TH1_UI.View.Outside
private GameObject _chatAreaGo;
private SteamLobbyManager _lobby;
private const int MaxRoomSeatCount = 4;
private int _roomSeatCount = 4;
private int _openMemberRowCount = 0;
//关闭时执行的委托
@ -191,6 +199,30 @@ namespace TH1_UI.View.Outside
LeaveRoomButton.onClick.RemoveAllListeners();
LeaveRoomButton.onClick.AddListener(LeaveRoom);
if (AddRoomSeatButton != null)
{
AddRoomSeatButton.onClick.RemoveAllListeners();
AddRoomSeatButton.onClick.AddListener(() => ChangeRoomSeatCount(1));
}
if (ReduceRoomSeatButton != null)
{
ReduceRoomSeatButton.onClick.RemoveAllListeners();
ReduceRoomSeatButton.onClick.AddListener(() => ChangeRoomSeatCount(-1));
}
if (RoomNameEditButton != null)
{
RoomNameEditButton.onClick.RemoveAllListeners();
RoomNameEditButton.onClick.AddListener(StartEditRoomName);
}
if (RoomNameCheckButton != null)
{
RoomNameCheckButton.onClick.RemoveAllListeners();
RoomNameCheckButton.onClick.AddListener(ConfirmEditRoomName);
}
if (ReadyButton != null)
{
SetButtonText(ReadyButton, MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayReadyTitle));
@ -388,6 +420,7 @@ namespace TH1_UI.View.Outside
{
_roomMemberRows.Clear();
var humanRows = new List<RoomMemberRowData>();
var openRows = new List<RoomMemberRowData>();
var emptyAiRows = new List<RoomMemberRowData>();
for (int i = 0; i < multiCivs.Count; i++)
@ -402,16 +435,25 @@ namespace TH1_UI.View.Outside
{
emptyAiRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.AI, MemberCiv = mc, SlotIndex = i });
}
else if (mc.MemberId == 0)
{
openRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Open, MemberCiv = mc, SlotIndex = i });
}
}
int totalPlayerCount = Mathf.Max(0, (int)Main.Instance.MapConfig.PlayerCount);
int roomSeatCount = Mathf.Clamp(_roomSeatCount, humanRows.Count, totalPlayerCount);
int roomSeatCount = Mathf.Clamp(_roomSeatCount, humanRows.Count, Mathf.Min(totalPlayerCount, MaxRoomSeatCount));
_openMemberRowCount = Mathf.Max(0, roomSeatCount - humanRows.Count);
int aiCount = Mathf.Max(0, totalPlayerCount - roomSeatCount);
_roomMemberRows.AddRange(humanRows);
for (int i = 0; i < _openMemberRowCount; i++)
_roomMemberRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Open });
{
if (i < openRows.Count)
_roomMemberRows.Add(openRows[i]);
else
_roomMemberRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Open });
}
_roomMemberRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Line });
int aiStart = Mathf.Max(0, emptyAiRows.Count - aiCount);
@ -451,7 +493,7 @@ namespace TH1_UI.View.Outside
var sameCountDict = new Dictionary<(CivEnum, ForceEnum), int>();
foreach (var row in rows)
{
if (row.MemberCiv == null || row.Type == RoomMemberRowType.Open || row.Type == RoomMemberRowType.Line) continue;
if (row.MemberCiv == null || row.Type == RoomMemberRowType.Line) continue;
var dispCiv = Table.Instance.TransCivIdToCivEnum(row.MemberCiv.CivId);
var dispForce = Table.Instance.TransForceIdToForceEnum(row.MemberCiv.ForceId);
var key = (dispCiv, dispForce);
@ -465,7 +507,7 @@ namespace TH1_UI.View.Outside
private void RenderMemberRow(UIOutsideMultiplayMemberRowMono memberRow, RoomMemberRowData row, List<MemberCiv> multiCivs, Dictionary<(CivEnum, ForceEnum), int> sameCountDict)
{
memberRow.gameObject.SetActive(true);
if (row.Type == RoomMemberRowType.Open)
if (row.Type == RoomMemberRowType.Open && row.MemberCiv == null)
{
memberRow.SetOpenContent();
return;
@ -477,6 +519,13 @@ namespace TH1_UI.View.Outside
var teamId = GetValidTeamId(mc);
var forceNameOverride = GetForceNameOverride(mc, civ, force, multiCivs, sameCountDict);
int maxTeamId = Mathf.Max(1, (int)Main.Instance.MapConfig.PlayerCount);
if (row.Type == RoomMemberRowType.Open)
{
memberRow.SetOpenContent(civ, force, teamId, maxTeamId, _lobby.IsLobbyOwner(), forceNameOverride,
direction => OnOpenRowForceChanged(row.SlotIndex, direction),
direction => OnOpenRowTeamChanged(row.SlotIndex, direction));
return;
}
if (row.Type == RoomMemberRowType.AI)
{
memberRow.SetAIContent(civ, force, teamId, maxTeamId, _lobby.IsLobbyOwner(), forceNameOverride,
@ -562,6 +611,60 @@ namespace TH1_UI.View.Outside
bool isReady = Main.Instance.MapConfig.IsMemberReady(_lobby.GetSelfMemberId());
SetReadyButtonsVisible(!isReady, isReady);
}
RefreshRoomSeatControls();
}
private void ChangeRoomSeatCount(int delta)
{
if (!_lobby.IsLobbyOwner()) return;
int targetSeatCount = Mathf.Clamp(_roomSeatCount + delta, GetMinRoomSeatCount(), GetMaxRoomSeatCount());
if (targetSeatCount == _roomSeatCount) return;
_roomSeatCount = targetSeatCount;
if (!_lobby.SetMemberLimit(_roomSeatCount))
{
int lobbyMemberLimit = _lobby.GetMemberLimit();
if (lobbyMemberLimit > 0) _roomSeatCount = lobbyMemberLimit;
RefreshRoomSeatControls();
return;
}
ReconcileRoomMembers();
RefreshRoomInfo();
}
private void RefreshRoomSeatControls()
{
int minSeatCount = GetMinRoomSeatCount();
int maxSeatCount = GetMaxRoomSeatCount();
_roomSeatCount = Mathf.Clamp(_roomSeatCount, minSeatCount, maxSeatCount);
if (RoomSeatText != null) RoomSeatText.text = _roomSeatCount.ToString();
bool canEdit = _lobby.IsLobbyOwner();
if (AddRoomSeatButton != null)
{
AddRoomSeatButton.gameObject.SetActive(true);
AddRoomSeatButton.interactable = canEdit && _roomSeatCount < maxSeatCount;
}
if (ReduceRoomSeatButton != null)
{
ReduceRoomSeatButton.gameObject.SetActive(true);
ReduceRoomSeatButton.interactable = canEdit && _roomSeatCount > minSeatCount;
}
}
private int GetMinRoomSeatCount()
{
return Mathf.Max(2, _lobby.GetMemberCount());
}
private int GetMaxRoomSeatCount()
{
return Mathf.Max(GetMinRoomSeatCount(), Mathf.Min(MaxRoomSeatCount, (int)Main.Instance.MapConfig.PlayerCount));
}
private static void SetButtonText(Button button, string text)
@ -590,15 +693,29 @@ namespace TH1_UI.View.Outside
Main.Instance.MapConfig.SetPlayerCount(Main.Instance.MapConfig.PlayerCount, NetMode.Multi);
var multiCivs = Main.Instance.MapConfig.MultiCivs;
int totalPlayerCount = Mathf.Max(0, (int)Main.Instance.MapConfig.PlayerCount);
_roomSeatCount = Mathf.Clamp(_roomSeatCount, _lobby.GetMemberCount(), totalPlayerCount);
int maxRoomSeatCount = Mathf.Min(totalPlayerCount, MaxRoomSeatCount);
_roomSeatCount = Mathf.Clamp(_roomSeatCount, _lobby.GetMemberCount(), maxRoomSeatCount);
int lobbyMemberLimit = _lobby.GetMemberLimit();
if (lobbyMemberLimit > 0) _roomSeatCount = Mathf.Clamp(lobbyMemberLimit, _lobby.GetMemberCount(), totalPlayerCount);
if (lobbyMemberLimit > 0) _roomSeatCount = Mathf.Clamp(lobbyMemberLimit, _lobby.GetMemberCount(), maxRoomSeatCount);
int openSlotBudget = Mathf.Max(0, _roomSeatCount - _lobby.GetMemberCount());
bool canReconcileOpenSlots = _lobby.IsLobbyOwner();
bool slotLayoutChanged = false;
var usedCivs = new HashSet<uint>();
var usedTeams = new HashSet<int>();
for (int i = 0; i < multiCivs.Count; i++)
{
var mc = multiCivs[i];
if (mc == null) continue;
if (canReconcileOpenSlots && mc.MemberId == 0)
{
bool shouldBeOpenSlot = openSlotBudget > 0;
if (mc.IsAI == shouldBeOpenSlot)
{
mc.IsAI = !shouldBeOpenSlot;
slotLayoutChanged = true;
}
if (shouldBeOpenSlot) openSlotBudget--;
}
if (mc.TeamId <= 0 || mc.TeamId > totalPlayerCount) mc.TeamId = PickDefaultTeamId(i, totalPlayerCount, usedTeams);
if (!mc.IsCivFixed)
{
@ -610,6 +727,11 @@ namespace TH1_UI.View.Outside
usedTeams.Add(mc.TeamId);
usedCivs.Add(mc.CivId);
}
if (slotLayoutChanged)
{
Main.Instance.MapConfig.ResetGuestReadyStates();
Main.Instance.MapConfig.CheckMapConfigChanged();
}
}
private int GetValidTeamId(MemberCiv mc)
@ -696,6 +818,44 @@ namespace TH1_UI.View.Outside
}
}
private void OnOpenRowForceChanged(int slotIndex, int direction)
{
SetEmptyPlayerSlotForce(slotIndex, direction);
}
private void OnOpenRowTeamChanged(int slotIndex, int direction)
{
SetEmptyPlayerSlotTeam(slotIndex, direction);
}
private void SetEmptyPlayerSlotForce(int slotIndex, int direction)
{
if (!_lobby.IsLobbyOwner()) return;
var slot = Main.Instance.MapConfig.GetPlayerSlot(slotIndex, NetMode.Multi);
if (slot == null || slot.MemberId != 0 || slot.IsAI) return;
var next = GetNextForce(slot.CivId, slot.ForceId, direction);
if (Main.Instance.MapConfig.SetPlayerSlotCiv(slotIndex,
Table.Instance.TransCivEnumToCivId(next.Civ),
Table.Instance.TransForceEnumToForceId(next.Force)))
{
Main.Instance.MapConfig.ResetGuestReadyStates();
Main.Instance.MapConfig.CheckMapConfigChanged();
}
}
private void SetEmptyPlayerSlotTeam(int slotIndex, int direction)
{
if (!_lobby.IsLobbyOwner()) return;
var slot = Main.Instance.MapConfig.GetPlayerSlot(slotIndex, NetMode.Multi);
if (slot == null || slot.MemberId != 0 || slot.IsAI) return;
int nextTeamId = GetNextTeamId(GetValidTeamId(slot), direction);
if (Main.Instance.MapConfig.SetPlayerSlotTeam(slotIndex, nextTeamId))
{
Main.Instance.MapConfig.ResetGuestReadyStates();
Main.Instance.MapConfig.CheckMapConfigChanged();
}
}
private int GetNextTeamId(int teamId, int direction)
{
int maxTeamId = Mathf.Max(1, (int)Main.Instance.MapConfig.PlayerCount);
@ -804,6 +964,7 @@ namespace TH1_UI.View.Outside
// 只有房主才能修改房间设置
bool isOwner = _lobby.IsLobbyOwner();
Debug.Log($"[SetRoomInfoSetting] IsLobbyOwner={isOwner}, SelfID={_lobby.SelfID}, OwnerID={_lobby.GetLobbyOwnerId()}");
SetRoomNameEditMode(false);
// 绑定回调(先绑再 Init保证 OnGameModeOptionClicked 触发时回调已就位)
if (GameMode != null) GameMode.OnOptionClicked = OnGameModeOptionClicked;
@ -836,6 +997,57 @@ namespace TH1_UI.View.Outside
if (TimeGroup != null) TimeGroup.Init(timeIdx);
if (Water != null) Water.Init(waterIdx);
}
private void StartEditRoomName()
{
if (!_lobby.IsLobbyOwner()) return;
if (RoomNameInput != null)
{
RoomNameInput.text = _lobby.GetRoomName();
RoomNameInput.ActivateInputField();
}
SetRoomNameEditMode(true);
}
private void ConfirmEditRoomName()
{
if (!_lobby.IsLobbyOwner())
{
SetRoomNameEditMode(false);
return;
}
var roomName = RoomNameInput != null ? RoomNameInput.text?.Trim() : string.Empty;
if (!string.IsNullOrEmpty(roomName))
{
_lobby.SetRoomName(roomName);
}
SetRoomNameEditMode(false);
}
private void SetRoomNameEditMode(bool isEditing)
{
string roomName = _lobby.GetRoomName();
bool canEdit = _lobby.IsLobbyOwner();
if (RoomNameText != null)
{
RoomNameText.text = roomName;
RoomNameText.gameObject.SetActive(!isEditing);
}
if (RoomNameInput != null)
{
if (!isEditing) RoomNameInput.text = roomName;
RoomNameInput.gameObject.SetActive(isEditing);
}
if (RoomNameEditButton != null) RoomNameEditButton.gameObject.SetActive(canEdit && !isEditing);
if (RoomNameCheckButton != null) RoomNameCheckButton.gameObject.SetActive(canEdit && isEditing);
}
public void OnPlayerOptionClicked(uint idx)
{