联机功能开发
This commit is contained in:
parent
d1c226e717
commit
a5a0bf51d2
@ -1,5 +1,5 @@
|
||||
{
|
||||
"nextId": 270,
|
||||
"nextId": 272,
|
||||
"bugs": [
|
||||
{
|
||||
"id": 2,
|
||||
@ -2672,6 +2672,28 @@
|
||||
"longTerm": false,
|
||||
"createdAt": 1779810291469,
|
||||
"updatedAt": 1779810291469
|
||||
},
|
||||
{
|
||||
"id": 270,
|
||||
"title": "战地协同 标记不一样",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1779882909992,
|
||||
"updatedAt": 1779882909992
|
||||
},
|
||||
{
|
||||
"id": 271,
|
||||
"title": "推测为占领全部城市后间谍单位消失但没有及时更新状态",
|
||||
"description": "",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"module": "",
|
||||
"longTerm": false,
|
||||
"createdAt": 1779891352529,
|
||||
"updatedAt": 1779891352529
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -39,6 +39,11 @@ MonoBehaviour:
|
||||
OutsideMultiplayOpenHint: "\u4ECD\u6709\u5F85\u52A0\u5165\u7684\u5E2D\u4F4D\uFF0C\u8BF7\u51CF\u5C11\u5E2D\u4F4D"
|
||||
OutsideMultiplayRoomFull: "\u623F\u95F4\u5DF2\u6EE1"
|
||||
OutsideMultiplayRoomGone: "\u623F\u95F4\u5DF2\u89E3\u6563\u6216\u5DF2\u5F00\u59CB\u6E38\u620F"
|
||||
OutsideMultiplayRoomNameIllegal: "\u623F\u95F4\u540D\u79F0\u53EF\u80FD\u5B58\u5728\u4E0D\u5408\u9002\u7528\u8BCD\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E"
|
||||
OutsideMultiplayRoomNameTooShort: "\u623F\u95F4\u540D\u79F0\u8FC7\u77ED"
|
||||
OutsideMultiplayRoomNetError: "\u7F51\u7EDC\u9519\u8BEF"
|
||||
OutsideMultiplayRoomJubaoSuccess: "\u4E3E\u62A5\u5DF2\u63D0\u4EA4"
|
||||
OutsideMultiplayRoomMuteSuccess: "\u5DF2\u9690\u85CF\u8BE5\u623F\u95F4\uFF0C24\u5C0F\u65F6\u5185\u4E0D\u518D\u663E\u793A"
|
||||
OutsideHistoryDropListNoLimitP: "\u4E0D\u9650"
|
||||
OutsideHistoryDropList2P: "2\u4EBA"
|
||||
OutsideHistoryDropList3P: "3\u4EBA"
|
||||
|
||||
@ -798,3 +798,4 @@ MonoBehaviour:
|
||||
Priority: 0
|
||||
EnableAction: 0
|
||||
EnableActions: []
|
||||
CultureTaskDataList: []
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -39,6 +39,11 @@ MonoBehaviour:
|
||||
OutsideMultiplayOpenHint: 20077
|
||||
OutsideMultiplayRoomFull: 20078
|
||||
OutsideMultiplayRoomGone: 20079
|
||||
OutsideMultiplayRoomNameIllegal: 20093
|
||||
OutsideMultiplayRoomNameTooShort: 20094
|
||||
OutsideMultiplayRoomNetError: 17451
|
||||
OutsideMultiplayRoomJubaoSuccess: 20095
|
||||
OutsideMultiplayRoomMuteSuccess: 20096
|
||||
OutsideHistoryDropListNoLimitP: 17352
|
||||
OutsideHistoryDropList2P: 17353
|
||||
OutsideHistoryDropList3P: 17354
|
||||
@ -50,6 +55,9 @@ MonoBehaviour:
|
||||
OutsideHistoryDropListTimeOrder: 17360
|
||||
OutsideHistoryDropListTimeOrderR: 17361
|
||||
OutsideHistoryDropListScoreOrder: 17362
|
||||
BugReportUploadingHint:
|
||||
BugReportUploadingSuccessHint:
|
||||
BugReportUploadingFailedHint:
|
||||
HeroTaskFinishedDesc: 2360
|
||||
GridInfoText_PerTurn: 2503
|
||||
GridInfoText_Population: 2504
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -387,7 +387,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 16949
|
||||
ID: 20080
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
|
||||
@ -162,6 +162,41 @@ MonoBehaviour:
|
||||
m_OnValueChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &2078440796911255766
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2559237646761523103}
|
||||
m_Layer: 5
|
||||
m_Name: NetErrorPanelRoot
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &2559237646761523103
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2078440796911255766}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7448979660496629192}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -671.3968, y: 408.8651}
|
||||
m_SizeDelta: {x: 577.2065, y: 97.9193}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!1 &2440790000169401958
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -197,6 +232,7 @@ RectTransform:
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4797701493746855371}
|
||||
- {fileID: 2559237646761523103}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
|
||||
@ -109,11 +109,11 @@ RectTransform:
|
||||
- {fileID: 318087679502976148}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: -110.46619, y: -409.99628}
|
||||
m_SizeDelta: {x: 692.1076, y: 165.3526}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 691.1385, y: 68.448}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &-1084591119027101579
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -324,7 +324,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 389.04, y: -129.46}
|
||||
m_AnchoredPosition: {x: 389.04, y: -42.460007}
|
||||
m_SizeDelta: {x: 606.13, y: 71.777}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!1 &2833781254162354808
|
||||
@ -835,7 +835,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 389.04, y: -93.572}
|
||||
m_AnchoredPosition: {x: 389.04, y: -6.5719986}
|
||||
m_SizeDelta: {x: 606.13, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0}
|
||||
--- !u!114 &4393837735354183377
|
||||
@ -1085,7 +1085,7 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 47, y: -123}
|
||||
m_AnchoredPosition: {x: 47, y: -36}
|
||||
m_SizeDelta: {x: 47.909485, y: 47.9095}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1808859806862558063
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 425dde724c862994887949c605a5e0ee
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -336,6 +336,8 @@ MonoBehaviour:
|
||||
PasswordInput: {fileID: 5655071534055511772}
|
||||
PasswordHintObject: {fileID: 5872298964086764219}
|
||||
PasswordHintText: {fileID: 8047929761724619342}
|
||||
RoomNameHintObject: {fileID: 9107250659884233687}
|
||||
RoomNameHintText: {fileID: 8432045260240010102}
|
||||
--- !u!1 &1712044533332343156
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -703,6 +705,160 @@ MonoBehaviour:
|
||||
ID: 20063
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &2383265354460307738
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 9196843804145772340}
|
||||
- component: {fileID: 4147712615985337980}
|
||||
- component: {fileID: 7197738449294962291}
|
||||
- component: {fileID: 4916706260937510150}
|
||||
m_Layer: 5
|
||||
m_Name: RoomNameLegalHint
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &9196843804145772340
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2383265354460307738}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 7463303194629128016}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 459.67932, y: 46.5352}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &4147712615985337980
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2383265354460307738}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7197738449294962291
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2383265354460307738}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: "\u8BF7\u4F7F\u7528\u53CB\u5584\u3001\u6E05\u6670\u3001\u9002\u5408\u516C\u5F00\u5C55\u793A\u7684\u623F\u95F4\u540D\u79F0\uFF0C\u5171\u540C\u521B\u5EFA\u5065\u5EB7\u6587\u660E\u7684\u8054\u673A\u73AF\u5883\u3002"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_sharedMaterial: {fileID: -8081454072124122709, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4287401100
|
||||
m_fontColor: {r: 0.5471698, g: 0.5471698, b: 0.5471698, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 18.6
|
||||
m_fontSizeBase: 24
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 14
|
||||
m_fontSizeMax: 22
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!114 &4916706260937510150
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2383265354460307738}
|
||||
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: 20085
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &2960065425293172538
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -2984,10 +3140,11 @@ RectTransform:
|
||||
- {fileID: 2823495548145270491}
|
||||
- {fileID: 7559943356464538090}
|
||||
- {fileID: 4104221981773057835}
|
||||
- {fileID: 5055858450282614166}
|
||||
- {fileID: 9196843804145772340}
|
||||
- {fileID: 287912720796096540}
|
||||
- {fileID: 5266944002495899053}
|
||||
- {fileID: 748938986039668253}
|
||||
- {fileID: 5055858450282614166}
|
||||
m_Father: {fileID: 1937094061063432054}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
@ -5099,12 +5256,12 @@ GameObject:
|
||||
- component: {fileID: 8432045260240010102}
|
||||
- component: {fileID: -3369302157754019626}
|
||||
m_Layer: 5
|
||||
m_Name: NoRoomNameHint
|
||||
m_Name: RoomNameHint
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &5055858450282614166
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@ -20,7 +20,7 @@ GameObject:
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
m_IsActive: 0
|
||||
--- !u!224 &1937094061063432054
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -1249,7 +1249,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 20064
|
||||
ID: 20088
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &5921627962211736030
|
||||
@ -1403,7 +1403,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 2233
|
||||
ID: 20087
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -1697,7 +1697,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 2228
|
||||
ID: 20086
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -2037,7 +2037,7 @@ MonoBehaviour:
|
||||
m_CharacterValidation: 0
|
||||
m_RegexValue:
|
||||
m_GlobalPointSize: 14
|
||||
m_CharacterLimit: 0
|
||||
m_CharacterLimit: 4
|
||||
m_OnEndEdit:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
|
||||
@ -1136,6 +1136,81 @@ MonoBehaviour:
|
||||
LineSpacing: 0
|
||||
ApplyParagraphSpacing: 0
|
||||
ParagraphSpacing: 0
|
||||
--- !u!1 &5759544334706913819
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4652863007342779236}
|
||||
- component: {fileID: 5410305486104802002}
|
||||
- component: {fileID: 4825784848556651340}
|
||||
m_Layer: 5
|
||||
m_Name: Lock
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4652863007342779236
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5759544334706913819}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 2527218108953289808}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 359, y: 1}
|
||||
m_SizeDelta: {x: 42.3955, y: 51.408}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &5410305486104802002
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5759544334706913819}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &4825784848556651340
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5759544334706913819}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.509434, g: 0.509434, b: 0.509434, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: c4149874829f08d478da4bfae558335a, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &5763353435607814537
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -1287,7 +1362,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 17377
|
||||
ID: 20089
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -1501,6 +1576,7 @@ RectTransform:
|
||||
- {fileID: 2522315637815645420}
|
||||
- {fileID: 6452178927417364351}
|
||||
- {fileID: 1809688155015995307}
|
||||
- {fileID: 4652863007342779236}
|
||||
- {fileID: 555533381074390939}
|
||||
- {fileID: 96625707259803793}
|
||||
- {fileID: 768525217504304060}
|
||||
@ -1569,6 +1645,7 @@ MonoBehaviour:
|
||||
VersionText: {fileID: 0}
|
||||
JoinButton: {fileID: 5777197804309038576}
|
||||
ActionButton: {fileID: 5800461245087449632}
|
||||
Lock: {fileID: 5759544334706913819}
|
||||
--- !u!1 &8852519433211468652
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@ -1581,8 +1658,6 @@ GameObject:
|
||||
- component: {fileID: 5815633150663516744}
|
||||
- component: {fileID: 60679182699258626}
|
||||
- component: {fileID: 5800461245087449632}
|
||||
- component: {fileID: 1345292219526059393}
|
||||
- component: {fileID: 8403910360848460228}
|
||||
m_Layer: 5
|
||||
m_Name: JubaoButton
|
||||
m_TagString: Untagged
|
||||
@ -1692,116 +1767,3 @@ MonoBehaviour:
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!82 &1345292219526059393
|
||||
AudioSource:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8852519433211468652}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 4
|
||||
OutputAudioMixerGroup: {fileID: 0}
|
||||
m_audioClip: {fileID: 0}
|
||||
m_PlayOnAwake: 1
|
||||
m_Volume: 1
|
||||
m_Pitch: 1
|
||||
Loop: 0
|
||||
Mute: 0
|
||||
Spatialize: 0
|
||||
SpatializePostEffects: 0
|
||||
Priority: 128
|
||||
DopplerLevel: 1
|
||||
MinDistance: 1
|
||||
MaxDistance: 500
|
||||
Pan2D: 0
|
||||
rolloffMode: 0
|
||||
BypassEffects: 0
|
||||
BypassListenerEffects: 0
|
||||
BypassReverbZones: 0
|
||||
rolloffCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
- serializedVersion: 3
|
||||
time: 1
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
panLevelCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
spreadCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 0
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
reverbZoneMixCustomCurve:
|
||||
serializedVersion: 2
|
||||
m_Curve:
|
||||
- serializedVersion: 3
|
||||
time: 0
|
||||
value: 1
|
||||
inSlope: 0
|
||||
outSlope: 0
|
||||
tangentMode: 0
|
||||
weightedMode: 0
|
||||
inWeight: 0.33333334
|
||||
outWeight: 0.33333334
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
--- !u!114 &8403910360848460228
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8852519433211468652}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 33d75335a9dad784a91baba5578371fb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
hoverSound: {fileID: 0}
|
||||
targetScale: 1.1
|
||||
clickSound: {fileID: 0}
|
||||
pressScale: 0.8
|
||||
scaleDuration: 0.1
|
||||
|
||||
@ -151,7 +151,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 19879
|
||||
ID: 20092
|
||||
FontID: 0
|
||||
TextCfg: []
|
||||
--- !u!1 &1484864756966099653
|
||||
@ -668,7 +668,7 @@ RectTransform:
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 956.9043, y: -532}
|
||||
m_SizeDelta: {x: 0, y: 322.3732}
|
||||
m_SizeDelta: {x: 0, y: 347.869}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &9162338914215527152
|
||||
CanvasRenderer:
|
||||
@ -1031,7 +1031,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 99
|
||||
ID: 20089
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -1620,7 +1620,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 17706
|
||||
ID: 20090
|
||||
FontID: 2
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
@ -1975,8 +1975,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 0, y: 1}
|
||||
m_AnchoredPosition: {x: 475.04083, y: -253.69449}
|
||||
m_SizeDelta: {x: 830.08167, y: 137.3574}
|
||||
m_AnchoredPosition: {x: 475.04083, y: -279.19028}
|
||||
m_SizeDelta: {x: 0, y: 137.3574}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!114 &1640998311177812195
|
||||
MonoBehaviour:
|
||||
@ -2085,7 +2085,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: "\u5C4F\u853D\u623F\u95F4\u540E\u5C06\u57281\u5929\u5185\u4E0D\u518D\u663E\u793A\u8BE5\u73A9\u5BB6\u7684\u623F\u95F4\uFF0C\u4E3E\u62A5\u529F\u80FD\u4ECD\u5728\u5F00\u53D1\u4E2D\u3002"
|
||||
m_text: "\u5C4F\u853D\u623F\u95F4\u540E\u5C06\u572824\u5C0F\u65F6\u5185\u4E0D\u518D\u663E\u793A\u8BE5\u623F\u95F4\u3002\u88AB\u591A\u4EBA\u4E3E\u62A5\u7684\u623F\u95F4\u5C06\u4F1A\u81EA\u52A8\u8C03\u6574\u623F\u95F4\u540D\u79F0\u4E3A\u9ED8\u8BA4\u540D\u3002"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
m_sharedMaterial: {fileID: -8081454072124122709, guid: 8e119f168f1a6b745be02ef19f51610f, type: 2}
|
||||
@ -2170,7 +2170,7 @@ MonoBehaviour:
|
||||
NoExport: 0
|
||||
FontBan: 0
|
||||
Preset: 0
|
||||
ID: 17707
|
||||
ID: 20101
|
||||
FontID: 1
|
||||
TextCfg:
|
||||
- Type: 1
|
||||
|
||||
@ -135,7 +135,10 @@ namespace TH1_Core.Events
|
||||
ExamineCityExp,
|
||||
InfiltrateStealCoin,
|
||||
OutsideMultiplayRoomFull,
|
||||
OutsideMultiplayRoomGone
|
||||
OutsideMultiplayRoomGone,
|
||||
OutsideMultiplayRoomNetError,
|
||||
OutsideMultiplayOpenHint,
|
||||
OutsideMultiplayCantStartCount
|
||||
}
|
||||
public struct ShowUINotifyCommon
|
||||
{
|
||||
|
||||
@ -113,6 +113,7 @@ namespace RuntimeData
|
||||
[MemoryPackOnDeserialized]
|
||||
public void OnAfterMemoryPackDeserialize()
|
||||
{
|
||||
MatchSettlement = MatchSettlementInfo.NormalizeType(MatchSettlement);
|
||||
if (TimeLimitSeconds == 0) TimeLimitSeconds = 180;
|
||||
// 旧存档兼容:WaterType 字段不存在时默认为 Pangea
|
||||
if (!System.Enum.IsDefined(typeof(Logic.MapWaterType), WaterType))
|
||||
@ -1952,6 +1953,8 @@ namespace RuntimeData
|
||||
Net ??= new NetData();
|
||||
Net.Players ??= new Dictionary<ulong, uint>();
|
||||
Net.Actions ??= new List<ActionNetData>();
|
||||
if (MapConfig != null) MapConfig.MatchSettlement = MatchSettlementInfo.NormalizeType(MapConfig.MatchSettlement);
|
||||
if (MatchSettlement != null) MatchSettlement.SettlementType = MatchSettlementInfo.NormalizeType(MatchSettlement.SettlementType);
|
||||
CityToPlayerDict ??= new Dictionary<uint, uint>();
|
||||
UnitToCityDict ??= new Dictionary<uint, uint>();
|
||||
UnitToGridDict ??= new Dictionary<uint, uint>();
|
||||
|
||||
@ -149,6 +149,33 @@ public class CultureCardInfo
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class CultureTaskInfo
|
||||
{
|
||||
[Tooltip("文化任务标识,对应 MomentItemBase.GetMomentSubType()")]
|
||||
public MomentSubType TaskType;
|
||||
|
||||
[Tooltip("是否启用该文化任务文案")]
|
||||
public bool IsActive = true;
|
||||
|
||||
[MultilingualField]
|
||||
[Tooltip("文化任务名称")]
|
||||
public string Name;
|
||||
|
||||
[MultilingualField]
|
||||
[Tooltip("文化任务描述")]
|
||||
public string Description;
|
||||
|
||||
[Tooltip("文化任务图标")]
|
||||
public Sprite Icon;
|
||||
|
||||
[Tooltip("文化任务奖励文化值,仅用于展示,实际奖励仍由 MomentItemBase 控制")]
|
||||
public int CultureValue = 5;
|
||||
|
||||
[Tooltip("排序优先级,数值越小越靠前")]
|
||||
public int Priority = 0;
|
||||
}
|
||||
|
||||
|
||||
[CreateAssetMenu(fileName = "CultureCardDataAssets", menuName = "TH1 Game Data/CultureCard Data Asset")]
|
||||
public class CultureCardDataAssets : ScriptableObject
|
||||
@ -156,10 +183,15 @@ public class CultureCardDataAssets : ScriptableObject
|
||||
[Header("市政卡数据列表")]
|
||||
[Tooltip("所有市政卡的基础配置数据")]
|
||||
public List<CultureCardInfo> CultureCardDataList = new List<CultureCardInfo>();
|
||||
|
||||
[Header("文化任务数据列表")]
|
||||
[Tooltip("文化任务的多语言文案与展示数据")]
|
||||
public List<CultureTaskInfo> CultureTaskDataList = new List<CultureTaskInfo>();
|
||||
|
||||
|
||||
// 运行时缓存字典
|
||||
[NonSerialized] private Dictionary<CultureCardType, CultureCardInfo> _cardInfoDict;
|
||||
[NonSerialized] private Dictionary<MomentSubType, CultureTaskInfo> _cultureTaskInfoDict;
|
||||
[NonSerialized] private bool _initialized = false;
|
||||
|
||||
|
||||
@ -168,7 +200,7 @@ public class CultureCardDataAssets : ScriptableObject
|
||||
/// </summary>
|
||||
public void Init()
|
||||
{
|
||||
if (_initialized && _cardInfoDict != null) return;
|
||||
if (_initialized && _cardInfoDict != null && _cultureTaskInfoDict != null) return;
|
||||
|
||||
_cardInfoDict = new Dictionary<CultureCardType, CultureCardInfo>();
|
||||
foreach (var cardInfo in CultureCardDataList)
|
||||
@ -182,6 +214,18 @@ public class CultureCardDataAssets : ScriptableObject
|
||||
_cardInfoDict[cardInfo.CardType] = cardInfo;
|
||||
}
|
||||
|
||||
_cultureTaskInfoDict = new Dictionary<MomentSubType, CultureTaskInfo>();
|
||||
foreach (var taskInfo in CultureTaskDataList)
|
||||
{
|
||||
if (taskInfo.TaskType == MomentSubType.None) continue;
|
||||
if (_cultureTaskInfoDict.ContainsKey(taskInfo.TaskType))
|
||||
{
|
||||
Debug.LogWarning($"[CultureCardDataAssets] 重复的文化任务类型: {taskInfo.TaskType}");
|
||||
continue;
|
||||
}
|
||||
_cultureTaskInfoDict[taskInfo.TaskType] = taskInfo;
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@ -197,4 +241,10 @@ public class CultureCardDataAssets : ScriptableObject
|
||||
Init();
|
||||
return _cardInfoDict.TryGetValue(cardType, out cardInfo) && cardInfo.IsActive;
|
||||
}
|
||||
|
||||
public bool GetCultureTaskInfo(MomentSubType taskType, out CultureTaskInfo taskInfo)
|
||||
{
|
||||
Init();
|
||||
return _cultureTaskInfoDict.TryGetValue(taskType, out taskInfo) && taskInfo.IsActive;
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +87,13 @@ public enum MomentSubType
|
||||
ExploitFirstBigMarket = 402009,
|
||||
ExploitFirstBigForge = 402010,
|
||||
ExploitFirstBigSawmill = 402011,
|
||||
ExploitWonderPEACE = 402012,
|
||||
ExploitWonderKNOWLEDGE = 402013,
|
||||
ExploitWonderTRADE = 402014,
|
||||
ExploitWonderWEALTH = 402015,
|
||||
ExploitWonderPOWER = 402016,
|
||||
ExploitWonderPARK = 402017,
|
||||
ExploitWonderEYE = 402018,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@ -206,4 +213,4 @@ public struct MomentImagePack
|
||||
public Sprite Image;
|
||||
public Empire Empire;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,11 @@ public class TextDataAssets : ScriptableObject
|
||||
[MultilingualField]public string OutsideMultiplayOpenHint;
|
||||
[MultilingualField]public string OutsideMultiplayRoomFull;
|
||||
[MultilingualField]public string OutsideMultiplayRoomGone;
|
||||
[MultilingualField]public string OutsideMultiplayRoomNameIllegal;
|
||||
[MultilingualField]public string OutsideMultiplayRoomNameTooShort;
|
||||
[MultilingualField]public string OutsideMultiplayRoomNetError;
|
||||
[MultilingualField]public string OutsideMultiplayRoomJubaoSuccess;
|
||||
[MultilingualField]public string OutsideMultiplayRoomMuteSuccess;
|
||||
|
||||
[MultilingualField]public string OutsideHistoryDropListNoLimitP;
|
||||
[MultilingualField]public string OutsideHistoryDropList2P;
|
||||
@ -56,6 +61,10 @@ public class TextDataAssets : ScriptableObject
|
||||
[MultilingualField]public string OutsideHistoryDropListTimeOrderR;
|
||||
[MultilingualField]public string OutsideHistoryDropListScoreOrder;
|
||||
|
||||
[MultilingualField]public string BugReportUploadingHint;
|
||||
[MultilingualField]public string BugReportUploadingSuccessHint;
|
||||
[MultilingualField]public string BugReportUploadingFailedHint;
|
||||
|
||||
|
||||
//-------- UI --------
|
||||
[MultilingualField] public string HeroTaskFinishedDesc;
|
||||
|
||||
@ -1328,16 +1328,6 @@ namespace Logic.Action
|
||||
//Step #3 更新成就完成情况
|
||||
AchievementDataManager.Instance.OnBuildWonder(actionParams.MapData, player, city, actionParams.GridData);
|
||||
|
||||
//Step #4 Moment
|
||||
if (actionParams.MapData == Main.MapData &&
|
||||
actionParams.PlayerData == Main.MapData.PlayerMap.SelfPlayerData)
|
||||
{
|
||||
EventManager.Publish(new ShowUINotifyMoment()
|
||||
{
|
||||
MomentSubType = MomentSubType.ExploitWonder,
|
||||
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -10,19 +10,19 @@ namespace Logic.Editor
|
||||
{
|
||||
public static class DumpMapConfigEditor
|
||||
{
|
||||
[MenuItem("Tools/Debug/Dump 关卡2配置(Domination)")]
|
||||
[MenuItem("Tools/Debug/Dump 关卡2配置(Normal)")]
|
||||
private static void DumpId2()
|
||||
{
|
||||
DumpId(2);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Debug/Dump 关卡6配置(Perfect)")]
|
||||
[MenuItem("Tools/Debug/Dump 关卡6配置(Normal)")]
|
||||
private static void DumpId6()
|
||||
{
|
||||
DumpId(6);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Debug/Dump 关卡7配置(Creative)")]
|
||||
[MenuItem("Tools/Debug/Dump 关卡7配置(Normal)")]
|
||||
private static void DumpId7()
|
||||
{
|
||||
DumpId(7);
|
||||
|
||||
@ -20,11 +20,9 @@ namespace TH1_Logic.MatchConfig
|
||||
public enum MatchSettlementType
|
||||
{
|
||||
None = 0,
|
||||
Domination = 1,
|
||||
Perfect = 2,
|
||||
Normal = 1,
|
||||
Tutor = 3,
|
||||
Story = 4,
|
||||
Creative = 5,
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +32,19 @@ namespace TH1_Logic.MatchConfig
|
||||
public MatchSettlementType SettlementType;
|
||||
public bool IsFinished;
|
||||
public Dictionary<uint, PlayerSettlementGroup> PlayerSettlements;
|
||||
|
||||
public static MatchSettlementType NormalizeType(MatchSettlementType matchType)
|
||||
{
|
||||
return (int)matchType switch
|
||||
{
|
||||
1 => MatchSettlementType.Normal,
|
||||
2 => MatchSettlementType.Normal,
|
||||
5 => MatchSettlementType.Normal,
|
||||
3 => MatchSettlementType.Tutor,
|
||||
4 => MatchSettlementType.Story,
|
||||
_ => MatchSettlementType.None
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
[MemoryPackConstructor]
|
||||
@ -45,7 +56,7 @@ namespace TH1_Logic.MatchConfig
|
||||
|
||||
public void Init(MapData map, MapConfig config)
|
||||
{
|
||||
SettlementType = config?.MatchSettlement ?? MatchSettlementType.None;
|
||||
SettlementType = NormalizeType(config?.MatchSettlement ?? MatchSettlementType.None);
|
||||
IsFinished = false;
|
||||
PlayerSettlements ??= new Dictionary<uint, PlayerSettlementGroup>();
|
||||
PlayerSettlements.Clear();
|
||||
@ -67,7 +78,7 @@ namespace TH1_Logic.MatchConfig
|
||||
|
||||
public MatchSettlementInfo(MatchSettlementInfo copyData)
|
||||
{
|
||||
SettlementType = copyData.SettlementType;
|
||||
SettlementType = NormalizeType(copyData.SettlementType);
|
||||
IsFinished = copyData.IsFinished;
|
||||
PlayerSettlements = new Dictionary<uint, PlayerSettlementGroup>();
|
||||
foreach (var kv in copyData.PlayerSettlements)
|
||||
@ -78,7 +89,7 @@ namespace TH1_Logic.MatchConfig
|
||||
|
||||
public void DeepCopy(MatchSettlementInfo copyData)
|
||||
{
|
||||
SettlementType = copyData.SettlementType;
|
||||
SettlementType = NormalizeType(copyData.SettlementType);
|
||||
IsFinished = copyData.IsFinished;
|
||||
PlayerSettlements ??= new Dictionary<uint, PlayerSettlementGroup>();
|
||||
PlayerSettlements.Clear();
|
||||
@ -91,6 +102,7 @@ namespace TH1_Logic.MatchConfig
|
||||
private static List<PlayerSettlementInfo> BuildProtectedSettlements(MapConfig config)
|
||||
{
|
||||
var protectedSettlements = new List<PlayerSettlementInfo>();
|
||||
var matchType = NormalizeType(config?.MatchSettlement ?? MatchSettlementType.None);
|
||||
var sourceSettlements = config?.PlayerSettlements;
|
||||
if (sourceSettlements != null)
|
||||
{
|
||||
@ -104,15 +116,15 @@ namespace TH1_Logic.MatchConfig
|
||||
|
||||
protectedSettlements.Add(BuildProtectedSettlement(
|
||||
sourceSettlement,
|
||||
config?.MatchSettlement ?? MatchSettlementType.None));
|
||||
matchType));
|
||||
}
|
||||
}
|
||||
|
||||
if (protectedSettlements.Count > 0) return protectedSettlements;
|
||||
|
||||
LogSystem.LogWarning(
|
||||
$"MatchSettlement.Init: 结算配置为空或无效,已注入默认结算任务。MatchType={config?.MatchSettlement}");
|
||||
protectedSettlements.Add(CreateFallbackSettlement(config?.MatchSettlement ?? MatchSettlementType.None));
|
||||
$"MatchSettlement.Init: 结算配置为空或无效,已注入默认结算任务。MatchType={matchType}");
|
||||
protectedSettlements.Add(CreateFallbackSettlement(matchType));
|
||||
return protectedSettlements;
|
||||
}
|
||||
|
||||
@ -232,15 +244,14 @@ namespace TH1_Logic.MatchConfig
|
||||
{
|
||||
private static Dictionary<MatchSettlementType, IMatchSettlement> _logicDict = new Dictionary<MatchSettlementType, IMatchSettlement>()
|
||||
{
|
||||
{ MatchSettlementType.Domination, new DominationMatchSettlement() },
|
||||
{ MatchSettlementType.Perfect, new PerfectMatchSettlement() },
|
||||
{ MatchSettlementType.Normal, new NormalMatchSettlement() },
|
||||
{ MatchSettlementType.Tutor, new TutorMatchSettlement() },
|
||||
{ MatchSettlementType.Story, new StoryMatchSettlement() },
|
||||
{ MatchSettlementType.Creative, new StoryMatchSettlement() },
|
||||
};
|
||||
|
||||
public static void RefreshMatchSettlementInfo(MapData map)
|
||||
{
|
||||
map.MatchSettlement.SettlementType = MatchSettlementInfo.NormalizeType(map.MatchSettlement.SettlementType);
|
||||
if (!_logicDict.TryGetValue(map.MatchSettlement.SettlementType, out var logic))
|
||||
{
|
||||
LogSystem.LogError($"RefreshPlayerSettlementInfo Error TaskType:{map.MatchSettlement.SettlementType} No Logic");
|
||||
@ -291,12 +302,12 @@ namespace TH1_Logic.MatchConfig
|
||||
}
|
||||
|
||||
|
||||
// DOMINATION 任意玩家结算且为胜,游戏结束 所有非AI玩家结算且均为败,游戏结束
|
||||
public class DominationMatchSettlement : IMatchSettlement
|
||||
// NORMAL 任意玩家结算且为胜,游戏结束 所有非AI玩家结算且均为败,游戏结束
|
||||
public class NormalMatchSettlement : IMatchSettlement
|
||||
{
|
||||
public override MatchSettlementType GetSettlementType()
|
||||
{
|
||||
return MatchSettlementType.Domination;
|
||||
return MatchSettlementType.Normal;
|
||||
}
|
||||
|
||||
public override void Refresh(MapData map, MatchSettlementInfo info)
|
||||
@ -314,37 +325,7 @@ namespace TH1_Logic.MatchConfig
|
||||
{
|
||||
if (map.CheckIsAI(kv.Key)) continue;
|
||||
if (kv.Value.IsSettlement && !kv.Value.IsWin) continue;
|
||||
MatchSettlementStuckGuard.CheckAndRecover(map, info, MatchSettlementType.Domination, kv.Key);
|
||||
return;
|
||||
}
|
||||
info.IsFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
// PERFECT 所有非AI玩家均结算后,游戏结束
|
||||
public class PerfectMatchSettlement : IMatchSettlement
|
||||
{
|
||||
public override MatchSettlementType GetSettlementType()
|
||||
{
|
||||
return MatchSettlementType.Perfect;
|
||||
}
|
||||
|
||||
public override void Refresh(MapData map, MatchSettlementInfo info)
|
||||
{
|
||||
if (info.IsFinished) return;
|
||||
foreach (var kv in info.PlayerSettlements)
|
||||
{
|
||||
if (kv.Value.IsSettlement && kv.Value.IsWin)
|
||||
{
|
||||
info.IsFinished = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
foreach (var kv in info.PlayerSettlements)
|
||||
{
|
||||
if (map.CheckIsAI(kv.Key)) continue;
|
||||
if (kv.Value.IsSettlement && !kv.Value.IsWin) continue;
|
||||
MatchSettlementStuckGuard.CheckAndRecover(map, info, MatchSettlementType.Perfect, kv.Key);
|
||||
MatchSettlementStuckGuard.CheckAndRecover(map, info, MatchSettlementType.Normal, kv.Key);
|
||||
return;
|
||||
}
|
||||
info.IsFinished = true;
|
||||
@ -435,38 +416,6 @@ namespace TH1_Logic.MatchConfig
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CREATIVE 所有非AI玩家均结算后,游戏结束
|
||||
public class CreativeMatchSettlement : IMatchSettlement
|
||||
{
|
||||
public override MatchSettlementType GetSettlementType()
|
||||
{
|
||||
return MatchSettlementType.Creative;
|
||||
}
|
||||
|
||||
public override void Refresh(MapData map, MatchSettlementInfo info)
|
||||
{
|
||||
if (info.IsFinished) return;
|
||||
foreach (var kv in info.PlayerSettlements)
|
||||
{
|
||||
if (kv.Value.IsSettlement && kv.Value.IsWin)
|
||||
{
|
||||
info.IsFinished = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
foreach (var kv in info.PlayerSettlements)
|
||||
{
|
||||
if (map.CheckIsAI(kv.Key)) continue;
|
||||
if (kv.Value.IsSettlement && !kv.Value.IsWin) continue;
|
||||
MatchSettlementStuckGuard.CheckAndRecover(map, info, MatchSettlementType.Creative, kv.Key);
|
||||
return;
|
||||
}
|
||||
info.IsFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 偶现 bug 兜底:玩家明明已经满足"应当结束"的客观条件,但 task 链路因为某种运行时累积态没把
|
||||
/// PlayerSettlementGroup 翻成 IsSettlement,导致游戏一直能下一回合。已知"退出再读档"可恢复。
|
||||
|
||||
@ -47,17 +47,43 @@ namespace TH1_Logic.MatchConfig
|
||||
public void OnAfterMemoryPackDeserialize()
|
||||
{
|
||||
Items ??= new List<MomentItemBase>();
|
||||
EnsureMomentItems();
|
||||
}
|
||||
|
||||
public void InitItems()
|
||||
{
|
||||
Items.Clear();
|
||||
AddAllMomentItems();
|
||||
}
|
||||
|
||||
private void EnsureMomentItems()
|
||||
{
|
||||
for (int i = Items.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Items[i] is ExploitWonderMomentItem)
|
||||
Items.RemoveAt(i);
|
||||
}
|
||||
|
||||
var existingTypes = new HashSet<System.Type>();
|
||||
foreach (var item in Items)
|
||||
{
|
||||
if (item != null)
|
||||
existingTypes.Add(item.GetType());
|
||||
}
|
||||
|
||||
AddAllMomentItems(existingTypes);
|
||||
}
|
||||
|
||||
private void AddAllMomentItems(HashSet<System.Type> existingTypes = null)
|
||||
{
|
||||
var baseType = typeof(MomentItemBase);
|
||||
var assembly = baseType.Assembly;
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.IsClass && !type.IsAbstract && baseType.IsAssignableFrom(type))
|
||||
{
|
||||
if (type == typeof(ExploitWonderMomentItem)) continue;
|
||||
if (existingTypes != null && existingTypes.Contains(type)) continue;
|
||||
var item = (MomentItemBase)System.Activator.CreateInstance(type);
|
||||
Items.Add(item);
|
||||
}
|
||||
@ -137,6 +163,13 @@ namespace TH1_Logic.MatchConfig
|
||||
[MemoryPackUnion(56, typeof(ExploitFirstBigSawmillMomentItem))]
|
||||
[MemoryPackUnion(57, typeof(BattleHasTwoHeroMomentItem))]
|
||||
[MemoryPackUnion(58, typeof(BattleHasThreeHeroMomentItem))]
|
||||
[MemoryPackUnion(59, typeof(ExploitWonderPeaceMomentItem))]
|
||||
[MemoryPackUnion(60, typeof(ExploitWonderKnowledgeMomentItem))]
|
||||
[MemoryPackUnion(61, typeof(ExploitWonderTradeMomentItem))]
|
||||
[MemoryPackUnion(62, typeof(ExploitWonderWealthMomentItem))]
|
||||
[MemoryPackUnion(63, typeof(ExploitWonderPowerMomentItem))]
|
||||
[MemoryPackUnion(64, typeof(ExploitWonderParkMomentItem))]
|
||||
[MemoryPackUnion(65, typeof(ExploitWonderEyeMomentItem))]
|
||||
public abstract partial class MomentItemBase
|
||||
{
|
||||
public bool IsExecute = false;
|
||||
@ -144,6 +177,16 @@ namespace TH1_Logic.MatchConfig
|
||||
public int ExecuteCount = 0;
|
||||
public abstract MomentSubType GetMomentSubType();
|
||||
|
||||
public virtual string GetFallbackDesc()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public virtual string GetProgressText(MapData map, PlayerData player)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public virtual void Execute(PlayerData player)
|
||||
{
|
||||
ExecuteCount++;
|
||||
@ -844,17 +887,141 @@ namespace TH1_Logic.MatchConfig
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderMomentItem : MomentItemBase
|
||||
{
|
||||
public ExploitWonderMomentItem()
|
||||
{
|
||||
PlayerCultureValue = 5;
|
||||
}
|
||||
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonder;
|
||||
|
||||
public override void OnWonderBuild(MapData map, PlayerData player, WonderTypeEnum wonderType)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public abstract partial class ExploitSpecificWonderMomentItem : MomentItemBase
|
||||
{
|
||||
protected abstract WonderTypeEnum WonderType { get; }
|
||||
|
||||
protected abstract string FallbackDesc { get; }
|
||||
|
||||
public ExploitSpecificWonderMomentItem()
|
||||
{
|
||||
PlayerCultureValue = 5;
|
||||
}
|
||||
|
||||
public override string GetFallbackDesc()
|
||||
{
|
||||
return FallbackDesc;
|
||||
}
|
||||
|
||||
public override void OnWonderBuild(MapData map, PlayerData player, WonderTypeEnum wonderType)
|
||||
{
|
||||
if (IsExecute) return;
|
||||
if (wonderType != WonderType) return;
|
||||
IsExecute = true;
|
||||
Execute(player);
|
||||
}
|
||||
|
||||
public override string GetProgressText(MapData map, PlayerData player)
|
||||
{
|
||||
if (map == null || player == null) return string.Empty;
|
||||
if (IsExecute) return string.Empty;
|
||||
|
||||
switch (WonderType)
|
||||
{
|
||||
case WonderTypeEnum.PEACE:
|
||||
return $"{ClampProgress(player.TurnNoAttack, 5)}/5";
|
||||
case WonderTypeEnum.KNOWLEDGE:
|
||||
return GetTechProgress(player);
|
||||
case WonderTypeEnum.TRADE:
|
||||
return $"{ClampProgress(Main.PlayerLogic.GetConnectedCityCount(map, player), 5)}/5";
|
||||
case WonderTypeEnum.WEALTH:
|
||||
return $"{ClampProgress(player.PlayerCoin, 100)}/100";
|
||||
case WonderTypeEnum.POWER:
|
||||
return $"{ClampProgress(player.TotalKill, 10)}/10";
|
||||
case WonderTypeEnum.PARK:
|
||||
return $"{ClampProgress(Main.PlayerLogic.GetMaxCityLevel(map, player), 6)}/6";
|
||||
case WonderTypeEnum.EYE:
|
||||
return $"{GetTowerInSightCount(map, player)}/4";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static int ClampProgress(int value, int max)
|
||||
{
|
||||
if (value < 0) return 0;
|
||||
return value > max ? max : value;
|
||||
}
|
||||
|
||||
private static string GetTechProgress(PlayerData player)
|
||||
{
|
||||
if (player.TechTree.HasAllTech(player)) return "1/1";
|
||||
return "0/1";
|
||||
}
|
||||
|
||||
private static int GetTowerInSightCount(MapData map, PlayerData player)
|
||||
{
|
||||
int count = 0;
|
||||
if (map.GridMap.GetGridDataByPos(0, 0, out var g1) && player.Sight.CheckIsInSight(g1.Id)) count++;
|
||||
if (map.GridMap.GetGridDataByPos(0, (int)map.MapConfig.Height - 1, out var g2) && player.Sight.CheckIsInSight(g2.Id)) count++;
|
||||
if (map.GridMap.GetGridDataByPos((int)map.MapConfig.Width - 1, 0, out var g3) && player.Sight.CheckIsInSight(g3.Id)) count++;
|
||||
if (map.GridMap.GetGridDataByPos((int)map.MapConfig.Width - 1, (int)map.MapConfig.Height - 1, out var g4) && player.Sight.CheckIsInSight(g4.Id)) count++;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderPeaceMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.PEACE;
|
||||
protected override string FallbackDesc => "完成和平奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderPEACE;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderKnowledgeMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.KNOWLEDGE;
|
||||
protected override string FallbackDesc => "完成知识奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderKNOWLEDGE;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderTradeMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.TRADE;
|
||||
protected override string FallbackDesc => "完成贸易奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderTRADE;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderWealthMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.WEALTH;
|
||||
protected override string FallbackDesc => "完成财富奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderWEALTH;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderPowerMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.POWER;
|
||||
protected override string FallbackDesc => "完成力量奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderPOWER;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderParkMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.PARK;
|
||||
protected override string FallbackDesc => "完成乐园奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderPARK;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ExploitWonderEyeMomentItem : ExploitSpecificWonderMomentItem
|
||||
{
|
||||
protected override WonderTypeEnum WonderType => WonderTypeEnum.EYE;
|
||||
protected override string FallbackDesc => "完成结界塔奇观";
|
||||
public override MomentSubType GetMomentSubType() => MomentSubType.ExploitWonderEYE;
|
||||
}
|
||||
|
||||
|
||||
@ -1145,4 +1312,4 @@ namespace TH1_Logic.MatchConfig
|
||||
IsExecute = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +63,8 @@ namespace TH1_Logic.Steam
|
||||
// 监听套接字
|
||||
private float _socketRecord;
|
||||
private HSteamListenSocket _listenSocket;
|
||||
private float _lastConnectionFailureTime = -999f;
|
||||
private string _lastConnectionFailureReason = string.Empty;
|
||||
|
||||
// 回调
|
||||
private Callback<SteamNetConnectionStatusChangedCallback_t> _cbConnectionStatusChanged;
|
||||
@ -76,6 +78,11 @@ namespace TH1_Logic.Steam
|
||||
public event Action<string> OnConnectionErrorEvent;
|
||||
|
||||
public bool IsInitialized => _listenSocket != HSteamListenSocket.Invalid;
|
||||
public bool HasRecentConnectionFailure(float seconds, out string reason)
|
||||
{
|
||||
reason = _lastConnectionFailureReason;
|
||||
return _lastConnectionFailureTime > 0f && Time.time - _lastConnectionFailureTime <= seconds;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
public void Initialize()
|
||||
@ -107,6 +114,8 @@ namespace TH1_Logic.Steam
|
||||
_listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(TargetPort, 0, null);
|
||||
if (_listenSocket != HSteamListenSocket.Invalid)
|
||||
{
|
||||
_lastConnectionFailureTime = -999f;
|
||||
_lastConnectionFailureReason = string.Empty;
|
||||
LogSystem.LogInfo($"成功在虚拟端口 {TargetPort} 创建监听套接字: {_listenSocket}");
|
||||
}
|
||||
}
|
||||
@ -176,7 +185,9 @@ namespace TH1_Logic.Steam
|
||||
var connection = SteamNetworkingSockets.ConnectP2P(ref identity, TargetPort, options.Length, options);
|
||||
if (connection == HSteamNetConnection.Invalid)
|
||||
{
|
||||
OnConnectionErrorEvent?.Invoke($"Failed to connect to {steamID}");
|
||||
var reason = $"Failed to connect to {steamID}";
|
||||
MarkConnectionFailure(reason);
|
||||
OnConnectionErrorEvent?.Invoke(reason);
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PConnectionFailed);
|
||||
return false;
|
||||
}
|
||||
@ -283,6 +294,7 @@ namespace TH1_Logic.Steam
|
||||
foreach (var steamID in timeoutList)
|
||||
{
|
||||
var reason = $"Connection to {steamID} timed out";
|
||||
MarkConnectionFailure(reason);
|
||||
LogSystem.LogError(reason);
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PConnectionTimeout);
|
||||
_connectionTimeouts.Remove(steamID);
|
||||
@ -404,6 +416,8 @@ namespace TH1_Logic.Steam
|
||||
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
|
||||
LogSystem.LogInfo($"Successfully connected to {remote}");
|
||||
_lastConnectionFailureTime = -999f;
|
||||
_lastConnectionFailureReason = string.Empty;
|
||||
if (!_connections.ContainsKey(remote))
|
||||
{
|
||||
_connections[remote] = info.m_hConn;
|
||||
@ -422,6 +436,7 @@ namespace TH1_Logic.Steam
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
LogSystem.LogWarning($"Connection problem detected locally: {remote}");
|
||||
LogSystem.LogWarning($"Problem details: {info.m_info.m_szEndDebug}");
|
||||
MarkConnectionFailure($"Connection problem detected locally: {remote}, {info.m_info.m_szEndDebug}");
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PConnectionFailed);
|
||||
HandleDisconnection(remote, info.m_hConn);
|
||||
break;
|
||||
@ -430,6 +445,7 @@ namespace TH1_Logic.Steam
|
||||
// 检查具体的失败原因
|
||||
var endReason = info.m_info.m_eEndReason;
|
||||
LogSystem.LogError($"Connection failed - Reason: {endReason}");
|
||||
MarkConnectionFailure($"Connection failed: {remote}, reason: {endReason}");
|
||||
var isTimeout = endReason == (int)ESteamNetConnectionEnd.k_ESteamNetConnectionEnd_Misc_Timeout
|
||||
|| endReason == (int)ESteamNetConnectionEnd.k_ESteamNetConnectionEnd_Remote_Timeout;
|
||||
NetworkPlayerTipManager.Instance.Request(isTimeout
|
||||
@ -469,6 +485,12 @@ namespace TH1_Logic.Steam
|
||||
}
|
||||
|
||||
// 检查是否应该接受这个连接
|
||||
private void MarkConnectionFailure(string reason)
|
||||
{
|
||||
_lastConnectionFailureTime = Time.time;
|
||||
_lastConnectionFailureReason = reason ?? string.Empty;
|
||||
}
|
||||
|
||||
private bool ShouldAcceptIncomingConnection(CSteamID steamID)
|
||||
{
|
||||
// 基本检查:确保是有效的Steam ID
|
||||
|
||||
@ -578,6 +578,31 @@ namespace TH1_Logic.Steam
|
||||
|| details.m_eAvail == ESteamNetworkingAvailability.k_ESteamNetworkingAvailability_Current;
|
||||
return relayOk;
|
||||
}
|
||||
|
||||
public bool CanCreateLobbyNow(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
if (!IsSteamSessionLikelyAlive())
|
||||
{
|
||||
reason = "Steam session is not alive.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SimpleP2P.Instance == null || !SimpleP2P.Instance.IsInitialized)
|
||||
{
|
||||
reason = "P2P listen socket is not ready.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SimpleP2P.Instance.HasRecentConnectionFailure(30f, out var p2pReason))
|
||||
{
|
||||
reason = p2pReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查 steam_appid.txt 文件
|
||||
private void CheckSteamAppIdFile()
|
||||
@ -636,6 +661,13 @@ namespace TH1_Logic.Steam
|
||||
public void CreateLobby(int maxMembers = 4, bool isPublic = true, string password = "", string roomName = "")
|
||||
{
|
||||
if (!EnsureSteamReadyForLobbyAction(nameof(CreateLobby))) return;
|
||||
if (!CanCreateLobbyNow(out var reason))
|
||||
{
|
||||
LogSystem.LogWarning($"Cannot create lobby now: {reason}");
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentState != LobbyState.None)
|
||||
{
|
||||
LogSystem.LogInfo($"Cannot create lobby in state: {CurrentState}");
|
||||
@ -644,7 +676,11 @@ namespace TH1_Logic.Steam
|
||||
|
||||
_pendingLobbyPassword = password ?? "";
|
||||
_pendingLobbyRoomName = FilterRoomName(string.IsNullOrWhiteSpace(roomName) ? GetDefaultRoomName(SelfName) : roomName.Trim());
|
||||
_pendingLobbyIsPublic = isPublic;
|
||||
if (_pendingLobbyRoomName.Length < 2)
|
||||
_pendingLobbyRoomName = FilterRoomName(GetDefaultRoomName(SelfName));
|
||||
if (_pendingLobbyRoomName.Length < 2)
|
||||
_pendingLobbyRoomName = "Default";
|
||||
_pendingLobbyIsPublic = isPublic;
|
||||
CurrentState = LobbyState.Creating;
|
||||
LogSystem.LogInfo($"Creating {(isPublic ? "public" : "friends-only")} lobby with max members: {maxMembers}, hasPassword: {!string.IsNullOrEmpty(_pendingLobbyPassword)}");
|
||||
if (!TrySteamApi("SteamMatchmaking.CreateLobby", () => SteamMatchmaking.CreateLobby(isPublic?ELobbyType.k_ELobbyTypePublic:ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers)))
|
||||
@ -1352,7 +1388,11 @@ namespace TH1_Logic.Steam
|
||||
|
||||
if (!CurrentLobby.IsValid()) return false;
|
||||
|
||||
roomName = FilterRoomName(roomName);
|
||||
roomName = FilterRoomName(string.IsNullOrWhiteSpace(roomName) ? GetRoomName() : roomName.Trim());
|
||||
if (roomName.Length < 2)
|
||||
roomName = FilterRoomName(GetRoomName());
|
||||
if (roomName.Length < 2)
|
||||
roomName = "Default";
|
||||
bool success = SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", roomName);
|
||||
if (success)
|
||||
{
|
||||
|
||||
@ -28,9 +28,12 @@ namespace TH1_UI.View.Bottom
|
||||
|
||||
// ChatArea
|
||||
public RectTransform ChatAreaRoot;
|
||||
public RectTransform NetErrorPanelRoot;
|
||||
|
||||
private UIChatAreaMono _chatArea;
|
||||
private GameObject _chatAreaGo;
|
||||
private UINetErrorAreaMono _netErrorArea;
|
||||
private GameObject _netErrorAreaGo;
|
||||
|
||||
private List<UIBottomNetRowMono> _netInfoRowList = null;
|
||||
private bool _initialized = false;
|
||||
@ -49,7 +52,7 @@ namespace TH1_UI.View.Bottom
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
//Init将确定进入游戏时所有房间成员的状态,除非有新成员加入,否则不会删减
|
||||
// Initializes the room member state when entering a match.
|
||||
private void Init()
|
||||
{
|
||||
_initialized = true;
|
||||
@ -58,11 +61,17 @@ namespace TH1_UI.View.Bottom
|
||||
if (NetInfo != null)
|
||||
NetInfo.gameObject.SetActive(isMulti);
|
||||
|
||||
// ChatArea 跟随 NetInfo 一起初始化(每局重新订阅 OnSendMessage)
|
||||
// ChatArea and NetErrorArea follow NetInfo lifetime.
|
||||
if (isMulti)
|
||||
{
|
||||
InitChatArea();
|
||||
InitNetErrorArea();
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseChatArea();
|
||||
CloseNetErrorArea();
|
||||
}
|
||||
|
||||
if (!isMulti) return;
|
||||
|
||||
@ -107,7 +116,7 @@ namespace TH1_UI.View.Bottom
|
||||
ScrollViewRect.sizeDelta = sd;
|
||||
}
|
||||
|
||||
//UpdateView只会修改NetStatus和房主标记
|
||||
// UpdateView only changes NetStatus and room owner marker.
|
||||
public void UpdateView()
|
||||
{
|
||||
|
||||
@ -123,7 +132,7 @@ namespace TH1_UI.View.Bottom
|
||||
var mids = lobbyInfo.GetAllMemberInfo();
|
||||
PlayMemberEnterAudioIfNeeded(mids.Count);
|
||||
|
||||
//如果发现人数变多了,重新Init(比如继续一个3人局游戏,开始进来的时候只有2人
|
||||
// Reinitialize if the lobby member count grows.
|
||||
if (mids.Count > _netInfoRowList.Count)
|
||||
Init();
|
||||
|
||||
@ -175,7 +184,7 @@ namespace TH1_UI.View.Bottom
|
||||
|
||||
var parent = ChatAreaRoot != null ? ChatAreaRoot : transform as RectTransform;
|
||||
_chatAreaGo = Instantiate(prefab, parent);
|
||||
FitChatAreaToRoot(_chatAreaGo.transform as RectTransform);
|
||||
FitToRoot(_chatAreaGo.transform as RectTransform, parent);
|
||||
_chatArea = _chatAreaGo.GetComponent<UIChatAreaMono>();
|
||||
if (_chatArea == null)
|
||||
{
|
||||
@ -190,9 +199,9 @@ namespace TH1_UI.View.Bottom
|
||||
_chatArea.Init();
|
||||
}
|
||||
|
||||
private void FitChatAreaToRoot(RectTransform rectTransform)
|
||||
private void FitToRoot(RectTransform rectTransform, RectTransform root)
|
||||
{
|
||||
if (rectTransform == null || ChatAreaRoot == null) return;
|
||||
if (rectTransform == null || root == null) return;
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.offsetMin = Vector2.zero;
|
||||
@ -215,13 +224,67 @@ namespace TH1_UI.View.Bottom
|
||||
_chatAreaGo = null;
|
||||
}
|
||||
|
||||
private void InitNetErrorArea()
|
||||
{
|
||||
if (_netErrorArea != null) return;
|
||||
|
||||
var prefab = Resources.Load<GameObject>("Prefab/UI/Common/Chat/NetErrorAreaPanel");
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[UIBottomNet] NetErrorAreaPanel prefab not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var parent = GetNetErrorPanelRoot();
|
||||
_netErrorAreaGo = Instantiate(prefab, parent);
|
||||
FitToRoot(_netErrorAreaGo.transform as RectTransform, parent);
|
||||
_netErrorAreaGo.transform.SetAsLastSibling();
|
||||
_netErrorArea = _netErrorAreaGo.GetComponent<UINetErrorAreaMono>();
|
||||
if (_netErrorArea == null)
|
||||
{
|
||||
Debug.LogError("[UIBottomNet] NetErrorAreaPanel missing UINetErrorAreaMono.");
|
||||
Destroy(_netErrorAreaGo);
|
||||
_netErrorAreaGo = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_netErrorArea.Init();
|
||||
}
|
||||
|
||||
private RectTransform GetNetErrorPanelRoot()
|
||||
{
|
||||
if (NetErrorPanelRoot != null) return NetErrorPanelRoot;
|
||||
|
||||
var root = transform.Find("NetErrorPanelRoot") as RectTransform;
|
||||
if (root != null)
|
||||
{
|
||||
NetErrorPanelRoot = root;
|
||||
return NetErrorPanelRoot;
|
||||
}
|
||||
|
||||
return ChatAreaRoot != null ? ChatAreaRoot : transform as RectTransform;
|
||||
}
|
||||
|
||||
private void CloseNetErrorArea()
|
||||
{
|
||||
if (_netErrorArea != null)
|
||||
_netErrorArea.Shutdown();
|
||||
|
||||
if (_netErrorAreaGo != null)
|
||||
Destroy(_netErrorAreaGo);
|
||||
|
||||
_netErrorArea = null;
|
||||
_netErrorAreaGo = null;
|
||||
}
|
||||
|
||||
public void CloseView()
|
||||
{
|
||||
_initialized = false;
|
||||
_lastNetMemberCount = -1;
|
||||
|
||||
// 清理 ChatArea
|
||||
// 娓呯悊 ChatArea
|
||||
CloseChatArea();
|
||||
CloseNetErrorArea();
|
||||
|
||||
//AudioManager.Instance.StopMusic();
|
||||
}
|
||||
|
||||
136
Unity/Assets/Scripts/TH1_UI/View/Common/UINetErrorAreaMono.cs
Normal file
136
Unity/Assets/Scripts/TH1_UI/View/Common/UINetErrorAreaMono.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using TH1_Logic.Net;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TH1_UI.View.Common
|
||||
{
|
||||
public class UINetErrorAreaMono : MonoBehaviour
|
||||
{
|
||||
[Header("Toggle")]
|
||||
public Button ToggleButton;
|
||||
public GameObject LogArea;
|
||||
|
||||
[Header("Log List")]
|
||||
public RectTransform Content;
|
||||
public GameObject LogItemPrefab;
|
||||
public ScrollRect ScrollRect;
|
||||
|
||||
[Header("Config")]
|
||||
public int MaxLogCount = 100;
|
||||
|
||||
private readonly Queue<GameObject> _logItems = new Queue<GameObject>();
|
||||
private bool _initialized;
|
||||
private bool _logAreaVisible;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
if (_initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
if (ToggleButton != null)
|
||||
{
|
||||
ToggleButton.gameObject.SetActive(true);
|
||||
ToggleButton.onClick.RemoveListener(ToggleLogArea);
|
||||
ToggleButton.onClick.AddListener(ToggleLogArea);
|
||||
}
|
||||
|
||||
SetLogAreaVisible(false);
|
||||
NetworkPlayerTipManager.Instance.OnTipRequested += OnNetworkPlayerTipRequested;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
if (!_initialized) return;
|
||||
_initialized = false;
|
||||
|
||||
NetworkPlayerTipManager.Instance.OnTipRequested -= OnNetworkPlayerTipRequested;
|
||||
|
||||
if (ToggleButton != null)
|
||||
ToggleButton.onClick.RemoveListener(ToggleLogArea);
|
||||
|
||||
ClearLogs();
|
||||
}
|
||||
|
||||
private void OnNetworkPlayerTipRequested(NetworkPlayerTipPayload payload)
|
||||
{
|
||||
AddLog(payload);
|
||||
}
|
||||
|
||||
private void ToggleLogArea()
|
||||
{
|
||||
SetLogAreaVisible(!_logAreaVisible);
|
||||
}
|
||||
|
||||
private void SetLogAreaVisible(bool visible)
|
||||
{
|
||||
_logAreaVisible = visible;
|
||||
if (LogArea != null)
|
||||
LogArea.SetActive(visible);
|
||||
}
|
||||
|
||||
private void AddLog(NetworkPlayerTipPayload payload)
|
||||
{
|
||||
if (Content == null || LogItemPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("[NetErrorArea] Log prefab or content is not assigned.");
|
||||
return;
|
||||
}
|
||||
|
||||
while (_logItems.Count >= Mathf.Max(1, MaxLogCount))
|
||||
{
|
||||
var oldest = _logItems.Dequeue();
|
||||
if (oldest != null)
|
||||
Destroy(oldest);
|
||||
}
|
||||
|
||||
var go = Instantiate(LogItemPrefab, Content);
|
||||
go.SetActive(true);
|
||||
|
||||
var text = go.GetComponentInChildren<TMP_Text>();
|
||||
if (text != null)
|
||||
text.text = BuildLogText(payload);
|
||||
|
||||
_logItems.Enqueue(go);
|
||||
|
||||
if (ScrollRect != null)
|
||||
{
|
||||
Canvas.ForceUpdateCanvases();
|
||||
ScrollRect.verticalNormalizedPosition = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildLogText(NetworkPlayerTipPayload payload)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('[').Append(DateTime.Now.ToString("HH:mm:ss")).Append("] ");
|
||||
sb.Append(payload?.TipType.ToString() ?? "NetworkTip");
|
||||
|
||||
if (!string.IsNullOrEmpty(payload?.Title))
|
||||
sb.Append(" | ").Append(payload.Title);
|
||||
|
||||
if (!string.IsNullOrEmpty(payload?.Message))
|
||||
sb.Append('\n').Append(payload.Message);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void ClearLogs()
|
||||
{
|
||||
while (_logItems.Count > 0)
|
||||
{
|
||||
var go = _logItems.Dequeue();
|
||||
if (go != null)
|
||||
Destroy(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2f7cc80c9bc4d179c6cbebdb94477bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -22,6 +22,7 @@ namespace TH1_UI.View.Info
|
||||
|
||||
private MomentItemBase _momentItem;
|
||||
private MomentSubData _momentSubData;
|
||||
private CultureTaskInfo _cultureTaskInfo;
|
||||
|
||||
public void Init(MomentItemBase momentItem)
|
||||
{
|
||||
@ -29,19 +30,29 @@ namespace TH1_UI.View.Info
|
||||
if (_momentItem == null) return;
|
||||
|
||||
var subType = _momentItem.GetMomentSubType();
|
||||
if (!Table.Instance.MomentDataAssets.GetMomentSubData(subType, out _momentSubData)) return;
|
||||
Table.Instance.CultureCardDataAssets.GetCultureTaskInfo(subType, out _cultureTaskInfo);
|
||||
Table.Instance.MomentDataAssets.GetMomentSubData(subType, out _momentSubData);
|
||||
|
||||
// 描述
|
||||
if (Desc != null)
|
||||
MultilingualManager.Instance.SetUIText(Desc, _momentSubData.Desc);
|
||||
{
|
||||
if (_cultureTaskInfo != null)
|
||||
MultilingualManager.Instance.SetUIText(Desc, _cultureTaskInfo.Description);
|
||||
else if (_momentSubData != null)
|
||||
MultilingualManager.Instance.SetUIText(Desc, _momentSubData.Desc);
|
||||
else
|
||||
Desc.text = _momentItem.GetFallbackDesc();
|
||||
}
|
||||
|
||||
// 图标
|
||||
if (Icon != null && _momentSubData.Icon != null)
|
||||
if (Icon != null && _cultureTaskInfo != null && _cultureTaskInfo.Icon != null)
|
||||
Icon.sprite = _cultureTaskInfo.Icon;
|
||||
else if (Icon != null && _momentSubData != null && _momentSubData.Icon != null)
|
||||
Icon.sprite = _momentSubData.Icon;
|
||||
|
||||
// 文化值
|
||||
if (CultureValue != null)
|
||||
CultureValue.text = _momentItem.PlayerCultureValue.ToString();
|
||||
CultureValue.text = (_cultureTaskInfo != null ? _cultureTaskInfo.CultureValue : _momentItem.PlayerCultureValue).ToString();
|
||||
|
||||
// 默认隐藏CompletedGroupPer,由UpdateInfo根据条件决定是否显示
|
||||
if (CompletedGroupPer != null)
|
||||
@ -50,11 +61,13 @@ namespace TH1_UI.View.Info
|
||||
|
||||
public void UpdateInfo(PlayerData player)
|
||||
{
|
||||
if (_momentItem == null || _momentSubData == null) return;
|
||||
if (_momentItem == null) return;
|
||||
|
||||
bool isEveryTime = _momentSubData.EveryTime;
|
||||
bool isEveryTime = _momentSubData != null && _momentSubData.EveryTime;
|
||||
bool oneTimeCompleted = _momentItem.IsExecute;
|
||||
bool everyTimeCompleted = isEveryTime && _momentItem.ExecuteCount >= _momentSubData.MaxTime;
|
||||
string progressText = _momentItem.GetProgressText(Main.MapData, player);
|
||||
bool hasCustomProgress = !string.IsNullOrEmpty(progressText);
|
||||
|
||||
// 一次性显示AwardTitle;EveryTime且未达MaxTime显示AwardTitlePer;达到MaxTime两者都不显示
|
||||
if (AwardTitle != null) AwardTitle.SetActive(!isEveryTime && !oneTimeCompleted);
|
||||
@ -63,7 +76,12 @@ namespace TH1_UI.View.Info
|
||||
// 执行次数:仅EveryTime且未达MaxTime时显示,格式为 "当前次数/最大次数"
|
||||
if (ExecuteCountText != null)
|
||||
{
|
||||
if (isEveryTime && !everyTimeCompleted)
|
||||
if (hasCustomProgress && !oneTimeCompleted)
|
||||
{
|
||||
ExecuteCountText.gameObject.SetActive(true);
|
||||
ExecuteCountText.text = progressText;
|
||||
}
|
||||
else if (isEveryTime && !everyTimeCompleted)
|
||||
{
|
||||
ExecuteCountText.gameObject.SetActive(true);
|
||||
ExecuteCountText.text = _momentItem.ExecuteCount + "/" + _momentSubData.MaxTime;
|
||||
|
||||
@ -62,6 +62,15 @@ namespace TH1_UI.View.Notify
|
||||
case UINotifyCommonType.OutsideMultiplayRoomGone:
|
||||
MultilingualManager.Instance.SetUIText(content,Table.Instance.TextDataAssets.OutsideMultiplayRoomGone);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayRoomNetError:
|
||||
MultilingualManager.Instance.SetUIText(content,Table.Instance.TextDataAssets.OutsideMultiplayRoomNetError);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayOpenHint:
|
||||
MultilingualManager.Instance.SetUIText(content,Table.Instance.TextDataAssets.OutsideMultiplayOpenHint);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayCantStartCount:
|
||||
MultilingualManager.Instance.SetUIText(content,Table.Instance.TextDataAssets.OutsideMultiplayCantStartCount);
|
||||
break;
|
||||
}
|
||||
Timer.Instance.TimerRegister(this, AutoClose,1f,"UINotifyCommonView");
|
||||
}
|
||||
|
||||
@ -4,10 +4,18 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
internal static class RoomNameInputValidator
|
||||
{
|
||||
public const int MinRoomNameLength = 2;
|
||||
public const int MaxRoomNameLength = 20;
|
||||
public const string InvalidRoomNameHint = "房间名称包含不适当内容,请修改后再确认。禁止使用辱骂、歧视、色情、广告、冒充官方或泄露隐私等内容。";
|
||||
public const string InvalidCharactersHint = "房间名称只能使用中文、英文字母、数字、下划线、中划线和点。";
|
||||
|
||||
public enum ErrorType
|
||||
{
|
||||
None,
|
||||
Illegal,
|
||||
TooShort
|
||||
}
|
||||
|
||||
public static string SanitizeAllowedCharacters(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return string.Empty;
|
||||
@ -32,20 +40,38 @@ namespace TH1_UI.View.Outside
|
||||
}
|
||||
|
||||
public static bool TryValidateForSubmit(string input, string fallbackRoomName, out string roomName, out string hint)
|
||||
{
|
||||
ErrorType errorType;
|
||||
if (TryValidateForSubmitWithErrorType(input, fallbackRoomName, out roomName, out errorType))
|
||||
{
|
||||
hint = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
hint = errorType == ErrorType.TooShort ? string.Empty : InvalidRoomNameHint;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryValidateForSubmitWithErrorType(string input, string fallbackRoomName, out string roomName, out ErrorType errorType)
|
||||
{
|
||||
roomName = SanitizeAllowedCharacters(input);
|
||||
if (string.IsNullOrEmpty(roomName)) roomName = SanitizeAllowedCharacters(fallbackRoomName);
|
||||
if (string.IsNullOrEmpty(roomName)) roomName = "Default";
|
||||
if (roomName.Length < MinRoomNameLength)
|
||||
{
|
||||
errorType = ErrorType.TooShort;
|
||||
return false;
|
||||
}
|
||||
|
||||
var filtered = BannedWordFilter.Filter(roomName, BannedTextContext.Name);
|
||||
if (filtered != roomName)
|
||||
{
|
||||
hint = InvalidRoomNameHint;
|
||||
errorType = ErrorType.Illegal;
|
||||
return false;
|
||||
}
|
||||
|
||||
hint = null;
|
||||
errorType = ErrorType.None;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Logic.Multilingual;
|
||||
using TH1_Logic.Steam;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
@ -68,6 +69,7 @@ namespace TH1_UI.View.Outside
|
||||
InitRoomNameInput();
|
||||
EnsureSeatCountOption();
|
||||
AutoBindPasswordLabel();
|
||||
AutoBindRoomNameHintText();
|
||||
EnsurePasswordHint();
|
||||
}
|
||||
|
||||
@ -94,9 +96,10 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
args = default;
|
||||
var inputRoomName = RoomNameInput != null ? RoomNameInput.text : string.Empty;
|
||||
if (!RoomNameInputValidator.TryValidateForSubmit(inputRoomName, _defaultRoomName, out var roomName, out var roomNameHint))
|
||||
RoomNameInputValidator.ErrorType roomNameError;
|
||||
if (!RoomNameInputValidator.TryValidateForSubmitWithErrorType(inputRoomName, _defaultRoomName, out var roomName, out roomNameError))
|
||||
{
|
||||
ShowRoomNameHint(roomNameHint);
|
||||
ShowRoomNameHint(roomNameError);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -197,14 +200,19 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
RoomNameInput.SetTextWithoutNotify(sanitized);
|
||||
RoomNameInput.caretPosition = sanitized.Length;
|
||||
ShowRoomNameHint(RoomNameInputValidator.InvalidCharactersHint);
|
||||
ShowRoomNameHint(RoomNameInputValidator.ErrorType.Illegal);
|
||||
}
|
||||
|
||||
private void ShowRoomNameHint(string hint)
|
||||
private void ShowRoomNameHint(RoomNameInputValidator.ErrorType errorType)
|
||||
{
|
||||
EnsureRoomNameHint();
|
||||
if (RoomNameHintText != null) RoomNameHintText.text = hint;
|
||||
if (RoomNameHintObject != null) RoomNameHintObject.SetActive(true);
|
||||
if (RoomNameHintObject == null || RoomNameHintText == null)
|
||||
{
|
||||
Debug.LogError("[UIOutsideMultiplayCreateRoomPanel] RoomNameHint is not assigned.");
|
||||
return;
|
||||
}
|
||||
|
||||
RoomNameHintObject.SetActive(true);
|
||||
MultilingualManager.Instance.SetUIText(RoomNameHintText, GetRoomNameHintTextKey(errorType));
|
||||
RebuildPanelLayout();
|
||||
}
|
||||
|
||||
@ -327,28 +335,28 @@ namespace TH1_UI.View.Outside
|
||||
PasswordHintObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void EnsureRoomNameHint()
|
||||
private void AutoBindRoomNameHintText()
|
||||
{
|
||||
if (RoomNameHintObject != null && RoomNameHintText != null) return;
|
||||
if (RoomNameInput == null || RoomNameInput.textComponent == null) return;
|
||||
if (RoomNameHintObject == null)
|
||||
{
|
||||
var hint = transform.Find("RoomNameHint");
|
||||
if (hint == null && RoomNameInput != null && RoomNameInput.transform.parent != null)
|
||||
hint = RoomNameInput.transform.parent.Find("RoomNameHint");
|
||||
if (hint == null && PanelRoot != null)
|
||||
hint = PanelRoot.transform.Find("RoomNameHint");
|
||||
if (hint != null)
|
||||
RoomNameHintObject = hint.gameObject;
|
||||
}
|
||||
|
||||
var hintObject = new GameObject("RoomNameHint", typeof(RectTransform));
|
||||
hintObject.transform.SetParent(RoomNameInput.transform.parent, false);
|
||||
var rect = hintObject.GetComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
rect.anchoredPosition = new Vector2(0f, -34f);
|
||||
rect.sizeDelta = new Vector2(520f, 54f);
|
||||
if (RoomNameHintObject != null && RoomNameHintText == null)
|
||||
RoomNameHintText = RoomNameHintObject.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
}
|
||||
|
||||
RoomNameHintText = hintObject.AddComponent<TextMeshProUGUI>();
|
||||
RoomNameHintText.font = RoomNameInput.textComponent.font;
|
||||
RoomNameHintText.fontSize = 18f;
|
||||
RoomNameHintText.color = Color.red;
|
||||
RoomNameHintText.alignment = TextAlignmentOptions.Center;
|
||||
RoomNameHintText.text = RoomNameInputValidator.InvalidRoomNameHint;
|
||||
RoomNameHintObject = hintObject;
|
||||
RoomNameHintObject.SetActive(false);
|
||||
private string GetRoomNameHintTextKey(RoomNameInputValidator.ErrorType errorType)
|
||||
{
|
||||
return errorType == RoomNameInputValidator.ErrorType.TooShort
|
||||
? Table.Instance.TextDataAssets.OutsideMultiplayRoomNameTooShort
|
||||
: Table.Instance.TextDataAssets.OutsideMultiplayRoomNameIllegal;
|
||||
}
|
||||
|
||||
private int GetSeatCount()
|
||||
|
||||
@ -15,6 +15,7 @@ public class UIOutsideMultiplayLobbyRowMono : MonoBehaviour
|
||||
public TextMeshProUGUI VersionText;
|
||||
public Button JoinButton;
|
||||
public Button ActionButton;
|
||||
public GameObject Lock;
|
||||
|
||||
private LobbyListInfo _lobbyInfo;
|
||||
|
||||
@ -48,6 +49,8 @@ public class UIOutsideMultiplayLobbyRowMono : MonoBehaviour
|
||||
GameStateText.text = lobbyInfo.GameState.ToString();
|
||||
if (VersionText != null)
|
||||
VersionText.text = lobbyInfo.Version ?? "";
|
||||
if (Lock != null)
|
||||
Lock.SetActive(lobbyInfo.HasPassword);
|
||||
|
||||
JoinButton.onClick.RemoveAllListeners();
|
||||
JoinButton.onClick.AddListener(() => onJoinClicked?.Invoke(_lobbyInfo));
|
||||
@ -62,15 +65,29 @@ public class UIOutsideMultiplayLobbyRowMono : MonoBehaviour
|
||||
private void AutoBindButtons()
|
||||
{
|
||||
if (ActionButton == null)
|
||||
ActionButton = FindButton("ActionButton") ?? FindButton("MoreButton") ?? FindButton("OptionButton");
|
||||
ActionButton = FindButton("ActionButton") ?? FindButton("MoreButton") ?? FindButton("OptionButton") ?? FindButton("JubaoButton");
|
||||
if (Lock == null)
|
||||
Lock = FindChild("Lock");
|
||||
}
|
||||
|
||||
private Button FindButton(string buttonName)
|
||||
{
|
||||
var child = transform.Find(buttonName);
|
||||
var child = FindChild(buttonName);
|
||||
return child != null ? child.GetComponent<Button>() : null;
|
||||
}
|
||||
|
||||
private GameObject FindChild(string childName)
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; i++)
|
||||
{
|
||||
var child = transform.GetChild(i);
|
||||
if (child.name == childName)
|
||||
return child.gameObject;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
private string _forceNameOverride;
|
||||
private Action<int> _onForceChanged;
|
||||
private Action<int> _onTeamChanged;
|
||||
private bool _showTeamControls = true;
|
||||
private bool _useAllForceOptions;
|
||||
private bool _shrinkFallbackLeaderAvatar;
|
||||
private Vector3 _leaderImageDefaultScale = Vector3.one;
|
||||
@ -77,13 +78,15 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
bool canEdit,
|
||||
string forceNameOverride,
|
||||
Action<int> onForceChanged,
|
||||
Action<int> onTeamChanged)
|
||||
Action<int> onTeamChanged,
|
||||
bool showTeamControls = true)
|
||||
{
|
||||
if (!CheckParam()) return;
|
||||
ResetButtons();
|
||||
SetRootBackground(false);
|
||||
_lobby = lobby;
|
||||
_forceNameOverride = forceNameOverride;
|
||||
_showTeamControls = showTeamControls;
|
||||
_teamId = Mathf.Max(1, teamId);
|
||||
_maxTeamId = Mathf.Max(1, maxTeamId);
|
||||
_onForceChanged = onForceChanged;
|
||||
@ -118,6 +121,7 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
SetOpenHintVisible(true);
|
||||
StatusText.text = string.Empty;
|
||||
ForcesText.text = string.Empty;
|
||||
_showTeamControls = false;
|
||||
SetTeamText(0);
|
||||
SetEditButtonsVisible(false);
|
||||
if (QuitButton != null) QuitButton.gameObject.SetActive(false);
|
||||
@ -131,12 +135,14 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
bool canEdit,
|
||||
string forceNameOverride,
|
||||
Action<int> onForceChanged,
|
||||
Action<int> onTeamChanged)
|
||||
Action<int> onTeamChanged,
|
||||
bool showTeamControls = true)
|
||||
{
|
||||
if (!CheckParam()) return;
|
||||
ResetButtons();
|
||||
SetRootBackground(false);
|
||||
_forceNameOverride = forceNameOverride;
|
||||
_showTeamControls = showTeamControls;
|
||||
_teamId = Mathf.Max(1, teamId);
|
||||
_maxTeamId = Mathf.Max(1, maxTeamId);
|
||||
_onForceChanged = onForceChanged;
|
||||
@ -164,12 +170,14 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
bool canEdit,
|
||||
string forceNameOverride,
|
||||
Action<int> onForceChanged,
|
||||
Action<int> onTeamChanged)
|
||||
Action<int> onTeamChanged,
|
||||
bool showTeamControls = true)
|
||||
{
|
||||
if (!CheckParam()) return;
|
||||
ResetButtons();
|
||||
SetRootBackground(true);
|
||||
_forceNameOverride = forceNameOverride;
|
||||
_showTeamControls = showTeamControls;
|
||||
_teamId = Mathf.Max(1, teamId);
|
||||
_maxTeamId = Mathf.Max(1, maxTeamId);
|
||||
_onForceChanged = onForceChanged;
|
||||
@ -300,6 +308,13 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
private void SetTeamText(int teamId)
|
||||
{
|
||||
if (TeamText == null) return;
|
||||
if (!_showTeamControls)
|
||||
{
|
||||
TeamText.text = string.Empty;
|
||||
TeamText.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (teamId <= 0)
|
||||
{
|
||||
TeamText.text = string.Empty;
|
||||
@ -316,9 +331,9 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
if (RefreshButton != null && RefreshButton != ForcesButton) RefreshButton.gameObject.SetActive(visible);
|
||||
if (ForceLeftButton != null) ForceLeftButton.gameObject.SetActive(visible);
|
||||
if (ForceRightButton != null) ForceRightButton.gameObject.SetActive(visible);
|
||||
if (TeamLeftButton != null) TeamLeftButton.gameObject.SetActive(visible);
|
||||
if (TeamRightButton != null) TeamRightButton.gameObject.SetActive(visible);
|
||||
if (TeamText != null) TeamText.gameObject.SetActive(visible || !string.IsNullOrEmpty(TeamText.text));
|
||||
if (TeamLeftButton != null) TeamLeftButton.gameObject.SetActive(_showTeamControls && visible);
|
||||
if (TeamRightButton != null) TeamRightButton.gameObject.SetActive(_showTeamControls && visible);
|
||||
if (TeamText != null) TeamText.gameObject.SetActive(_showTeamControls && (visible || !string.IsNullOrEmpty(TeamText.text)));
|
||||
if (ForcesText != null) ForcesText.gameObject.SetActive(visible || !string.IsNullOrEmpty(ForcesText.text));
|
||||
}
|
||||
|
||||
@ -328,8 +343,8 @@ public class UIOutsideMultiplayMemberRowMono : MonoBehaviour
|
||||
if (ForceLeftButton != null) ForceLeftButton.onClick.AddListener(() => ChangeForce(-1));
|
||||
if (ForceRightButton != null) ForceRightButton.onClick.AddListener(() => ChangeForce(1));
|
||||
if (ForcesButton != null && ForceLeftButton == null && ForceRightButton == null) ForcesButton.onClick.AddListener(OnClickForces);
|
||||
if (TeamLeftButton != null) TeamLeftButton.onClick.AddListener(() => ChangeTeam(-1));
|
||||
if (TeamRightButton != null) TeamRightButton.onClick.AddListener(() => ChangeTeam(1));
|
||||
if (_showTeamControls && TeamLeftButton != null) TeamLeftButton.onClick.AddListener(() => ChangeTeam(-1));
|
||||
if (_showTeamControls && TeamRightButton != null) TeamRightButton.onClick.AddListener(() => ChangeTeam(1));
|
||||
}
|
||||
|
||||
private void BindQuitButton(MemberInfo info, SteamLobbyManager lobby)
|
||||
|
||||
@ -12,10 +12,10 @@ namespace TH1_UI.View.Outside
|
||||
public Button ReportButton;
|
||||
|
||||
private LobbyListInfo _lobbyInfo;
|
||||
private Action<LobbyListInfo> _onBlock;
|
||||
private Action<LobbyListInfo> _onReport;
|
||||
private Func<LobbyListInfo, bool> _onBlock;
|
||||
private Func<LobbyListInfo, bool> _onReport;
|
||||
|
||||
public void Init(Action<LobbyListInfo> onBlock, Action<LobbyListInfo> onReport)
|
||||
public void Init(Func<LobbyListInfo, bool> onBlock, Func<LobbyListInfo, bool> onReport)
|
||||
{
|
||||
_onBlock = onBlock;
|
||||
_onReport = onReport;
|
||||
@ -54,14 +54,14 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
private void OnClickBlock()
|
||||
{
|
||||
Hide();
|
||||
_onBlock?.Invoke(_lobbyInfo);
|
||||
if (_onBlock?.Invoke(_lobbyInfo) == true)
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void OnClickReport()
|
||||
{
|
||||
Hide();
|
||||
_onReport?.Invoke(_lobbyInfo);
|
||||
if (_onReport?.Invoke(_lobbyInfo) == true)
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,10 +42,6 @@ namespace TH1_UI.View.Outside
|
||||
public TextMeshProUGUI RoomSeatText;
|
||||
|
||||
[Header("相关提示对象")]
|
||||
public GameObject CantStartHint;
|
||||
public TextMeshProUGUI CantStartHintText;
|
||||
public AnimancerComponent CantStartHintAnimancer;
|
||||
public Button CantStartHintCloseButton;
|
||||
public GameObject NoRoomHint;
|
||||
public GameObject NotOwnerHint;
|
||||
|
||||
@ -88,9 +84,13 @@ namespace TH1_UI.View.Outside
|
||||
public UIOutsideMultiplayJoinPasswordPanelMono JoinPasswordPanelMono;
|
||||
public GameObject RoomActionPanel;
|
||||
public UIOutsideMultiplayRoomActionPanelMono RoomActionPanelMono;
|
||||
public GameObject MultiplayInsideNotify;
|
||||
public TextMeshProUGUI MultiplayInsideNotifyText;
|
||||
public CanvasGroup MultiplayInsideNotifyCanvasGroup;
|
||||
|
||||
[Header("聊天区域")]
|
||||
public RectTransform ChatAreaRoot;
|
||||
public RectTransform NetErrorAreaRoot;
|
||||
|
||||
|
||||
[Header("房间设置选项组")]
|
||||
@ -117,9 +117,14 @@ namespace TH1_UI.View.Outside
|
||||
private List<UIOutsideMultiplayLobbyRowMono> _lobbyRowList;
|
||||
private UIChatAreaMono _chatArea;
|
||||
private GameObject _chatAreaGo;
|
||||
private UINetErrorAreaMono _netErrorArea;
|
||||
private GameObject _netErrorAreaGo;
|
||||
private LobbyListInfo _pendingJoinLobbyInfo;
|
||||
private int _multiplayInsideNotifyVersion;
|
||||
|
||||
private SteamLobbyManager _lobby;
|
||||
private const string MutedRoomNamesPrefsKey = "OutsideMultiplayMutedRoomNames";
|
||||
private const long MutedRoomNameDurationSeconds = 24 * 60 * 60;
|
||||
private const int MaxRoomSeatCount = 4;
|
||||
private int _roomSeatCount = 4;
|
||||
private int _openMemberRowCount = 0;
|
||||
@ -164,6 +169,19 @@ namespace TH1_UI.View.Outside
|
||||
(CivEnum.Indian, ForceEnum.Satori),
|
||||
};
|
||||
|
||||
[Serializable]
|
||||
private class MutedRoomNameRecord
|
||||
{
|
||||
public string RoomName;
|
||||
public long ExpireAtUnixSeconds;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class MutedRoomNameStore
|
||||
{
|
||||
public List<MutedRoomNameRecord> Records = new List<MutedRoomNameRecord>();
|
||||
}
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
base.OnInit();
|
||||
@ -260,22 +278,12 @@ namespace TH1_UI.View.Outside
|
||||
_lobbyRowList = new List<UIOutsideMultiplayLobbyRowMono>();
|
||||
_lobby = LobbyManager.Instance.Lobby as SteamLobbyManager;
|
||||
|
||||
EnsureMultiplayInsideNotify();
|
||||
EnsureMultiplaySubPanels();
|
||||
|
||||
RefreshHallButton.onClick.RemoveAllListeners();
|
||||
RefreshHallButton.onClick.AddListener(OnRefreshLobbyClicked);
|
||||
|
||||
// 注册CantStartHint关闭按钮
|
||||
CantStartHintCloseButton.onClick.RemoveAllListeners();
|
||||
CantStartHintCloseButton.onClick.AddListener(() =>
|
||||
{
|
||||
var fadeOutAnim = ResourceCache.Instance.AnimCache.UICommonPanelFadeOut;
|
||||
CantStartHintAnimancer.Play(fadeOutAnim).Events.OnEnd = () =>
|
||||
{
|
||||
CantStartHint.SetActive(false);
|
||||
};
|
||||
});
|
||||
|
||||
//_inLobby = _lobby.IsInLobby();
|
||||
}
|
||||
|
||||
@ -295,6 +303,8 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
public void SetContent(ShowUIOutsideMultiplay evt)
|
||||
{
|
||||
InitNetErrorArea();
|
||||
|
||||
//Step #1 设置朋友列表
|
||||
RefreshFriendList();
|
||||
//Step #2 设置房间列表
|
||||
@ -394,6 +404,7 @@ namespace TH1_UI.View.Outside
|
||||
else
|
||||
{
|
||||
InitChatArea();
|
||||
InitNetErrorArea();
|
||||
_noRoom = false;
|
||||
PlayMemberEnterAudioIfNeeded();
|
||||
SetRoomInfoSetting();
|
||||
@ -473,6 +484,7 @@ namespace TH1_UI.View.Outside
|
||||
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);
|
||||
bool showAiRows = IsTeamAndAIConfigEnabled();
|
||||
|
||||
_roomMemberRows.AddRange(humanRows);
|
||||
for (int i = 0; i < _openMemberRowCount; i++)
|
||||
@ -483,10 +495,13 @@ namespace TH1_UI.View.Outside
|
||||
_roomMemberRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Open });
|
||||
}
|
||||
|
||||
_roomMemberRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Line });
|
||||
int aiStart = Mathf.Max(0, emptyAiRows.Count - aiCount);
|
||||
for (int i = aiStart; i < emptyAiRows.Count; i++)
|
||||
_roomMemberRows.Add(emptyAiRows[i]);
|
||||
if (showAiRows && aiCount > 0)
|
||||
{
|
||||
_roomMemberRows.Add(new RoomMemberRowData { Type = RoomMemberRowType.Line });
|
||||
int aiStart = Mathf.Max(0, emptyAiRows.Count - aiCount);
|
||||
for (int i = aiStart; i < emptyAiRows.Count; i++)
|
||||
_roomMemberRows.Add(emptyAiRows[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderRoomMemberRows(List<MemberCiv> multiCivs)
|
||||
@ -547,18 +562,21 @@ 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);
|
||||
bool showTeamControls = IsTeamAndAIConfigEnabled();
|
||||
if (row.Type == RoomMemberRowType.Open)
|
||||
{
|
||||
memberRow.SetOpenContent(civ, force, teamId, maxTeamId, _lobby.IsLobbyOwner(), forceNameOverride,
|
||||
direction => OnOpenRowForceChanged(row.SlotIndex, direction),
|
||||
direction => OnOpenRowTeamChanged(row.SlotIndex, direction));
|
||||
direction => OnOpenRowTeamChanged(row.SlotIndex, direction),
|
||||
showTeamControls);
|
||||
return;
|
||||
}
|
||||
if (row.Type == RoomMemberRowType.AI)
|
||||
{
|
||||
memberRow.SetAIContent(civ, force, teamId, maxTeamId, _lobby.IsLobbyOwner(), forceNameOverride,
|
||||
direction => OnAIRowForceChanged(row.SlotIndex, direction),
|
||||
direction => OnAIRowTeamChanged(row.SlotIndex, direction));
|
||||
direction => OnAIRowTeamChanged(row.SlotIndex, direction),
|
||||
showTeamControls);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -566,7 +584,8 @@ namespace TH1_UI.View.Outside
|
||||
bool canEditHuman = mc.MemberId == _lobby.GetSelfMemberId();
|
||||
memberRow.SetHumanContent(row.MemberInfo, status, civ, force, teamId, maxTeamId, _lobby, canEditHuman, forceNameOverride,
|
||||
direction => OnHumanRowForceChanged(mc.Index, direction),
|
||||
direction => OnHumanRowTeamChanged(mc.Index, direction));
|
||||
direction => OnHumanRowTeamChanged(mc.Index, direction),
|
||||
showTeamControls);
|
||||
}
|
||||
|
||||
private UIOutsideMultiplayMemberRowMono GetMemberRow(int index)
|
||||
@ -687,7 +706,7 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
private int GetMinRoomSeatCount()
|
||||
{
|
||||
return Mathf.Max(2, _lobby.GetMemberCount());
|
||||
return Mathf.Max(1, _lobby.GetMemberCount());
|
||||
}
|
||||
|
||||
private int GetMaxRoomSeatCount()
|
||||
@ -730,6 +749,7 @@ namespace TH1_UI.View.Outside
|
||||
bool slotLayoutChanged = false;
|
||||
var usedCivs = new HashSet<uint>();
|
||||
var usedTeams = new HashSet<int>();
|
||||
bool forceSoloTeams = !IsTeamAndAIConfigEnabled();
|
||||
for (int i = 0; i < multiCivs.Count; i++)
|
||||
{
|
||||
var mc = multiCivs[i];
|
||||
@ -744,7 +764,16 @@ namespace TH1_UI.View.Outside
|
||||
}
|
||||
if (shouldBeOpenSlot) openSlotBudget--;
|
||||
}
|
||||
if (mc.TeamId <= 0 || mc.TeamId > totalPlayerCount) mc.TeamId = PickDefaultTeamId(i, totalPlayerCount, usedTeams);
|
||||
int soloTeamId = GetSoloTeamId(i, totalPlayerCount);
|
||||
if (forceSoloTeams && mc.TeamId != soloTeamId)
|
||||
{
|
||||
mc.TeamId = soloTeamId;
|
||||
slotLayoutChanged = true;
|
||||
}
|
||||
else if (!forceSoloTeams && (mc.TeamId <= 0 || mc.TeamId > totalPlayerCount))
|
||||
{
|
||||
mc.TeamId = PickDefaultTeamId(i, totalPlayerCount, usedTeams);
|
||||
}
|
||||
if (!mc.IsCivFixed)
|
||||
{
|
||||
var civId = PickDefaultCivId(i, usedCivs);
|
||||
@ -762,6 +791,16 @@ namespace TH1_UI.View.Outside
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsTeamAndAIConfigEnabled()
|
||||
{
|
||||
return Main.Instance?.MapConfig?.GameMode == RuntimeData.GameMode.CREATIVE;
|
||||
}
|
||||
|
||||
private static int GetSoloTeamId(int slotIndex, int totalPlayerCount)
|
||||
{
|
||||
return Mathf.Clamp(slotIndex + 1, 1, Mathf.Max(1, totalPlayerCount));
|
||||
}
|
||||
|
||||
private int GetValidTeamId(MemberCiv mc)
|
||||
{
|
||||
int maxTeamId = Mathf.Max(1, (int)Main.Instance.MapConfig.PlayerCount);
|
||||
@ -809,6 +848,7 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
private void OnHumanRowTeamChanged(int slotIndex, int direction)
|
||||
{
|
||||
if (!IsTeamAndAIConfigEnabled()) return;
|
||||
var slot = Main.Instance.MapConfig.GetPlayerSlot(slotIndex, NetMode.Multi);
|
||||
if (slot == null) return;
|
||||
int nextTeamId = GetNextTeamId(GetValidTeamId(slot), direction);
|
||||
@ -835,6 +875,7 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
private void OnAIRowTeamChanged(int slotIndex, int direction)
|
||||
{
|
||||
if (!IsTeamAndAIConfigEnabled()) return;
|
||||
if (!_lobby.IsLobbyOwner()) return;
|
||||
var slot = Main.Instance.MapConfig.GetPlayerSlot(slotIndex, NetMode.Multi);
|
||||
if (slot == null) return;
|
||||
@ -873,6 +914,7 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
private void SetEmptyPlayerSlotTeam(int slotIndex, int direction)
|
||||
{
|
||||
if (!IsTeamAndAIConfigEnabled()) return;
|
||||
if (!_lobby.IsLobbyOwner()) return;
|
||||
var slot = Main.Instance.MapConfig.GetPlayerSlot(slotIndex, NetMode.Multi);
|
||||
if (slot == null || slot.MemberId != 0 || slot.IsAI) return;
|
||||
@ -1049,9 +1091,10 @@ namespace TH1_UI.View.Outside
|
||||
}
|
||||
|
||||
var inputRoomName = RoomNameInput != null ? RoomNameInput.text : string.Empty;
|
||||
if (!RoomNameInputValidator.TryValidateForSubmit(inputRoomName, _lobby.GetRoomName(), out var roomName, out var roomNameHint))
|
||||
RoomNameInputValidator.ErrorType roomNameError;
|
||||
if (!RoomNameInputValidator.TryValidateForSubmitWithErrorType(inputRoomName, _lobby.GetRoomName(), out var roomName, out roomNameError))
|
||||
{
|
||||
ShowRoomNameHint(roomNameHint);
|
||||
ShowRoomNameHint(MultilingualManager.Instance.GetMultilingualText(GetRoomNameHintTextKey(roomNameError)));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1106,6 +1149,13 @@ namespace TH1_UI.View.Outside
|
||||
if (RoomNameHintObject != null) RoomNameHintObject.SetActive(false);
|
||||
}
|
||||
|
||||
private string GetRoomNameHintTextKey(RoomNameInputValidator.ErrorType errorType)
|
||||
{
|
||||
return errorType == RoomNameInputValidator.ErrorType.TooShort
|
||||
? Table.Instance.TextDataAssets.OutsideMultiplayRoomNameTooShort
|
||||
: Table.Instance.TextDataAssets.OutsideMultiplayRoomNameIllegal;
|
||||
}
|
||||
|
||||
private void EnsureRoomNameHint()
|
||||
{
|
||||
if (RoomNameHintObject != null && RoomNameHintText != null) return;
|
||||
@ -1136,6 +1186,13 @@ namespace TH1_UI.View.Outside
|
||||
if (!_lobby.IsLobbyOwner()) return;
|
||||
|
||||
// CREATIVE 模式下 MapSize 由玩家自由选择,不再被 PlayerCount 联动
|
||||
if (GetPlayerCountByOptionIndex(idx) < _roomSeatCount)
|
||||
{
|
||||
PlayerCount.Select(GetPlayerCountOptionIndex(Main.Instance.MapConfig.PlayerCount));
|
||||
ShowLobbyNotify(UINotifyCommonType.OutsideMultiplayCantStartCount);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMode != null && GameMode.SelectedIndex == 2)
|
||||
{
|
||||
SetMapConfig(resetGuestReady: true);
|
||||
@ -1155,6 +1212,16 @@ namespace TH1_UI.View.Outside
|
||||
RoomSettingOnPlayerCountClicked(Main.Instance.MapConfig.PlayerCount,Main.Instance.MapConfig.Width);
|
||||
}
|
||||
|
||||
private static uint GetPlayerCountByOptionIndex(uint optionIndex)
|
||||
{
|
||||
return optionIndex + 2;
|
||||
}
|
||||
|
||||
private static uint GetPlayerCountOptionIndex(uint playerCount)
|
||||
{
|
||||
return playerCount >= 2 ? playerCount - 2 : 0;
|
||||
}
|
||||
|
||||
public void OnDiffOptionClicked(uint idx)
|
||||
{
|
||||
if (!_lobby.IsLobbyOwner()) return;
|
||||
@ -1254,8 +1321,7 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
if (NoRoomHint == null) return;
|
||||
|
||||
var lobbyInfos = _lobby.LobbyListInfos;
|
||||
int lobbyCount = lobbyInfos?.Count ?? 0;
|
||||
int lobbyCount = GetVisibleLobbyInfos().Count;
|
||||
|
||||
//搜索中:隐藏NoRoomHint
|
||||
//搜索完成且有房间:隐藏NoRoomHint
|
||||
@ -1279,6 +1345,8 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
public void CreateRoom()
|
||||
{
|
||||
if (!CanCreateRoomNow()) return;
|
||||
|
||||
//如果已经在lobby了,刷新界面
|
||||
if (_lobby.IsInLobby())
|
||||
{
|
||||
@ -1300,11 +1368,28 @@ namespace TH1_UI.View.Outside
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanCreateRoomNow()
|
||||
{
|
||||
string reason = string.Empty;
|
||||
if (_lobby != null && _lobby.CanCreateLobbyNow(out reason))
|
||||
return true;
|
||||
|
||||
if (!string.IsNullOrEmpty(reason))
|
||||
Debug.LogWarning($"[UIOutsideMultiplay] Cannot create room: {reason}");
|
||||
|
||||
CreateRoomPanelMono?.Hide();
|
||||
CreateRoomPanel?.SetActive(false);
|
||||
ShowLobbyNotify(UINotifyCommonType.OutsideMultiplayRoomNetError);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据选择的房间类型创建房间
|
||||
/// </summary>
|
||||
public void CreateRoomWithType()
|
||||
{
|
||||
if (!CanCreateRoomNow()) return;
|
||||
|
||||
//隐藏创建房间面板
|
||||
CreateRoomPanel?.SetActive(false);
|
||||
|
||||
@ -1314,16 +1399,18 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
// 旧入口兜底:没有绑定新面板 Mono 时仍能创建公开/好友房。
|
||||
bool isPublic = RoomType?.SelectedIndex != 1;
|
||||
_roomSeatCount = Mathf.Clamp(_roomSeatCount, 2, 4);
|
||||
_roomSeatCount = Mathf.Clamp(_roomSeatCount, 1, 4);
|
||||
_lobby.CreateLobby(_roomSeatCount, isPublic, string.Empty, SteamLobbyManager.GetDefaultRoomName(_lobby.SelfName));
|
||||
}
|
||||
|
||||
private void OnCreateRoomPanelConfirm(UIOutsideMultiplayCreateRoomPanelMono.CreateRoomParams args)
|
||||
{
|
||||
if (!CanCreateRoomNow()) return;
|
||||
|
||||
_lobby.OnLobbyEnteredEvent += OnLobbyJoinSuccess;
|
||||
_lobby.OnLobbyErrorEvent += OnLobbyJoinFailed;
|
||||
|
||||
_roomSeatCount = Mathf.Clamp(args.SeatCount, 2, 4);
|
||||
_roomSeatCount = Mathf.Clamp(args.SeatCount, 1, 4);
|
||||
_lobby.CreateLobby(_roomSeatCount, args.IsPublic, args.Password, args.RoomName);
|
||||
}
|
||||
|
||||
@ -1351,7 +1438,7 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
if (_openMemberRowCount > 0)
|
||||
{
|
||||
ShowCantStartHint(Table.Instance.TextDataAssets.OutsideMultiplayOpenHint);
|
||||
ShowLobbyNotify(UINotifyCommonType.OutsideMultiplayOpenHint);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1376,28 +1463,13 @@ namespace TH1_UI.View.Outside
|
||||
if (members.Count > playerCount)
|
||||
{
|
||||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMembersNotSynced);
|
||||
ShowCantStartHint(Table.Instance.TextDataAssets.OutsideMultiplayCantStartCount);
|
||||
ShowLobbyNotify(UINotifyCommonType.OutsideMultiplayCantStartCount);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowLoadingAndStartGame(false);
|
||||
}
|
||||
|
||||
private void ShowCantStartHint(string textKey)
|
||||
{
|
||||
CantStartHint.SetActive(true);
|
||||
CantStartHintText.text = MultilingualManager.Instance.GetMultilingualText(textKey);
|
||||
CantStartHintAnimancer.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeIn);
|
||||
Timer.Instance.TimerRegister(this, () =>
|
||||
{
|
||||
CantStartHintAnimancer.Play(ResourceCache.Instance.AnimCache.UICommonPanelFadeOut);
|
||||
},1f,"MultiplayStartGame");
|
||||
Timer.Instance.TimerRegister(this, () =>
|
||||
{
|
||||
CantStartHint.SetActive(false);
|
||||
},1f + ResourceCache.Instance.AnimCache.UICommonPanelFadeOut.length,"MultiplayStartGame");
|
||||
}
|
||||
|
||||
private void SetMapConfig(bool resetGuestReady = false)
|
||||
{
|
||||
|
||||
@ -1533,12 +1605,13 @@ namespace TH1_UI.View.Outside
|
||||
public void OnGameModeOptionClicked(uint idx)
|
||||
{
|
||||
if (!_lobby.IsLobbyOwner()) return;
|
||||
OnGameModeOptionClickedInternal(idx, isOwner: true, syncConfig: true);
|
||||
bool wasCreative = Main.Instance?.MapConfig?.GameMode == RuntimeData.GameMode.CREATIVE;
|
||||
OnGameModeOptionClickedInternal(idx, isOwner: true, syncConfig: true, resetTeamsWhenLeavingCreative: wasCreative && idx != 2);
|
||||
}
|
||||
|
||||
// 统一处理 GameMode 的 Passive 联动(SetRoomInfoSetting 初始化/手动点击 共用)
|
||||
// syncConfig=true 时才写 MapConfig 并广播;Init 流程只刷 Passive,避免重复广播
|
||||
private void OnGameModeOptionClickedInternal(uint idx, bool isOwner, bool syncConfig)
|
||||
private void OnGameModeOptionClickedInternal(uint idx, bool isOwner, bool syncConfig, bool resetTeamsWhenLeavingCreative = false)
|
||||
{
|
||||
bool isCreative = idx == 2;
|
||||
if (WinRow != null) WinRow.SetActive(isCreative);
|
||||
@ -1588,6 +1661,8 @@ namespace TH1_UI.View.Outside
|
||||
if (syncConfig)
|
||||
{
|
||||
SetMapConfig(resetGuestReady: true);
|
||||
if (resetTeamsWhenLeavingCreative)
|
||||
ResetAllSlotsToSoloTeams();
|
||||
// 复用既有房主→成员广播入口(CheckMapConfigChanged 内部已调用)
|
||||
RoomSettingOnPlayerCountClicked(Main.Instance.MapConfig.PlayerCount, Main.Instance.MapConfig.Width);
|
||||
}
|
||||
@ -1599,6 +1674,30 @@ namespace TH1_UI.View.Outside
|
||||
SetMapConfig(resetGuestReady: true);
|
||||
Main.Instance.MapConfig.CheckMapConfigChanged();
|
||||
}
|
||||
|
||||
private bool ResetAllSlotsToSoloTeams()
|
||||
{
|
||||
var mapConfig = Main.Instance?.MapConfig;
|
||||
if (mapConfig == null) return false;
|
||||
|
||||
mapConfig.SetPlayerCount(mapConfig.PlayerCount, NetMode.Multi);
|
||||
var multiCivs = mapConfig.MultiCivs;
|
||||
if (multiCivs == null) return false;
|
||||
|
||||
int totalPlayerCount = Mathf.Max(1, (int)mapConfig.PlayerCount);
|
||||
bool changed = false;
|
||||
for (int i = 0; i < multiCivs.Count; i++)
|
||||
{
|
||||
var mc = multiCivs[i];
|
||||
if (mc == null) continue;
|
||||
int soloTeamId = GetSoloTeamId(i, totalPlayerCount);
|
||||
if (mc.TeamId == soloTeamId) continue;
|
||||
mc.TeamId = soloTeamId;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
private void InitChatArea()
|
||||
@ -1617,8 +1716,7 @@ namespace TH1_UI.View.Outside
|
||||
return;
|
||||
}
|
||||
|
||||
_chatAreaGo = Instantiate(prefab);
|
||||
_chatAreaGo.transform.SetParent(ChatAreaRoot, false);
|
||||
_chatAreaGo = Instantiate(prefab, ChatAreaRoot, false);
|
||||
_chatAreaGo.transform.SetAsLastSibling();
|
||||
FitChatAreaToRoot(_chatAreaGo.transform as RectTransform);
|
||||
_chatArea = _chatAreaGo.GetComponent<UIChatAreaMono>();
|
||||
@ -1662,6 +1760,48 @@ namespace TH1_UI.View.Outside
|
||||
_chatAreaGo = null;
|
||||
}
|
||||
|
||||
private void InitNetErrorArea()
|
||||
{
|
||||
if (_netErrorArea != null) return;
|
||||
if (NetErrorAreaRoot == null)
|
||||
{
|
||||
Debug.LogError("[UIOutsideMultiplay] NetErrorAreaRoot is not assigned.");
|
||||
return;
|
||||
}
|
||||
|
||||
var prefab = Resources.Load<GameObject>("Prefab/UI/Common/Chat/NetErrorAreaPanel");
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[UIOutsideMultiplay] NetErrorAreaPanel prefab not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
_netErrorAreaGo = Instantiate(prefab, NetErrorAreaRoot, false);
|
||||
_netErrorAreaGo.transform.SetAsLastSibling();
|
||||
_netErrorArea = _netErrorAreaGo.GetComponent<UINetErrorAreaMono>();
|
||||
if (_netErrorArea == null)
|
||||
{
|
||||
Debug.LogError("[UIOutsideMultiplay] NetErrorAreaPanel missing UINetErrorAreaMono.");
|
||||
Destroy(_netErrorAreaGo);
|
||||
_netErrorAreaGo = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_netErrorArea.Init();
|
||||
}
|
||||
|
||||
private void CloseNetErrorArea()
|
||||
{
|
||||
if (_netErrorArea != null)
|
||||
_netErrorArea.Shutdown();
|
||||
|
||||
if (_netErrorAreaGo != null)
|
||||
Destroy(_netErrorAreaGo);
|
||||
|
||||
_netErrorArea = null;
|
||||
_netErrorAreaGo = null;
|
||||
}
|
||||
|
||||
private void OnChatMessageSendInternal(string message)
|
||||
{
|
||||
var lobby = LobbyManager.Instance.Lobby;
|
||||
@ -1673,6 +1813,8 @@ namespace TH1_UI.View.Outside
|
||||
public void OnCloseView()
|
||||
{
|
||||
CloseChatArea();
|
||||
CloseNetErrorArea();
|
||||
HideMultiplayInsideNotify();
|
||||
|
||||
_lobby.OnMembersChangedEvent -= RefreshAll;
|
||||
_lobby.OnLobbyLeftEvent -= RefreshAll;
|
||||
@ -1734,8 +1876,8 @@ namespace TH1_UI.View.Outside
|
||||
{
|
||||
if (!_lobby.IsInitialized()) return;
|
||||
|
||||
var lobbyInfos = _lobby.LobbyListInfos;
|
||||
int lobbyCount = lobbyInfos?.Count ?? 0;
|
||||
var lobbyInfos = GetVisibleLobbyInfos();
|
||||
int lobbyCount = lobbyInfos.Count;
|
||||
|
||||
//动态补足行对象(遵循现有FriendRow模式)
|
||||
while (_lobbyRowList.Count < lobbyCount)
|
||||
@ -1817,28 +1959,262 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
private void OnLobbyActionClicked(LobbyListInfo lobbyInfo)
|
||||
{
|
||||
EnsureMultiplaySubPanels();
|
||||
RoomActionPanelMono?.Show(lobbyInfo);
|
||||
}
|
||||
|
||||
private void OnBlockLobbyClicked(LobbyListInfo lobbyInfo)
|
||||
private bool OnBlockLobbyClicked(LobbyListInfo lobbyInfo)
|
||||
{
|
||||
Debug.Log($"[UIOutsideMultiplay] Block lobby owner: {lobbyInfo?.OwnerId}");
|
||||
if (!MuteLobbyRoomName(lobbyInfo))
|
||||
return false;
|
||||
|
||||
RefreshLobbyList();
|
||||
UpdateNoRoomHintVisibility();
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomMuteSuccess);
|
||||
Debug.Log($"[UIOutsideMultiplay] Muted lobby room name: {GetLobbyMuteRoomName(lobbyInfo)}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnReportLobbyClicked(LobbyListInfo lobbyInfo)
|
||||
private bool OnReportLobbyClicked(LobbyListInfo lobbyInfo)
|
||||
{
|
||||
if (lobbyInfo == null)
|
||||
return false;
|
||||
|
||||
Debug.Log($"[UIOutsideMultiplay] Report lobby owner: {lobbyInfo?.OwnerId}");
|
||||
EventManager.Publish(new ShowUIGlobalBugReport());
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomJubaoSuccess);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShowLobbyNotify(UINotifyCommonType type)
|
||||
{
|
||||
EventManager.Publish(new ShowUINotifyCommon { UINotifyCommonType = type });
|
||||
switch (type)
|
||||
{
|
||||
case UINotifyCommonType.OutsideMultiplayRoomFull:
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomFull);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayRoomGone:
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomGone);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayRoomNetError:
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayRoomNetError);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayOpenHint:
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayOpenHint);
|
||||
break;
|
||||
case UINotifyCommonType.OutsideMultiplayCantStartCount:
|
||||
ShowMultiplayInsideNotify(Table.Instance.TextDataAssets.OutsideMultiplayCantStartCount);
|
||||
break;
|
||||
default:
|
||||
EventManager.Publish(new ShowUINotifyCommon { UINotifyCommonType = type });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowMultiplayInsideNotify(string textKey)
|
||||
{
|
||||
EnsureMultiplayInsideNotify();
|
||||
if (MultiplayInsideNotify == null)
|
||||
return;
|
||||
|
||||
if (MultiplayInsideNotifyText != null)
|
||||
SetTextDataAssetText(MultiplayInsideNotifyText, textKey);
|
||||
|
||||
if (MultiplayInsideNotifyCanvasGroup != null)
|
||||
MultiplayInsideNotifyCanvasGroup.alpha = 1f;
|
||||
|
||||
int notifyVersion = ++_multiplayInsideNotifyVersion;
|
||||
MultiplayInsideNotify.SetActive(true);
|
||||
Timer.Instance.TimerRegister(this, () =>
|
||||
{
|
||||
if (_multiplayInsideNotifyVersion == notifyVersion)
|
||||
HideMultiplayInsideNotify();
|
||||
}, 2f, "MultiplayInsideNotify");
|
||||
}
|
||||
|
||||
private void SetTextDataAssetText(TextMeshProUGUI text, string textKey)
|
||||
{
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
if (uint.TryParse(textKey, out _))
|
||||
{
|
||||
MultilingualManager.Instance.SetUIText(text, textKey);
|
||||
return;
|
||||
}
|
||||
|
||||
text.text = textKey ?? string.Empty;
|
||||
}
|
||||
|
||||
private void HideMultiplayInsideNotify()
|
||||
{
|
||||
if (MultiplayInsideNotify != null)
|
||||
MultiplayInsideNotify.SetActive(false);
|
||||
}
|
||||
|
||||
private void EnsureMultiplayInsideNotify()
|
||||
{
|
||||
if (MultiplayInsideNotify == null)
|
||||
{
|
||||
var existing = transform.Find("MultiplayInsideNotify");
|
||||
MultiplayInsideNotify = existing != null ? existing.gameObject : null;
|
||||
}
|
||||
|
||||
if (MultiplayInsideNotify == null)
|
||||
return;
|
||||
|
||||
if (MultiplayInsideNotifyText == null)
|
||||
{
|
||||
var textTransform = MultiplayInsideNotify.transform.Find("Text");
|
||||
MultiplayInsideNotifyText = textTransform != null
|
||||
? textTransform.GetComponent<TextMeshProUGUI>()
|
||||
: MultiplayInsideNotify.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
}
|
||||
|
||||
if (MultiplayInsideNotifyCanvasGroup == null)
|
||||
MultiplayInsideNotifyCanvasGroup = MultiplayInsideNotify.GetComponent<CanvasGroup>();
|
||||
|
||||
MultiplayInsideNotify.SetActive(false);
|
||||
}
|
||||
|
||||
private List<LobbyListInfo> GetVisibleLobbyInfos()
|
||||
{
|
||||
var visibleLobbyInfos = new List<LobbyListInfo>();
|
||||
var lobbyInfos = _lobby?.LobbyListInfos;
|
||||
if (lobbyInfos == null)
|
||||
return visibleLobbyInfos;
|
||||
|
||||
var store = LoadMutedRoomNameStore();
|
||||
long now = GetNowUnixSeconds();
|
||||
bool changed = PruneExpiredMutedRoomNames(store, now);
|
||||
|
||||
for (int i = 0; i < lobbyInfos.Count; i++)
|
||||
{
|
||||
if (!IsLobbyRoomNameMuted(lobbyInfos[i], store, now))
|
||||
visibleLobbyInfos.Add(lobbyInfos[i]);
|
||||
}
|
||||
|
||||
if (changed)
|
||||
SaveMutedRoomNameStore(store);
|
||||
|
||||
return visibleLobbyInfos;
|
||||
}
|
||||
|
||||
private bool MuteLobbyRoomName(LobbyListInfo lobbyInfo)
|
||||
{
|
||||
string roomName = GetLobbyMuteRoomName(lobbyInfo);
|
||||
if (string.IsNullOrWhiteSpace(roomName))
|
||||
return false;
|
||||
|
||||
var store = LoadMutedRoomNameStore();
|
||||
long now = GetNowUnixSeconds();
|
||||
PruneExpiredMutedRoomNames(store, now);
|
||||
|
||||
for (int i = store.Records.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var record = store.Records[i];
|
||||
if (record == null || string.Equals(record.RoomName, roomName, StringComparison.Ordinal))
|
||||
store.Records.RemoveAt(i);
|
||||
}
|
||||
|
||||
store.Records.Add(new MutedRoomNameRecord
|
||||
{
|
||||
RoomName = roomName,
|
||||
ExpireAtUnixSeconds = now + MutedRoomNameDurationSeconds
|
||||
});
|
||||
SaveMutedRoomNameStore(store);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsLobbyRoomNameMuted(LobbyListInfo lobbyInfo, MutedRoomNameStore store, long now)
|
||||
{
|
||||
string roomName = GetLobbyMuteRoomName(lobbyInfo);
|
||||
if (string.IsNullOrWhiteSpace(roomName) || store?.Records == null)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < store.Records.Count; i++)
|
||||
{
|
||||
var record = store.Records[i];
|
||||
if (record != null
|
||||
&& record.ExpireAtUnixSeconds > now
|
||||
&& string.Equals(record.RoomName, roomName, StringComparison.Ordinal))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetLobbyMuteRoomName(LobbyListInfo lobbyInfo)
|
||||
{
|
||||
if (lobbyInfo == null)
|
||||
return string.Empty;
|
||||
|
||||
string roomName = string.IsNullOrWhiteSpace(lobbyInfo.RoomName)
|
||||
? $"Room {lobbyInfo.LobbyId}"
|
||||
: lobbyInfo.RoomName;
|
||||
return SteamLobbyManager.FilterRoomName(roomName).Trim();
|
||||
}
|
||||
|
||||
private MutedRoomNameStore LoadMutedRoomNameStore()
|
||||
{
|
||||
string json = PlayerPrefs.GetString(MutedRoomNamesPrefsKey, string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return new MutedRoomNameStore();
|
||||
|
||||
try
|
||||
{
|
||||
var store = JsonUtility.FromJson<MutedRoomNameStore>(json);
|
||||
if (store == null)
|
||||
store = new MutedRoomNameStore();
|
||||
if (store.Records == null)
|
||||
store.Records = new List<MutedRoomNameRecord>();
|
||||
return store;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[UIOutsideMultiplay] Failed to load muted room names: {e.Message}");
|
||||
return new MutedRoomNameStore();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveMutedRoomNameStore(MutedRoomNameStore store)
|
||||
{
|
||||
if (store == null)
|
||||
store = new MutedRoomNameStore();
|
||||
if (store.Records == null)
|
||||
store.Records = new List<MutedRoomNameRecord>();
|
||||
|
||||
PlayerPrefs.SetString(MutedRoomNamesPrefsKey, JsonUtility.ToJson(store));
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private bool PruneExpiredMutedRoomNames(MutedRoomNameStore store, long now)
|
||||
{
|
||||
if (store?.Records == null)
|
||||
return false;
|
||||
|
||||
bool changed = false;
|
||||
for (int i = store.Records.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var record = store.Records[i];
|
||||
if (record == null || string.IsNullOrWhiteSpace(record.RoomName) || record.ExpireAtUnixSeconds <= now)
|
||||
{
|
||||
store.Records.RemoveAt(i);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private long GetNowUnixSeconds()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
private void EnsureMultiplaySubPanels()
|
||||
{
|
||||
if (JoinPasswordPanelMono == null)
|
||||
if (!IsValidSubPanelInstance(JoinPasswordPanel))
|
||||
{
|
||||
if (JoinPasswordPanel == null)
|
||||
{
|
||||
@ -1846,19 +2222,22 @@ namespace TH1_UI.View.Outside
|
||||
JoinPasswordPanel = existing != null ? existing.gameObject : null;
|
||||
}
|
||||
|
||||
if (JoinPasswordPanel == null)
|
||||
if (!IsValidSubPanelInstance(JoinPasswordPanel))
|
||||
{
|
||||
var prefab = Resources.Load<GameObject>("Prefab/UI/Outside/UIOutsideMultiplayJoinPasswordPanel");
|
||||
var prefab = JoinPasswordPanel;
|
||||
if (prefab == null)
|
||||
prefab = Resources.Load<GameObject>("Prefab/UI/Outside/UIOutsideMultiplayJoinPasswordPanel");
|
||||
if (prefab != null)
|
||||
JoinPasswordPanel = Instantiate(prefab, transform);
|
||||
}
|
||||
|
||||
JoinPasswordPanelMono = JoinPasswordPanel?.GetComponent<UIOutsideMultiplayJoinPasswordPanelMono>();
|
||||
}
|
||||
|
||||
if (JoinPasswordPanelMono == null || JoinPasswordPanelMono.gameObject != JoinPasswordPanel)
|
||||
JoinPasswordPanelMono = JoinPasswordPanel?.GetComponent<UIOutsideMultiplayJoinPasswordPanelMono>();
|
||||
|
||||
JoinPasswordPanelMono?.Init(OnJoinPasswordConfirmed);
|
||||
|
||||
if (RoomActionPanelMono == null)
|
||||
if (!IsValidSubPanelInstance(RoomActionPanel))
|
||||
{
|
||||
if (RoomActionPanel == null)
|
||||
{
|
||||
@ -1866,19 +2245,27 @@ namespace TH1_UI.View.Outside
|
||||
RoomActionPanel = existing != null ? existing.gameObject : null;
|
||||
}
|
||||
|
||||
if (RoomActionPanel == null)
|
||||
if (!IsValidSubPanelInstance(RoomActionPanel))
|
||||
{
|
||||
var prefab = Resources.Load<GameObject>("Prefab/UI/Outside/UIOutsideMultiplayRoomActionPanel");
|
||||
var prefab = RoomActionPanel;
|
||||
if (prefab == null)
|
||||
prefab = Resources.Load<GameObject>("Prefab/UI/Outside/UIOutsideMultiplayRoomActionPanel");
|
||||
if (prefab != null)
|
||||
RoomActionPanel = Instantiate(prefab, transform);
|
||||
}
|
||||
|
||||
RoomActionPanelMono = RoomActionPanel?.GetComponent<UIOutsideMultiplayRoomActionPanelMono>();
|
||||
}
|
||||
|
||||
if (RoomActionPanelMono == null || RoomActionPanelMono.gameObject != RoomActionPanel)
|
||||
RoomActionPanelMono = RoomActionPanel?.GetComponent<UIOutsideMultiplayRoomActionPanelMono>();
|
||||
|
||||
RoomActionPanelMono?.Init(OnBlockLobbyClicked, OnReportLobbyClicked);
|
||||
}
|
||||
|
||||
private bool IsValidSubPanelInstance(GameObject panel)
|
||||
{
|
||||
return panel != null && panel.scene.IsValid() && panel.transform.IsChildOf(transform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加入成功回调
|
||||
/// </summary>
|
||||
@ -1902,9 +2289,7 @@ namespace TH1_UI.View.Outside
|
||||
|
||||
|
||||
//显示加入失败提示
|
||||
CantStartHint.SetActive(true);
|
||||
var fadeInAnim = ResourceCache.Instance.AnimCache.UICommonPanelFadeIn;
|
||||
CantStartHintAnimancer.Play(fadeInAnim);
|
||||
ShowLobbyNotify(UINotifyCommonType.OutsideMultiplayRoomNetError);
|
||||
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user