diff --git a/assets/resources/gui/element/SInfoBox.prefab b/assets/resources/gui/element/SInfoBox.prefab
index 30a3b6fb..f3d1c364 100644
--- a/assets/resources/gui/element/SInfoBox.prefab
+++ b/assets/resources/gui/element/SInfoBox.prefab
@@ -28,37 +28,40 @@
"__id__": 57
},
{
- "__id__": 69
+ "__id__": 73
},
{
"__id__": 85
},
{
- "__id__": 95
+ "__id__": 110
+ },
+ {
+ "__id__": 129
}
],
"_active": true,
"_components": [
{
- "__id__": 101
+ "__id__": 135
},
{
- "__id__": 103
+ "__id__": 137
},
{
- "__id__": 105
+ "__id__": 139
},
{
- "__id__": 107
+ "__id__": 141
}
],
"_prefab": {
- "__id__": 197
+ "__id__": 231
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
- "y": 241.528,
+ "y": 310.354,
"z": 0
},
"_lrot": {
@@ -836,7 +839,7 @@
},
{
"__type__": "cc.Node",
- "_name": "info",
+ "_name": "mstatus",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
@@ -845,20 +848,410 @@
"_children": [
{
"__id__": 58
+ },
+ {
+ "__id__": 64
}
],
"_active": true,
"_components": [
{
- "__id__": 64
- },
- {
- "__id__": 66
+ "__id__": 70
}
],
"_prefab": {
+ "__id__": 72
+ },
+ "_lpos": {
+ "__type__": "cc.Vec3",
+ "x": 244.02,
+ "y": 68.665,
+ "z": 0
+ },
+ "_lrot": {
+ "__type__": "cc.Quat",
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "w": 1
+ },
+ "_lscale": {
+ "__type__": "cc.Vec3",
+ "x": 1,
+ "y": 1,
+ "z": 1
+ },
+ "_mobility": 0,
+ "_layer": 1073741824,
+ "_euler": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_id": ""
+ },
+ {
+ "__type__": "cc.Node",
+ "_name": "bg_line",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "_parent": {
+ "__id__": 57
+ },
+ "_children": [],
+ "_active": true,
+ "_components": [
+ {
+ "__id__": 59
+ },
+ {
+ "__id__": 61
+ }
+ ],
+ "_prefab": {
+ "__id__": 63
+ },
+ "_lpos": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_lrot": {
+ "__type__": "cc.Quat",
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "w": 1
+ },
+ "_lscale": {
+ "__type__": "cc.Vec3",
+ "x": 0.4,
+ "y": 0.5,
+ "z": 1
+ },
+ "_mobility": 0,
+ "_layer": 1073741824,
+ "_euler": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_id": ""
+ },
+ {
+ "__type__": "cc.UITransform",
+ "_name": "",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "node": {
+ "__id__": 58
+ },
+ "_enabled": true,
+ "__prefab": {
+ "__id__": 60
+ },
+ "_contentSize": {
+ "__type__": "cc.Size",
+ "width": 250,
+ "height": 63
+ },
+ "_anchorPoint": {
+ "__type__": "cc.Vec2",
+ "x": 0.5,
+ "y": 0.5
+ },
+ "_id": ""
+ },
+ {
+ "__type__": "cc.CompPrefabInfo",
+ "fileId": "5a0hgGs01GPo6ehxRJAzdY"
+ },
+ {
+ "__type__": "cc.Sprite",
+ "_name": "",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "node": {
+ "__id__": 58
+ },
+ "_enabled": true,
+ "__prefab": {
+ "__id__": 62
+ },
+ "_customMaterial": null,
+ "_srcBlendFactor": 2,
+ "_dstBlendFactor": 4,
+ "_color": {
+ "__type__": "cc.Color",
+ "r": 255,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "_spriteFrame": {
+ "__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@7f50d",
+ "__expectedType__": "cc.SpriteFrame"
+ },
+ "_type": 1,
+ "_fillType": 0,
+ "_sizeMode": 1,
+ "_fillCenter": {
+ "__type__": "cc.Vec2",
+ "x": 0,
+ "y": 0
+ },
+ "_fillStart": 0,
+ "_fillRange": 0,
+ "_isTrimmedMode": true,
+ "_useGrayscale": false,
+ "_atlas": null,
+ "_id": ""
+ },
+ {
+ "__type__": "cc.CompPrefabInfo",
+ "fileId": "c4iHzSfbJEe7x9cKPKAGcm"
+ },
+ {
+ "__type__": "cc.PrefabInfo",
+ "root": {
+ "__id__": 1
+ },
+ "asset": {
+ "__id__": 0
+ },
+ "fileId": "a2TrUWWKNHXL8cAF2US6Vq",
+ "instance": null,
+ "targetOverrides": null,
+ "nestedPrefabInstanceRoots": null
+ },
+ {
+ "__type__": "cc.Node",
+ "_name": "Label",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "_parent": {
+ "__id__": 57
+ },
+ "_children": [],
+ "_active": true,
+ "_components": [
+ {
+ "__id__": 65
+ },
+ {
+ "__id__": 67
+ }
+ ],
+ "_prefab": {
+ "__id__": 69
+ },
+ "_lpos": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_lrot": {
+ "__type__": "cc.Quat",
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "w": 1
+ },
+ "_lscale": {
+ "__type__": "cc.Vec3",
+ "x": 1,
+ "y": 1,
+ "z": 1
+ },
+ "_mobility": 0,
+ "_layer": 1073741824,
+ "_euler": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "_id": ""
+ },
+ {
+ "__type__": "cc.UITransform",
+ "_name": "",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "node": {
+ "__id__": 64
+ },
+ "_enabled": true,
+ "__prefab": {
+ "__id__": 66
+ },
+ "_contentSize": {
+ "__type__": "cc.Size",
+ "width": 60,
+ "height": 50.4
+ },
+ "_anchorPoint": {
+ "__type__": "cc.Vec2",
+ "x": 0.5,
+ "y": 0.5
+ },
+ "_id": ""
+ },
+ {
+ "__type__": "cc.CompPrefabInfo",
+ "fileId": "a1TMheINREmJg+rrqej/zh"
+ },
+ {
+ "__type__": "cc.Label",
+ "_name": "",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "node": {
+ "__id__": 64
+ },
+ "_enabled": true,
+ "__prefab": {
"__id__": 68
},
+ "_customMaterial": null,
+ "_srcBlendFactor": 2,
+ "_dstBlendFactor": 4,
+ "_color": {
+ "__type__": "cc.Color",
+ "r": 0,
+ "g": 255,
+ "b": 10,
+ "a": 255
+ },
+ "_string": "已激活",
+ "_horizontalAlign": 1,
+ "_verticalAlign": 1,
+ "_actualFontSize": 20,
+ "_fontSize": 20,
+ "_fontFamily": "Arial",
+ "_lineHeight": 40,
+ "_overflow": 0,
+ "_enableWrapText": true,
+ "_font": null,
+ "_isSystemFontUsed": true,
+ "_spacingX": 0,
+ "_isItalic": false,
+ "_isBold": false,
+ "_isUnderline": false,
+ "_underlineHeight": 2,
+ "_cacheMode": 0,
+ "_enableOutline": false,
+ "_outlineColor": {
+ "__type__": "cc.Color",
+ "r": 0,
+ "g": 0,
+ "b": 0,
+ "a": 255
+ },
+ "_outlineWidth": 2,
+ "_enableShadow": false,
+ "_shadowColor": {
+ "__type__": "cc.Color",
+ "r": 0,
+ "g": 0,
+ "b": 0,
+ "a": 255
+ },
+ "_shadowOffset": {
+ "__type__": "cc.Vec2",
+ "x": 2,
+ "y": 2
+ },
+ "_shadowBlur": 2,
+ "_id": ""
+ },
+ {
+ "__type__": "cc.CompPrefabInfo",
+ "fileId": "09m5LM9aFFrazsHOVq7b1h"
+ },
+ {
+ "__type__": "cc.PrefabInfo",
+ "root": {
+ "__id__": 1
+ },
+ "asset": {
+ "__id__": 0
+ },
+ "fileId": "bc0QMqyYRONoQZIuoDsrkB",
+ "instance": null,
+ "targetOverrides": null,
+ "nestedPrefabInstanceRoots": null
+ },
+ {
+ "__type__": "cc.UITransform",
+ "_name": "",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "node": {
+ "__id__": 57
+ },
+ "_enabled": true,
+ "__prefab": {
+ "__id__": 71
+ },
+ "_contentSize": {
+ "__type__": "cc.Size",
+ "width": 100,
+ "height": 30
+ },
+ "_anchorPoint": {
+ "__type__": "cc.Vec2",
+ "x": 0.5,
+ "y": 0.5
+ },
+ "_id": ""
+ },
+ {
+ "__type__": "cc.CompPrefabInfo",
+ "fileId": "7bwrAKIGhLYI4SQ2jttwCK"
+ },
+ {
+ "__type__": "cc.PrefabInfo",
+ "root": {
+ "__id__": 1
+ },
+ "asset": {
+ "__id__": 0
+ },
+ "fileId": "dbQZygtzVONrsadIEAXCnc",
+ "instance": null,
+ "targetOverrides": null,
+ "nestedPrefabInstanceRoots": null
+ },
+ {
+ "__type__": "cc.Node",
+ "_name": "info",
+ "_objFlags": 0,
+ "__editorExtras__": {},
+ "_parent": {
+ "__id__": 1
+ },
+ "_children": [
+ {
+ "__id__": 74
+ }
+ ],
+ "_active": true,
+ "_components": [
+ {
+ "__id__": 80
+ },
+ {
+ "__id__": 82
+ }
+ ],
+ "_prefab": {
+ "__id__": 84
+ },
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
@@ -894,25 +1287,25 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 57
+ "__id__": 73
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 59
+ "__id__": 75
},
{
- "__id__": 61
+ "__id__": 77
}
],
"_prefab": {
- "__id__": 63
+ "__id__": 79
},
"_lpos": {
"__type__": "cc.Vec3",
- "x": 0,
- "y": 0,
+ "x": -287.729,
+ "y": 42.663,
"z": 0
},
"_lrot": {
@@ -944,21 +1337,21 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 58
+ "__id__": 74
},
"_enabled": true,
"__prefab": {
- "__id__": 60
+ "__id__": 76
},
"_contentSize": {
"__type__": "cc.Size",
- "width": 95.86181640625,
- "height": 50.4
+ "width": 570,
+ "height": 26.46
},
"_anchorPoint": {
"__type__": "cc.Vec2",
- "x": 0.5,
- "y": 0.5
+ "x": 0,
+ "y": 1
},
"_id": ""
},
@@ -972,17 +1365,17 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 58
+ "__id__": 74
},
"_enabled": true,
"__prefab": {
- "__id__": 62
+ "__id__": 78
},
- "_lineHeight": 40,
+ "_lineHeight": 21,
"_string": "RichText",
"_horizontalAlign": 0,
"_verticalAlign": 0,
- "_fontSize": 25,
+ "_fontSize": 20,
"_fontColor": {
"__type__": "cc.Color",
"r": 255,
@@ -990,7 +1383,7 @@
"b": 255,
"a": 255
},
- "_maxWidth": 0,
+ "_maxWidth": 570,
"_fontFamily": "Arial",
"_font": null,
"_isSystemFontUsed": true,
@@ -1023,11 +1416,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 57
+ "__id__": 73
},
"_enabled": true,
"__prefab": {
- "__id__": 65
+ "__id__": 81
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1051,11 +1444,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 57
+ "__id__": 73
},
"_enabled": true,
"__prefab": {
- "__id__": 67
+ "__id__": 83
},
"_alignFlags": 40,
"_target": null,
@@ -1094,215 +1487,6 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
- {
- "__type__": "cc.Node",
- "_objFlags": 0,
- "_parent": {
- "__id__": 1
- },
- "_prefab": {
- "__id__": 70
- },
- "__editorExtras__": {}
- },
- {
- "__type__": "cc.PrefabInfo",
- "root": {
- "__id__": 69
- },
- "asset": {
- "__uuid__": "8c1e0e41-6db0-4b9e-a408-1b52707d2524",
- "__expectedType__": "cc.Prefab"
- },
- "fileId": "d0zMpqFyRP1aafIzYmc9m1",
- "instance": {
- "__id__": 71
- },
- "targetOverrides": null
- },
- {
- "__type__": "cc.PrefabInstance",
- "fileId": "9bwt9kuE1HXqC6ALJIFfao",
- "prefabRootNode": {
- "__id__": 1
- },
- "mountedChildren": [],
- "mountedComponents": [],
- "propertyOverrides": [
- {
- "__id__": 72
- },
- {
- "__id__": 74
- },
- {
- "__id__": 75
- },
- {
- "__id__": 76
- },
- {
- "__id__": 77
- },
- {
- "__id__": 79
- },
- {
- "__id__": 81
- },
- {
- "__id__": 83
- }
- ],
- "removedComponents": []
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 73
- },
- "propertyPath": [
- "_name"
- ],
- "value": "sinfo2"
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "d0zMpqFyRP1aafIzYmc9m1"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 73
- },
- "propertyPath": [
- "_lpos"
- ],
- "value": {
- "__type__": "cc.Vec3",
- "x": 0,
- "y": -260.446,
- "z": 0
- }
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 73
- },
- "propertyPath": [
- "_lrot"
- ],
- "value": {
- "__type__": "cc.Quat",
- "x": 0,
- "y": 0,
- "z": 0,
- "w": 1
- }
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 73
- },
- "propertyPath": [
- "_euler"
- ],
- "value": {
- "__type__": "cc.Vec3",
- "x": 0,
- "y": 0,
- "z": 0
- }
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 78
- },
- "propertyPath": [
- "_contentSize"
- ],
- "value": {
- "__type__": "cc.Size",
- "width": 640,
- "height": 200
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "f06egrdO9DSpfJqj1k11KD",
- "3723+pq/ROe6+vSu0jWwmS"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 80
- },
- "propertyPath": [
- "_contentSize"
- ],
- "value": {
- "__type__": "cc.Size",
- "width": 640,
- "height": 200
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "f06egrdO9DSpfJqj1k11KD",
- "f0m93vukNMvoxkQHoI+iL8"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 82
- },
- "propertyPath": [
- "_lpos"
- ],
- "value": {
- "__type__": "cc.Vec3",
- "x": 0,
- "y": 86,
- "z": 0
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "f06egrdO9DSpfJqj1k11KD",
- "e4Ca3fwTxFCbFJi+ysH+sp"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 84
- },
- "propertyPath": [
- "_contentSize"
- ],
- "value": {
- "__type__": "cc.Size",
- "width": 632,
- "height": 24
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "f06egrdO9DSpfJqj1k11KD",
- "2bSl5MVoJErbWJ6u/sWqCM"
- ]
- },
{
"__type__": "cc.Node",
"_objFlags": 0,
@@ -1331,7 +1515,7 @@
},
{
"__type__": "cc.PrefabInstance",
- "fileId": "0dQuAEYtpGDKQY/hzbaVW5",
+ "fileId": "9bwt9kuE1HXqC6ALJIFfao",
"prefabRootNode": {
"__id__": 1
},
@@ -1352,6 +1536,33 @@
},
{
"__id__": 93
+ },
+ {
+ "__id__": 95
+ },
+ {
+ "__id__": 97
+ },
+ {
+ "__id__": 99
+ },
+ {
+ "__id__": 101
+ },
+ {
+ "__id__": 103
+ },
+ {
+ "__id__": 105
+ },
+ {
+ "__id__": 106
+ },
+ {
+ "__id__": 107
+ },
+ {
+ "__id__": 109
}
],
"removedComponents": []
@@ -1364,7 +1575,7 @@
"propertyPath": [
"_name"
],
- "value": "sinfo3"
+ "value": "sinfo2"
},
{
"__type__": "cc.TargetInfo",
@@ -1383,7 +1594,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
- "y": -469.796,
+ "y": -257.92,
"z": 0
}
},
@@ -1423,6 +1634,315 @@
"targetInfo": {
"__id__": 94
},
+ "propertyPath": [
+ "_contentSize"
+ ],
+ "value": {
+ "__type__": "cc.Size",
+ "width": 640,
+ "height": 200
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "f06egrdO9DSpfJqj1k11KD",
+ "3723+pq/ROe6+vSu0jWwmS"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 96
+ },
+ "propertyPath": [
+ "_contentSize"
+ ],
+ "value": {
+ "__type__": "cc.Size",
+ "width": 640,
+ "height": 200
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "f06egrdO9DSpfJqj1k11KD",
+ "f0m93vukNMvoxkQHoI+iL8"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 98
+ },
+ "propertyPath": [
+ "_lpos"
+ ],
+ "value": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 86,
+ "z": 0
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "f06egrdO9DSpfJqj1k11KD",
+ "e4Ca3fwTxFCbFJi+ysH+sp"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 100
+ },
+ "propertyPath": [
+ "_contentSize"
+ ],
+ "value": {
+ "__type__": "cc.Size",
+ "width": 632,
+ "height": 24
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "f06egrdO9DSpfJqj1k11KD",
+ "2bSl5MVoJErbWJ6u/sWqCM"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 102
+ },
+ "propertyPath": [
+ "_contentSize"
+ ],
+ "value": {
+ "__type__": "cc.Size",
+ "width": 570,
+ "height": 26.46
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "b3l6okVw1DzqUVhra1Zh4f"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 104
+ },
+ "propertyPath": [
+ "_fontSize"
+ ],
+ "value": 20
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "6f+JG64u9GoKoWU6bpF8y3"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 102
+ },
+ "propertyPath": [
+ "_anchorPoint"
+ ],
+ "value": {
+ "__type__": "cc.Vec2",
+ "x": 0,
+ "y": 1
+ }
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 104
+ },
+ "propertyPath": [
+ "_maxWidth"
+ ],
+ "value": 570
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 108
+ },
+ "propertyPath": [
+ "_lpos"
+ ],
+ "value": {
+ "__type__": "cc.Vec3",
+ "x": -287.729,
+ "y": 9.745,
+ "z": 0
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "dfwr51mcRMEKxQ1trdXPqh"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 104
+ },
+ "propertyPath": [
+ "_lineHeight"
+ ],
+ "value": 21
+ },
+ {
+ "__type__": "cc.Node",
+ "_objFlags": 0,
+ "_parent": {
+ "__id__": 1
+ },
+ "_prefab": {
+ "__id__": 111
+ },
+ "__editorExtras__": {}
+ },
+ {
+ "__type__": "cc.PrefabInfo",
+ "root": {
+ "__id__": 110
+ },
+ "asset": {
+ "__uuid__": "8c1e0e41-6db0-4b9e-a408-1b52707d2524",
+ "__expectedType__": "cc.Prefab"
+ },
+ "fileId": "d0zMpqFyRP1aafIzYmc9m1",
+ "instance": {
+ "__id__": 112
+ },
+ "targetOverrides": null
+ },
+ {
+ "__type__": "cc.PrefabInstance",
+ "fileId": "0dQuAEYtpGDKQY/hzbaVW5",
+ "prefabRootNode": {
+ "__id__": 1
+ },
+ "mountedChildren": [],
+ "mountedComponents": [],
+ "propertyOverrides": [
+ {
+ "__id__": 113
+ },
+ {
+ "__id__": 115
+ },
+ {
+ "__id__": 116
+ },
+ {
+ "__id__": 117
+ },
+ {
+ "__id__": 118
+ },
+ {
+ "__id__": 120
+ },
+ {
+ "__id__": 122
+ },
+ {
+ "__id__": 124
+ },
+ {
+ "__id__": 125
+ },
+ {
+ "__id__": 126
+ },
+ {
+ "__id__": 128
+ }
+ ],
+ "removedComponents": []
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 114
+ },
+ "propertyPath": [
+ "_name"
+ ],
+ "value": "sinfo3"
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "d0zMpqFyRP1aafIzYmc9m1"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 114
+ },
+ "propertyPath": [
+ "_lpos"
+ ],
+ "value": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": -464.113,
+ "z": 0
+ }
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 114
+ },
+ "propertyPath": [
+ "_lrot"
+ ],
+ "value": {
+ "__type__": "cc.Quat",
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "w": 1
+ }
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 114
+ },
+ "propertyPath": [
+ "_euler"
+ ],
+ "value": {
+ "__type__": "cc.Vec3",
+ "x": 0,
+ "y": 0,
+ "z": 0
+ }
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 119
+ },
"propertyPath": [
"_string"
],
@@ -1434,6 +1954,97 @@
"9fHoBo6AhAEbkJa7wim6sA"
]
},
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 121
+ },
+ "propertyPath": [
+ "_contentSize"
+ ],
+ "value": {
+ "__type__": "cc.Size",
+ "width": 570,
+ "height": 26.46
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "b3l6okVw1DzqUVhra1Zh4f"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 123
+ },
+ "propertyPath": [
+ "_fontSize"
+ ],
+ "value": 20
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "6f+JG64u9GoKoWU6bpF8y3"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 121
+ },
+ "propertyPath": [
+ "_anchorPoint"
+ ],
+ "value": {
+ "__type__": "cc.Vec2",
+ "x": 0,
+ "y": 1
+ }
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 123
+ },
+ "propertyPath": [
+ "_maxWidth"
+ ],
+ "value": 570
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 127
+ },
+ "propertyPath": [
+ "_lpos"
+ ],
+ "value": {
+ "__type__": "cc.Vec3",
+ "x": -287.729,
+ "y": 9.745,
+ "z": 0
+ }
+ },
+ {
+ "__type__": "cc.TargetInfo",
+ "localID": [
+ "dfwr51mcRMEKxQ1trdXPqh"
+ ]
+ },
+ {
+ "__type__": "CCPropertyOverrideInfo",
+ "targetInfo": {
+ "__id__": 123
+ },
+ "propertyPath": [
+ "_lineHeight"
+ ],
+ "value": 21
+ },
{
"__type__": "cc.Node",
"_name": "close",
@@ -1446,19 +2057,19 @@
"_active": true,
"_components": [
{
- "__id__": 96
+ "__id__": 130
},
{
- "__id__": 98
+ "__id__": 132
}
],
"_prefab": {
- "__id__": 100
+ "__id__": 134
},
"_lpos": {
"__type__": "cc.Vec3",
- "x": 0,
- "y": -650.484,
+ "x": 252.822,
+ "y": 138.181,
"z": 0
},
"_lrot": {
@@ -1470,8 +2081,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
- "x": 1,
- "y": 1,
+ "x": 0.7,
+ "y": 0.7,
"z": 1
},
"_mobility": 0,
@@ -1490,11 +2101,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 95
+ "__id__": 129
},
"_enabled": true,
"__prefab": {
- "__id__": 97
+ "__id__": 131
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1518,11 +2129,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 95
+ "__id__": 129
},
"_enabled": true,
"__prefab": {
- "__id__": 99
+ "__id__": 133
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1583,7 +2194,7 @@
},
"_enabled": true,
"__prefab": {
- "__id__": 102
+ "__id__": 136
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1611,7 +2222,7 @@
},
"_enabled": true,
"__prefab": {
- "__id__": 104
+ "__id__": 138
},
"_id": ""
},
@@ -1629,7 +2240,7 @@
},
"_enabled": true,
"__prefab": {
- "__id__": 106
+ "__id__": 140
},
"_alignFlags": 42,
"_target": null,
@@ -1638,7 +2249,7 @@
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
- "_verticalCenter": 241.528,
+ "_verticalCenter": 310.354,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
@@ -1665,34 +2276,37 @@
},
"_enabled": true,
"__prefab": {
- "__id__": 108
+ "__id__": 142
},
"icon_node": {
- "__id__": 109
+ "__id__": 143
},
"name_label": {
- "__id__": 125
+ "__id__": 159
},
"lv_label": {
- "__id__": 131
+ "__id__": 165
},
"main_info": {
- "__id__": 61
+ "__id__": 77
+ },
+ "main_status": {
+ "__id__": 67
},
"lv2_status": {
- "__id__": 141
+ "__id__": 175
},
"lv2_desc": {
- "__id__": 157
+ "__id__": 191
},
"lv3_status": {
- "__id__": 169
+ "__id__": 203
},
"lv3_desc": {
- "__id__": 185
+ "__id__": 219
},
"close_btn": {
- "__id__": 95
+ "__id__": 129
},
"_id": ""
},
@@ -1706,20 +2320,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 110
+ "__id__": 144
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 120
+ "__id__": 154
},
{
- "__id__": 122
+ "__id__": 156
}
],
"_prefab": {
- "__id__": 124
+ "__id__": 158
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -1760,26 +2374,26 @@
},
"_children": [
{
- "__id__": 109
+ "__id__": 143
}
],
"_active": true,
"_components": [
{
- "__id__": 111
+ "__id__": 145
},
{
- "__id__": 113
+ "__id__": 147
},
{
- "__id__": 115
+ "__id__": 149
},
{
- "__id__": 117
+ "__id__": 151
}
],
"_prefab": {
- "__id__": 119
+ "__id__": 153
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -1816,11 +2430,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 110
+ "__id__": 144
},
"_enabled": true,
"__prefab": {
- "__id__": 112
+ "__id__": 146
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1844,11 +2458,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 110
+ "__id__": 144
},
"_enabled": true,
"__prefab": {
- "__id__": 114
+ "__id__": 148
},
"_type": 3,
"_inverted": false,
@@ -1866,11 +2480,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 110
+ "__id__": 144
},
"_enabled": true,
"__prefab": {
- "__id__": 116
+ "__id__": 150
},
"_alignFlags": 45,
"_target": null,
@@ -1902,11 +2516,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 110
+ "__id__": 144
},
"_enabled": true,
"__prefab": {
- "__id__": 118
+ "__id__": 152
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1963,11 +2577,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 109
+ "__id__": 143
},
"_enabled": true,
"__prefab": {
- "__id__": 121
+ "__id__": 155
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1991,11 +2605,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 109
+ "__id__": 143
},
"_enabled": true,
"__prefab": {
- "__id__": 123
+ "__id__": 157
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -2052,11 +2666,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 126
+ "__id__": 160
},
"_enabled": true,
"__prefab": {
- "__id__": 130
+ "__id__": 164
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -2122,14 +2736,14 @@
"_active": true,
"_components": [
{
- "__id__": 127
+ "__id__": 161
},
{
- "__id__": 125
+ "__id__": 159
}
],
"_prefab": {
- "__id__": 129
+ "__id__": 163
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2166,11 +2780,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 126
+ "__id__": 160
},
"_enabled": true,
"__prefab": {
- "__id__": 128
+ "__id__": 162
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2211,11 +2825,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 132
+ "__id__": 166
},
"_enabled": true,
"__prefab": {
- "__id__": 140
+ "__id__": 174
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -2275,20 +2889,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 133
+ "__id__": 167
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 137
+ "__id__": 171
},
{
- "__id__": 131
+ "__id__": 165
}
],
"_prefab": {
- "__id__": 139
+ "__id__": 173
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2329,17 +2943,17 @@
},
"_children": [
{
- "__id__": 132
+ "__id__": 166
}
],
"_active": true,
"_components": [
{
- "__id__": 134
+ "__id__": 168
}
],
"_prefab": {
- "__id__": 136
+ "__id__": 170
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2376,11 +2990,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 133
+ "__id__": 167
},
"_enabled": true,
"__prefab": {
- "__id__": 135
+ "__id__": 169
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2417,11 +3031,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 132
+ "__id__": 166
},
"_enabled": true,
"__prefab": {
- "__id__": 138
+ "__id__": 172
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2462,11 +3076,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 142
+ "__id__": 176
},
"_enabled": true,
"__prefab": {
- "__id__": 156
+ "__id__": 190
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -2526,20 +3140,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 143
+ "__id__": 177
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 153
+ "__id__": 187
},
{
- "__id__": 141
+ "__id__": 175
}
],
"_prefab": {
- "__id__": 155
+ "__id__": 189
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2576,24 +3190,24 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 69
+ "__id__": 85
},
"_children": [
{
- "__id__": 144
+ "__id__": 178
},
{
- "__id__": 142
+ "__id__": 176
}
],
"_active": true,
"_components": [
{
- "__id__": 150
+ "__id__": 184
}
],
"_prefab": {
- "__id__": 152
+ "__id__": 186
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2630,20 +3244,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 143
+ "__id__": 177
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 145
+ "__id__": 179
},
{
- "__id__": 147
+ "__id__": 181
}
],
"_prefab": {
- "__id__": 149
+ "__id__": 183
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2680,11 +3294,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 144
+ "__id__": 178
},
"_enabled": true,
"__prefab": {
- "__id__": 146
+ "__id__": 180
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2708,11 +3322,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 144
+ "__id__": 178
},
"_enabled": true,
"__prefab": {
- "__id__": 148
+ "__id__": 182
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -2766,11 +3380,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 143
+ "__id__": 177
},
"_enabled": true,
"__prefab": {
- "__id__": 151
+ "__id__": 185
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2807,11 +3421,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 142
+ "__id__": 176
},
"_enabled": true,
"__prefab": {
- "__id__": 154
+ "__id__": 188
},
"_contentSize": {
"__type__": "cc.Size",
@@ -2852,17 +3466,17 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 158
+ "__id__": 192
},
"_enabled": true,
"__prefab": {
- "__id__": 168
+ "__id__": 202
},
- "_lineHeight": 40,
+ "_lineHeight": 21,
"_string": "RichText",
"_horizontalAlign": 0,
"_verticalAlign": 0,
- "_fontSize": 25,
+ "_fontSize": 20,
"_fontColor": {
"__type__": "cc.Color",
"r": 255,
@@ -2870,7 +3484,7 @@
"b": 255,
"a": 255
},
- "_maxWidth": 0,
+ "_maxWidth": 570,
"_fontFamily": "Arial",
"_font": null,
"_isSystemFontUsed": true,
@@ -2886,25 +3500,25 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 159
+ "__id__": 193
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 165
+ "__id__": 199
},
{
- "__id__": 157
+ "__id__": 191
}
],
"_prefab": {
- "__id__": 167
+ "__id__": 201
},
"_lpos": {
"__type__": "cc.Vec3",
- "x": 0,
- "y": 0,
+ "x": -287.729,
+ "y": 9.745,
"z": 0
},
"_lrot": {
@@ -2936,24 +3550,24 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 69
+ "__id__": 85
},
"_children": [
{
- "__id__": 158
+ "__id__": 192
}
],
"_active": true,
"_components": [
{
- "__id__": 160
+ "__id__": 194
},
{
- "__id__": 162
+ "__id__": 196
}
],
"_prefab": {
- "__id__": 164
+ "__id__": 198
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2990,11 +3604,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 159
+ "__id__": 193
},
"_enabled": true,
"__prefab": {
- "__id__": 161
+ "__id__": 195
},
"_contentSize": {
"__type__": "cc.Size",
@@ -3018,11 +3632,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 159
+ "__id__": 193
},
"_enabled": true,
"__prefab": {
- "__id__": 163
+ "__id__": 197
},
"_alignFlags": 40,
"_target": null,
@@ -3067,21 +3681,21 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 158
+ "__id__": 192
},
"_enabled": true,
"__prefab": {
- "__id__": 166
+ "__id__": 200
},
"_contentSize": {
"__type__": "cc.Size",
- "width": 95.86181640625,
- "height": 50.4
+ "width": 570,
+ "height": 26.46
},
"_anchorPoint": {
"__type__": "cc.Vec2",
- "x": 0.5,
- "y": 0.5
+ "x": 0,
+ "y": 1
},
"_id": ""
},
@@ -3112,11 +3726,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 170
+ "__id__": 204
},
"_enabled": true,
"__prefab": {
- "__id__": 184
+ "__id__": 218
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -3176,20 +3790,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 171
+ "__id__": 205
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 181
+ "__id__": 215
},
{
- "__id__": 169
+ "__id__": 203
}
],
"_prefab": {
- "__id__": 183
+ "__id__": 217
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -3226,24 +3840,24 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 85
+ "__id__": 110
},
"_children": [
{
- "__id__": 172
+ "__id__": 206
},
{
- "__id__": 170
+ "__id__": 204
}
],
"_active": true,
"_components": [
{
- "__id__": 178
+ "__id__": 212
}
],
"_prefab": {
- "__id__": 180
+ "__id__": 214
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -3280,20 +3894,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 171
+ "__id__": 205
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 173
+ "__id__": 207
},
{
- "__id__": 175
+ "__id__": 209
}
],
"_prefab": {
- "__id__": 177
+ "__id__": 211
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -3330,11 +3944,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 172
+ "__id__": 206
},
"_enabled": true,
"__prefab": {
- "__id__": 174
+ "__id__": 208
},
"_contentSize": {
"__type__": "cc.Size",
@@ -3358,11 +3972,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 172
+ "__id__": 206
},
"_enabled": true,
"__prefab": {
- "__id__": 176
+ "__id__": 210
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -3416,11 +4030,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 171
+ "__id__": 205
},
"_enabled": true,
"__prefab": {
- "__id__": 179
+ "__id__": 213
},
"_contentSize": {
"__type__": "cc.Size",
@@ -3457,11 +4071,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 170
+ "__id__": 204
},
"_enabled": true,
"__prefab": {
- "__id__": 182
+ "__id__": 216
},
"_contentSize": {
"__type__": "cc.Size",
@@ -3502,17 +4116,17 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 186
+ "__id__": 220
},
"_enabled": true,
"__prefab": {
- "__id__": 196
+ "__id__": 230
},
- "_lineHeight": 40,
+ "_lineHeight": 21,
"_string": "RichText",
"_horizontalAlign": 0,
"_verticalAlign": 0,
- "_fontSize": 25,
+ "_fontSize": 20,
"_fontColor": {
"__type__": "cc.Color",
"r": 255,
@@ -3520,7 +4134,7 @@
"b": 255,
"a": 255
},
- "_maxWidth": 0,
+ "_maxWidth": 570,
"_fontFamily": "Arial",
"_font": null,
"_isSystemFontUsed": true,
@@ -3536,25 +4150,25 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 187
+ "__id__": 221
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 193
+ "__id__": 227
},
{
- "__id__": 185
+ "__id__": 219
}
],
"_prefab": {
- "__id__": 195
+ "__id__": 229
},
"_lpos": {
"__type__": "cc.Vec3",
- "x": 0,
- "y": 0,
+ "x": -287.729,
+ "y": 9.745,
"z": 0
},
"_lrot": {
@@ -3586,24 +4200,24 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 85
+ "__id__": 110
},
"_children": [
{
- "__id__": 186
+ "__id__": 220
}
],
"_active": true,
"_components": [
{
- "__id__": 188
+ "__id__": 222
},
{
- "__id__": 190
+ "__id__": 224
}
],
"_prefab": {
- "__id__": 192
+ "__id__": 226
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -3640,11 +4254,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 187
+ "__id__": 221
},
"_enabled": true,
"__prefab": {
- "__id__": 189
+ "__id__": 223
},
"_contentSize": {
"__type__": "cc.Size",
@@ -3668,11 +4282,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 187
+ "__id__": 221
},
"_enabled": true,
"__prefab": {
- "__id__": 191
+ "__id__": 225
},
"_alignFlags": 40,
"_target": null,
@@ -3717,21 +4331,21 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 186
+ "__id__": 220
},
"_enabled": true,
"__prefab": {
- "__id__": 194
+ "__id__": 228
},
"_contentSize": {
"__type__": "cc.Size",
- "width": 95.86181640625,
- "height": 50.4
+ "width": 570,
+ "height": 26.46
},
"_anchorPoint": {
"__type__": "cc.Vec2",
- "x": 0.5,
- "y": 0.5
+ "x": 0,
+ "y": 1
},
"_id": ""
},
@@ -3768,33 +4382,33 @@
"instance": null,
"targetOverrides": [
{
- "__id__": 198
+ "__id__": 232
},
{
- "__id__": 200
+ "__id__": 234
},
{
- "__id__": 202
+ "__id__": 236
},
{
- "__id__": 204
+ "__id__": 238
},
{
- "__id__": 206
+ "__id__": 240
},
{
- "__id__": 208
+ "__id__": 242
},
{
- "__id__": 210
+ "__id__": 244
}
],
"nestedPrefabInstanceRoots": [
{
- "__id__": 85
+ "__id__": 110
},
{
- "__id__": 69
+ "__id__": 85
},
{
"__id__": 26
@@ -3807,7 +4421,7 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
@@ -3817,7 +4431,7 @@
"__id__": 26
},
"targetInfo": {
- "__id__": 199
+ "__id__": 233
}
},
{
@@ -3829,7 +4443,7 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
@@ -3839,7 +4453,7 @@
"__id__": 26
},
"targetInfo": {
- "__id__": 201
+ "__id__": 235
}
},
{
@@ -3851,7 +4465,7 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
@@ -3861,7 +4475,7 @@
"__id__": 26
},
"targetInfo": {
- "__id__": 203
+ "__id__": 237
}
},
{
@@ -3873,17 +4487,17 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
"lv2_status"
],
"target": {
- "__id__": 69
+ "__id__": 85
},
"targetInfo": {
- "__id__": 205
+ "__id__": 239
}
},
{
@@ -3895,17 +4509,17 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
"lv2_desc"
],
"target": {
- "__id__": 69
+ "__id__": 85
},
"targetInfo": {
- "__id__": 207
+ "__id__": 241
}
},
{
@@ -3917,17 +4531,17 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
"lv3_status"
],
"target": {
- "__id__": 85
+ "__id__": 110
},
"targetInfo": {
- "__id__": 209
+ "__id__": 243
}
},
{
@@ -3939,17 +4553,17 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 107
+ "__id__": 141
},
"sourceInfo": null,
"propertyPath": [
"lv3_desc"
],
"target": {
- "__id__": 85
+ "__id__": 110
},
"targetInfo": {
- "__id__": 211
+ "__id__": 245
}
},
{
diff --git a/assets/resources/gui/element/cHero.prefab b/assets/resources/gui/element/cHero.prefab
index e1ff6566..6aa330cc 100644
--- a/assets/resources/gui/element/cHero.prefab
+++ b/assets/resources/gui/element/cHero.prefab
@@ -37,23 +37,23 @@
"__id__": 240
},
{
- "__id__": 280
+ "__id__": 270
}
],
"_active": true,
"_components": [
{
- "__id__": 286
+ "__id__": 276
},
{
- "__id__": 288
+ "__id__": 278
},
{
- "__id__": 290
+ "__id__": 280
}
],
"_prefab": {
- "__id__": 334
+ "__id__": 324
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -3728,10 +3728,13 @@
"__id__": 258
},
{
- "__id__": 260
+ "__id__": 259
},
{
- "__id__": 262
+ "__id__": 261
+ },
+ {
+ "__id__": 263
},
{
"__id__": 264
@@ -3744,24 +3747,6 @@
},
{
"__id__": 269
- },
- {
- "__id__": 271
- },
- {
- "__id__": 273
- },
- {
- "__id__": 274
- },
- {
- "__id__": 275
- },
- {
- "__id__": 277
- },
- {
- "__id__": 279
}
],
"removedComponents": []
@@ -3913,67 +3898,6 @@
"propertyPath": [
"_contentSize"
],
- "value": {
- "__type__": "cc.Size",
- "width": 130,
- "height": 50
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "cdti5U6ZNG2Z/RykAJNVTw"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 257
- },
- "propertyPath": [
- "_contentSize"
- ],
- "value": {
- "__type__": "cc.Size",
- "width": 122,
- "height": 38
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "3cTH6kgb9D1bt8syQyX+k8"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 259
- },
- "propertyPath": [
- "_lpos"
- ],
- "value": {
- "__type__": "cc.Vec3",
- "x": -55,
- "y": 15.5,
- "z": 0
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "3eGuUajkVIeK78uF29soeR"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 261
- },
- "propertyPath": [
- "_contentSize"
- ],
"value": {
"__type__": "cc.Size",
"width": 230,
@@ -3989,7 +3913,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 263
+ "__id__": 257
},
"propertyPath": [
"_top"
@@ -4005,7 +3929,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 263
+ "__id__": 257
},
"propertyPath": [
"_bottom"
@@ -4015,7 +3939,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 266
+ "__id__": 260
},
"propertyPath": [
"_active"
@@ -4031,7 +3955,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 268
+ "__id__": 262
},
"propertyPath": [
"_lpos"
@@ -4052,51 +3976,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 270
- },
- "propertyPath": [
- "_color"
- ],
- "value": {
- "__type__": "cc.Color",
- "r": 133,
- "g": 208,
- "b": 72,
- "a": 255
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "e1PNQAe0ZJH5asasLtteLw"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 272
- },
- "propertyPath": [
- "_color"
- ],
- "value": {
- "__type__": "cc.Color",
- "r": 198,
- "g": 239,
- "b": 150,
- "a": 255
- }
- },
- {
- "__type__": "cc.TargetInfo",
- "localID": [
- "72Qmr45pdBjaa4/XGRGS5u"
- ]
- },
- {
- "__type__": "CCPropertyOverrideInfo",
- "targetInfo": {
- "__id__": 268
+ "__id__": 262
},
"propertyPath": [
"_active"
@@ -4106,7 +3986,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 266
+ "__id__": 260
},
"propertyPath": [
"_lpos"
@@ -4121,7 +4001,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 276
+ "__id__": 266
},
"propertyPath": [
"_string"
@@ -4137,7 +4017,7 @@
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
- "__id__": 278
+ "__id__": 268
},
"propertyPath": [
"_contentSize"
@@ -4176,14 +4056,14 @@
"_active": true,
"_components": [
{
- "__id__": 281
+ "__id__": 271
},
{
- "__id__": 283
+ "__id__": 273
}
],
"_prefab": {
- "__id__": 285
+ "__id__": 275
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4220,16 +4100,16 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 280
+ "__id__": 270
},
"_enabled": true,
"__prefab": {
- "__id__": 282
+ "__id__": 272
},
"_contentSize": {
"__type__": "cc.Size",
- "width": 200,
- "height": 22.68
+ "width": 215,
+ "height": 18.9
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -4248,13 +4128,13 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 280
+ "__id__": 270
},
"_enabled": true,
"__prefab": {
- "__id__": 284
+ "__id__": 274
},
- "_lineHeight": 18,
+ "_lineHeight": 15,
"_string": "RichText",
"_horizontalAlign": 0,
"_verticalAlign": 0,
@@ -4266,7 +4146,7 @@
"b": 255,
"a": 255
},
- "_maxWidth": 200,
+ "_maxWidth": 215,
"_fontFamily": "Arial",
"_font": null,
"_isSystemFontUsed": true,
@@ -4303,7 +4183,7 @@
},
"_enabled": true,
"__prefab": {
- "__id__": 287
+ "__id__": 277
},
"_contentSize": {
"__type__": "cc.Size",
@@ -4331,7 +4211,7 @@
},
"_enabled": false,
"__prefab": {
- "__id__": 289
+ "__id__": 279
},
"_alignFlags": 0,
"_target": null,
@@ -4367,10 +4247,10 @@
},
"_enabled": true,
"__prefab": {
- "__id__": 291
+ "__id__": 281
},
"icon_node": {
- "__id__": 292
+ "__id__": 282
},
"bg_node": {
"__id__": 2
@@ -4388,7 +4268,7 @@
"__id__": 228
},
"info_rich": {
- "__id__": 283
+ "__id__": 273
},
"level_label": {
"__id__": 68
@@ -4407,23 +4287,23 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 293
+ "__id__": 283
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 327
+ "__id__": 317
},
{
- "__id__": 329
+ "__id__": 319
},
{
- "__id__": 331
+ "__id__": 321
}
],
"_prefab": {
- "__id__": 333
+ "__id__": 323
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4464,38 +4344,38 @@
},
"_children": [
{
- "__id__": 294
+ "__id__": 284
},
{
- "__id__": 292
+ "__id__": 282
},
{
- "__id__": 300
+ "__id__": 290
},
{
- "__id__": 306
+ "__id__": 296
},
{
- "__id__": 312
+ "__id__": 302
}
],
"_active": true,
"_components": [
{
- "__id__": 318
+ "__id__": 308
},
{
- "__id__": 320
+ "__id__": 310
},
{
- "__id__": 322
+ "__id__": 312
},
{
- "__id__": 324
+ "__id__": 314
}
],
"_prefab": {
- "__id__": 326
+ "__id__": 316
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4532,20 +4412,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 293
+ "__id__": 283
},
"_children": [],
"_active": true,
"_components": [
{
- "__id__": 295
+ "__id__": 285
},
{
- "__id__": 297
+ "__id__": 287
}
],
"_prefab": {
- "__id__": 299
+ "__id__": 289
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4582,11 +4462,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 294
+ "__id__": 284
},
"_enabled": true,
"__prefab": {
- "__id__": 296
+ "__id__": 286
},
"_contentSize": {
"__type__": "cc.Size",
@@ -4610,11 +4490,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 294
+ "__id__": 284
},
"_enabled": true,
"__prefab": {
- "__id__": 298
+ "__id__": 288
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -4668,20 +4548,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 293
+ "__id__": 283
},
"_children": [],
"_active": false,
"_components": [
{
- "__id__": 301
+ "__id__": 291
},
{
- "__id__": 303
+ "__id__": 293
}
],
"_prefab": {
- "__id__": 305
+ "__id__": 295
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4718,11 +4598,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 300
+ "__id__": 290
},
"_enabled": true,
"__prefab": {
- "__id__": 302
+ "__id__": 292
},
"_contentSize": {
"__type__": "cc.Size",
@@ -4746,11 +4626,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 300
+ "__id__": 290
},
"_enabled": true,
"__prefab": {
- "__id__": 304
+ "__id__": 294
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -4804,20 +4684,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 293
+ "__id__": 283
},
"_children": [],
"_active": false,
"_components": [
{
- "__id__": 307
+ "__id__": 297
},
{
- "__id__": 309
+ "__id__": 299
}
],
"_prefab": {
- "__id__": 311
+ "__id__": 301
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4854,11 +4734,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 306
+ "__id__": 296
},
"_enabled": true,
"__prefab": {
- "__id__": 308
+ "__id__": 298
},
"_contentSize": {
"__type__": "cc.Size",
@@ -4882,11 +4762,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 306
+ "__id__": 296
},
"_enabled": true,
"__prefab": {
- "__id__": 310
+ "__id__": 300
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -4940,20 +4820,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
- "__id__": 293
+ "__id__": 283
},
"_children": [],
"_active": false,
"_components": [
{
- "__id__": 313
+ "__id__": 303
},
{
- "__id__": 315
+ "__id__": 305
}
],
"_prefab": {
- "__id__": 317
+ "__id__": 307
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4990,11 +4870,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 312
+ "__id__": 302
},
"_enabled": true,
"__prefab": {
- "__id__": 314
+ "__id__": 304
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5018,11 +4898,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 312
+ "__id__": 302
},
"_enabled": true,
"__prefab": {
- "__id__": 316
+ "__id__": 306
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -5076,11 +4956,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 293
+ "__id__": 283
},
"_enabled": true,
"__prefab": {
- "__id__": 319
+ "__id__": 309
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5104,11 +4984,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 293
+ "__id__": 283
},
"_enabled": false,
"__prefab": {
- "__id__": 321
+ "__id__": 311
},
"_type": 0,
"_inverted": false,
@@ -5126,11 +5006,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 293
+ "__id__": 283
},
"_enabled": true,
"__prefab": {
- "__id__": 323
+ "__id__": 313
},
"_alignFlags": 45,
"_target": null,
@@ -5162,11 +5042,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 293
+ "__id__": 283
},
"_enabled": true,
"__prefab": {
- "__id__": 325
+ "__id__": 315
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -5221,11 +5101,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 292
+ "__id__": 282
},
"_enabled": true,
"__prefab": {
- "__id__": 328
+ "__id__": 318
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5249,11 +5129,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 292
+ "__id__": 282
},
"_enabled": true,
"__prefab": {
- "__id__": 330
+ "__id__": 320
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -5297,11 +5177,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
- "__id__": 292
+ "__id__": 282
},
"_enabled": false,
"__prefab": {
- "__id__": 332
+ "__id__": 322
},
"_alignFlags": 45,
"_target": null,
@@ -5352,10 +5232,10 @@
"instance": null,
"targetOverrides": [
{
- "__id__": 335
+ "__id__": 325
},
{
- "__id__": 337
+ "__id__": 327
}
],
"nestedPrefabInstanceRoots": [
@@ -5373,7 +5253,7 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
- "__id__": 290
+ "__id__": 280
},
"sourceInfo": null,
"propertyPath": [
@@ -5383,7 +5263,7 @@
"__id__": 84
},
"targetInfo": {
- "__id__": 336
+ "__id__": 326
}
},
{
@@ -5403,7 +5283,7 @@
"__id__": 240
},
"targetInfo": {
- "__id__": 338
+ "__id__": 328
}
},
{
diff --git a/assets/script/game/common/HeroSkillText.ts b/assets/script/game/common/HeroSkillText.ts
index 4c05f0a5..8e5799ed 100644
--- a/assets/script/game/common/HeroSkillText.ts
+++ b/assets/script/game/common/HeroSkillText.ts
@@ -2,7 +2,7 @@
* @file HeroSkillText.ts
* @description 英雄技能文案生成器(heroInfo 内联描述模板驱动)
*
- * 与 HeroSkillDesc(SkillDescSet 中央模板表)并列的新一代文案层:
+ * 英雄语境描述文案层(原 HeroSkillDesc 已由本模块 + CardSkillText 替代并移除):
* - 描述模板直接配置在 heroInfo(HSkillInfo.desc / LvSkillEntry.desc / LvTriggerEntry.desc),
* "同一底座技能、不同英雄语境"的表述差异在英雄配置处表达;
* - 数值永远从 mergeSkillParams 合并后的实时配置取值(占位符渲染),
@@ -21,10 +21,10 @@
* {字段名} 合并后技能配置数值(ap/para/frz/stun/crt/bck/hit_count/num/summon_count…)
*
* 依赖:heroSet(配置与 resolver)、SkillSet/BuffSet/SkillDescSet(底座)、smc(局外等级)。
- * HeroSkillDesc.ts 暂时保留兼容旧 UI 引用,待迁移完成后移除。
+ * 卡牌/道具语境描述见 config/CardSkillText.ts。
*/
import {
- HeroInfo, SkillTriggerType, SkillTriggerName, HSkillInfo, LvTriggerEntry,
+ HeroInfo, heroInfo, SkillTriggerType, SkillTriggerName, HSkillInfo, LvTriggerEntry,
TriggerGrouped, resolveSkillOverrides, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv,
getGroupedTriggerName, getFieldSkillName, getReviveSkillName,
} from "./config/heroSet";
@@ -34,7 +34,7 @@ import { FieldDescSet, FieldValueFmt } from "./config/SkillDescSet";
import { FacSet } from "./config/GameSet";
import { smc } from "./SingletonModuleComp";
-/* ==================== 富文本片段(与 HeroSkillDesc 同配色,保证新旧面板视觉一致) ==================== */
+/* ==================== 富文本片段(与 CardSkillText 同配色,保证各面板视觉一致) ==================== */
/** 富文本数值高亮色(鲜亮绿色) */
const RICH_VALUE_COLOR = "#3CE66E";
@@ -344,6 +344,84 @@ function collectSkillTiers(hSkill: HSkillInfo): ISkillTierInfo[] {
return tiers;
}
+/** 驻场光环词条数值展示片段(与 buildHeroSkillDesc 口径一致:比例/整数/减免) */
+function buildFieldValueRich(fieldUuid: number, value: number): string {
+ const fs = FieldSkillSet[fieldUuid];
+ const desc = fs ? FieldDescSet[fs.type] : undefined;
+ if (!desc) return valueRich(value);
+ const val = desc.fmt === FieldValueFmt.Percent
+ ? `+${Math.round(value * 100)}%`
+ : desc.fmt === FieldValueFmt.IntMinus ? `-${value}` : `+${value}`;
+ return valueRich(val);
+}
+
+/**
+ * 收集驻场光环词条分档行(hero.field 各英雄等级档,无 s_lv 概念,按档位序号编号)。
+ * slv1 给完整效果(动词 + 数值);slv2+ 给数值提升描述。
+ * 档位配置了 desc 时直接采用(策划自定义表述优先)。
+ */
+function collectFieldTierLines(hero: heroInfo, fieldUuid: number): HeroSkillTierLine[] {
+ const matched = (hero.field ?? [])
+ .filter(e => e.uuids.some(u => u.uuid === fieldUuid))
+ .sort((a, b) => a.lv - b.lv);
+ if (matched.length === 0) return [];
+
+ const fs = FieldSkillSet[fieldUuid];
+ const descCfg = fs ? FieldDescSet[fs.type] : undefined;
+ const verb = descCfg?.verb ?? "";
+ const lines: HeroSkillTierLine[] = [];
+ let prevValue: number | undefined;
+ matched.forEach((e, i) => {
+ const entry = e.uuids.find(u => u.uuid === fieldUuid)!;
+ if (e.desc) {
+ // 配置描述优先(策划自定义表述)
+ lines.push({ sLv: i + 1, heroLv: e.lv, text: e.desc });
+ } else if (i === 0 || !descCfg) {
+ // 首档:完整效果(动词 + 数值高亮);未入表类型回退词条 info
+ lines.push({ sLv: i + 1, heroLv: e.lv, text: descCfg ? `${verb}${buildFieldValueRich(fieldUuid, entry.value)}` : (fs?.info ?? "") });
+ } else {
+ // 提升档:数值由旧值提升至新值
+ lines.push({
+ sLv: i + 1, heroLv: e.lv,
+ text: `${verb}${buildFieldValueRich(fieldUuid, prevValue!)}提升至${buildFieldValueRich(fieldUuid, entry.value)}`,
+ });
+ }
+ prevValue = entry.value;
+ });
+ return lines;
+}
+
+/**
+ * 收集复活技能分档行(hero.revive 各英雄等级档,无 s_lv 概念,按档位序号编号)。
+ * slv1 给完整效果(复活次数 + 重生血量);slv2+ 给复活次数变化。
+ * 档位配置了 desc 时直接采用。
+ */
+function collectReviveTierLines(hero: heroInfo, sUuid: number): HeroSkillTierLine[] {
+ const matched = (hero.revive ?? [])
+ .filter(e => e.s_uuid === sUuid)
+ .sort((a, b) => a.lv - b.lv);
+ if (matched.length === 0) return [];
+
+ const lines: HeroSkillTierLine[] = [];
+ let prevCount: number | undefined;
+ matched.forEach((e, i) => {
+ // 复活次数复刻战斗公式:r_num + floor((英雄等级-1)*upr)
+ const count = e.r_num + Math.floor((e.lv - 1) * e.upr);
+ const hpPct = Math.round(SkillSet[e.s_uuid]?.ap ?? 0);
+ if (e.desc) {
+ lines.push({ sLv: i + 1, heroLv: e.lv, text: e.desc });
+ } else if (i === 0) {
+ lines.push({ sLv: 1, heroLv: e.lv, text: `每回合可复活${valueRich(count)}次, 死亡后以${valueRich(`${hpPct}%`)}生命原地重生` });
+ } else if (prevCount !== undefined && count !== prevCount) {
+ lines.push({ sLv: i + 1, heroLv: e.lv, text: `复活次数${valueRich(prevCount)}次提升至${valueRich(count)}次` });
+ } else {
+ lines.push({ sLv: i + 1, heroLv: e.lv, text: "无变化" });
+ }
+ prevCount = count;
+ });
+ return lines;
+}
+
/** 单个技能等级档位的展示行(结构化,供弹窗逐档渲染激活状态与描述) */
export interface HeroSkillTierLine {
/** 技能等级(slv) */
@@ -360,11 +438,17 @@ export interface HeroSkillTierLine {
* slv1 给完整能力 + 数值;slv2+ 给相对上一级的提升。每档附带解锁英雄等级,
* 调用方可自行对比英雄当前等级(smc.getHeroOuterLv)判定激活状态。
*
- * 技能定位顺序:hero.skills[skillUuid](普攻/大招)→ 5 类触发技能组 → 未找到返回 []。
+ * 技能定位顺序(覆盖英雄图鉴全部 5 类技能形态):
+ * 1. hero.skills[skillUuid] —— 普攻/大招
+ * 2. 5 类触发技能组 —— call/dead/fstart/atking/atked
+ * 3. hero.field —— 驻场光环词条(FieldSkillSet uuid)
+ * 4. hero.revive —— 复活技能(SkillSet uuid)
+ * 均未命中返回 []。
+ *
* Why: 同一底座技能可能被多个英雄以不同 overrides 引用,必须以英雄语境取档。
*
* @param heroUuid 英雄 uuid(HeroInfo key)
- * @param skillUuid 技能 uuid(skills key 或触发组 s_uuid)
+ * @param skillUuid 技能 uuid(skills key / 触发组 s_uuid / FieldSkillSet uuid / 复活 SkillSet uuid)
* @returns 档位行数组;技能不属于该英雄时返回 []
*/
export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number): HeroSkillTierLine[] {
@@ -404,7 +488,15 @@ export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number): He
break;
}
}
- if (tiers.length === 0) return [];
+
+ // 光环/复活路径(无 ISkillTierInfo 中间结构,直接生成 HeroSkillTierLine)
+ if (tiers.length === 0) {
+ const fieldLines = collectFieldTierLines(hero, skillUuid);
+ if (fieldLines.length > 0) return fieldLines;
+ const reviveLines = collectReviveTierLines(hero, skillUuid);
+ if (reviveLines.length > 0) return reviveLines;
+ return [];
+ }
const lines: HeroSkillTierLine[] = [];
for (let i = 0; i < tiers.length; i++) {
@@ -450,11 +542,19 @@ export function buildHeroSkillTierDesc(heroUuid: number, skillUuid: number, show
.join("\n");
}
-/** 在英雄触发组中解析技能对外展示名(非触发技能返回底座名) */
-function resolveTriggerSkillName(hero: typeof HeroInfo[number], skillUuid: number): string {
+/** 在英雄技能配置中解析技能对外展示名(触发组名 / 光环词条名 / 复活名 / 底座名) */
+function resolveTriggerSkillName(hero: heroInfo, skillUuid: number): string {
for (const type of TRIGGER_KEYS) {
const grouped = hero[type] as TriggerGrouped | undefined;
if (grouped?.[skillUuid]?.length) return getGroupedTriggerName(grouped, skillUuid);
}
+ // 驻场光环:词条名取 FieldSkillSet
+ if (hero.field?.some(e => e.uuids.some(u => u.uuid === skillUuid))) {
+ return getFieldSkillName(skillUuid);
+ }
+ // 复活技能:统一复活称谓
+ if (hero.revive?.some(e => e.s_uuid === skillUuid)) {
+ return getReviveSkillName();
+ }
return SkillSet[skillUuid]?.name ?? "";
}
diff --git a/assets/script/game/common/config/CardSkillText.ts b/assets/script/game/common/config/CardSkillText.ts
new file mode 100644
index 00000000..523c7931
--- /dev/null
+++ b/assets/script/game/common/config/CardSkillText.ts
@@ -0,0 +1,341 @@
+/**
+ * @file CardSkillText.ts
+ * @description 卡牌/道具语境技能描述文案层(从已弃用的 HeroSkillDesc.ts 迁出)
+ *
+ * 职责:不依赖英雄语境的卡牌/道具描述富文本生成,数值永远来自
+ * mergeSkillParams 合并后的实时配置,杜绝"改数值忘改文案"的漂移。
+ *
+ * 输出(均为富文本 BBCode,RichText 直接可用):
+ * - buildCardDescRich(card) —— 商店卡片描述(技能卡/药品卡/装备卡统一入口)
+ * - buildEquipRich(entries, showName) —— 装备/驻场光环词条详情
+ * - buildSingleSkillRich(uuid, ov, lv) —— 单技能详情(CD/持续 + 效果,IBox 弹窗用)
+ *
+ * 英雄语境描述请使用 HeroSkillText.ts(heroInfo 内联模板驱动)。
+ *
+ * 依赖:
+ * - SkillSet / FieldSkillSet(技能/光环静态配置与合并)
+ * - SkillDescSet / FieldDescSet(结构化描述模板)
+ * - BuffList(计时 buff 默认值兜底)
+ * - CardConfig(卡牌统一配置结构)
+ */
+import { CardConfig } from "./CardSet";
+import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
+import { FieldDescSet, FieldValueFmt, SkillDescSet, SkillDescConfig } from "./SkillDescSet";
+import { BuffList } from "./BuffSet";
+import { FieldEntry } from "./heroSet";
+
+/**
+ * 一条能力的结构化行:数值片段单独抽出,供富文本高亮与纯文本复用。
+ * 渲染结果 = prefix + value(可高亮) + suffix。
+ */
+interface ISkillLine {
+ /** 数值前的文本(含触发条件、技能名、目标、动词等) */
+ prefix: string;
+ /** 需要高亮的核心数值文本(如 "15%"、"4次"、"+30"),无数值时为 "" */
+ value: string;
+ /** 数值后的文本(如 ",持续5秒" 之类尾部补充) */
+ suffix: string;
+}
+
+/** 富文本数值高亮色(鲜亮绿色,深色面板上高亮醒目) */
+const RICH_VALUE_COLOR = "#3CE66E";
+
+/** 数值描边色(深绿,衬托亮绿填充,与技能名橙填充+深褐描边同一设计思路) */
+const RICH_VALUE_OUTLINE = "#145A32";
+
+/**
+ * 数值富文本片段:亮绿填充 + 深绿描边。
+ * 高亮数值明度高,深色描边增强边缘对比、提升深色面板上的锐度。
+ * @param text 数值文本(含单位,如 "15%"、"4次")
+ */
+function valueRich(text: string | number): string {
+ return `${text}`;
+}
+
+/** 富文本技能名高亮色(蜜桃橙,暖色系,与面板绿/蓝/红/黄/紫均不冲突) */
+const RICH_NAME_COLOR = "#F8C471";
+
+/** 技能名描边色(深褐,衬托蜜桃橙填充,深色面板上温馨醒目) */
+const RICH_NAME_OUTLINE = "#6E2C00";
+
+/**
+ * 技能名富文本片段:[技能名]整体蜜桃橙填充 + 深褐描边(含方括号)。
+ * 括号与技能名同色同描边,正文/数值不描边,突出技能名视觉层级。
+ * @param name 技能名(纯文本,不含括号)
+ * @returns [技能名] 富文本片段
+ */
+function skillNameRich(name: string): string {
+ return `[${name}]`;
+}
+
+/** 技能名「」匹配模式(仅本模块行首/行中技能名片段使用) */
+const SKILL_NAME_PATTERN = /「([^」]*)」/g;
+
+/** "持续X秒"匹配模式(卡片描述后处理:把持续时间数值也高亮) */
+const DURATION_PATTERN = /持续(\d+)秒/g;
+
+/** 解析目标描述文本(与战斗 SCastSystem 目标选取语义严格一致) */
+function buildTargetDesc(skill: SkillConfig): string {
+ switch (skill.TGroup) {
+ case TGroup.Self:
+ return "自身";
+ case TGroup.Enemy:
+ return "敌方";
+ case TGroup.Ally:
+ case TGroup.All:
+ return "全体友方";
+ case TGroup.Team: {
+ const n = Math.max(1, Math.floor(skill.hit_count ?? 1));
+ // 治疗类命中低血量队友(SCastSystem.pickHealTargetsByMostMissingHp)
+ if (skill.kind === SkillKind.Heal) {
+ return n > 1 ? `${n}名低血量队友` : "低血量队友";
+ }
+ return `${n}名队友`;
+ }
+ default:
+ return "全体友方";
+ }
+}
+
+/**
+ * 占位符模板替换,并把"核心数值片段"用 \x01...\x02 标记,便于上层拆分高亮。
+ *
+ * Why: 模板中 {value}{unit} 是玩家最关心的数值。这里在渲染时将其单独包裹,
+ * 使富文本可对其单独上色,保证高亮片段与模板数值严格一致。
+ *
+ * @returns 含 \x01hlValue\x02 标记的完整文本
+ */
+function renderTemplate(templ: string, vars: Record, hlValue: string): string {
+ let out = templ;
+ for (const key of Object.keys(vars)) {
+ out = out.split(`{${key}}`).join(String(vars[key]));
+ }
+ // 用 \x01...\x02 包裹数值片段作为标记,便于上层拆分为 prefix/value/suffix
+ return out.split(hlValue).join(`\x01${hlValue}\x02`);
+}
+
+/**
+ * 由一行模板渲染结果构造结构化行(剥离 \x01\x02 标记,把数值片段拆到 value 字段)
+ * @param head 行首(触发条件:技能名 )
+ * @param rendered 已用 \x01...\x02 标记数值片段的效果文本
+ */
+function makeLine(head: string, rendered: string): ISkillLine {
+ const start = rendered.indexOf("\x01");
+ const end = rendered.indexOf("\x02");
+ if (start >= 0 && end > start) {
+ // 恰好含一个数值片段:prefix=行首+数值前文本,value=数值,suffix=数值后文本
+ return {
+ prefix: `${head}${rendered.slice(0, start)}`,
+ value: rendered.slice(start + 1, end),
+ suffix: rendered.slice(end + 1),
+ };
+ }
+ // 无数值片段(未命中模板或兜底 info):剥离残留标记,整行作为 prefix,不高亮
+ return { prefix: `${head}${rendered.replace(/[\x01\x02]/g, "")}`, value: "", suffix: "" };
+}
+
+/**
+ * 根据合并后的技能配置构造结构化效果行(查 SkillDescSet 模板渲染 + 数值高亮抽取)
+ *
+ * 计时模式默认值兜底:
+ * overrides 仅给 timed_buff_id 而未覆写 buff_value / buff_duration 时,
+ * 从 BuffList[timed_buff_id] 取默认值,保证 1 级基础档文案完整。
+ */
+function buildEffectLine(head: string, skill: SkillConfig, descOverride?: SkillDescConfig): ISkillLine {
+ const desc = descOverride ?? SkillDescSet[skill.uuid];
+ // 未入表技能回退基础备注 info,无法抽出数值,整行不高亮
+ if (!desc) return { prefix: `${head}${String(skill.info ?? "")}`, value: "", suffix: "" };
+
+ const buff = skill.timed_buff_id !== undefined ? BuffList[skill.timed_buff_id] : undefined;
+ const isTimed = buff !== undefined && !!desc.templTimed;
+ const templ = isTimed ? desc.templTimed! : desc.templPerm;
+ const valueField = isTimed ? (desc.timedField ?? desc.permField) : desc.permField;
+
+ // 计时模式默认值兜底:buff_value 未覆写时用 BuffList 默认修饰值
+ let value = (skill[valueField as keyof SkillConfig] as number | undefined);
+ if (value === undefined && isTimed) {
+ value = buff!.modifiers?.[0]?.value ?? buff!.tick?.damage_or_heal ?? 0;
+ }
+ value = value ?? 0;
+ const dur = skill.buff_duration ?? buff?.duration ?? 0;
+
+ // 高亮片段 = 数值 + 单位(如 "15%"、"4次"、"+30"),是玩家视觉焦点
+ const hlValue = `${value}${desc.unit}`;
+ const rendered = renderTemplate(templ, {
+ target: buildTargetDesc(skill),
+ value: value,
+ unit: desc.unit,
+ verb: desc.verb,
+ dur: dur,
+ }, hlValue);
+ return makeLine(head, rendered);
+}
+
+/**
+ * 驻场光环(field)结构化行:查 FieldDescSet 按 type 渲染,value 实时取数值并高亮。
+ *
+ * Why: FieldSkillConfig.info 是手写文案,改 value 易漂移。这里以 FieldSkillType
+ * 为 key 查标准动词,数值按 fmt(比例/整数/减免)从 value 实时渲染。
+ *
+ * @param showName 是否在行中包含「技能名」片段(英雄面板需要,装备商店不需要)
+ */
+function buildFieldLine(head: string, entry: FieldEntry, showName: boolean = true): ISkillLine | null {
+ const fs = FieldSkillSet[entry.uuid];
+ if (!fs) return null;
+ const desc = FieldDescSet[fs.type];
+ const nameSeg = showName ? `「${fs.name}」 ` : "";
+ // 未入表类型回退基础备注 info,整行不高亮
+ if (!desc) return { prefix: `${head}${nameSeg}${fs.info}`, value: "", suffix: "" };
+
+ // 数值按格式渲染为高亮片段(数值取自词条自身 value)
+ let valueText: string;
+ switch (desc.fmt) {
+ case FieldValueFmt.Percent:
+ valueText = `+${Math.round(entry.value * 100)}%`;
+ break;
+ case FieldValueFmt.IntMinus:
+ valueText = `-${entry.value}`;
+ break;
+ case FieldValueFmt.Int:
+ default:
+ valueText = `+${entry.value}`;
+ break;
+ }
+ return { prefix: `${head}${nameSeg}${desc.verb}`, value: valueText, suffix: "" };
+}
+
+/**
+ * 把"持续X秒"中的 X 也包裹绿色高亮(卡片描述后处理)。
+ * Why: 计时模板 {dur} 不在 \x01\x02 高亮标记内,结构化行仅高亮主数值;
+ * 持续时间对药水/计时 buff 同样关键,这里补充高亮,保证视觉一致。
+ */
+function highlightDuration(text: string): string {
+ return text.replace(DURATION_PATTERN, `持续${valueRich("$1")}秒`);
+}
+
+/**
+ * 结构化行 → 富文本串(技能名橙色、数值绿色)。
+ * buildEquipRich / buildCardDescRich 共用,保证渲染规则唯一。
+ */
+function renderLineRich(line: ISkillLine): string {
+ const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
+ return line.value
+ ? `${prefix}${valueRich(line.value)}${line.suffix}`
+ : `${prefix}${line.suffix}`;
+}
+
+/**
+ * 扫描合并后技能的附加概率字段(crt/frz/stun/bck),非零的拼为高亮后缀。
+ * Why: 模板不再硬编码"有概率击晕"等文案,由实际配置驱动,避免描述与数值漂移。
+ * @returns 形如 ",30%击晕,20%冰冻";全为 0 时返回 ""
+ */
+function buildChanceSuffix(skill: SkillConfig): string {
+ const items: string[] = [];
+ const push = (val: number | undefined, label: string) => {
+ if (val && val > 0) items.push(`附加${valueRich(val)}%${label}`);
+ };
+ push(skill.crt, "暴击");
+ push(skill.frz, "冰冻");
+ push(skill.stun, "击晕");
+ push(skill.bck, "击退");
+ return items.length > 0 ? `,${items.join(",")}` : "";
+}
+
+/**
+ * 单个技能详情富文本(IBox 弹窗用):展示 CD/持续时间 + 结构化效果描述。
+ * @param skillUuid SkillSet 技能 UUID
+ * @param overrides 可选覆写参数(如药水卡自定义档位)
+ * @param lv 技能等级(仅用于展示,不影响数值计算)
+ * @returns 多行富文本串:第一行名称/等级/持续时间,第二行效果描述
+ */
+export function buildSingleSkillRich(skillUuid: number, overrides?: SkillOverrides, lv: number = 1): string {
+ const base = SkillSet[skillUuid];
+ if (!base) return "未知技能";
+ const skill = mergeSkillParams(base, overrides);
+ // SkillConfig 未定义 cd 字段,冷却时间由外部运行时数据传入;计时模式展示持续时间
+ const dur = skill.buff_duration ?? (skill.timed_buff_id !== undefined ? BuffList[skill.timed_buff_id]?.duration : undefined) ?? 0;
+ const cdTag = dur > 0 ? valueRich(`持续:${dur}s`) : "";
+ const nameTag = `${skillNameRich(skill.name)} Lv.${lv}`;
+
+ // 复用结构化效果行,head 为空避免重复触发类型前缀
+ const line = buildEffectLine("", skill);
+ const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
+ const effect = line.value
+ ? `${prefix}${valueRich(line.value)}${line.suffix}`
+ : `${prefix}${line.suffix}`;
+
+ return cdTag ? `${nameTag} ${cdTag}\n${effect}` : `${nameTag}\n${effect}`;
+}
+
+/**
+ * 装备/驻场光环详情富文本:展示全部词条,数值绿色高亮。
+ * @param entries 驻场光环词条数组(uuid 指向 FieldSkillSet 底座,value 为生效数值)
+ * @param showName 是否包含「技能名」片段(英雄面板 true,装备商店 false)
+ * @returns 多行富文本串,每行一个词条
+ */
+export function buildEquipRich(entries: FieldEntry[], showName: boolean = false): string {
+ if (!entries?.length) return "无词条";
+ const lines: string[] = [];
+ for (const entry of entries) {
+ const line = buildFieldLine("", entry, showName);
+ if (!line) continue;
+ const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
+ const text = line.value
+ ? `${prefix}${valueRich(line.value)}${line.suffix}`
+ : `${prefix}${line.suffix}`;
+ lines.push(text);
+ }
+ return lines.length > 0 ? lines.join("\n") : "无词条";
+}
+
+/**
+ * 商店卡片描述富文本(SCardComp / ItemListComp 用,BBCode)。
+ *
+ * Why: 卡片 info 为手写文案,与 SkillSet/BuffList 数值易漂移。
+ * 本函数从 CardConfig.skill + overrides 合并后的实时配置生成描述,
+ * 数值随档位(如高级药水 buff_value)自动同步,数值绿色高亮、技能名橙色。
+ *
+ * 描述规则:
+ * - 药品/技能卡(card.skill 有效)→ 查 SkillDescSet 模板渲染(计时模式取 BuffList 默认值兜底);
+ * 未入表技能回退 SkillSet.info / card.info,整行不高亮。
+ * - 装备卡(card.field 有效)→ 复用 buildEquipRich 逐词条渲染。
+ * - 触发节奏(t_times/t_inv)融入单行描述:定时触发"每M秒召唤K个「技能名」攻击敌人,xxx,共N次"(K=overrides.num+1);即时触发仅显示效果。
+ *
+ * @param card 卡片配置(SCardSet 技能卡 / ICardSet 药品卡 / EquipSet 装备卡)
+ * @returns 多行富文本串;无可描述内容时回退 card.info
+ */
+export function buildCardDescRich(card: CardConfig): string {
+ // 装备卡:逐词条渲染驻场光环
+ if (card.field && card.field.length > 0) {
+ return buildEquipRich(card.field);
+ }
+
+ // 药品/技能卡:合并覆写后按 SkillDescSet 模板渲染
+ const base = card.skill ? SkillSet[card.skill] : undefined;
+ if (base) {
+ const skill = mergeSkillParams(base, card.overrides);
+ const line = buildEffectLine("", skill);
+ // 未入表技能回退链:SkillSet.info → card.info
+ if (!line.value && !SkillDescSet[skill.uuid]) {
+ return String(skill.info || card.info || "");
+ }
+
+ // 触发节奏融入描述:定时触发"每M秒释放一次「技能名」,xxx,共N次";即时触发直接描述
+ const tTimes = card.t_times ?? 1;
+ const tInv = card.t_inv ?? 0;
+ const isInstant = card.is_inst || tInv <= 0;
+ // 附加概率后缀(暴击/冰冻/击晕/击退),由实际配置驱动
+ const suffix = buildChanceSuffix(skill);
+ const rich = highlightDuration(renderLineRich(line)) + suffix;
+
+ if (isInstant) {
+ return rich;
+ }
+
+ const rhythm = `每${valueRich(tInv)}秒召唤${valueRich((card.overrides?.num ?? 0) + 1)}个${skillNameRich(skill.name)}攻击敌人`;
+ const times = `, 共${valueRich(tTimes)}次`;
+ return `${rhythm}, ${rich}${times}`;
+ }
+
+ return card.info || "";
+}
diff --git a/assets/script/game/common/config/HeroSkillDesc.ts.meta b/assets/script/game/common/config/CardSkillText.ts.meta
similarity index 70%
rename from assets/script/game/common/config/HeroSkillDesc.ts.meta
rename to assets/script/game/common/config/CardSkillText.ts.meta
index 7e2a2a79..8a0bb563 100644
--- a/assets/script/game/common/config/HeroSkillDesc.ts.meta
+++ b/assets/script/game/common/config/CardSkillText.ts.meta
@@ -2,7 +2,7 @@
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
- "uuid": "de462338-a674-4ccf-b621-bb67d0d9b901",
+ "uuid": "9855de2d-2900-4e2d-831d-c9f177716a71",
"files": [],
"subMetas": {},
"userData": {}
diff --git a/assets/script/game/common/config/HeroSkillDesc.ts b/assets/script/game/common/config/HeroSkillDesc.ts
deleted file mode 100644
index 7d5a655b..00000000
--- a/assets/script/game/common/config/HeroSkillDesc.ts
+++ /dev/null
@@ -1,943 +0,0 @@
-/**
-* @file HeroSkillDesc.ts
-* @description 英雄技能/能力描述的【唯一文字表达标准层】
-*
-* @deprecated 已被 HeroSkillText.ts(heroInfo 内联 desc 模板驱动)取代。
-* 本文件暂时保留兼容旧 UI 引用(HerosListComp/CHeroComp/HeroBoxComp/HeroMoreCom/
-* SCardComp/ItemListComp/IBoxComp/EquipListComp/SinfoComp),待各面板迁移至
-* buildHeroSkillDesc / buildHeroSkillTierDesc 后移除。新代码请勿再引用本模块。
-*
-* 设计目标:
-* heroSet / SkillSet 中的 info / infos 仅作"基础备注",不再承担面板展示。
-* 所有功能性描述(触发技能、驻场光环、复活、等级额外属性加成)统一由本模块
-* 依据结构化配置动态拼出,数值永远来自 mergeSkillParams 合并后的实时配置,
-* 杜绝"改数值忘改文案"的漂移。
-*
-* 输出(双格式,供 UI 按需取用):
-* - buildSkillDesc(source) → 纯文本(Label 用),数值无颜色
-* - buildSkillDescRich(source) → 富文本(RichText 用,BBCode),技能名蓝色、数值绿色高亮
-* 两者共用同一套结构化行构建逻辑,仅渲染阶段区分,保证纯/富文本内容严格一致。
-*
-* 通用性(纯配置驱动):
-* 1. 触发框架:按触发类型遍历(SkillSet + overrides,不动)
-* 2. 效果文案:查 SkillDescSet 结构化模板渲染
-* 3. 目标/属性名:由本模块统一语义化(target/hit_count、ATTR_NAME)
-*
-* 数据源(入参抽象 ISkillDescSource):
-* heroInfo(静态 1 级配置)与 HeroAttrsComp(运行时应用 evolve 后的当前等级配置)
-* 字段结构一致,天然都满足。HeroBoxComp 传 model 显示当前等级;卡池/商店传 heroInfo 显示 1 级。
-*
-* 依赖:
-* - heroSet :SkillTriggerName、SkillTriggerType、TriggerGrouped/LvFieldEntry/LvReviveEntry/LvBonusEntry 及 resolver
-* - SkillSet :SkillSet、mergeSkillParams、SkillOverrides、SkillKind、TGroup
-* - SkillDescSet:结构化描述模板
-* - FieldSkillSet(驻场技能)、BuffList(计时 buff 默认值兜底)、Attrs(属性枚举)
-*/
-import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, getGroupedTriggerName, getFieldSkillName, getReviveSkillName, HGrowType, calcGrowBonus, HSkillInfo } from "./heroSet";
-import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
-import { FieldDescSet, FieldValueFmt, SkillDescSet, SkillDescConfig } from "./SkillDescSet";
-import { BuffList } from "./BuffSet";
-import { Attrs } from "./HeroAttrs";
-import { CardConfig } from "./CardSet";
-
-/**
- * 生成器入参窄接口:只约束实际读取的字段。
- * heroInfo 与 HeroAttrsComp 的对应字段结构完全一致(均为分组 + lv 数组),天然都满足此接口。
- */
-export interface ISkillDescSource {
- /** 当前等级(用于 resolve 触发技能/光环/复活取档;静态 heroInfo 恒为 1) */
- lv?: number;
- /** 携带技能(普攻技能,支持 lv_entries 按等级分档 resolve) */
- skills?: Record;
- call?: TriggerGrouped;
- dead?: TriggerGrouped;
- fstart?: TriggerGrouped;
- atking?: TriggerGrouped;
- atked?: TriggerGrouped;
- field?: LvFieldEntry[];
- revive?: LvReviveEntry[];
- /** 额外攻击/生命加成(按 lv 累加,用于成长文案) */
- ap_bonus?: LvBonusEntry[];
- hp_bonus?: LvBonusEntry[];
- /** 成长类型(决定每级 hp/ap 标准递增增量) */
- grow_type?: HGrowType;
- /** 基础攻击/生命(成长基数,线性公式 = 基础 × 系数 × (lv-1)) */
- ap?: number;
- hp?: number;
- /** 原始基础攻击/生命(HeroAttrsComp 场景优先用作成长基数,避免运行时 hp/ap 重复计成长) */
- base_ap?: number;
- base_hp?: number;
-}
-
-/**
- * 一条能力的结构化行:数值片段单独抽出,供富文本高亮与纯文本复用。
- * 渲染结果 = prefix + value(可高亮) + suffix。
- */
-interface ISkillLine {
- /** 数值前的文本(含触发条件、技能名、目标、动词等) */
- prefix: string;
- /** 需要高亮的核心数值文本(如 "15%"、"4次"、"+30"),无数值时为 "" */
- value: string;
- /** 数值后的文本(如 ",持续5秒" 之类尾部补充) */
- suffix: string;
- /** 尚未激活(档位等级高于当前等级):富文本整行灰色、不做高亮 */
- locked?: boolean;
- /** 同一技能未来档位的数值列表(全档位合并模式专用):渲染为 value 后灰色 "/v1/v2" 拼接 */
- nextValues?: string[];
-}
-
-/** 需要遍历的触发技能类型列表(不含 Field 和 Revive,这两者结构不同需单独处理) */
-const TRIGGER_KEYS: SkillTriggerType[] = [
- SkillTriggerType.Call,
- SkillTriggerType.Dead,
- SkillTriggerType.FStart,
- SkillTriggerType.Atking,
- SkillTriggerType.Atked,
-];
-
-/** 属性标准中文名映射(统一称谓,供 evolve 额外加成文案使用),仅覆盖 ap_bonus/hp_bonus 用到的属性 */
-const ATTR_NAME: Partial> = {
- [Attrs.ap]: "攻击力",
- [Attrs.hp_max]: "最大生命",
-};
-
-/** 富文本数值高亮色(鲜亮绿色,深色面板上高亮醒目) */
-const RICH_VALUE_COLOR = "#3CE66E";
-
-/** 数值描边色(深绿,衬托亮绿填充,与技能名橙填充+深褐描边同一设计思路) */
-const RICH_VALUE_OUTLINE = "#145A32";
-
-/**
- * 数值富文本片段:亮绿填充 + 深绿描边。
- * 高亮数值明度高,深色描边增强边缘对比、提升深色面板上的锐度。
- * @param text 数值文本(含单位,如 "15%"、"4次")
- */
-function valueRich(text: string | number): string {
- return `${text}`;
-}
-
-/** 富文本技能名高亮色(蜜桃橙,暖色系,与面板绿/蓝/红/黄/紫均不冲突) */
-const RICH_NAME_COLOR = "#F8C471";
-
-/** 技能名描边色(深褐,衬托蜜桃橙填充,深色面板上温馨醒目) */
-const RICH_NAME_OUTLINE = "#6E2C00";
-
-/**
- * 技能名富文本片段:[技能名]整体蜜桃橙填充 + 深褐描边(含方括号)。
- * 括号与技能名同色同描边,正文/数值不描边,突出技能名视觉层级。
- * @param name 技能名(纯文本,不含括号)
- * @returns [技能名] 富文本片段
- */
-function skillNameRich(name: string): string {
- return `[${name}]`;
-}
-
-/** 富文本未激活档位整行灰色(低饱和,深色面板上可读但明显弱化) */
-const RICH_LOCKED_COLOR = "#95A5A6";
-
-/** 技能名「」匹配模式(仅本模块行首/行中技能名片段使用) */
-const SKILL_NAME_PATTERN = /「([^」]*)」/g;
-
-/** "持续X秒"匹配模式(卡片描述后处理:把持续时间数值也高亮) */
-const DURATION_PATTERN = /持续(\d+)秒/g;
-
-/** 解析目标描述文本(与战斗 SCastSystem 目标选取语义严格一致) */
-function buildTargetDesc(skill: SkillConfig): string {
- switch (skill.TGroup) {
- case TGroup.Self:
- return "自身";
- case TGroup.Enemy:
- return "敌方";
- case TGroup.Ally:
- case TGroup.All:
- return "全体友方";
- case TGroup.Team: {
- const n = Math.max(1, Math.floor(skill.hit_count ?? 1));
- // 治疗类命中低血量队友(SCastSystem.pickHealTargetsByMostMissingHp)
- if (skill.kind === SkillKind.Heal) {
- return n > 1 ? `${n}名低血量队友` : "低血量队友";
- }
- return `${n}名队友`;
- }
- default:
- return "全体友方";
- }
-}
-
-/**
- * 占位符模板替换,并把"核心数值片段"用 \x01...\x02 标记,便于上层拆分高亮。
- *
- * Why: 模板中 {value}{unit} 是玩家最关心的数值。这里在渲染时将其单独包裹,
- * 使富文本可对其单独上色,纯文本则剥离标记后原样拼接,保证两种输出内容一致。
- *
- * @returns 含 \x01hlValue\x02 标记的完整文本
- */
-function renderTemplate(templ: string, vars: Record, hlValue: string): string {
- let out = templ;
- for (const key of Object.keys(vars)) {
- out = out.split(`{${key}}`).join(String(vars[key]));
- }
- // 用 \x01...\x02 包裹数值片段作为标记,便于上层拆分为 prefix/value/suffix
- return out.split(hlValue).join(`\x01${hlValue}\x02`);
-}
-
-/**
- * 由一行模板渲染结果构造结构化行(剥离 \x01\x02 标记,把数值片段拆到 value 字段)
- * @param head 行首(触发条件:技能名 )
- * @param rendered 已用 \x01...\x02 标记数值片段的效果文本
- */
-function makeLine(head: string, rendered: string): ISkillLine {
- const start = rendered.indexOf("\x01");
- const end = rendered.indexOf("\x02");
- if (start >= 0 && end > start) {
- // 恰好含一个数值片段:prefix=行首+数值前文本,value=数值,suffix=数值后文本
- return {
- prefix: `${head}${rendered.slice(0, start)}`,
- value: rendered.slice(start + 1, end),
- suffix: rendered.slice(end + 1),
- };
- }
- // 无数值片段(未命中模板或兜底 info):剥离残留标记,整行作为 prefix,不高亮
- return { prefix: `${head}${rendered.replace(/[\x01\x02]/g, "")}`, value: "", suffix: "" };
-}
-
-/**
- * 根据合并后的技能配置构造结构化效果行(查 SkillDescSet 模板渲染 + 数值高亮抽取)
- *
- * 计时模式默认值兜底:
- * overrides 仅给 timed_buff_id 而未覆写 buff_value / buff_duration 时,
- * 从 BuffList[timed_buff_id] 取默认值,保证 1 级基础档文案完整。
- */
-function buildEffectLine(head: string, skill: SkillConfig, descOverride?: SkillDescConfig): ISkillLine {
- const desc = descOverride ?? SkillDescSet[skill.uuid];
- // 未入表技能回退基础备注 info,无法抽出数值,整行不高亮
- if (!desc) return { prefix: `${head}${String(skill.info ?? "")}`, value: "", suffix: "" };
-
- const buff = skill.timed_buff_id !== undefined ? BuffList[skill.timed_buff_id] : undefined;
- const isTimed = buff !== undefined && !!desc.templTimed;
- const templ = isTimed ? desc.templTimed! : desc.templPerm;
- const valueField = isTimed ? (desc.timedField ?? desc.permField) : desc.permField;
-
- // 计时模式默认值兜底:buff_value 未覆写时用 BuffList 默认修饰值
- let value = (skill[valueField as keyof SkillConfig] as number | undefined);
- if (value === undefined && isTimed) {
- value = buff!.modifiers?.[0]?.value ?? buff!.tick?.damage_or_heal ?? 0;
- }
- value = value ?? 0;
- const dur = skill.buff_duration ?? buff?.duration ?? 0;
-
- // 高亮片段 = 数值 + 单位(如 "15%"、"4次"、"+30"),是玩家视觉焦点
- const hlValue = `${value}${desc.unit}`;
- const rendered = renderTemplate(templ, {
- target: buildTargetDesc(skill),
- value: value,
- unit: desc.unit,
- verb: desc.verb,
- dur: dur,
- }, hlValue);
- return makeLine(head, rendered);
-}
-
-/**
- * 驻场光环(field)结构化行:查 FieldDescSet 按 type 渲染,value 实时取数值并高亮。
- *
- * Why: FieldSkillConfig.info 是手写文案,改 value 易漂移。这里以 FieldSkillType
- * 为 key 查标准动词,数值按 fmt(比例/整数/减免)从 value 实时渲染。
- *
- * @param showName 是否在行中包含「技能名」片段(英雄面板需要,装备商店不需要)
- * @param nameOverride 技能名覆盖(英雄语境传入 heroSet 语境专属命名,缺省用 FieldSkillSet 通用名)
- */
-function buildFieldLine(head: string, entry: FieldEntry, showName: boolean = true, nameOverride?: string): ISkillLine | null {
- const fs = FieldSkillSet[entry.uuid];
- if (!fs) return null;
- const desc = FieldDescSet[fs.type];
- const nameSeg = showName ? `「${nameOverride ?? fs.name}」 ` : "";
- // 未入表类型回退基础备注 info,整行不高亮
- if (!desc) return { prefix: `${head}${nameSeg}${fs.info}`, value: "", suffix: "" };
-
- // 数值按格式渲染为高亮片段(数值取自词条自身 value)
- let valueText: string;
- switch (desc.fmt) {
- case FieldValueFmt.Percent:
- valueText = `+${Math.round(entry.value * 100)}%`;
- break;
- case FieldValueFmt.IntMinus:
- valueText = `-${entry.value}`;
- break;
- case FieldValueFmt.Int:
- default:
- valueText = `+${entry.value}`;
- break;
- }
- return { prefix: `${head}${nameSeg}${desc.verb}`, value: valueText, suffix: "" };
-}
-
-/**
- * 复活(revive)结构化行:复刻战斗公式,高亮复活次数与回血百分比
- * 复活次数 = r_num + floor((lv - 1) * upr);回血百分比 = SkillSet[s_uuid].ap
- *
- * @param showLocked true 时列出全部档位并标激活状态(未激活档位次数按解锁等级预估);
- * false 时仅输出当前等级生效档
- */
-function buildReviveLines(source: ISkillDescSource, showName: boolean = true, showLocked: boolean = false): ISkillLine[] {
- const lv = Math.max(1, source.lv ?? 1);
- const entries = source.revive;
- if (!entries?.length) return [];
-
- const tpl = SkillTriggerName[SkillTriggerType.Revive] ?? "复活";
- const makeLine = (entry: { s_uuid: number; r_num: number; upr: number }, head: string, countLv: number, locked: boolean): ISkillLine | null => {
- const base = SkillSet[entry.s_uuid];
- if (!base) return null;
- const maxCount = entry.r_num + Math.floor((countLv - 1) * entry.upr);
- const hpPct = base.ap ?? 0;
- const nameSeg = showName ? `「${base.name}」 ` : "";
- // 高亮片段选回血百分比(数值中最关键的收益指标)
- return {
- prefix: `${head}${nameSeg}可复活${maxCount}次, 每次恢复`,
- value: `${hpPct}%`,
- suffix: "生命",
- locked,
- };
- };
-
- if (!showLocked) {
- const revive = resolveReviveByLv(entries, lv);
- if (!revive) return [];
- const line = makeLine(revive, `${tpl}:`, lv, false);
- return line ? [line] : [];
- }
-
- // 全档位合并模式:每个 s_uuid 只出一行,等级前缀置前("Lv2: 复活 ...")
- const groups: Record = {};
- for (const e of entries) {
- (groups[e.s_uuid] ??= []).push(e);
- }
- const lines: ISkillLine[] = [];
- for (const key in groups) {
- const sorted = [...groups[Number(key)]].sort((a, b) => a.lv - b.lv);
- let active: LvReviveEntry | undefined;
- for (const e of sorted) { if (e.lv <= lv) active = e; }
-
- if (active) {
- const line = makeLine(active, `Lv${active.lv}: ${tpl} `, lv, false);
- if (!line) continue;
- // 未来档回血数值以灰色追加(同 s_uuid 的 base.ap 通常相同,仅不同时才拼接)
- const nextVals: string[] = [];
- for (const e of sorted) {
- if (e.lv <= lv) continue;
- const v = `${SkillSet[e.s_uuid]?.ap ?? 0}%`;
- if (v !== line.value && !nextVals.includes(v)) nextVals.push(v);
- }
- if (nextVals.length) line.nextValues = nextVals;
- lines.push(line);
- } else {
- // 完全未激活:预览最低档,次数按解锁等级预估,整行灰色
- const first = sorted[0];
- const line = makeLine(first, `Lv${first.lv}: ${tpl} `, first.lv, true);
- if (line) lines.push(line);
- }
- }
- return lines;
-}
-
-/** 额外属性加成(ap/hp)累计结构化行:成长递增 + 一次性奖励,高亮累计数值 */
-function buildEvolveBonusLines(source: ISkillDescSource): ISkillLine[] {
- const lv = Math.max(1, source.lv ?? 1);
- // 成长线性增量:优先用原始基础值(base_*,HeroAttrsComp 场景),否则用配置 hp/ap;
- // 缺基础值时按 0 处理(如纯触发技能描述场景),避免运行时 hp/ap 重复计成长
- const baseHp = source.base_hp ?? source.hp ?? 0;
- const baseAp = source.base_ap ?? source.ap ?? 0;
- const grow = calcGrowBonus({ hp: baseHp, ap: baseAp, grow_type: source.grow_type }, lv);
- let apTotal = grow.ap;
- let hpTotal = grow.hp;
- if (source.ap_bonus) {
- for (const e of source.ap_bonus) { if (e.lv <= lv) apTotal += e.value; }
- }
- if (source.hp_bonus) {
- for (const e of source.hp_bonus) { if (e.lv <= lv) hpTotal += e.value; }
- }
- const lines: ISkillLine[] = [];
- if (apTotal !== 0) lines.push({ prefix: "成长:", value: `+${apTotal}`, suffix: ATTR_NAME[Attrs.ap] ?? "攻击力" });
- if (hpTotal !== 0) lines.push({ prefix: "成长:", value: `+${hpTotal}`, suffix: ATTR_NAME[Attrs.hp_max] ?? "最大生命" });
- return lines;
-}
-
-/**
- * 构建全部能力结构化行(触发技能 + 驻场光环 + 复活 + 额外加成)。
- * 纯文本与富文本两个公开接口共用此结果,仅渲染阶段不同。
- *
- * @param showName 是否在行中包含「技能名」片段
- * @param showLocked true 时为全档位合并模式:每个技能只出一行——已激活档正常渲染且
- * 未来档数值灰色拼接在 value 后(nextValues);完全未激活的技能
- * 预览最低档、整行 locked 灰色、Lv 前缀置前。false 时仅输出当前生效档
- */
-function buildSkillLines(source: ISkillDescSource, showName: boolean = true, showLocked: boolean = false): ISkillLine[] {
- const lines: ISkillLine[] = [];
- const lv = Math.max(1, source.lv ?? 1);
-
- // ---- 普攻技能(skills):按英雄等级 resolve 分档 overrides ----
- if (source.skills) {
- for (const key in source.skills) {
- const hSkill = source.skills[key];
- if (!hSkill) continue;
- const base = SkillSet[hSkill.uuid];
- if (!base) continue;
- const overrides = resolveSkillOverrides(hSkill, lv);
- const skill = mergeSkillParams(base, overrides);
- // 技能等级展示:lv_entries 有档时取档位 s_lv,否则用运行时 lv
- const sLv = hSkill.lv_entries?.length
- ? (hSkill.lv_entries.filter(e => e.lv <= lv).pop()?.s_lv ?? hSkill.lv)
- : hSkill.lv;
- const nameSeg = showName ? `「${hSkill.name ?? base.name}」 ` : "";
- lines.push(buildEffectLine(`Lv${sLv}: ${nameSeg}`, skill));
- }
- }
-
- // ---- 6 种标准触发技能 ----
- for (const key of TRIGGER_KEYS) {
- const grouped = source[key] as TriggerGrouped | undefined;
- if (!grouped) continue;
- // 触发类型简称(召唤/亡语/起手/生息/进击/受击)
- const name = SkillTriggerName[key] ?? key;
- // 仅进击/受击带触发次数,拼为"×n";其余简称直接用
- const needCount = key === SkillTriggerType.Atking || key === SkillTriggerType.Atked;
-
- if (!showLocked) {
- // 仅当前等级生效档(resolve 取各 s_uuid ≤lv 的最大档)
- for (const item of resolveTriggerByLv(grouped, lv)) {
- const base = SkillSet[item.s_uuid];
- if (!base) continue;
- const skill = mergeSkillParams(base, item.overrides);
- const trigger = needCount ? `${name}×${item.t_num}` : name;
- // 触发技能名走 HeroInfo 语境配置(如 atked.6301=不屈壁垒),未配置回退 SkillSet 内部名
- const nameSeg = showName ? `「${getGroupedTriggerName(grouped, item.s_uuid)}」 ` : "";
- lines.push(buildEffectLine(`${trigger}:${nameSeg}`, skill));
- }
- continue;
- }
-
- // 全档位合并模式:每个 s_uuid 只出一行,等级前缀置前("Lv1: 进击×5 ...")
- for (const sKey in grouped) {
- const entries = grouped[sKey];
- if (!entries?.length) continue;
- const base = SkillSet[Number(sKey)];
- if (!base) continue;
- const sorted = [...entries].sort((a, b) => a.lv - b.lv);
- const nameSeg = showName ? `「${getGroupedTriggerName(grouped, Number(sKey))}」 ` : "";
- const headOf = (e: LvTriggerEntry) => `Lv${e.lv}: ${needCount ? `${name}×${e.t_num}` : name} ${nameSeg}`;
-
- // 取当前等级生效档(≤lv 的最大档)
- let active: LvTriggerEntry | undefined;
- for (const e of sorted) { if (e.lv <= lv) active = e; }
-
- if (active) {
- const line = buildEffectLine(headOf(active), mergeSkillParams(base, active.overrides));
- // 未来档数值以灰色 "/v1/v2" 追加在高亮数值后(仅收集与当前不同的数值,避免重复)
- const nextVals: string[] = [];
- for (const e of sorted) {
- if (e.lv <= lv) continue;
- const v = buildEffectLine("", mergeSkillParams(base, e.overrides)).value;
- if (v && v !== line.value && !nextVals.includes(v)) nextVals.push(v);
- }
- if (nextVals.length) line.nextValues = nextVals;
- lines.push(line);
- } else {
- // 技能完全未激活:预览最低档效果,整行灰色
- const first = sorted[0];
- const line = buildEffectLine(headOf(first), mergeSkillParams(base, first.overrides));
- line.locked = true;
- lines.push(line);
- }
- }
- }
-
- // ---- 驻场光环(field):查 FieldDescSet 结构化渲染,value 数值高亮 ----
- const fieldEntries = source[SkillTriggerType.Field] as LvFieldEntry[] | undefined;
- if (fieldEntries?.length) {
- const tpl = SkillTriggerName[SkillTriggerType.Field] ?? "光环";
- if (!showLocked) {
- for (const entry of resolveFieldByLv(fieldEntries, lv)) {
- const line = buildFieldLine(`${tpl}:`, entry, showName);
- if (line) lines.push(line);
- }
- } else {
- // 全档位合并模式:生效档(≤lv 最大档)正常显示,未来档整行灰色且 Lv 置前;
- // 被替换的历史档不显示(resolve 语义下升级后旧档光环失效)
- const sorted = [...fieldEntries].sort((a, b) => a.lv - b.lv);
- let activeTier: LvFieldEntry | undefined;
- for (const tier of sorted) { if (tier.lv <= lv) activeTier = tier; }
- if (activeTier) {
- for (const entry of activeTier.uuids) {
- const line = buildFieldLine(`Lv${activeTier.lv}: ${tpl} `, entry, showName);
- if (line) lines.push(line);
- }
- }
- for (const tier of sorted) {
- if (tier.lv <= lv) continue;
- for (const entry of tier.uuids) {
- const line = buildFieldLine(`Lv${tier.lv}: ${tpl} `, entry, showName);
- if (!line) continue;
- line.locked = true;
- lines.push(line);
- }
- }
- }
- }
-
- // ---- 复活(revive)----
- lines.push(...buildReviveLines(source, showName, showLocked));
-
- // ---- 额外属性加成(ap/hp,按当前等级累计)----
- lines.push(...buildEvolveBonusLines(source));
-
- return lines;
-}
-
-/**
- * 纯文本描述(Label 用):数值无颜色。
- * @param source 触发技能数据源(heroInfo 静态配置 或 HeroAttrsComp 运行时当前等级配置)
- * @returns 多行纯文本,每行一条能力,用 \n 分隔
- */
-export function buildSkillDesc(source: ISkillDescSource): string {
- return buildSkillLines(source)
- .map(l => `${l.prefix}${l.value}${l.suffix}`)
- .join("\n");
-}
-
-/**
- * 富文本描述(RichText 用,BBCode):技能名用 蓝色、数值用 亮绿色高亮。
- * @param source 触发技能数据源(同 buildSkillDesc)
- * @param showName 是否包含「技能名」片段(默认 true)
- * @param showLocked true 时为全档位合并模式:每个技能只出一行,已激活档数值高亮、
- * 未来档数值灰色 "/v1/v2" 追加;完全未激活的技能整行灰色且 Lv 前缀置前
- * (英雄盒面板用);false 时仅输出当前等级生效档
- * @returns 多行富文本串,用 \n 分隔
- */
-export function buildSkillDescRich(source: ISkillDescSource, showName: boolean = true, showLocked: boolean = false): string {
- return buildSkillLines(source, showName, showLocked)
- // 未激活行保持整行灰色,不再补充"持续X秒"数值高亮
- .map(l => l.locked ? renderLineRich(l) : highlightDuration(renderLineRich(l)))
- .join("\n");
-}
-
-/**
- * 单个技能详情富文本(IBox 弹窗用):展示 CD/持续时间 + 结构化效果描述。
- * @param skillUuid SkillSet 技能 UUID
- * @param overrides 可选覆写参数(如药水卡自定义档位)
- * @param lv 技能等级(仅用于展示,不影响数值计算)
- * @returns 多行富文本串:第一行 CD/持续时间,第二行效果描述
- */
-export function buildSingleSkillRich(skillUuid: number, overrides?: SkillOverrides, lv: number = 1): string {
- const base = SkillSet[skillUuid];
- if (!base) return "未知技能";
- const skill = mergeSkillParams(base, overrides);
- // SkillConfig 未定义 cd 字段,冷却时间由外部运行时数据传入;计时模式展示持续时间
- const dur = skill.buff_duration ?? (skill.timed_buff_id !== undefined ? BuffList[skill.timed_buff_id]?.duration : undefined) ?? 0;
- const cdTag = dur > 0 ? valueRich(`持续:${dur}s`) : "";
- const nameTag = `${skillNameRich(skill.name)} Lv.${lv}`;
-
- // 复用结构化效果行,head 为空避免重复触发类型前缀
- const line = buildEffectLine("", skill);
- const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
- const effect = line.value
- ? `${prefix}${valueRich(line.value)}${line.suffix}`
- : `${prefix}${line.suffix}`;
-
- return cdTag ? `${nameTag} ${cdTag}\n${effect}` : `${nameTag}\n${effect}`;
-}
-
-/**
- * 单个技能纯效果富文本(SInfo 提升面板用):不含技能名与 Lv 前缀,仅输出效果描述。
- * @param skillUuid SkillSet 技能 UUID
- * @param overrides 可选覆写参数
- * @param desc 可选 SkillDescConfig(heroSet 技能项自带说明,优先于 SkillDescSet)
- * @returns 单行富文本串:如 "造成攻击力250%的伤害"
- */
-export function buildEffectOnlyRich(skillUuid: number, overrides?: SkillOverrides, desc?: SkillDescConfig): string {
- const base = SkillSet[skillUuid];
- if (!base) return "未知技能";
- const skill = mergeSkillParams(base, overrides);
- const line = buildEffectLine("", skill, desc);
- const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
- return line.value
- ? `${prefix}${valueRich(line.value)}${line.suffix}`
- : `${prefix}${line.suffix}`;
-}
-
-/**
- * 装备/驻场光环详情富文本:展示全部词条,数值绿色高亮。
- * @param entries 驻场光环词条数组(uuid 指向 FieldSkillSet 底座,value 为生效数值)
- * @param showName 是否包含「技能名」片段(英雄面板 true,装备商店 false)
- * @returns 多行富文本串,每行一个词条
- */
-export function buildEquipRich(entries: FieldEntry[], showName: boolean = false): string {
- if (!entries?.length) return "无词条";
- const lines: string[] = [];
- for (const entry of entries) {
- const line = buildFieldLine("", entry, showName);
- if (!line) continue;
- const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
- const text = line.value
- ? `${prefix}${valueRich(line.value)}${line.suffix}`
- : `${prefix}${line.suffix}`;
- lines.push(text);
- }
- return lines.length > 0 ? lines.join("\n") : "无词条";
-}
-
-/**
- * 英雄信息弹窗技能描述富文本(HeroMoreCom 用)。
- *
- * 格式:
- * 「技能名」 Lv.当前技能等级:当前档技能效果
- *
- * 仅展示当前生效档位,不含升级提示;升级前后对比由升级确认框单独提供。
- * 数据源为运行时 HeroAttrsComp(lv 取当前英雄等级),逐触发类型逐技能渲染。
- * 技能等级取 LvTriggerEntry.s_lv,未标注时按档位序号(1/2/3...)兜底。
- *
- * @param source 英雄运行时数据源(含 lv 与触发技能分组)
- * @param showName 是否展示「技能名」片段(英雄信息弹窗 true,英雄卡池卡片 false)
- * @returns 多行富文本串,\n 分隔
- */
-export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boolean = true): string {
- const lv = Math.max(1, source.lv ?? 1);
- const lines: string[] = [];
-
- // 单条效果 → 富文本(技能名蓝色、数值绿色高亮、持续秒数高亮)
- const effectRich = (line: ISkillLine): string => {
- const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
- const text = line.value
- ? `${prefix}${valueRich(line.value)}${line.suffix}`
- : `${prefix}${line.suffix}`;
- return highlightDuration(text);
- };
-
- // ---- 普攻技能(skills):按英雄等级 resolve 分档 overrides,展示当前生效档 ----
- if (source.skills) {
- for (const key in source.skills) {
- const hSkill = source.skills[key];
- if (!hSkill) continue;
- const base = SkillSet[hSkill.uuid];
- if (!base) continue;
- const overrides = resolveSkillOverrides(hSkill, lv);
- const skill = mergeSkillParams(base, overrides);
- // 技能等级展示:lv_entries 有档时取档位 s_lv,否则用运行时 lv
- const sLv = hSkill.lv_entries?.length
- ? (hSkill.lv_entries.filter(e => e.lv <= lv).pop()?.s_lv ?? hSkill.lv)
- : hSkill.lv;
- const head = showName ? `「${hSkill.name ?? base.name}」 Lv.${sLv}: ` : `Lv.${sLv}: `;
- lines.push(effectRich(buildEffectLine(head, skill)));
- }
- }
-
- for (const key of TRIGGER_KEYS) {
- const grouped = source[key] as TriggerGrouped | undefined;
- if (!grouped) continue;
- const trigName = SkillTriggerName[key] ?? key;
- const needCount = key === SkillTriggerType.Atking || key === SkillTriggerType.Atked;
-
- for (const sKey in grouped) {
- const entries = grouped[sKey];
- if (!entries?.length) continue;
- const sUuid = Number(sKey);
- const base = SkillSet[sUuid];
- if (!base) continue;
- const sorted = [...entries].sort((a, b) => a.lv - b.lv);
- // 技能名走 HeroInfo 触发语境配置(如 atked.6301=不屈壁垒)
- const skillName = getGroupedTriggerName(grouped, sUuid);
-
- // 当前生效档(≤lv 最大档),未激活则用最低档
- let activeIdx = -1;
- for (let i = 0; i < sorted.length; i++) {
- if (sorted[i].lv <= lv) activeIdx = i;
- }
- const curIdx = activeIdx >= 0 ? activeIdx : 0;
- const cur = sorted[curIdx];
- const curSLv = cur.s_lv ?? (curIdx + 1);
- // 触发次数直接写为富文本(数字绿色高亮),如 "受伤3次"
- const curTrig = needCount
- ? `${trigName}${valueRich(cur.t_num)}次`
- : trigName;
- // 技能名直接展示,不附加档位数字后缀(由 lv 字段控制生效档,玩家无需关注内部档号)
- const curHead = showName ? `「${skillName}」: ${curTrig}, ` : `${curTrig}, `;
- lines.push(effectRich(buildEffectLine(curHead, mergeSkillParams(base, cur.overrides))));
- }
- }
-
- // 驻场光环(field):直接展示技能名,不附加档位数字后缀
- const fieldEntries = source[SkillTriggerType.Field] as LvFieldEntry[] | undefined;
- if (fieldEntries?.length) {
- for (const entry of resolveFieldByLv(fieldEntries, lv)) {
- const baseName = getFieldSkillName(entry.uuid);
- const line = buildFieldLine("", entry, showName, baseName);
- if (line) lines.push(effectRich(line));
- }
- }
-
- // 重生(revive):每回合拥有 N 次重生机会,死亡后以 X% 血量原地重生
- // 血量百分比来自 SkillSet[s_uuid].ap(运行时口径),upr 是每级追加次数,不在此展示
- const revive = resolveReviveByLv(source[SkillTriggerType.Revive] as LvReviveEntry[] | undefined, lv);
- if (revive) {
- const reviveName = getReviveSkillName();
- const reviveHpPct = SkillSet[revive.s_uuid]?.ap ?? 50;
- lines.push(showName
- ? `${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(reviveHpPct)}%`)}血量原地重生`
- : `每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(reviveHpPct)}%`)}血量原地重生`);
- }
-
- return lines.join("\n");
-}
-
-/**
- * 把"持续X秒"中的 X 也包裹绿色高亮(卡片描述后处理)。
- * Why: 计时模板 {dur} 不在 \x01\x02 高亮标记内,renderLineRich 仅高亮主数值;
- * 持续时间对药水/计时 buff 同样关键,这里补充高亮,保证视觉一致。
- */
-function highlightDuration(text: string): string {
- return text.replace(DURATION_PATTERN, `持续${valueRich("$1")}秒`);
-}
-
-/**
- * 结构化行 → 富文本串(技能名蓝色、数值绿色)。
- * buildEquipRich / buildCardDescRich / buildSkillDescRich 共用,保证渲染规则唯一。
- * 未激活行(locked)整行灰色弱化、不做技能名/数值高亮。
- */
-function renderLineRich(line: ISkillLine): string {
- if (line.locked) return `${line.prefix}${line.value}${line.suffix}`;
- const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
- // 未来档数值灰色追加在当前高亮数值后("15%/20%/30%"),suffix 仍位于整段数值之后
- const next = line.nextValues?.length ? `/${line.nextValues.join("/")}` : "";
- return line.value
- ? `${prefix}${valueRich(line.value)}${next}${line.suffix}`
- : `${prefix}${next}${line.suffix}`;
-}
-
-/**
- * 扫描合并后技能的附加概率字段(crt/frz/stun/bck),非零的拼为高亮后缀。
- * Why: 模板不再硬编码"有概率击晕"等文案,由实际配置驱动,避免描述与数值漂移。
- * @returns 形如 ",30%击晕,20%冰冻";全为 0 时返回 ""
- */
-function buildChanceSuffix(skill: SkillConfig): string {
- const items: string[] = [];
- const push = (val: number | undefined, label: string) => {
- if (val && val > 0) items.push(`附加${valueRich(val)}%${label}`);
- };
- push(skill.crt, "暴击");
- push(skill.frz, "冰冻");
- push(skill.stun, "击晕");
- push(skill.bck, "击退");
- return items.length > 0 ? `,${items.join(",")}` : "";
-}
-
-/**
- * 商店卡片描述富文本(SCardComp / ItemListComp 用,BBCode)。
- *
- * Why: 卡片 info 为手写文案,与 SkillSet/BuffList 数值易漂移。
- * 本函数从 CardConfig.skill + overrides 合并后的实时配置生成描述,
- * 数值随档位(如高级药水 buff_value)自动同步,数值绿色高亮、技能名蓝色。
- *
- * 描述规则:
- * - 药品/技能卡(card.skill 有效)→ 查 SkillDescSet 模板渲染(计时模式取 BuffList 默认值兜底);
- * 未入表技能回退 SkillSet.info / card.info,整行不高亮。
- * - 装备卡(card.field 有效)→ 复用 buildEquipRich 逐词条渲染。
- * - 触发节奏(t_times/t_inv)融入单行描述:定时触发"每M秒召唤K个「技能名」攻击敌人,xxx,共N次"(K=overrides.num+1);即时触发仅显示效果。
- *
- * @param card 卡片配置(SCardSet 技能卡 / ICardSet 药品卡 / EquipSet 装备卡)
- * @returns 多行富文本串;无可描述内容时回退 card.info
- */
-export function buildCardDescRich(card: CardConfig): string {
- // 装备卡:逐词条渲染驻场光环
- if (card.field && card.field.length > 0) {
- return buildEquipRich(card.field);
- }
-
- // 药品/技能卡:合并覆写后按 SkillDescSet 模板渲染
- const base = card.skill ? SkillSet[card.skill] : undefined;
- if (base) {
- const skill = mergeSkillParams(base, card.overrides);
- const line = buildEffectLine("", skill);
- // 未入表技能回退链:SkillSet.info → card.info
- if (!line.value && !SkillDescSet[skill.uuid]) {
- return String(skill.info || card.info || "");
- }
-
- // 触发节奏融入描述:定时触发"每M秒释放一次「技能名」,xxx,共N次";即时触发直接描述
- const tTimes = card.t_times ?? 1;
- const tInv = card.t_inv ?? 0;
- const isInstant = card.is_inst || tInv <= 0;
- // 附加概率后缀(暴击/冰冻/击晕/击退),由实际配置驱动
- const suffix = buildChanceSuffix(skill);
- const rich = highlightDuration(renderLineRich(line)) + suffix;
-
- if (isInstant) {
- return rich;
- }
-
- const rhythm = `每${valueRich(tInv)}秒召唤${valueRich((card.overrides?.num ?? 0) + 1)}个${skillNameRich(skill.name)}攻击敌人`;
- const times = `, 共${valueRich(tTimes)}次`;
- return `${rhythm}, ${rich}${times}`;
- }
-
- return card.info || "";
-}
-
-/* =====================================================================================
- * 按技能等级分段说明(升级引导 / 图鉴逐档预览)
- *
- * 与 buildSkillDesc(当前等级)/ buildSkillDescRich(全档位合并单行)并列的第三种模式:
- * 每个技能按等级档位逐段输出 —— 技能 Lv1 给完整能力+数值,Lv2 起只给相对上一级的
- * 「数值漂移 + 新功能」,让玩家一眼看清每一级"多得到了什么"。
- * ===================================================================================*/
-
-/** 参与"新功能/数值漂移"对比的展示字段(排除动画/弹道/特效等玩家无感字段) */
-const DIFF_DISPLAY_KEYS: (keyof SkillOverrides)[] = [
- "ap", "hit_count", "num", "summon_count", "crt", "frz", "stun", "para",
- "bck", "bck_chance", "timed_buff_id", "buff_value", "buff_duration",
-];
-
-/** 概率/次数类展示字段的单位与称谓(新功能标记 + 数值漂移描述共用) */
-const DIFF_FIELD_META: Partial> = {
- ap: { label: "伤害", unit: "%" },
- hit_count: { label: "命中", unit: "次" },
- num: { label: "双发", unit: "" },
- summon_count: { label: "召唤", unit: "" },
- crt: { label: "暴击", unit: "%" },
- frz: { label: "冰冻", unit: "%" },
- stun: { label: "击晕", unit: "%" },
- para: { label: "麻痹", unit: "%" },
- bck: { label: "击退", unit: "" },
- bck_chance: { label: "击退概率", unit: "%" },
- buff_duration: { label: "持续", unit: "秒" },
-};
-
-/** 一个技能的等级档位(统一普攻 lv_entries 与触发技能 LvTriggerEntry 两种结构) */
-interface ISkillTier {
- /** 技能等级(展示用,取 s_lv,缺省按档位序号) */
- sLv: number;
- /** 该档生效的英雄等级(解锁提示) */
- heroLv: number;
- /** 合并底座后的完整技能配置 */
- skill: SkillConfig;
-}
-
-/** 收集普攻/主技能的全部等级档位(按 sLv 升序)。Lv1=基础档(底+基础 overrides),Lv2+=lv_entries 各档 */
-function collectTiers(base: SkillConfig, baseOverrides: SkillOverrides | undefined,
- entries: { lv: number; s_lv?: number; overrides?: SkillOverrides }[]): ISkillTier[] {
- const tiers: ISkillTier[] = [];
- // Lv1 = 基础档(底 + 基础 overrides)
- const lv1Entry = entries.find(e => (e.s_lv ?? 0) === 1);
- tiers.push({
- sLv: lv1Entry?.s_lv ?? 1,
- heroLv: lv1Entry?.lv ?? 1,
- skill: mergeSkillParams(base, baseOverrides),
- });
- // Lv2+ = lv_entries 中 s_lv≥2 的档(与基础档浅合并,档位优先)
- for (const e of entries) {
- const sLv = e.s_lv ?? 0;
- if (sLv < 2) continue;
- tiers.push({ sLv, heroLv: e.lv, skill: mergeSkillParams(mergeSkillParams(base, baseOverrides), e.overrides) });
- }
- return tiers.sort((a, b) => a.sLv - b.sLv);
-}
-
-/** 相邻两档的 diff:数值漂移(ap 等差值)+ 新功能(上级没有的 timed_buff/暴击/冰冻等) */
-function buildTierDiff(prev: SkillConfig, cur: SkillConfig): string[] {
- const parts: string[] = [];
- for (const key of DIFF_DISPLAY_KEYS) {
- const pv = (prev[key as keyof SkillConfig] as number | undefined) ?? 0;
- const cv = (cur[key as keyof SkillConfig] as number | undefined) ?? 0;
- if (cv === pv) continue;
- // 新功能:上级为 0/无,本档新增(含 timed_buff_id 新注入)
- if (key === "timed_buff_id") {
- if (cv > 0 && pv === 0) parts.push(`新增${valueRich("计时增益")}`);
- continue;
- }
- const meta = DIFF_FIELD_META[key];
- if (!meta) continue;
- if (pv === 0 && cv > 0) {
- parts.push(`新增${meta.label}${valueRich(`${cv}${meta.unit}`)}`);
- } else {
- const diff = cv - pv;
- const sign = diff > 0 ? "+" : "";
- parts.push(`${meta.label}${valueRich(`${sign}${diff}${meta.unit}`)}`);
- }
- }
- return parts;
-}
-
-/** 单个技能的分段说明行(Lv1 完整 / Lv2+ diff),返回富文本行数组 */
-function buildTierLines(name: string, tiers: ISkillTier[], headPrefix: string): string[] {
- const lines: string[] = [];
- for (let i = 0; i < tiers.length; i++) {
- const t = tiers[i];
- const head = `${skillNameRich(name)} ${valueRich(`Lv.${t.sLv}`)}${headPrefix}`;
- if (i === 0) {
- // 技能 1 级:完整能力 + 数值
- const line = buildEffectLine("", t.skill);
- const effect = `${line.prefix}${line.value ? valueRich(line.value) : ""}${line.suffix}`;
- lines.push(`${head}: ${highlightDuration(effect)}`);
- } else {
- // 技能 2 级起:相对上一级的数值漂移 + 新功能
- const diff = buildTierDiff(tiers[i - 1].skill, t.skill);
- const unlock = t.heroLv > 1 ? `(英雄${valueRich(t.heroLv)}级解锁)` : "";
- lines.push(diff.length > 0
- ? `${head}${unlock}: ${diff.join(",")}`
- : `${head}${unlock}: 无变化`);
- }
- }
- return lines;
-}
-
-/**
- * 按技能等级分段输出说明(富文本)。
- *
- * 两种对外说明模式的第二种(第一种为 buildSkillDesc/buildSkillDescMoreRich 的"当前等级"模式):
- * 每个技能逐档输出 —— Lv1 给完整能力与数值,Lv2 起给相对上一级的提升(数值漂移 + 新功能),
- * 并标注解锁所需英雄等级。适合升级引导、图鉴逐档预览。
- *
- * @param source 英雄数据源(heroInfo 静态配置 或 HeroAttrsComp 运行时配置)
- * @returns 多行富文本串,\n 分隔;按技能分组、组内按技能等级升序
- */
-export function buildSkillTierDescRich(source: ISkillDescSource): string {
- const lines: string[] = [];
-
- // ---- 普攻/主技能(skills):lv_entries 分档 ----
- if (source.skills) {
- for (const key in source.skills) {
- const hSkill = source.skills[key];
- if (!hSkill) continue;
- const base = SkillSet[hSkill.uuid];
- if (!base) continue;
- const entries = hSkill.lv_entries ?? [];
- // 无分档的单档技能只出 Lv1 完整行
- const tiers = collectTiers(base, hSkill.overrides, entries);
- lines.push(...buildTierLines(hSkill.name ?? base.name, tiers, ""));
- }
- }
-
- // ---- 触发技能(atking/atked/dead/fstart/call):LvTriggerEntry 分档 ----
- for (const key of TRIGGER_KEYS) {
- const grouped = source[key] as TriggerGrouped | undefined;
- if (!grouped) continue;
- const trigName = SkillTriggerName[key] ?? key;
- const needCount = key === SkillTriggerType.Atking || key === SkillTriggerType.Atked;
- for (const sKey in grouped) {
- const entries = grouped[sKey];
- if (!entries?.length) continue;
- const base = SkillSet[Number(sKey)];
- if (!base) continue;
- const sorted = [...entries].sort((a, b) => (a.s_lv ?? 0) - (b.s_lv ?? 0));
- const name = getGroupedTriggerName(grouped, Number(sKey));
- // 触发技能以第 1 档为基础档,逐档 diff(t_num 变化并入新功能/数值漂移)
- const tiers: ISkillTier[] = sorted.map((e, i) => ({
- sLv: e.s_lv ?? (i + 1),
- heroLv: e.lv,
- skill: mergeSkillParams(base, e.overrides),
- }));
- const headPrefix = needCount && sorted[0] ? ` ${trigName}${valueRich(sorted[0].t_num)}次` : ` ${trigName}`;
- lines.push(...buildTierLines(name, tiers, headPrefix));
- }
- }
-
- return lines.join("\n");
-}
diff --git a/assets/script/game/common/config/SkillDescSet.ts b/assets/script/game/common/config/SkillDescSet.ts
index 950ba953..5c3373c2 100644
--- a/assets/script/game/common/config/SkillDescSet.ts
+++ b/assets/script/game/common/config/SkillDescSet.ts
@@ -3,7 +3,7 @@
* @description 技能描述结构化配置表(方案 B:独立描述表,纯 UI 用)
*
* 职责:
- * 为触发技能提供"结构化字段 + 占位符模板",供 HeroSkillDesc.buildSkillDesc
+ * 为触发技能提供"结构化字段 + 占位符模板",供 CardSkillText.buildCardDescRich
* 在合并 overrides 后动态拼出效果文案。杜绝 SkillConfig.info 写死数值导致的
* "改数值忘改文案"漂移。
*
diff --git a/assets/script/game/common/config/heroSet.ts b/assets/script/game/common/config/heroSet.ts
index 0b2f3372..43b1d920 100644
--- a/assets/script/game/common/config/heroSet.ts
+++ b/assets/script/game/common/config/heroSet.ts
@@ -1,4 +1,4 @@
-import { v3 } from "cc"
+import { v3 } from "cc"
import { BoxSet, FacSet, FightSet } from "./GameSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet"
import { BuffList, BuffConfig, ModOp } from "./BuffSet"
@@ -286,7 +286,7 @@ export interface heroInfo {
hp_bonus?: LvBonusEntry[]; // 额外生命加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档)
info: string; // 描述文案
/**
- * @deprecated 已废弃:无任何 UI/运行时读取。技能提升说明由 HeroSkillDesc 依据
+ * @deprecated 已废弃:无任何 UI/运行时读取。技能提升说明由 HeroSkillText 依据
* 结构化配置动态生成(含数值漂移与新功能),请勿再填充此字段。
*/
infos?: string[];
@@ -380,6 +380,8 @@ export interface FieldEntry {
export interface LvFieldEntry {
lv: number;
uuids: FieldEntry[];
+ /** 档位描述(可选,缺省由 HeroSkillText 按 FieldDescSet 自动生成) */
+ desc?: string;
}
/**
@@ -390,6 +392,8 @@ export interface LvReviveEntry {
s_uuid: number;
r_num: number;
upr: number;
+ /** 档位描述(可选,缺省由 HeroSkillText 自动生成) */
+ desc?: string;
}
/**
@@ -403,7 +407,7 @@ export interface LvBonusEntry {
*=============英雄配置列表================
* 几何王国复刻版 — 依据 英雄设计指引.md(A/B 级复刻英雄)配置
*
- * 规则:lv2 解锁触发技能(atking/atked/fstart),lv3/5/7 大招走 lv_entries 分档成长,
+ * 规则:lv3 解锁 atking/atked 触发技能,lv2 解锁 fstart,lv6/12 大招走 lv_entries 分档成长,
* lv9 card_lv 4/5 配 field 光环,card_lv 3 配 ap_bonus/hp_bonus 基础属性+50%
* 编号:本批复刻英雄集中于 5001~5009,card_lv 按配置(不遵循段位规则)
*/
@@ -422,17 +426,16 @@ export const HeroInfo: Record = {
6203: {
uuid: 6203, name: "能量波", lv: 1, cd: 8, ccd: 0,
overrides: { ap: 250, bck: 30, para: 40, summon_count: 1 },
- desc: "释放{summon_count}道能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹",
+ desc: "释放能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 250, bck: 60, para: 40, summon_count: 1 } },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 300, bck: 90, timed_buff_id: 7005, para: 50, summon_count: 2 }, desc: "释放{summon_count}道能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹,并使目标中毒(每0.5秒受到{buff_value}点毒伤,持续{dur}秒)" },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 400, timed_buff_id: 7025, para: 70, summon_count: 3 }, desc: "释放{summon_count}道能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹,并使目标麻痹{dur}秒(技能CD减速50%)" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 250, bck: 60, para: 40, summon_count: 1 } },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 300, bck: 90, timed_buff_id: 7005, para: 50, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹,额外释放1次,并使目标中毒(每0.5秒受到{buff_value}点毒伤,持续{dur}秒)" },
],
},
},
atking: {
6010: [
- { lv: 2, s_lv: SkillLv.Lv1, name: "雷霆万钧", t_num: 3, overrides: { para: 100 }, desc: "释放紫魔球轰击{target},{para}%概率麻痹目标" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "雷霆万钧", t_num: 3, overrides: { para: 100 }, desc: "释放紫魔球轰击{target},{para}%概率麻痹目标" },
],
},
field: [
@@ -451,15 +454,14 @@ export const HeroInfo: Record = {
overrides: { ap: 150 },
desc: "斩出光闪,对{target}造成攻击力{ap}%伤害",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7007 }, desc: "斩出光闪,对{target}造成攻击力{ap}%伤害,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 180, hit_count: 8, num: 1 }, desc: "斩出光闪,对{target}连击{hit_count}次,每次造成攻击力{ap}%伤害,并双发{num}次" },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 360, bck: 50 }, desc: "斩出光闪,对{target}造成攻击力{ap}%伤害,并击退{bck}" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7007 }, desc: "斩出光闪,对{target}造成攻击力{ap}%伤害,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, hit_count: 8, num: 1 }, desc: "斩出光闪,对{target}连击{hit_count}次,每次造成攻击力{ap}%伤害,并双发{num}次" },
],
},
},
atking: {
6004: [
- { lv: 2, s_lv: SkillLv.Lv1, name: "致命节奏", t_num: 3, overrides: { crt: 30 }, desc: "释放斩击,{crt}%概率造成暴击" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "致命节奏", t_num: 3, overrides: { crt: 30 }, desc: "释放斩击,{crt}%概率造成暴击" },
],
},
field: [
@@ -478,15 +480,14 @@ export const HeroInfo: Record = {
overrides: { ap: 150, para: 40, summon_count: 1 },
desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7006, para: 40, summon_count: 1 }, desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹,并使目标攻击力{buff_value}%(持续{dur}秒)" },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 180, timed_buff_id: 7006, buff_value: -15, para: 50, summon_count: 2 } },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 200, hit_count: 6, bck: 40, para: 60, summon_count: 3 }, desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害(命中{hit_count}次),{para}%概率麻痹,击退{bck}" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7006, para: 40, summon_count: 1 }, desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹,并使目标攻击力{buff_value}%(持续{dur}秒)" },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, timed_buff_id: 7006, buff_value: -15, para: 50, summon_count: 2 } },
],
},
},
atked: {
6301: [
- { lv: 5, s_lv: SkillLv.Lv1, name: "罗生门", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 2 }, desc: "{target}获得{ap}次格挡护盾" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "罗生门", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 2 }, desc: "{target}获得{ap}次格挡护盾" },
],
},
fstart: {
@@ -510,15 +511,14 @@ export const HeroInfo: Record = {
overrides: { ap: 150, summon_count: 1 },
desc: "释放能量波,对{target}造成攻击力{ap}%伤害",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 150, frz: 15, summon_count: 1 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻" },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 180, frz: 30, hit_count: 8, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻,命中{hit_count}次" },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 200, timed_buff_id: 7003, summon_count: 3 }, desc: "释放{summon_count}道能量波,对{target}造成攻击力{ap}%伤害,并冰冻目标{dur}秒" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, frz: 15, summon_count: 1 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻" },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, frz: 30, hit_count: 8, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻,命中{hit_count}次" },
],
},
},
atking: {
6009: [
- { lv: 2, s_lv: SkillLv.Lv1, name: "霜寒之触", t_num: 3, overrides: { timed_buff_id: 7003 }, desc: "释放寒气,冰冻{target}{dur}秒" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "霜寒之触", t_num: 3, overrides: { timed_buff_id: 7003 }, desc: "释放寒气,冰冻{target}{dur}秒" },
],
},
field: [
@@ -537,15 +537,14 @@ export const HeroInfo: Record = {
overrides: { TGroup: TGroup.Self, ap: 3, summon_count: 1 },
desc: "{target}获得{ap}次格挡护盾",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Self, ap: 5, summon_count: 1 } },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Self, ap: 6, summon_count: 2 } },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { TGroup: TGroup.Self, ap: 8, summon_count: 3 } },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Self, ap: 5, summon_count: 1 } },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Self, ap: 6, summon_count: 2 } },
],
},
},
atked: {
6004: [
- { lv: 5, s_lv: SkillLv.Lv1, name: "反击减攻", t_num: 4, overrides: { timed_buff_id: 7006 }, desc: "反击使攻击者攻击力{buff_value}%,持续{dur}秒" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "反击减攻", t_num: 4, overrides: { timed_buff_id: 7006 }, desc: "反击使攻击者攻击力{buff_value}%,持续{dur}秒" },
],
},
fstart: {
@@ -578,15 +577,14 @@ export const HeroInfo: Record = {
overrides: { TGroup: TGroup.Team, ap: 300, summon_count: 1 },
desc: "为{target}回复攻击力{ap}%生命",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Team, ap: 400, summon_count: 1 } },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Team, ap: 450, summon_count: 2 } },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { TGroup: TGroup.Team, ap: 450, timed_buff_id: 7010, summon_count: 3 }, desc: "为{target}回复攻击力{ap}%生命,并附加再生(每0.5秒回复{buff_value}点生命,持续{dur}秒)" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Team, ap: 400, summon_count: 1 } },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Team, ap: 450, summon_count: 2 } },
],
},
},
atking: {
6302: [
- { lv: 5, s_lv: SkillLv.Lv1, name: "圣洁之锤", t_num: 3, overrides: { TGroup: TGroup.Self, ap: 100 }, desc: "释放圣光,为{target}回复攻击力{ap}%生命" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "圣洁之锤", t_num: 3, overrides: { TGroup: TGroup.Self, ap: 100 }, desc: "释放圣光,为{target}回复攻击力{ap}%生命" },
],
},
fstart: {
@@ -610,15 +608,14 @@ export const HeroInfo: Record = {
overrides: { ap: 150, summon_count: 1 },
desc: "斩出气刃,对{target}造成攻击力{ap}%伤害",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7002, summon_count: 1 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)" },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 180, bck: 60, timed_buff_id: 7002, buff_value: 20, summon_count: 2 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck},并使目标易伤(持续{dur}秒)" },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 220, bck: 80, summon_count: 3 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck}" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7002, summon_count: 1 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)" },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, bck: 60, timed_buff_id: 7002, buff_value: 20, summon_count: 2 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck},并使目标易伤(持续{dur}秒)" },
],
},
},
atking: {
6004: [
- { lv: 2, s_lv: SkillLv.Lv1, name: "雷霆一击", t_num: 3, overrides: { bck: 40 }, desc: "重击{target},击退{bck}" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "雷霆一击", t_num: 3, overrides: { bck: 40 }, desc: "重击{target},击退{bck}" },
],
},
// card_lv 3:lv9 无光环,改为基础属性+50%
@@ -637,15 +634,14 @@ export const HeroInfo: Record = {
overrides: { ap: 300, summon_count: 1 },
desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 300, stun: 20, summon_count: 1 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕" },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 350, stun: 35, hit_count: 8, summon_count: 2 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕,命中{hit_count}次" },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 400, timed_buff_id: 7004, summon_count: 3 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,并击晕目标{dur}秒" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 300, stun: 20, summon_count: 1 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕" },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 350, stun: 35, hit_count: 8, summon_count: 2 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕,命中{hit_count}次" },
],
},
},
atking: {
6004: [
- { lv: 2, s_lv: SkillLv.Lv1, name: "火眼金睛", t_num: 3, overrides: { crt: 25 }, desc: "释放斩击,{crt}%概率造成暴击" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "火眼金睛", t_num: 3, overrides: { crt: 25 }, desc: "释放斩击,{crt}%概率造成暴击" },
],
},
field: [
@@ -664,21 +660,20 @@ export const HeroInfo: Record = {
overrides: { ap: 150, summon_count: 1 },
desc: "斩出气刃,对{target}造成攻击力{ap}%伤害",
lv_entries: [
- { lv: 3, s_lv: SkillLv.Lv2, overrides: { ap: 180, summon_count: 1 } },
- { lv: 5, s_lv: SkillLv.Lv3, overrides: { ap: 200, summon_count: 2 } },
- { lv: 7, s_lv: SkillLv.Lv4, overrides: { ap: 220, bck: 40, summon_count: 3 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck}" },
+ { lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 180, summon_count: 1 } },
+ { lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 200, summon_count: 2 } },
],
},
},
atking: {
6302: [
- { lv: 5, s_lv: SkillLv.Lv1, name: "嗜血战斧", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 120 }, desc: "释放嗜血斩击,为{target}回复攻击力{ap}%生命" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "嗜血战斧", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 120 }, desc: "释放嗜血斩击,为{target}回复攻击力{ap}%生命" },
],
},
atked: {
// 坚硬皮肤:借 6513 防御爆发底座注入 7023 防御强化(+50%,可无限叠层)
6513: [
- { lv: 2, s_lv: SkillLv.Lv1, name: "坚硬皮肤", t_num: 2, overrides: { TGroup: TGroup.Self, timed_buff_id: 7023, buff_duration: 10 }, desc: "{target}防御提升{buff_value}%,持续{dur}秒" },
+ { lv: 3, s_lv: SkillLv.Lv1, name: "坚硬皮肤", t_num: 2, overrides: { TGroup: TGroup.Self, timed_buff_id: 7023, buff_duration: 10 }, desc: "{target}防御提升{buff_value}%,持续{dur}秒" },
],
},
// card_lv 3:lv9 无光环,改为基础属性+50%
diff --git a/assets/script/game/map/EquipListComp.ts b/assets/script/game/map/EquipListComp.ts
index 6be32730..d4dce995 100644
--- a/assets/script/game/map/EquipListComp.ts
+++ b/assets/script/game/map/EquipListComp.ts
@@ -17,7 +17,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { CardConfig } from "../common/config/CardSet";
import { EquipPoolList } from "../common/config/EquipSet";
-import { buildEquipRich } from "../common/config/HeroSkillDesc";
+import { buildEquipRich } from "../common/config/CardSkillText";
import { outlineRich } from "../common/RichOutline";
import { oops } from "db://oops-framework/core/Oops";
import { getCardIconId, setSpriteFrame } from "../common/CardIconHelper";
diff --git a/assets/script/game/map/HeroBoxComp.ts b/assets/script/game/map/HeroBoxComp.ts
index 9cb06480..9e0c3a5b 100644
--- a/assets/script/game/map/HeroBoxComp.ts
+++ b/assets/script/game/map/HeroBoxComp.ts
@@ -22,7 +22,7 @@ import { HeroInfo, calcGrowBonus } from "../common/config/heroSet";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { Hero } from "../hero/Hero";
import { FacSet, FightSet, getLvColor, NumberFormatter } from "../common/config/GameSet";
-import { buildSkillDescMoreRich, ISkillDescSource } from "../common/config/HeroSkillDesc";
+import { buildHeroSkillDesc } from "../common/HeroSkillText";
import { MissionEconomy } from "./MissionEconomy";
import { UIID } from "../common/config/GameUIConfig";
import { HeroMoreCom } from "./HeroMoreCom";
@@ -200,9 +200,9 @@ export class HeroBoxComp extends CCComp {
}
/**
- * 构建升级前后对比富文本(当前能力 vs 升级后能力)。
- * 数据源取 HeroInfo 静态配置,分别按当前 lv 与 nextLv 渲染同一份技能描述,
- * 与英雄信息弹窗(buildSkillDescMoreRich)同一套文案规则,杜绝数值漂移。
+ * 构建升级前后对比富文本(当前属性/能力 vs 升级后属性/能力)。
+ * 技能描述统一按局外等级口径渲染(buildHeroSkillDesc),与英雄信息弹窗同一套
+ * 文案规则;战斗内升级不改变局外等级,故技能段前后一致,仅属性段随等级变化。
*
* @param heroLv 当前英雄等级
* @param nextLv 目标等级
@@ -212,21 +212,6 @@ export class HeroBoxComp extends CCComp {
const hero = HeroInfo[this.model?.hero_uuid ?? 0];
if (!hero) return "";
- const buildSource = (lv: number): ISkillDescSource => ({
- lv: lv,
- skills: hero.skills,
- call: hero.call,
- dead: hero.dead,
- fstart: hero.fstart,
- atking: hero.atking,
- atked: hero.atked,
- field: hero.field,
- revive: hero.revive,
- grow_type: hero.grow_type,
- ap: hero.ap,
- hp: hero.hp,
- });
-
// ---- 属性成长(固定基础值 + 成长类型指数递增 + 一次性加成) ----
const attrLine = (lv: number): string => {
const grow = calcGrowBonus(hero, lv);
@@ -235,9 +220,10 @@ export class HeroBoxComp extends CCComp {
return `攻击 ${ap} 生命 ${hp}`;
};
- // ---- 升级前后能力对比(当前 vs 升级后,同一份文案规则按 lv 渲染) ----
- const curDesc = buildSkillDescMoreRich(buildSource(heroLv));
- const nextDesc = buildSkillDescMoreRich(buildSource(nextLv));
+ // ---- 升级前后能力对比(局外等级口径,战斗内升级技能段不变) ----
+ const skillDesc = buildHeroSkillDesc(hero.uuid);
+ const curDesc = skillDesc;
+ const nextDesc = skillDesc;
return `` + [
`当前 Lv.${heroLv}:`,
diff --git a/assets/script/game/map/HeroMoreCom.ts b/assets/script/game/map/HeroMoreCom.ts
index 798eb318..e3e937c3 100644
--- a/assets/script/game/map/HeroMoreCom.ts
+++ b/assets/script/game/map/HeroMoreCom.ts
@@ -11,13 +11,13 @@
*
* 依赖:
* - HeroAttrsComp —— 英雄属性数据模型
- * - HeroSkillDesc —— 技能描述富文本标准层
+ * - HeroSkillText —— 技能描述富文本标准层(局外等级口径)
*/
import { _decorator, Component, Label, NodeEventType, RichText } from "cc";
import { oops } from "db://oops-framework/core/Oops";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
-import { buildSkillDescMoreRich } from "../common/config/HeroSkillDesc";
+import { buildHeroSkillDesc } from "../common/HeroSkillText";
const { ccclass, property } = _decorator;
@@ -175,7 +175,7 @@ export class HeroMoreCom extends Component {
this.setStatRow(this.crt_all_label, this.crt_plus_label, finalCrit, finalCrit - (this.model.critical ?? 0), "%");
const finalCritDmg = this.model.getFinalCritDamage();
- this.setStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label, finalCritDmg, finalCritDmg - (this.model.crit_damage ?? 0), "%");
+ this.setStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label, finalCritDmg, finalCritDmg - (this.model.critical_damage ?? 0), "%");
// ---- 冰冻率 / 击退强化 / 穿透率 ----
const finalFreeze = this.model.getFinalFreezeChance();
@@ -195,7 +195,7 @@ export class HeroMoreCom extends Component {
// ---- 技能描述(技能名+技能等级 Lv.x,附升级提示 Lv.x→Lv.y 金色预览) ----
if (this.skill_label && this.skill_label.isValid) {
// 缓存未变则跳过重设,避免降频内 RichText 重复重解析排版
- const desc = buildSkillDescMoreRich(this.model);
+ const desc = buildHeroSkillDesc(this.model.hero_uuid);
if (desc !== this.skillDescCache) {
this.skillDescCache = desc;
this.skill_label.string = desc;
diff --git a/assets/script/game/map/HerosListComp.ts b/assets/script/game/map/HerosListComp.ts
index 7b6c150b..d421ec4d 100644
--- a/assets/script/game/map/HerosListComp.ts
+++ b/assets/script/game/map/HerosListComp.ts
@@ -15,7 +15,7 @@
* 依赖:
* - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表
* - CardLiteComp —— 轻量卡片组件
- * - buildSkillDesc(HeroSkillDesc)—— 技能描述生成器
+ * - buildHeroSkillDesc(HeroSkillText)—— 技能描述生成器(局外等级口径)
*/
import { _decorator, Animation, AnimationClip, Button, Label, Node, Prefab, Sprite, UITransform, Vec3, Widget, instantiate, resources, tween, Tween } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
@@ -23,7 +23,7 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
import { oops } from "db://oops-framework/core/Oops";
import { mLogger } from "../common/Logger";
import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvReviveEntry, LvTriggerEntry, TriggerGrouped } from "../common/config/heroSet";
-import { buildSkillDesc } from "../common/config/HeroSkillDesc";
+import { buildHeroSkillDesc } from "../common/HeroSkillText";
import { GameEvent } from "../common/config/GameEvent";
import { HERO_UPGRADE_NEED_FRAGMENTS, CARD_LV_BG_COLORS } from "../common/config/GameSet";
import { CardLiteComp } from "./CardLiteComp";
@@ -405,7 +405,8 @@ export class HerosListComp extends CCComp {
this.updateCdDisplay(hero)
if (this.info_node) {
- const desc = buildSkillDesc(hero)
+ // Label 承载:HeroSkillText 输出富文本,剥离 BBCode 标签转纯文本
+ const desc = buildHeroSkillDesc(hero.uuid, true, true).replace(/<[^>]+>/g, "")
const infoLabel = this.info_node.getChildByName("info")?.getComponent(Label)
|| this.info_node.getComponent(Label)
|| this.info_node.getComponentInChildren(Label)
@@ -518,7 +519,7 @@ export class HerosListComp extends CCComp {
// ---- 大招:skills[1] 及以后(英雄出场即激活,与能量条同语义,恒不锁定) ----
Object.values(hero.skills).slice(1).forEach(hSkill => {
- // 技能等级展示与 buildSkillDesc 同规则:lv_entries 取 ≤lv 最大档的 s_lv,无生效档回退配置 lv
+ // 技能等级展示与 HeroSkillText 取档同规则:lv_entries 取 ≤lv 最大档的 s_lv,无生效档回退配置 lv
const sLv = hSkill.lv_entries?.length
? (hSkill.lv_entries.filter(e => e.lv <= lv).pop()?.s_lv ?? hSkill.lv)
: hSkill.lv
diff --git a/assets/script/game/map/IBoxComp.ts b/assets/script/game/map/IBoxComp.ts
index 418957e3..dfb30b24 100644
--- a/assets/script/game/map/IBoxComp.ts
+++ b/assets/script/game/map/IBoxComp.ts
@@ -9,13 +9,13 @@
* 4. 点击弹窗任意区域关闭自身。
*
* 关键设计:
- * - 复用 HeroSkillDesc 的结构化模板渲染,数值实时从配置读取,杜绝漂移。
+ * - 复用 CardSkillText 的结构化模板渲染,数值实时从配置读取,杜绝漂移。
* - 技能:展示名称、等级、CD、效果描述(SkillDescSet 模板)。
* - 装备:展示名称、全部驻场光环词条(FieldDescSet 模板)。
* - 不走 oops.gui,由调用方手动实例化并添加到场景,通过 init() 初始化。
*
* 依赖:
- * - HeroSkillDesc(buildSingleSkillRich / buildEquipRich)—— 描述生成
+ * - CardSkillText(buildSingleSkillRich / buildEquipRich)—— 描述生成
* - SkillSet / FieldSkillSet(SkillSet)—— 技能/光环静态配置
* - EquipSet(findEquipByUuid)—— 装备静态配置
*/
@@ -23,7 +23,7 @@ import { _decorator, Node, NodeEventType, RichText, UITransform, Widget } from "
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { SkillSet, SkillOverrides } from "../common/config/SkillSet";
-import { buildEquipRich, buildSingleSkillRich } from "../common/config/HeroSkillDesc";
+import { buildEquipRich, buildSingleSkillRich } from "../common/config/CardSkillText";
import { findEquipByUuid } from "../common/config/EquipSet";
const { ccclass, property } = _decorator;
diff --git a/assets/script/game/map/ItemListComp.ts b/assets/script/game/map/ItemListComp.ts
index 02332c6d..68216517 100644
--- a/assets/script/game/map/ItemListComp.ts
+++ b/assets/script/game/map/ItemListComp.ts
@@ -19,7 +19,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { CardConfig, CardTriggerType, CKind } from "../common/config/CardSet";
import { EquipPoolList } from "../common/config/EquipSet";
-import { buildCardDescRich, buildEquipRich } from "../common/config/HeroSkillDesc";
+import { buildCardDescRich, buildEquipRich } from "../common/config/CardSkillText";
import { outlineRich } from "../common/RichOutline";
import { oops } from "db://oops-framework/core/Oops";
import { getCardIconId, setSpriteFrame } from "../common/CardIconHelper";
diff --git a/assets/script/game/map/SCardComp.ts b/assets/script/game/map/SCardComp.ts
index f6db6cb6..0eb25bc6 100644
--- a/assets/script/game/map/SCardComp.ts
+++ b/assets/script/game/map/SCardComp.ts
@@ -17,7 +17,7 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
import { CardConfig } from "../common/config/CardSet";
import { SkillCardList } from "../common/config/SCardSet";
import { SkillSet } from "../common/config/SkillSet";
-import { buildCardDescRich } from "../common/config/HeroSkillDesc";
+import { buildCardDescRich } from "../common/config/CardSkillText";
import { outlineRich } from "../common/RichOutline";
import { oops } from "db://oops-framework/core/Oops";
import { getCardIconId, setSpriteFrame } from "../common/CardIconHelper";
diff --git a/assets/script/game/map/SSkillBoxComp.ts b/assets/script/game/map/SSkillBoxComp.ts
index 7af458c2..ddb38694 100644
--- a/assets/script/game/map/SSkillBoxComp.ts
+++ b/assets/script/game/map/SSkillBoxComp.ts
@@ -3,7 +3,6 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "db://oops-framework/core/Oops";
import { CardTriggerType } from "../common/config/CardSet";
-import { EquipPoolList } from "../common/config/EquipSet";
import { SkillSet, FieldSkillSet } from "../common/config/SkillSet";
import { FieldEntry } from "../common/config/heroSet";
import { UIID } from "../common/config/GameUIConfig";
@@ -13,8 +12,8 @@ const { ccclass, property } = _decorator;
/**
* 技能小图标组件
*
- * 由 MissSkillsComp.addSkill() 实例化并初始化。
- * 在战场上以图标 + 剩余次数的形式呈现。
+ * 由英雄图鉴面板(HerosListComp.addSkillIcon)实例化,
+ * 以图标 + 技能等级 + 锁定遮罩的形式展示英雄技能,点击弹出技能信息框。
*/
@ccclass('SSkillBoxComp')
@ecs.register('SSkillBoxComp', false)
@@ -74,7 +73,7 @@ export class SSkillBoxComp extends CCComp {
oops.audio.playEffect("music/button");
oops.gui.open(UIID.SInfo, {
uuid: this.s_uuid,
- card_lv: this.card_lv,
+ skill_lv: this.card_lv,
card_icon: this.card_icon,
skill_name: this.resolveSkillName(),
field: this.field,
@@ -95,32 +94,6 @@ export class SSkillBoxComp extends CCComp {
}
}
- /**
- * 初始化技能卡效果:
- * 1. 从 EquipPoolList 查询装备卡的触发配置(含 trigger_type)。
- * 2. 更新 UI 显示(图标 + 次数)。
- *
- * @param uuid 卡牌 UUID
- * @param card_lv 技能卡等级
- */
- init(uuid: number, card_lv: number) {
- this.card_lv = card_lv;
-
- // 装备卡配置已迁移至 EquipPoolList
- const config = EquipPoolList.find(c => c.uuid === uuid);
- if (config) {
- this.s_uuid = config.skill ?? uuid; // 获取实际的技能 UUID
- this.card_icon = config.icon; // 保存卡牌自定义图标(优先级最高)
- this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
- this.skill_name = config.name ?? "";
- } else {
- this.s_uuid = uuid;
- this.skill_name = "";
- }
-
- this.updateUI();
- }
-
/**
* 初始化英雄技能图标(英雄图鉴面板专用):
* 图标按 s_uuid 直接取 SkillSet / FieldSkillSet 配置,不走 EquipPoolList。
diff --git a/assets/script/game/map/SinfoComp.ts b/assets/script/game/map/SinfoComp.ts
index 9b801ef1..588f422a 100644
--- a/assets/script/game/map/SinfoComp.ts
+++ b/assets/script/game/map/SinfoComp.ts
@@ -5,14 +5,13 @@
* 职责:
* 1. 展示技能小图标(SSkillBoxComp)点击后的详细信息弹窗。
* 2. 展示技能名称、当前等级、图标、主功能说明。
- * 3. 展示 2 级 / 3 级 激活状态(已激活显示"已激活",未激活显示"x级激活")
- * 及相对上一档的能力提升说明。
+ * 3. 展示 1 级 / 2 级 / 3 级 激活状态(已激活显示"已激活",未激活显示"x级激活")。
* 4. 点击弹窗外部区域关闭。
*
* 使用(oops.gui 模块):
* oops.gui.open(UIID.SInfo, {
* uuid: 8701, // 卡牌/技能 UUID
- * card_lv: 1, // 当前等级
+ * skill_lv: 1, // 当前等级
* card_icon: "xxx", // 图标ID(可选,缺省自动解析)
* skill_name: "战后恢复", // 名称(可选)
* field: [...], // 驻场词条(装备卡)
@@ -23,9 +22,10 @@
* 依赖:
* - oops.gui(UI 生命周期)—— open / removeByNode
* - SkillSet / FieldSkillSet(技能/光环静态配置)
- * - SkillCardList / EquipPoolList(卡牌静态配置)
- * - HeroSkillDesc(buildCardDescRich 富文本描述生成,卡牌模式)
- * - HeroSkillText(buildHeroSkillTierLines 技能分档描述,英雄技能模式)
+ * - SkillCardList(卡牌静态配置)
+ * - CardSkillText(buildCardDescRich 富文本描述生成,卡牌模式)
+ * - HeroSkillText(buildHeroSkillTierLines 技能分档描述,英雄技能模式,
+ * 覆盖普攻/大招、触发技能、驻场光环词条与复活)
*/
import { _decorator, Node, Label, RichText, NodeEventType, Sprite, Color, input, Input, EventTouch, UITransform, Vec3 } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
@@ -34,9 +34,8 @@ import { oops } from "db://oops-framework/core/Oops";
import { CardTriggerType, CardConfig } from "../common/config/CardSet";
import { SkillSet, FieldSkillSet } from "../common/config/SkillSet";
import { SkillCardList } from "../common/config/SCardSet";
-import { EquipPoolList } from "../common/config/EquipSet";
import { FieldEntry } from "../common/config/heroSet";
-import { buildCardDescRich } from "../common/config/HeroSkillDesc";
+import { buildCardDescRich } from "../common/config/CardSkillText";
import { buildHeroSkillTierLines, HeroSkillTierLine } from "../common/HeroSkillText";
import { smc } from "../common/SingletonModuleComp";
@@ -47,7 +46,7 @@ export interface SInfoParams {
/** 卡牌/技能 UUID */
uuid: number;
/** 当前等级 */
- card_lv: number;
+ skill_lv: number;
/** 图标ID(可选,优先级最高) */
card_icon?: string;
/** 技能名称(可选) */
@@ -68,8 +67,6 @@ interface LvUpgradeInfo {
lvText: string;
/** 激活状态文本("已激活" 或 "2级激活") */
statusText: string;
- /** 能力提升富文本描述 */
- desc: string;
}
@ccclass('SinfoComp')
@@ -93,6 +90,10 @@ export class SinfoComp extends CCComp {
@property({ type: RichText, displayName: "主功能说明" })
private main_info: RichText = null;
+ /** 1级激活状态标签 */
+ @property({ type: Label, displayName: "1级激活状态" })
+ private main_status: Label = null;
+
/** 2级激活状态标签 */
@property({ type: Label, displayName: "2级激活状态" })
private lv2_status: Label = null;
@@ -198,16 +199,16 @@ export class SinfoComp extends CCComp {
* - main_info:技能 slv1 完整描述
* - lv2_desc:slv2 相对 slv1 的提升;lv3_desc:slv3 及以后档(合并展示)
* - lv2/lv3_status:按英雄局外等级(smc.getHeroOuterLv)对比档位解锁等级判定激活
- * @returns true 已渲染(拦截通用卡牌逻辑);false 技能无分档数据(光环/复活词条),回退通用逻辑
+ * @returns true 已渲染(拦截通用卡牌逻辑);false 技能无分档数据,回退通用卡牌逻辑
*/
private renderHeroSkill(p: SInfoParams): boolean {
const tiers = buildHeroSkillTierLines(p.hero_uuid!, p.uuid);
if (tiers.length === 0) return false;
- // 图标 / 名称 / 当前技能等级与通用逻辑一致(card_lv 传的就是当前生效 s_lv)
+ // 图标 / 名称 / 当前技能等级与通用逻辑一致(skill_lv 传的就是当前生效 s_lv)
this.renderIcon(p);
this.setLabel(this.name_label, this.resolveName(p));
- this.setLabel(this.lv_label, `Lv.${p.card_lv}`);
+ this.setLabel(this.lv_label, `Lv.${p.skill_lv}`);
// 技能 1 级:完整能力描述
if (this.main_info && this.main_info.isValid) {
@@ -217,6 +218,9 @@ export class SinfoComp extends CCComp {
// 英雄当前局外等级:激活判定基准
const heroLv = Math.max(1, smc.getHeroOuterLv(p.hero_uuid!));
+ // 技能 1 级:激活状态与 2/3 级面板同一表示方式(描述已在 main_info 展示,此处仅渲染状态)
+ this.renderLvPanelFromTier(tiers[0], heroLv, this.main_status, null);
+
// 技能 2 级 / 3 级(3 级以后档位合并进 3 级栏,多行展示)
const lv2 = tiers.find(t => t.sLv === 2);
const lv3Plus = tiers.filter(t => t.sLv >= 3);
@@ -259,14 +263,16 @@ export class SinfoComp extends CCComp {
this.setLabel(this.name_label, name);
// ---- 当前等级 ----
- this.setLabel(this.lv_label, `Lv.${p.card_lv}`);
+ this.setLabel(this.lv_label, `Lv.${p.skill_lv}`);
// ---- 主功能说明 ----
this.renderMainInfo(p);
- // ---- 2 级 / 3 级 激活信息 ----
+ // ---- 1 级 / 2 级 / 3 级 激活信息 ----
+ const lv1Info = this.buildLvUpgradeInfo(p, 1);
const lv2Info = this.buildLvUpgradeInfo(p, 2);
const lv3Info = this.buildLvUpgradeInfo(p, 3);
+ this.renderLvPanel(lv1Info, this.main_status, null);
this.renderLvPanel(lv2Info, this.lv2_status, this.lv2_desc);
this.renderLvPanel(lv3Info, this.lv3_status, this.lv3_desc);
}
@@ -287,9 +293,6 @@ export class SinfoComp extends CCComp {
/** 解析图标ID */
private resolveIconId(p: SInfoParams): string | undefined {
if (p.card_icon) return p.card_icon;
- // 装备卡:从 EquipPoolList 查 icon
- const equip = EquipPoolList.find(c => c.uuid === p.uuid);
- if (equip?.icon) return equip.icon;
// 技能卡:从 SkillCardList 查 icon
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard?.icon) return skillCard.icon;
@@ -300,14 +303,14 @@ export class SinfoComp extends CCComp {
}
// 技能底座:从 SkillSet 取
const skillUuid = skillCard?.skill ?? p.uuid;
- return SkillSet[skillUuid]?.icon;
+ if (SkillSet[skillUuid]?.icon) return SkillSet[skillUuid]!.icon;
+ // 驻场光环词条:uuid 为 FieldSkillSet 词条(英雄图鉴光环弹窗)
+ return FieldSkillSet[p.uuid]?.icon;
}
/** 解析技能名称 */
private resolveName(p: SInfoParams): string {
if (p.skill_name) return p.skill_name;
- const equip = EquipPoolList.find(c => c.uuid === p.uuid);
- if (equip?.name) return equip.name;
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard?.name) return skillCard.name;
if (p.trigger_type === CardTriggerType.Field && p.field?.length) {
@@ -315,7 +318,9 @@ export class SinfoComp extends CCComp {
return FieldSkillSet[fieldUuid]?.name ?? "";
}
const skillUuid = skillCard?.skill ?? p.uuid;
- return SkillSet[skillUuid]?.name ?? "";
+ if (SkillSet[skillUuid]?.name) return SkillSet[skillUuid]!.name;
+ // 驻场光环词条:名称取 FieldSkillSet(英雄图鉴光环弹窗)
+ return FieldSkillSet[p.uuid]?.name ?? "";
}
/** 渲染主功能说明 */
@@ -334,10 +339,7 @@ export class SinfoComp extends CCComp {
/** 构建 CardConfig 供描述生成器使用 */
private buildCardConfig(p: SInfoParams): CardConfig | null {
- // 优先从装备卡池查
- const equip = EquipPoolList.find(c => c.uuid === p.uuid);
- if (equip) return equip;
- // 其次从技能卡池查
+ // 优先从技能卡池查
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard) return skillCard;
// 手动构建
@@ -347,7 +349,6 @@ export class SinfoComp extends CCComp {
cost: 0,
weight: 0,
kind: 2, // CKind.Skill
- card_lv: p.card_lv,
icon: p.card_icon,
name: p.skill_name,
field: p.field,
@@ -357,8 +358,6 @@ export class SinfoComp extends CCComp {
/** 解析主功能说明纯文本(兜底) */
private resolveInfoText(p: SInfoParams): string {
- const equip = EquipPoolList.find(c => c.uuid === p.uuid);
- if (equip?.info) return equip.info;
const skillCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
if (skillCard?.info) return skillCard.info;
const skillUuid = skillCard?.skill ?? p.uuid;
@@ -367,40 +366,10 @@ export class SinfoComp extends CCComp {
/** 构建指定等级的提升信息 */
private buildLvUpgradeInfo(p: SInfoParams, targetLv: number): LvUpgradeInfo {
- const unlocked = p.card_lv >= targetLv;
+ const unlocked = p.skill_lv >= targetLv;
const lvText = `Lv.${targetLv}`;
const statusText = unlocked ? "已激活" : `${targetLv}级激活`;
-
- // 能力提升描述:从对应等级的装备卡配置中提取
- const desc = this.buildLvDesc(p, targetLv);
- return { unlocked, lvText, statusText, desc };
- }
-
- /** 构建指定等级的提升描述 */
- private buildLvDesc(p: SInfoParams, targetLv: number): string {
- // 从装备卡池中查找同名更高档位配置
- const currentEquip = EquipPoolList.find(c => c.uuid === p.uuid);
- if (currentEquip) {
- const higher = EquipPoolList.find(c =>
- c.name === currentEquip.name && c.card_lv === targetLv
- );
- if (higher) {
- return buildCardDescRich(higher);
- }
- return "";
- }
- // 技能卡:从技能卡池查找同技能更高档位
- const currentCard = SkillCardList.find(c => c.uuid === p.uuid || c.skill === p.uuid);
- if (currentCard) {
- const skillUuid = currentCard.skill ?? currentCard.uuid;
- const higher = SkillCardList.find(c =>
- (c.skill ?? c.uuid) === skillUuid && c.card_lv === targetLv
- );
- if (higher) {
- return buildCardDescRich(higher);
- }
- }
- return "";
+ return { unlocked, lvText, statusText };
}
/** 渲染单个等级面板 */
@@ -413,7 +382,7 @@ export class SinfoComp extends CCComp {
: new Color(150, 150, 150, 255);
}
if (descRich && descRich.isValid) {
- descRich.string = info.desc || (info.unlocked ? "" : "未解锁");
+ descRich.string = info.unlocked ? "" : "未解锁";
}
}