From 10a6d1883667365395891de6672c021b9bcf6eb6 Mon Sep 17 00:00:00 2001 From: panFD Date: Fri, 28 Aug 2026 08:36:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(skill-text):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=8A=80=E8=83=BD=E6=96=87=E6=A1=88=E7=B3=BB=E7=BB=9F=EF=BC=8C?= =?UTF-8?q?=E6=8B=86=E5=88=86=E8=8B=B1=E9=9B=84/=E5=8D=A1=E7=89=8C?= =?UTF-8?q?=E8=AF=AD=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将原HeroSkillDesc拆分为CardSkillText(卡牌/道具通用)和HeroSkillText(英雄专属) 2. 移除废弃的HeroSkillDesc文件与相关引用 3. 统一技能文案生成逻辑,修复数值漂移问题 4. 调整英雄配置中技能解锁等级与分档规则 5. 优化技能弹窗UI与参数传递逻辑 --- assets/resources/gui/element/SInfoBox.prefab | 1490 ++++++++++++----- assets/resources/gui/element/cHero.prefab | 320 ++-- assets/script/game/common/HeroSkillText.ts | 118 +- .../game/common/config/CardSkillText.ts | 341 ++++ ...killDesc.ts.meta => CardSkillText.ts.meta} | 2 +- .../game/common/config/HeroSkillDesc.ts | 943 ----------- .../script/game/common/config/SkillDescSet.ts | 2 +- assets/script/game/common/config/heroSet.ts | 77 +- assets/script/game/map/EquipListComp.ts | 2 +- assets/script/game/map/HeroBoxComp.ts | 30 +- assets/script/game/map/HeroMoreCom.ts | 8 +- assets/script/game/map/HerosListComp.ts | 9 +- assets/script/game/map/IBoxComp.ts | 6 +- assets/script/game/map/ItemListComp.ts | 2 +- assets/script/game/map/SCardComp.ts | 2 +- assets/script/game/map/SSkillBoxComp.ts | 33 +- assets/script/game/map/SinfoComp.ts | 95 +- 17 files changed, 1698 insertions(+), 1782 deletions(-) create mode 100644 assets/script/game/common/config/CardSkillText.ts rename assets/script/game/common/config/{HeroSkillDesc.ts.meta => CardSkillText.ts.meta} (70%) delete mode 100644 assets/script/game/common/config/HeroSkillDesc.ts 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 ? "" : "未解锁"; } }