Compare commits
10 Commits
577a62193c
...
new2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4be5252cc4 | ||
|
|
04cea0fbb2 | ||
|
|
0db3404481 | ||
|
|
8c7748436b | ||
|
|
311b8ebb54 | ||
|
|
5d398d0a29 | ||
|
|
9c9cc99e64 | ||
|
|
1bb275607a | ||
|
|
95bf2a4e17 | ||
|
|
3f2989ab55 |
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,1095},{616,182}}</string>
|
||||
<string>{{675,1669},{616,182}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -51,7 +51,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{675,1775},{672,92}}</string>
|
||||
<string>{{675,1853},{672,92}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -68,9 +68,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{675,1869},{672,76}}</string>
|
||||
<string>{{1931,675},{672,76}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>0l5_mountains.png</key>
|
||||
<dict>
|
||||
@@ -102,7 +102,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1,1589},{672,162}}</string>
|
||||
<string>{{675,1193},{672,162}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -119,9 +119,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{675,1507},{672,138}}</string>
|
||||
<string>{{1663,675},{672,138}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>0l8_ground.png</key>
|
||||
<dict>
|
||||
@@ -136,9 +136,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,381},{672,216}}</string>
|
||||
<string>{{1729,1},{672,216}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>1l1_wall.png</key>
|
||||
<dict>
|
||||
@@ -221,9 +221,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,1279},{662,90}}</string>
|
||||
<string>{{1349,1349},{662,90}}</string>
|
||||
<key>textureRotated</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>1l6_ground.png</key>
|
||||
<dict>
|
||||
@@ -238,9 +238,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1441,1279},{672,76}}</string>
|
||||
<string>{{1293,1699},{672,76}}</string>
|
||||
<key>textureRotated</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>3l1_sky.png</key>
|
||||
<dict>
|
||||
@@ -272,9 +272,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{675,1353},{672,152}}</string>
|
||||
<string>{{1509,675},{672,152}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>3l3_pyramid.png</key>
|
||||
<dict>
|
||||
@@ -289,9 +289,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1621,1279},{528,154}}</string>
|
||||
<string>{{1349,1543},{528,154}}</string>
|
||||
<key>textureRotated</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>3l4_bg-ground01.png</key>
|
||||
<dict>
|
||||
@@ -306,7 +306,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1,1753},{672,156}}</string>
|
||||
<string>{{675,1357},{672,156}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -323,7 +323,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,599},{672,180}}</string>
|
||||
<string>{{1,1589},{672,180}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -340,9 +340,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{675,1647},{672,126}}</string>
|
||||
<string>{{1803,675},{672,126}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>3l7_ground.png</key>
|
||||
<dict>
|
||||
@@ -357,7 +357,24 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,941},{672,152}}</string>
|
||||
<string>{{675,1515},{672,152}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Icon_Battle.png</key>
|
||||
<dict>
|
||||
<key>aliases</key>
|
||||
<array/>
|
||||
<key>anchor</key>
|
||||
<string>{0.5,0.5}</string>
|
||||
<key>spriteOffset</key>
|
||||
<string>{0,0}</string>
|
||||
<key>spriteSize</key>
|
||||
<string>{64,57}</string>
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{64,57}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,1894},{64,57}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -374,9 +391,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{68,51}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1777,1626},{68,51}}</string>
|
||||
<string>{{1879,1629},{68,51}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>Stone_A_04.png</key>
|
||||
<dict>
|
||||
@@ -391,9 +408,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{60,51}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1850,1562},{60,51}}</string>
|
||||
<string>{{1293,1777},{60,51}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>Stone_A_07.png</key>
|
||||
<dict>
|
||||
@@ -408,9 +425,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{107,51}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1621,1809},{107,51}}</string>
|
||||
<string>{{1947,1},{107,51}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>Stone_B_01.png</key>
|
||||
<dict>
|
||||
@@ -425,7 +442,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{68,70}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1519,1865},{68,70}}</string>
|
||||
<string>{{1935,1441},{68,70}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -442,7 +459,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{66,45}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1777,1679},{66,45}}</string>
|
||||
<string>{{1415,1894},{66,45}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -459,7 +476,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{35,22}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1730,1809},{35,22}}</string>
|
||||
<string>{{1293,1669},{35,22}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -476,9 +493,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{71,62}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1777,1562},{71,62}}</string>
|
||||
<string>{{1899,1777},{71,62}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>Stone_B_05.png</key>
|
||||
<dict>
|
||||
@@ -493,7 +510,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{84,43}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1967,1095},{84,43}}</string>
|
||||
<string>{{1879,1543},{84,43}}</string>
|
||||
<key>textureRotated</key>
|
||||
<true/>
|
||||
</dict>
|
||||
@@ -510,7 +527,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{31,15}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1519,1937},{31,15}}</string>
|
||||
<string>{{1947,110},{31,15}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -527,7 +544,41 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{44,16}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1,1911},{44,16}}</string>
|
||||
<string>{{1,1931},{44,16}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Title_01_NoDeco_Orange.Png</key>
|
||||
<dict>
|
||||
<key>aliases</key>
|
||||
<array/>
|
||||
<key>anchor</key>
|
||||
<string>{0.5,0.5}</string>
|
||||
<key>spriteOffset</key>
|
||||
<string>{0,0}</string>
|
||||
<key>spriteSize</key>
|
||||
<string>{273,115}</string>
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{273,115}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,1777},{273,115}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Title_01_NoDeco_Tangerine.Png</key>
|
||||
<dict>
|
||||
<key>aliases</key>
|
||||
<array/>
|
||||
<key>anchor</key>
|
||||
<string>{0.5,0.5}</string>
|
||||
<key>spriteOffset</key>
|
||||
<string>{0,0}</string>
|
||||
<key>spriteSize</key>
|
||||
<string>{273,115}</string>
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{273,115}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1624,1777},{273,115}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -546,7 +597,7 @@
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,1},{672,378}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>l2_mountains.png</key>
|
||||
<dict>
|
||||
@@ -578,9 +629,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1519,1279},{584,100}}</string>
|
||||
<string>{{1349,1441},{584,100}}</string>
|
||||
<key>textureRotated</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>l4_bg-ground01.png</key>
|
||||
<dict>
|
||||
@@ -595,9 +646,9 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{675,1193},{672,158}}</string>
|
||||
<string>{{1349,675},{672,158}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>l5_bg-ground02.png</key>
|
||||
<dict>
|
||||
@@ -612,7 +663,7 @@
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{672,378}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1349,781},{672,158}}</string>
|
||||
<string>{{1,1771},{672,158}}</string>
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
@@ -633,23 +684,6 @@
|
||||
<key>textureRotated</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>logo2.png</key>
|
||||
<dict>
|
||||
<key>aliases</key>
|
||||
<array/>
|
||||
<key>anchor</key>
|
||||
<string>{0.5,0.5}</string>
|
||||
<key>spriteOffset</key>
|
||||
<string>{1,2}</string>
|
||||
<key>spriteSize</key>
|
||||
<string>{281,231}</string>
|
||||
<key>spriteSourceSize</key>
|
||||
<string>{307,307}</string>
|
||||
<key>textureRect</key>
|
||||
<string>{{1777,1279},{281,231}}</string>
|
||||
<key>textureRotated</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>metadata</key>
|
||||
<dict>
|
||||
@@ -662,9 +696,9 @@
|
||||
<key>realTextureFileName</key>
|
||||
<string>mps.png</string>
|
||||
<key>size</key>
|
||||
<string>{2022,1953}</string>
|
||||
<string>{2012,1952}</string>
|
||||
<key>smartupdate</key>
|
||||
<string>$TexturePacker:SmartUpdate:c995e79e2727f3d5b64d2e4345b903e7:ff13e6153e38a1ee043318ccbb0a835f:4c0e3901f0d5fcb2cf0cd4dcba1c7f8b$</string>
|
||||
<string>$TexturePacker:SmartUpdate:afc599cf42d3db8475edd4bf42d848d7:62891583945cafa92fc112c0ecd848ee:4c0e3901f0d5fcb2cf0cd4dcba1c7f8b$</string>
|
||||
<key>textureFileName</key>
|
||||
<string>mps.png</string>
|
||||
</dict>
|
||||
|
||||
@@ -7,6 +7,52 @@
|
||||
".json"
|
||||
],
|
||||
"subMetas": {
|
||||
"57674": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "81af97ed-7940-45e6-91a1-21bd0df1a343@57674",
|
||||
"displayName": "",
|
||||
"id": "57674",
|
||||
"name": "Icon_Battle",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1349,
|
||||
"trimY": 1894,
|
||||
"width": 64,
|
||||
"height": 57,
|
||||
"rawWidth": 64,
|
||||
"rawHeight": 57,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 0,
|
||||
"borderRight": 0,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [],
|
||||
"indexes": [],
|
||||
"uv": [],
|
||||
"nuv": [],
|
||||
"minPos": [],
|
||||
"maxPos": []
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "ca2b26a5-8d54-49ec-bdaf-e9f777afc9cb@6c48a",
|
||||
"atlasUuid": "81af97ed-7940-45e6-91a1-21bd0df1a343",
|
||||
"trimType": "auto"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"58094": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "81af97ed-7940-45e6-91a1-21bd0df1a343@58094",
|
||||
@@ -53,6 +99,98 @@
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"63222": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "81af97ed-7940-45e6-91a1-21bd0df1a343@63222",
|
||||
"displayName": "",
|
||||
"id": "63222",
|
||||
"name": "Title_01_NoDeco_Tangerine",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1624,
|
||||
"trimY": 1777,
|
||||
"width": 273,
|
||||
"height": 115,
|
||||
"rawWidth": 273,
|
||||
"rawHeight": 115,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 0,
|
||||
"borderRight": 0,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [],
|
||||
"indexes": [],
|
||||
"uv": [],
|
||||
"nuv": [],
|
||||
"minPos": [],
|
||||
"maxPos": []
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "ca2b26a5-8d54-49ec-bdaf-e9f777afc9cb@6c48a",
|
||||
"atlasUuid": "81af97ed-7940-45e6-91a1-21bd0df1a343",
|
||||
"trimType": "auto"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"86850": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "81af97ed-7940-45e6-91a1-21bd0df1a343@86850",
|
||||
"displayName": "",
|
||||
"id": "86850",
|
||||
"name": "Title_01_NoDeco_Orange",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1349,
|
||||
"trimY": 1777,
|
||||
"width": 273,
|
||||
"height": 115,
|
||||
"rawWidth": 273,
|
||||
"rawHeight": 115,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 135,
|
||||
"borderRight": 138,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [],
|
||||
"indexes": [],
|
||||
"uv": [],
|
||||
"nuv": [],
|
||||
"minPos": [],
|
||||
"maxPos": []
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "ca2b26a5-8d54-49ec-bdaf-e9f777afc9cb@6c48a",
|
||||
"atlasUuid": "81af97ed-7940-45e6-91a1-21bd0df1a343",
|
||||
"trimType": "auto"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"4f746": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "81af97ed-7940-45e6-91a1-21bd0df1a343@4f746",
|
||||
@@ -110,8 +248,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": -4,
|
||||
"offsetY": 82,
|
||||
"trimX": 1349,
|
||||
"trimY": 1095,
|
||||
"trimX": 675,
|
||||
"trimY": 1669,
|
||||
"width": 616,
|
||||
"height": 182,
|
||||
"rawWidth": 672,
|
||||
@@ -157,7 +295,7 @@
|
||||
"offsetX": 0,
|
||||
"offsetY": -3,
|
||||
"trimX": 675,
|
||||
"trimY": 1775,
|
||||
"trimY": 1853,
|
||||
"width": 672,
|
||||
"height": 92,
|
||||
"rawWidth": 672,
|
||||
@@ -199,11 +337,11 @@
|
||||
"name": "0l4_clouds02",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 122,
|
||||
"trimX": 675,
|
||||
"trimY": 1869,
|
||||
"trimX": 1931,
|
||||
"trimY": 675,
|
||||
"width": 672,
|
||||
"height": 76,
|
||||
"rawWidth": 672,
|
||||
@@ -294,8 +432,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": -108,
|
||||
"trimX": 1,
|
||||
"trimY": 1589,
|
||||
"trimX": 675,
|
||||
"trimY": 1193,
|
||||
"width": 672,
|
||||
"height": 162,
|
||||
"rawWidth": 672,
|
||||
@@ -337,11 +475,11 @@
|
||||
"name": "0l7_ground02",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": -120,
|
||||
"trimX": 675,
|
||||
"trimY": 1507,
|
||||
"trimX": 1663,
|
||||
"trimY": 675,
|
||||
"width": 672,
|
||||
"height": 138,
|
||||
"rawWidth": 672,
|
||||
@@ -383,11 +521,11 @@
|
||||
"name": "0l8_ground",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": -81,
|
||||
"trimX": 1349,
|
||||
"trimY": 381,
|
||||
"trimX": 1729,
|
||||
"trimY": 1,
|
||||
"width": 672,
|
||||
"height": 216,
|
||||
"rawWidth": 672,
|
||||
@@ -613,11 +751,11 @@
|
||||
"name": "1l5_crystals",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": true,
|
||||
"rotated": false,
|
||||
"offsetX": -2,
|
||||
"offsetY": -85,
|
||||
"trimX": 1349,
|
||||
"trimY": 1279,
|
||||
"trimY": 1349,
|
||||
"width": 662,
|
||||
"height": 90,
|
||||
"rawWidth": 672,
|
||||
@@ -659,11 +797,11 @@
|
||||
"name": "1l6_ground",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": true,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": -151,
|
||||
"trimX": 1441,
|
||||
"trimY": 1279,
|
||||
"trimX": 1293,
|
||||
"trimY": 1699,
|
||||
"width": 672,
|
||||
"height": 76,
|
||||
"rawWidth": 672,
|
||||
@@ -705,11 +843,11 @@
|
||||
"name": "3l2_clouds",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": -48,
|
||||
"trimX": 675,
|
||||
"trimY": 1353,
|
||||
"trimX": 1509,
|
||||
"trimY": 675,
|
||||
"width": 672,
|
||||
"height": 152,
|
||||
"rawWidth": 672,
|
||||
@@ -751,11 +889,11 @@
|
||||
"name": "3l3_pyramid",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": true,
|
||||
"rotated": false,
|
||||
"offsetX": 2,
|
||||
"offsetY": -7,
|
||||
"trimX": 1621,
|
||||
"trimY": 1279,
|
||||
"trimX": 1349,
|
||||
"trimY": 1543,
|
||||
"width": 528,
|
||||
"height": 154,
|
||||
"rawWidth": 672,
|
||||
@@ -800,8 +938,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": -111,
|
||||
"trimX": 1,
|
||||
"trimY": 1753,
|
||||
"trimX": 675,
|
||||
"trimY": 1357,
|
||||
"width": 672,
|
||||
"height": 156,
|
||||
"rawWidth": 672,
|
||||
@@ -846,8 +984,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": -99,
|
||||
"trimX": 1349,
|
||||
"trimY": 599,
|
||||
"trimX": 1,
|
||||
"trimY": 1589,
|
||||
"width": 672,
|
||||
"height": 180,
|
||||
"rawWidth": 672,
|
||||
@@ -889,11 +1027,11 @@
|
||||
"name": "3l6_bg-ground03",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": -126,
|
||||
"trimX": 675,
|
||||
"trimY": 1647,
|
||||
"trimX": 1803,
|
||||
"trimY": 675,
|
||||
"width": 672,
|
||||
"height": 126,
|
||||
"rawWidth": 672,
|
||||
@@ -938,8 +1076,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": -113,
|
||||
"trimX": 1349,
|
||||
"trimY": 941,
|
||||
"trimX": 675,
|
||||
"trimY": 1515,
|
||||
"width": 672,
|
||||
"height": 152,
|
||||
"rawWidth": 672,
|
||||
@@ -981,11 +1119,11 @@
|
||||
"name": "Stone_A_01",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1777,
|
||||
"trimY": 1626,
|
||||
"trimX": 1879,
|
||||
"trimY": 1629,
|
||||
"width": 68,
|
||||
"height": 51,
|
||||
"rawWidth": 68,
|
||||
@@ -1027,11 +1165,11 @@
|
||||
"name": "Stone_A_04",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1850,
|
||||
"trimY": 1562,
|
||||
"trimX": 1293,
|
||||
"trimY": 1777,
|
||||
"width": 60,
|
||||
"height": 51,
|
||||
"rawWidth": 60,
|
||||
@@ -1073,11 +1211,11 @@
|
||||
"name": "Stone_A_07",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1621,
|
||||
"trimY": 1809,
|
||||
"trimX": 1947,
|
||||
"trimY": 1,
|
||||
"width": 107,
|
||||
"height": 51,
|
||||
"rawWidth": 107,
|
||||
@@ -1122,8 +1260,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1519,
|
||||
"trimY": 1865,
|
||||
"trimX": 1935,
|
||||
"trimY": 1441,
|
||||
"width": 68,
|
||||
"height": 70,
|
||||
"rawWidth": 68,
|
||||
@@ -1168,8 +1306,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1777,
|
||||
"trimY": 1679,
|
||||
"trimX": 1415,
|
||||
"trimY": 1894,
|
||||
"width": 66,
|
||||
"height": 45,
|
||||
"rawWidth": 66,
|
||||
@@ -1214,8 +1352,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1730,
|
||||
"trimY": 1809,
|
||||
"trimX": 1293,
|
||||
"trimY": 1669,
|
||||
"width": 35,
|
||||
"height": 22,
|
||||
"rawWidth": 35,
|
||||
@@ -1257,11 +1395,11 @@
|
||||
"name": "Stone_B_04",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1777,
|
||||
"trimY": 1562,
|
||||
"trimX": 1899,
|
||||
"trimY": 1777,
|
||||
"width": 71,
|
||||
"height": 62,
|
||||
"rawWidth": 71,
|
||||
@@ -1306,8 +1444,8 @@
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1967,
|
||||
"trimY": 1095,
|
||||
"trimX": 1879,
|
||||
"trimY": 1543,
|
||||
"width": 84,
|
||||
"height": 43,
|
||||
"rawWidth": 84,
|
||||
@@ -1352,8 +1490,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1519,
|
||||
"trimY": 1937,
|
||||
"trimX": 1947,
|
||||
"trimY": 110,
|
||||
"width": 31,
|
||||
"height": 15,
|
||||
"rawWidth": 31,
|
||||
@@ -1399,7 +1537,7 @@
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1,
|
||||
"trimY": 1911,
|
||||
"trimY": 1931,
|
||||
"width": 44,
|
||||
"height": 16,
|
||||
"rawWidth": 44,
|
||||
@@ -1441,7 +1579,7 @@
|
||||
"name": "l1_sky",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 1349,
|
||||
@@ -1533,11 +1671,11 @@
|
||||
"name": "l3_clouds",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": true,
|
||||
"rotated": false,
|
||||
"offsetX": -6,
|
||||
"offsetY": 89,
|
||||
"trimX": 1519,
|
||||
"trimY": 1279,
|
||||
"trimX": 1349,
|
||||
"trimY": 1441,
|
||||
"width": 584,
|
||||
"height": 100,
|
||||
"rawWidth": 672,
|
||||
@@ -1579,11 +1717,11 @@
|
||||
"name": "l4_bg-ground01",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"rotated": true,
|
||||
"offsetX": 0,
|
||||
"offsetY": -110,
|
||||
"trimX": 675,
|
||||
"trimY": 1193,
|
||||
"trimX": 1349,
|
||||
"trimY": 675,
|
||||
"width": 672,
|
||||
"height": 158,
|
||||
"rawWidth": 672,
|
||||
@@ -1628,8 +1766,8 @@
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": -110,
|
||||
"trimX": 1349,
|
||||
"trimY": 781,
|
||||
"trimX": 1,
|
||||
"trimY": 1771,
|
||||
"width": 672,
|
||||
"height": 158,
|
||||
"rawWidth": 672,
|
||||
@@ -1708,52 +1846,6 @@
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"14c74": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "81af97ed-7940-45e6-91a1-21bd0df1a343@14c74",
|
||||
"displayName": "",
|
||||
"id": "14c74",
|
||||
"name": "logo2",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": true,
|
||||
"offsetX": 1,
|
||||
"offsetY": 2,
|
||||
"trimX": 1777,
|
||||
"trimY": 1279,
|
||||
"width": 281,
|
||||
"height": 231,
|
||||
"rawWidth": 307,
|
||||
"rawHeight": 307,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 0,
|
||||
"borderRight": 0,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [],
|
||||
"indexes": [],
|
||||
"uv": [],
|
||||
"nuv": [],
|
||||
"minPos": [],
|
||||
"maxPos": []
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "ca2b26a5-8d54-49ec-bdaf-e9f777afc9cb@6c48a",
|
||||
"atlasUuid": "81af97ed-7940-45e6-91a1-21bd0df1a343",
|
||||
"trimType": "auto"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 396 KiB |
File diff suppressed because it is too large
Load Diff
@@ -82,31 +82,40 @@ export enum SummonTMode {
|
||||
=== 技能配置系统使用说明 ===
|
||||
|
||||
1. 基础属性:
|
||||
- uuid: 技能唯一ID
|
||||
- name: 技能名称
|
||||
- uuid: 技能唯一ID(6001~6013 单体普攻、6100~6204 群体大招、6301~6302 治疗护盾、
|
||||
6401~6416 永久强化、6501~6519 计时强化/特殊、7001~7024 驻场光环)
|
||||
- name: 技能名称(内部底座代号;英雄语境对外名由 heroSet 侧 name 字段覆写)
|
||||
- sp_name: 特效名称
|
||||
- AtkedName: 攻击类型
|
||||
- path: 图片资源路径
|
||||
- icon: 图标ID
|
||||
|
||||
2. 目标和效果:
|
||||
- TGroup: 目标群体 (敌方、友方等)
|
||||
- SType: 技能类型 (伤害、治疗、护盾等)
|
||||
- TGroup: 目标群体(Self 自身 / Ally 全体友方含自己 / Team 全体友方 / Enemy 敌方 / All 全部)
|
||||
- kind: 主效果类型(Damage 伤害 / Heal 治疗 / Shield 护盾 / Support 强化)
|
||||
|
||||
3. 执行参数:
|
||||
- act: 角色执行的动画
|
||||
- DTType: 伤害类型 (单体、范围)
|
||||
- EType: 结束条件
|
||||
- fname: 特效文件名
|
||||
- DTType: 伤害类型(single 单体 / range 范围)
|
||||
- EType: 结束条件(动画结束/时间结束/碰撞次数)
|
||||
- RType: 弹道类型(linear 直线 / bezier 贝塞尔 / fixed 固定起点 / fixedEnd 固定终点)
|
||||
- with: 暂时无用
|
||||
|
||||
4. 数值参数:
|
||||
- ap: 攻击力百分比
|
||||
- cd: 冷却时间
|
||||
- hit_count: 可命中次数
|
||||
- t_num: 目标数量
|
||||
- hitcd: 持续伤害的伤害间隔
|
||||
- speed: 移动速度
|
||||
- cost: 消耗值
|
||||
- ap: 伤害/治疗为攻击力百分比;护盾为生命最大值百分比;强化技能为修饰数值
|
||||
- cd: 冷却时间(普攻 cd 即攻击间隔)
|
||||
- hit_count: 可命中目标数(伤害技能=弹道穿透数;辅助技能=作用目标数)
|
||||
- hitcd: 持续伤害的伤害间隔(秒)
|
||||
- speed: 弹道移动速度
|
||||
- summon_count: 单次施法召唤的弹道/实体个数(>1 视为召唤类技能)
|
||||
|
||||
5. 增益/减益:
|
||||
- buff_type + ap: 永久强化(直接累加到属性,无时长)
|
||||
- timed_buff_id + buff_value/buff_duration/buff_chance/buff_target: 计时强化(走 BuffList)
|
||||
- timed_debuff_id + debuff_value/debuff_duration/debuff_chance: 命中敌方后挂的计时减益
|
||||
- proc_skill: 施法后概率触发额外技能(技能级触发器,无视 CD)
|
||||
|
||||
6. 文案约定:
|
||||
- info 仅作语义描述,禁止写死数值;数值由 heroSet/卡片 overrides 注入,
|
||||
UI 文案由 SkillDescSet / HeroSkillText 按合并后实时配置动态生成
|
||||
*/
|
||||
export enum DType {
|
||||
ATK = 0, // 物理
|
||||
@@ -150,8 +159,8 @@ export interface SkillConfig {
|
||||
TGroup: TGroup, // 目标群体(敌方/友方/自身等)
|
||||
act: string, // 角色执行的动画
|
||||
DTType: DTType, // 伤害类型(单体/范围)
|
||||
ap: number, // 伤害/治疗技能为攻击百分比,护盾技能为免疫次数
|
||||
hit_count: number, // 可命中次数
|
||||
ap: number, // 伤害/治疗为攻击力百分比;护盾为生命最大值百分比;永久强化为修饰数值
|
||||
hit_count: number, // 可命中目标数(伤害技能=弹道穿透数;辅助技能=作用目标数,>1 生效)
|
||||
hitcd: number, // 持续伤害的伤害间隔(秒)
|
||||
speed: number, // 移动速度
|
||||
with: number, // 宽度(暂时无用)
|
||||
@@ -331,17 +340,17 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6013: {
|
||||
uuid: 6013, name: "闪电", sp_name: "m3", icon: "109-Red-Potion", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.fixedEnd, EType: EType.collision, info: "闪电",
|
||||
RType: RType.fixedEnd, EType: EType.collision, info: "单体雷击(固定终点落雷),可附加麻痹",
|
||||
},
|
||||
6014: {
|
||||
uuid: 6014, name: "小火球", sp_name: "m4", icon: "109-Red-Potion", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.fixedEnd, EType: EType.collision, info: "小火球",
|
||||
RType: RType.fixedEnd, EType: EType.collision, info: "小火球(固定终点),可附加灼烧/易伤",
|
||||
},
|
||||
6015: {
|
||||
uuid: 6015, name: "小飓风", sp_name: "m5", icon: "109-Red-Potion", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.fixedEnd, EType: EType.collision, info: "小飓风抛",
|
||||
RType: RType.fixedEnd, EType: EType.collision, info: "小飓风(固定终点),直线穿透",
|
||||
},
|
||||
// ==================== 群体大招(按职业分段:6100-6149 坦克/战士、6150-6199 刺客/射手、6200-6249 法师/辅助) ====================
|
||||
// 坦克/战士群体攻击大招
|
||||
@@ -405,30 +414,30 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6204: {
|
||||
uuid: 6204, name: "雷暴", sp_name: "maxm7", icon: "o_warlock_hyzy_skill", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max1",
|
||||
DTType: DTType.single, ap: 100, hit_count: 99, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "一个大毒气弹",
|
||||
RType: RType.linear, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "雷暴多段打击(hit_count=99 直线穿透),可附加麻痹",
|
||||
},
|
||||
|
||||
//============================= ====== 辅助技能 回合技能 ====== ==========================
|
||||
6301: {
|
||||
uuid: 6301, name: "护盾", sp_name: "buff_wind", icon: "28-Electric-Wave", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "", act: "max1",
|
||||
DTType: DTType.single, kind: SkillKind.Shield, ap: 30, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support, shield_duration: 7,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "获得施法者最大生命30%的护盾, 持续7秒",
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "获得施法者最大生命一定比例的护盾,持续7秒(ap=百分比)",
|
||||
},
|
||||
6302: {
|
||||
uuid: 6302, name: "治疗", sp_name: "buff_wind", icon: "g_priest_zldy_skill", TGroup: TGroup.Team, readyAnm: "up_green", endAnm: "", act: "max1",
|
||||
DTType: DTType.single, kind: SkillKind.Heal, ap: 300, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "治疗伙伴/自己",
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "治疗友方目标,回复量=目标最大生命×ap%",
|
||||
},
|
||||
//==========================buff 技能=====================
|
||||
6401: {
|
||||
uuid: 6401, name: "攻击强化", sp_name: "buff_wind", icon: "36-Guardian-Stance", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 5, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.ap, info: "全体友方攻击力提升5%, 持续1次",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.ap, info: "全体友方攻击力永久提升(ap=百分比,单次施加)",
|
||||
},
|
||||
6402: {
|
||||
uuid: 6402, name: "生命强化", sp_name: "buff_wind", icon: "29-Endure", TGroup: TGroup.Team, readyAnm: "up_hp", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 20, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.hp_max, info: "全体友方最大生命值提升20%, 持续1次",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.hp_max, info: "全体友方最大生命值永久提升(ap=百分比,单次施加)",
|
||||
},
|
||||
6403: {
|
||||
uuid: 6403, name: "暴击强化", sp_name: "buff_wind", icon: "5-Apply-Burn", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
@@ -481,7 +490,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6412: {
|
||||
uuid: 6412, name: "防御强化", sp_name: "buff_wind", icon: "46-Iron-Clad", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 50, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.defense, info: "全体友方防御提升50点, 持续1次",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.defense, info: "全体友方防御永久提升(ap=固定点数,单次施加)",
|
||||
},
|
||||
6413: {
|
||||
uuid: 6413, name: "反伤强化", sp_name: "buff_wind", icon: "75-Shield-Stun", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
@@ -539,7 +548,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6507: {
|
||||
uuid: 6507, name: "攻速爆发", sp_name: "buff_wind", icon: "147-Chronomancy", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.speed, timed_buff_id: 7015, info: "全体友方攻击速度提升40%, 持续5秒",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.speed, timed_buff_id: 7015, info: "全体友方攻击速度提升(buff_value=百分比,默认5秒)",
|
||||
},
|
||||
// 风怒机制已停用: 6508 风怒爆发技能已移除
|
||||
/*
|
||||
@@ -593,7 +602,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6517: {
|
||||
uuid: 6517, name: "生命爆发", sp_name: "buff_wind", icon: "29-Endure", TGroup: TGroup.Team, readyAnm: "up_green", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.hp_max, timed_buff_id: 7029, info: "全体友方最大生命值提升30%, 持续5秒",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.hp_max, timed_buff_id: 7029, info: "全体友方最大生命值提升(buff_value=百分比,默认5秒)",
|
||||
},
|
||||
6518: {
|
||||
uuid: 6518, name: "闪避爆发", sp_name: "buff_wind", icon: "98-Enchant-Wind", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
@@ -601,9 +610,9 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.dodge, timed_buff_id: 7030, info: "全体友方闪避概率提升20%, 持续5秒",
|
||||
},
|
||||
6519: {
|
||||
uuid: 6519, name: "命中强化", sp_name: "buff_wind", icon: "27-Hawkeye", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
uuid: 6519, name: "命中爆发", sp_name: "buff_wind", icon: "27-Hawkeye", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.accuracy, timed_buff_id: 7031, info: "全体友方命中概率提升20%, 持续5秒",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.accuracy, timed_buff_id: 7031, info: "全体友方命中概率提升(buff_value=百分比,默认5秒)",
|
||||
}
|
||||
};
|
||||
//***************驻场技能配置***************
|
||||
|
||||
@@ -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, BuffTarget } from "./SkillSet"
|
||||
import { BuffList, BuffConfig, ModOp } from "./BuffSet"
|
||||
@@ -577,11 +577,12 @@ export interface LvBonusAttrsEntry {
|
||||
*=============英雄配置列表================
|
||||
* 几何王国复刻版 — 依据 英雄设计指引.md(A/B 级复刻英雄)配置
|
||||
*
|
||||
* 规则:lv1 激活 skills[0]/skills[1],lv6 解锁 atking/atked/fstart/dead/field 触发技能(触发类统一 lv6 激活,无品质门槛),
|
||||
* 大招(skills[1])lv9/lv16 走 lv_entries 分档成长,lv12 按职业对单项基础属性 +50%(基数取 lv11 有效值),
|
||||
* revive[0] 与 skills[1] 互斥二选一(配置 revive 时按 lv1/lv9/lv16 走 lv_entries 3 级成长),
|
||||
* 品质(rarity)仅影响 lv1 初始基础属性(相邻差 5%,仅 lv1 初始 AP/HP/DEF),不影响 uuid 段位/光环/技能解锁/属性补偿
|
||||
* 编号:本批复刻英雄集中于 5001~5009,rarity 按配置(不遵循段位规则)
|
||||
* 规则:lv1 激活 skills[0]/skills[1],lv4 解锁 atking/atked/fstart/dead/field 触发技能(触发类统一 lv4 激活,无品质门槛),
|
||||
* 大招(skills[1])lv8/lv16 走 lv_entries 分档成长,lv12 按职业对单项基础属性 +50%(基数取"基础×品质+成长"快照),
|
||||
* revive[0] 与 skills[1] 互斥二选一(配置 revive 时按 lv1/lv8/lv16 走 lv_entries 3 级成长),
|
||||
* 品质(rarity)当前全部英雄统一为 1,不再区分品质;getHeroQualityCoef 保留但恒为 1.00,
|
||||
* 若未来重启品质体系,仅影响 lv1 初始基础属性(相邻差 5%),不影响 uuid 段位/光环/技能解锁/属性补偿
|
||||
* 编号:本批复刻英雄集中于 5001~5018,rarity 统一 1
|
||||
*/
|
||||
|
||||
// @hero-setup:region-begin
|
||||
|
||||
@@ -247,11 +247,13 @@ export class HeroInfoComp extends CCComp {
|
||||
* @param delta 1 下一个,-1 上一个
|
||||
*/
|
||||
private switchHero(delta: 1 | -1) {
|
||||
// 与图鉴页面排序同源:仅按品质降序 → uuid 升序
|
||||
// 与图鉴页面排序同源:按职业(HRole)升序 → uuid 升序
|
||||
const sorted = [...HeroList].sort((a, b) => {
|
||||
const ra = HeroInfo[a]?.rarity ?? 1;
|
||||
const rb = HeroInfo[b]?.rarity ?? 1;
|
||||
if (ra !== rb) return rb - ra;
|
||||
const heroA = HeroInfo[a];
|
||||
const heroB = HeroInfo[b];
|
||||
const roleA = heroA?.role ?? 0;
|
||||
const roleB = heroB?.role ?? 0;
|
||||
if (roleA !== roleB) return roleA - roleB;
|
||||
return a - b;
|
||||
});
|
||||
if (sorted.length === 0) return;
|
||||
|
||||
@@ -113,11 +113,13 @@ export class HerosListComp extends CCComp {
|
||||
|
||||
this.cards_node.removeAllChildren()
|
||||
|
||||
// 图鉴排序:仅按品质降序 → uuid 升序(与养成进度解耦,保持展示稳定)
|
||||
// 图鉴排序:按职业(HRole)升序 → uuid 升序(与养成进度解耦,保持展示稳定)
|
||||
const sorted = [...HeroList].sort((a, b) => {
|
||||
const ra = HeroInfo[a]?.rarity ?? 1;
|
||||
const rb = HeroInfo[b]?.rarity ?? 1;
|
||||
if (ra !== rb) return rb - ra;
|
||||
const heroA = HeroInfo[a];
|
||||
const heroB = HeroInfo[b];
|
||||
const roleA = heroA?.role ?? 0;
|
||||
const roleB = heroB?.role ?? 0;
|
||||
if (roleA !== roleB) return roleA - roleB;
|
||||
return a - b;
|
||||
})
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* - 第 5 节胜利 → open_Victory(final) 弹结算,确认退出并存档进下一关。
|
||||
* - cleanComponents() / clearBattlePools() 仅在整关开始/结束时执行,节间不调用。
|
||||
*/
|
||||
import { _decorator, Vec3, Animation, instantiate, Prefab, Node, NodeEventType, ProgressBar, Label, CCInteger, tween, v3, Tween, Widget, UIOpacity } from "cc";
|
||||
import { _decorator, Node, NodeEventType, Label, tween, v3, Tween, Widget, Color, Sprite, ProgressBar } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -31,7 +31,6 @@ import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { SkillView } from "../skill/SkillView";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { Monster } from "../hero/Mon";
|
||||
import { Skill } from "../skill/Skill";
|
||||
@@ -80,8 +79,6 @@ type SettleType = "defeat" | "final";
|
||||
export class MissionComp extends CCComp {
|
||||
@property({ tooltip: "是否启用调试日志" })
|
||||
private debugMode: boolean = true;
|
||||
@property({ tooltip: "是否显示战斗内存观测面板" })
|
||||
private showMemoryPanel: boolean = false;
|
||||
|
||||
|
||||
// ======================== 配置参数 ========================
|
||||
@@ -97,6 +94,21 @@ export class MissionComp extends CCComp {
|
||||
@property(Node)
|
||||
tooltip: Node = null!
|
||||
|
||||
/** 回合指示节点(5 个,索引 0~4 对应回合 1~5),按回合进度染色 */
|
||||
@property({ type: [Node], tooltip: "回合指示节点(按回合 1~5 顺序绑定)" })
|
||||
round_nodes: Node[] = [];
|
||||
|
||||
/** 回合进度条:第 1 回合 0%,每推进一回合 +25%(第 5 回合满格) */
|
||||
@property({ type: ProgressBar, tooltip: "回合进度条(第 1 回合 0%,每回合 +25%)" })
|
||||
round_progress: ProgressBar = null!;
|
||||
|
||||
/** 回合指示:未激活(白色) */
|
||||
private static readonly ROUND_INACTIVE = new Color(255, 255, 255, 255);
|
||||
/** 回合指示:已激活(橙黄 #FFA300) */
|
||||
private static readonly ROUND_ACTIVE = new Color(255, 163, 0, 255);
|
||||
/** 回合指示:Boss 当前回合(红色) */
|
||||
private static readonly ROUND_BOSS = new Color(230, 60, 60, 255);
|
||||
|
||||
/** 阶段名称映射表(用于 UI 显示) */
|
||||
private static readonly PhaseNameMap: Record<MissionPhase, string> = {
|
||||
[MissionPhase.None]: "未开始",
|
||||
@@ -133,26 +145,6 @@ export class MissionComp extends CCComp {
|
||||
private lastTimeStr: string = "";
|
||||
/** 上一次显示的秒数(避免重复计算) */
|
||||
private lastTimeSecond: number = -1;
|
||||
/** 性能监控面板 Label 引用 */
|
||||
private memoryLabel: Label | null = null;
|
||||
/** 性能监控刷新计时器 */
|
||||
private memoryRefreshTimer: number = 0;
|
||||
/** 上一次性能文本(避免重复渲染) */
|
||||
private lastMemoryText: string = "";
|
||||
/** 帧间隔累加(用于计算平均 FPS) */
|
||||
private perfDtAcc: number = 0;
|
||||
/** 帧数计数 */
|
||||
private perfFrameCount: number = 0;
|
||||
/** 初始堆内存基准值(MB) */
|
||||
private heapBaseMB: number = -1;
|
||||
/** 堆内存峰值(MB) */
|
||||
private heapPeakMB: number = 0;
|
||||
/** 堆内存增长趋势(MB/分钟) */
|
||||
private heapTrendPerMinMB: number = 0;
|
||||
/** 趋势计算计时器 */
|
||||
private heapTrendTimer: number = 0;
|
||||
/** 趋势计算基准(MB) */
|
||||
private heapTrendBaseMB: number = -1;
|
||||
/** 怪物数量同步计时器(降低同步频率) */
|
||||
private monsterCountSyncTimer: number = 0;
|
||||
/** 当前回合数(= 当前小节号,驱动 HUD 回合/进度显示) */
|
||||
@@ -161,8 +153,6 @@ export class MissionComp extends CCComp {
|
||||
private isBossWave: boolean = false;
|
||||
/** 超时回合已在 onBattleTimeout 预扣留存分,BattleEnd 需跳过重复累加 */
|
||||
private skipRemainScoreOnBattleEnd: boolean = false;
|
||||
/** 上节清场时间快照(含清场加速还原,供评分统计) */
|
||||
private lastWaveClearTime: number = 0;
|
||||
/** 当前任务阶段 */
|
||||
public currentPhase: MissionPhase = MissionPhase.None;
|
||||
/** 是否处于回合间倒计时状态 */
|
||||
@@ -200,7 +190,6 @@ export class MissionComp extends CCComp {
|
||||
this.heroViewMatcher = ecs.allOf(HeroViewComp);
|
||||
this.skillViewMatcher = ecs.allOf(SkillView);
|
||||
this.heroAttrsMatcher = ecs.allOf(HeroAttrsComp);
|
||||
this.showMemoryPanel = false
|
||||
// 注册生命周期事件
|
||||
this.on(GameEvent.MissionEnd, this.mission_end, this)
|
||||
this.on(GameEvent.NewWave, this.onNewWave, this)
|
||||
@@ -210,10 +199,9 @@ export class MissionComp extends CCComp {
|
||||
this.start_btn?.on(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this)
|
||||
// 开局免费三选一选完后自动进入战斗
|
||||
oops.message.on("OpeningPicksDone", this.onOpeningPicksDone, this);
|
||||
this.removeMemoryPanel()
|
||||
}
|
||||
|
||||
onAdded(args: any) {
|
||||
onAdded() {
|
||||
// 使用 scheduleOnce 将事件推迟到下一帧执行,
|
||||
// 确保所有关联组件(如 MissionCardComp 等)都已经完成其 onLoad 生命周期。
|
||||
this.scheduleOnce(() => {
|
||||
@@ -423,22 +411,20 @@ export class MissionComp extends CCComp {
|
||||
|
||||
Tween.stopAllByTarget(this.tooltip);
|
||||
|
||||
// 动感动画设计:右侧进入 -> 屏幕中央(带有轻微的弹跳和滑动) -> 左侧飞出
|
||||
// 假设屏幕宽度适配下,1200是一个足够的屏幕外距离,适配横竖屏
|
||||
const startPos = v3(1200, this.tooltip.position.y, this.tooltip.position.z);
|
||||
const centerPos = v3(0, this.tooltip.position.y, this.tooltip.position.z);
|
||||
const driftPos = v3(-50, this.tooltip.position.y, this.tooltip.position.z); // 在中央时的缓慢漂移
|
||||
const endPos = v3(-1200, this.tooltip.position.y, this.tooltip.position.z);
|
||||
|
||||
this.tooltip.setPosition(startPos);
|
||||
// 动感动画设计:原地缩放弹入(0 -> 1,带轻微回弹)-> 短暂停留 -> 收缩消失
|
||||
// 相比位移飞入飞出,缩放动画更轻快,避免横幅长时间占用屏幕
|
||||
this.tooltip.setPosition(0, this.tooltip.position.y, this.tooltip.position.z);
|
||||
this.tooltip.setScale(0, 0, 1);
|
||||
|
||||
tween(this.tooltip)
|
||||
// 1. 从右侧快速飞入并带回弹效果 (0.5秒)
|
||||
.to(0.5, { position: centerPos }, { easing: "backOut" })
|
||||
// 2. 在屏幕中央缓慢向左漂移,增强动感停留 (1.0秒)
|
||||
.to(1.0, { position: driftPos }, { easing: "sineInOut" })
|
||||
// 3. 快速向左飞出并消失 (0.4秒)
|
||||
.to(0.4, { position: endPos }, { easing: "backIn" })
|
||||
// 1. 缩放从 0 快速弹到 1.1,制造轻微"冲出来"的动感 (0.18秒)
|
||||
.to(0.18, { scale: v3(1.1, 1.1, 1) }, { easing: "sineOut" })
|
||||
// 2. 回弹收缩到标准大小 1.0 (0.1秒)
|
||||
.to(0.1, { scale: v3(1, 1, 1) }, { easing: "sineIn" })
|
||||
// 3. 停留展示 (0.5秒)
|
||||
.delay(0.5)
|
||||
// 4. 向上轻弹并收缩消失,带背入加速感 (0.2秒)
|
||||
.to(0.2, { scale: v3(0, 0, 1) }, { easing: "backIn" })
|
||||
.call(() => {
|
||||
this.tooltip.active = false;
|
||||
})
|
||||
@@ -452,7 +438,6 @@ export class MissionComp extends CCComp {
|
||||
*/
|
||||
private changePhase(targetPhase: MissionPhase) {
|
||||
if (this.currentPhase === targetPhase) return;
|
||||
const oldPhase = this.currentPhase;
|
||||
this.currentPhase = targetPhase;
|
||||
|
||||
const phaseName = MissionComp.PhaseNameMap[targetPhase] || "未知";
|
||||
@@ -553,8 +538,6 @@ export class MissionComp extends CCComp {
|
||||
// 英雄为真实死亡(实体已销毁),本节死亡数取死亡记录长度
|
||||
const heroDeathCount = smc.mission.dead_heroes.length;
|
||||
|
||||
// 快照清场时间供评分统计(onNewWave 会清零 clearTime,必须提前快照)
|
||||
this.lastWaveClearTime = this.clearTime;
|
||||
// 【评分系统 - 战绩分】记录全员存活的胜利节数(额外加分)
|
||||
if (hasHero && heroDeathCount === 0) {
|
||||
smc.vmdata.scores.wave_all_alive_count++;
|
||||
@@ -748,11 +731,11 @@ export class MissionComp extends CCComp {
|
||||
* - 胜利即 final(通关),存档推进到下一关;失败即 defeat(进度不变,可复活/再来一次)
|
||||
* - 切入 BattleEnd,2s 后由 autoNextPhase 自动弹出 VictoryComp
|
||||
*
|
||||
* @param e 事件对象(未使用)
|
||||
* @param is_hero_dead 是否因英雄全灭触发
|
||||
* @param _e 事件对象(未使用)
|
||||
* @param _is_hero_dead 是否因英雄全灭触发(当前未使用,保留签名以统一事件调用口径)
|
||||
* @param victory 是否胜利(默认 false)
|
||||
*/
|
||||
open_Victory(e: any, is_hero_dead: boolean = false, victory: boolean = false) {
|
||||
open_Victory(_e: any, _is_hero_dead: boolean = false, victory: boolean = false) {
|
||||
// 战斗结束(通关/失败),标记暂停状态以切断节间流转逻辑
|
||||
smc.mission.pause = true;
|
||||
this._victory = victory;
|
||||
@@ -828,20 +811,11 @@ export class MissionComp extends CCComp {
|
||||
this.currentWave = 1;
|
||||
this.isBossWave = false;
|
||||
this.skipRemainScoreOnBattleEnd = false;
|
||||
this.lastWaveClearTime = 0;
|
||||
this.updateRoundNodes();
|
||||
this.rewards = []
|
||||
this.revive_times = 1;
|
||||
this.lastTimeStr = "";
|
||||
this.lastTimeSecond = -1;
|
||||
this.memoryRefreshTimer = 0;
|
||||
this.lastMemoryText = "";
|
||||
this.perfDtAcc = 0;
|
||||
this.perfFrameCount = 0;
|
||||
this.heapBaseMB = -1;
|
||||
this.heapPeakMB = 0;
|
||||
this.heapTrendPerMinMB = 0;
|
||||
this.heapTrendTimer = 0;
|
||||
this.heapTrendBaseMB = -1;
|
||||
this.monsterCountSyncTimer = 0;
|
||||
|
||||
spawningEngine.reset();
|
||||
@@ -941,7 +915,7 @@ export class MissionComp extends CCComp {
|
||||
* @param event 事件名
|
||||
* @param data { wave: number, bossWave?: boolean }
|
||||
*/
|
||||
private onNewWave(event: string, data: any) {
|
||||
private onNewWave(_event: string, data: any) {
|
||||
const wave = Number(data?.wave ?? 0);
|
||||
if (wave <= 0) return;
|
||||
|
||||
@@ -950,12 +924,46 @@ export class MissionComp extends CCComp {
|
||||
|
||||
this.currentWave = wave;
|
||||
smc.vmdata.mission_data.level = wave;
|
||||
this.updateRoundNodes();
|
||||
// 金币改为怪物死亡掉落(见 HeroAtkSystem),不再每回合固定发放
|
||||
this.lastTimeSecond = -1;
|
||||
this.clearTime = 0;
|
||||
this.update_time();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新回合指示节点颜色:
|
||||
* - 已激活(已完成 + 当前回合)→ 黄色;当前回合且为 Boss 节 → 红色;未激活 → 白色。
|
||||
* Why: 回合推进时(onNewWave)与开局重置(data_init)调用,驱动 HUD 回合指示实时反馈。
|
||||
*/
|
||||
private updateRoundNodes() {
|
||||
const len = Math.min(this.round_nodes.length, SECTIONS_PER_STAGE);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const node = this.round_nodes[i];
|
||||
if (!node || !node.isValid) continue;
|
||||
const round = i + 1;
|
||||
const color = round > this.currentWave
|
||||
? MissionComp.ROUND_INACTIVE
|
||||
: (round === this.currentWave && this.isBossWave)
|
||||
? MissionComp.ROUND_BOSS
|
||||
: MissionComp.ROUND_ACTIVE;
|
||||
// 优先染色 Sprite(图标配色),无 Sprite 时退化到 Label,均无则跳过
|
||||
const sprite = node.getComponent(Sprite);
|
||||
if (sprite) {
|
||||
sprite.color = color;
|
||||
} else {
|
||||
const label = node.getComponent(Label);
|
||||
if (label) label.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
// 进度条:第 1 回合 0%,每推进一回合 +25%(第 5 回合满格)
|
||||
if (this.round_progress && this.round_progress.isValid) {
|
||||
this.round_progress.progress =
|
||||
Math.max(0, Math.min(SECTIONS_PER_STAGE, this.currentWave) - 1) / (SECTIONS_PER_STAGE - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 怪物数量管理 ========================
|
||||
|
||||
/**
|
||||
@@ -1164,115 +1172,6 @@ export class MissionComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取战斗层的英雄和技能节点数量(用于性能监控) */
|
||||
private getBattleLayerNodeCount() {
|
||||
const scene = smc.map?.MapView?.scene;
|
||||
const layer = scene?.entityLayer?.node;
|
||||
if (!layer) return { heroNodes: 0, skillNodes: 0 };
|
||||
const heroRoot = layer.getChildByName("HERO");
|
||||
const skillRoot = layer.getChildByName("SKILL");
|
||||
return {
|
||||
heroNodes: heroRoot?.children.length || 0,
|
||||
skillNodes: skillRoot?.children.length || 0
|
||||
};
|
||||
}
|
||||
|
||||
// ======================== 性能监控面板 ========================
|
||||
|
||||
/** 性能监控相关代码 */
|
||||
|
||||
/** 初始化性能监控面板:在本组件节点下创建 Label */
|
||||
private initMemoryPanel() {
|
||||
if (!this.showMemoryPanel) return;
|
||||
let panel = this.node.getChildByName("mem_panel");
|
||||
if (!panel) {
|
||||
panel = new Node("mem_panel");
|
||||
panel.parent = this.node;
|
||||
panel.setPosition(0, -32, 0);
|
||||
}
|
||||
let label = panel.getComponent(Label);
|
||||
if (!label) {
|
||||
label = panel.addComponent(Label);
|
||||
}
|
||||
label.fontSize = 16;
|
||||
label.lineHeight = 20;
|
||||
this.memoryLabel = label;
|
||||
}
|
||||
|
||||
/** 移除性能监控面板 */
|
||||
private removeMemoryPanel() {
|
||||
const panel = this.node?.getChildByName("mem_panel");
|
||||
if (panel) {
|
||||
panel.destroy();
|
||||
}
|
||||
this.memoryLabel = null;
|
||||
this.lastMemoryText = "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新性能监控面板内容(每 0.5 秒一次):
|
||||
* 显示 堆内存 / 增长趋势 / 帧率 / 实体数量 / 对象池状态 等信息。
|
||||
*/
|
||||
private updateMemoryPanel(dt: number) {
|
||||
if (!this.showMemoryPanel || !this.memoryLabel) return;
|
||||
this.perfDtAcc += dt;
|
||||
this.perfFrameCount += 1;
|
||||
this.memoryRefreshTimer += dt;
|
||||
if (this.memoryRefreshTimer < 0.5) return;
|
||||
this.memoryRefreshTimer = 0;
|
||||
let heroCount = 0;
|
||||
ecs.query(this.heroViewMatcher).forEach(() => {
|
||||
heroCount++;
|
||||
});
|
||||
let skillCount = 0;
|
||||
ecs.query(this.skillViewMatcher).forEach(() => {
|
||||
skillCount++;
|
||||
});
|
||||
const monPool = Monster.getPoolStats();
|
||||
const skillPool = Skill.getPoolStats();
|
||||
const tooltipPool = Tooltip.getPoolStats();
|
||||
const layerNodes = this.getBattleLayerNodeCount();
|
||||
const perf = (globalThis as any).performance;
|
||||
const heapBytes = perf && perf.memory ? perf.memory.usedJSHeapSize : 0;
|
||||
let heapMB = heapBytes > 0 ? heapBytes / 1024 / 1024 : -1;
|
||||
if (heapMB > 0 && this.heapBaseMB < 0) {
|
||||
this.heapBaseMB = heapMB;
|
||||
this.heapPeakMB = heapMB;
|
||||
this.heapTrendBaseMB = heapMB;
|
||||
this.heapTrendTimer = 0;
|
||||
}
|
||||
if (heapMB > this.heapPeakMB) {
|
||||
this.heapPeakMB = heapMB;
|
||||
}
|
||||
this.heapTrendTimer += 0.5;
|
||||
if (heapMB > 0 && this.heapTrendBaseMB > 0 && this.heapTrendTimer >= 10) {
|
||||
const deltaMB = heapMB - this.heapTrendBaseMB;
|
||||
this.heapTrendPerMinMB = (deltaMB / this.heapTrendTimer) * 60;
|
||||
this.heapTrendBaseMB = heapMB;
|
||||
this.heapTrendTimer = 0;
|
||||
}
|
||||
const heapText = heapMB > 0 ? heapMB.toFixed(1) : "N/A";
|
||||
const heapDeltaText = this.heapBaseMB > 0 && heapMB > 0 ? (heapMB - this.heapBaseMB).toFixed(1) : "N/A";
|
||||
const heapPeakText = this.heapPeakMB > 0 ? this.heapPeakMB.toFixed(1) : "N/A";
|
||||
const avgDt = this.perfFrameCount > 0 ? this.perfDtAcc / this.perfFrameCount : 0;
|
||||
const fps = avgDt > 0 ? 1 / avgDt : 0;
|
||||
this.perfDtAcc = 0;
|
||||
this.perfFrameCount = 0;
|
||||
const text =
|
||||
`Heap:${heapText}MB Δ:${heapDeltaText} Peak:${heapPeakText}\n` +
|
||||
`Trend:${this.heapTrendPerMinMB.toFixed(2)}MB/min\n` +
|
||||
`Perf dt:${(avgDt * 1000).toFixed(1)}ms fps:${fps.toFixed(1)}\n` +
|
||||
`Ent H:${heroCount} S:${skillCount} N:${layerNodes.heroNodes}/${layerNodes.skillNodes}\n` +
|
||||
`Pool M:${monPool.total}(${monPool.paths}) K:${skillPool.total}(${skillPool.paths}) T:${tooltipPool.total}`;
|
||||
if (text === this.lastMemoryText) return;
|
||||
this.lastMemoryText = text;
|
||||
this.memoryLabel.string = text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** ECS 组件移除时销毁节点 */
|
||||
reset() {
|
||||
this.PhaseTime = null as any;
|
||||
|
||||
447
docs/hero-numerical-review.md
Normal file
447
docs/hero-numerical-review.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# 全英雄基础属性与能力说明(数值评审稿)
|
||||
|
||||
> 数据来源:`assets/script/game/common/config/heroSet.ts`(英雄配置)、`SkillSet.ts`(技能底座)、`BuffSet.ts`(buff/debuff 底座)
|
||||
> 用途:交予数值设计师讨论分析。lv1 基础属性均已按 `HERO_ROLE_BASE + 手动增量` 实算。
|
||||
|
||||
---
|
||||
|
||||
## 〇、全局数值框架
|
||||
|
||||
### 职业基础模板 `HERO_ROLE_BASE`
|
||||
|
||||
英雄实际属性 = 模板 + 手动增量;品质(rarity)仅乘 lv1 基础值,相邻品质差 5%。
|
||||
|
||||
| 职业 | HP | AP | DEF | 成长系数 HP/AP/DEF |
|
||||
|---|---|---|---|---|
|
||||
| 坦克 Tank | 600 | 20 | 50 | 1.5 / 0.6 / 1.5 |
|
||||
| 战士 Warrior | 560 | 22 | 30 | 1.0 / 1.0 / 1.0 |
|
||||
| 刺客 Assassin | 560 | 22 | 10 | 0.7 / 1.4 / 0.5 |
|
||||
| 射手 Archer | 280 | 32 | 10 | 0.8 / 1.3 / 0.5 |
|
||||
| 法师 Mage | 280 | 32 | 10 | 0.9 / 1.3 / 0.5 |
|
||||
| 辅助 Support | 280 | 32 | 15 | 1.1 / 0.7 / 1.0 |
|
||||
|
||||
### 攻速档 `AtkSpeedSet`(攻击间隔,越小越快)
|
||||
|
||||
| 档位 | 名称 | 间隔 |
|
||||
|---|---|---|
|
||||
| VeryFast | 极速 | 0.75s |
|
||||
| Fast | 快速 | 0.90s |
|
||||
| Normal | 中速 | 1.05s |
|
||||
| Slow | 慢速 | 1.20s |
|
||||
| VerySlow | 极慢 | 1.50s |
|
||||
|
||||
### 大招 CD 档 `UltCdSet`
|
||||
|
||||
| 档位 | 名称 | CD | 适用类型 |
|
||||
|---|---|---|---|
|
||||
| VeryFast | 极频 | 6s | 单体爆发/高频压制 |
|
||||
| Fast | 高频 | 7s | 频繁压制(中间档) |
|
||||
| Normal | 标准 | 8s | 群体伤害/控制 |
|
||||
| Slow | 低频 | 9s | 中度团队增益(中间档) |
|
||||
| VerySlow | 稀有 | 10s | 全体增益/治疗/护盾 |
|
||||
|
||||
### 成长与解锁节奏
|
||||
|
||||
- lv1:激活普攻 + 大招
|
||||
- lv2:普攻强化档(lv_entries 第 1 档)
|
||||
- lv4:解锁触发技 / 光环(配置实际生效等级)
|
||||
- lv8 / lv16:大招升档(lv_entries)
|
||||
- lv12:专项 bonus(单项属性 +50%,基数取"基础×品质+成长"快照)
|
||||
|
||||
### 触发技概率模型
|
||||
|
||||
t_num 次行为后判定,t_chance% 概率触发;未触发则 pity_add 累加下次概率(封顶 100%,触发成功后清零)。
|
||||
|
||||
---
|
||||
|
||||
## 一、前排坦克 / 战士
|
||||
|
||||
### 5001 叠甲骑士(近战·坦克)
|
||||
|
||||
**lv1:HP 620 / AP 20 / DEF 60** | 攻速 1.20s(慢速)| 大招 CD 10s(稀有)
|
||||
> 定位:自身强护盾 + 防御强化
|
||||
|
||||
- **普攻 6004 强化普攻**:100% 攻击伤害 → lv2 提升至 120%
|
||||
- **大招 6301 魔法盾**:自身获得生命最大值 **30%** 护盾 → lv8 60% → lv16 120%
|
||||
- **受击触发 6412 防御强化**(lv4):受击 3 次后 50% 概率触发(保底 +20),全队防御 +50(单次)
|
||||
- **lv12 钢铁壁垒**:DEF +50%
|
||||
|
||||
### 5009 甲虫领主(近战·坦克)
|
||||
|
||||
**lv1:HP 600 / AP 20 / DEF 50** | 攻速 1.20s | 大招 CD 8s
|
||||
> 定位:受击自疗 + 击退,自愈坦克
|
||||
|
||||
- **普攻 6004 普攻强化** → lv2 伤害提升至 120%
|
||||
- **大招 6102 火焰斩**:250% 攻击伤害,并 100% 触发治疗(回复自身 10% 生命)→ lv8 300%/15% → lv16 350%/20%
|
||||
- **受击触发 6302 治疗**(lv4):受击 3 次后 30% 概率(保底 +20),回复自身 5% 生命
|
||||
- **lv12 钢铁壁垒**:DEF +50%
|
||||
|
||||
### 5011 控血大师(近战·坦克)
|
||||
|
||||
**lv1:HP 600 / AP 20 / DEF 50** | 攻速 1.05s(中速)| 大招 CD 10s(稀有)
|
||||
> 定位:永久 + 计时生命上限,控血流
|
||||
|
||||
- **普攻 6004 普攻强化** → lv2:10% 概率对自己释放回春(5s 回复 5% 生命)
|
||||
- **大招 6103 闪电战**:200% 攻击伤害,自身最大生命 +30% 持续 5s → lv8 250%/+40% → lv16 300%/+50%
|
||||
- **受击触发 6402 生命强化**(lv4):受击 5 次后 30% 概率(保底 +15),**全队永久最大生命 +1%**
|
||||
- **lv12 钢铁壁垒**:DEF +50%
|
||||
|
||||
### 5002 易伤剑仙(近战·战士)
|
||||
|
||||
**lv1:HP 565 / AP 42 / DEF 35** | 攻速 1.20s | 大招 CD 8s
|
||||
> 定位:易伤放大,近战 dps
|
||||
|
||||
- **普攻 6004 强化普攻** → lv2 提升至 120%
|
||||
- **大招 6101 气斩**:150% 伤害 + 易伤(每层附加 30 固定受伤,持续 5s,可叠 5 层)→ lv8 200%/70 → lv16 250%/150 持续 7s
|
||||
- **攻击触发 6001 飞剑**(lv4):普攻 3 次后 50% 概率(保底 +20),追加飞剑并挂易伤 10/5s
|
||||
- **lv12 不屈战意**:HP +50%
|
||||
|
||||
### 5003 眩晕暴击哥(近战·战士)
|
||||
|
||||
**lv1:HP 565 / AP 42 / DEF 35** | 攻速 1.20s | 大招 CD 8s
|
||||
> 定位:必晕爆发 + 暴击,近战收割
|
||||
|
||||
- **普攻 6004 强化普攻** → lv2:普攻 10% 概率击晕
|
||||
- **大招 6104 开山斧**:250% 伤害 **必定击晕** → lv8 300% → lv16 350%
|
||||
- **攻击触发 6504 暴击爆发**(lv4):普攻 3 次后 50% 概率(保底 +20),自身暴击率 +30% 持续 5s
|
||||
- **lv12 不屈战意**:HP +50%
|
||||
|
||||
### 5014 闪避哥(近战·战士)
|
||||
|
||||
**lv1:HP 560 / AP 22 / DEF 30** | 攻速 1.05s | 大招 CD 8s
|
||||
> 定位:强闪避
|
||||
|
||||
- **普攻 6004 普攻强化** → lv2:暴击率 +10%
|
||||
- **大招 6100 寒冰斩**:200% 伤害,自身闪避 +50%(7030 闪避爆发)→ lv8 250%/50% → lv16 350%/80%
|
||||
- **光环 7023(lv4)**:词条值 10(语义存疑,见待确认点 ①)
|
||||
- **lv12 不屈战意**:HP +50%
|
||||
|
||||
---
|
||||
|
||||
## 二、刺客 / 中程输出
|
||||
|
||||
### 5004 刀刀暴击(中程·刺客)
|
||||
|
||||
**lv1:HP 560 / AP 52 / DEF 10** | 攻速 0.90s(快速)| 大招 CD 6s(极频)
|
||||
> 定位:暴击流刺客主C
|
||||
|
||||
- **普攻 6002 匕首** → lv2:暴击率 +10%
|
||||
- **大招 6150 剑刃风暴**:150% 伤害,50% 暴击 → lv8 200%/60% → lv16 250%/70%
|
||||
- **攻击触发 6504 暴击爆发**(lv4):普攻 3 次后 50% 概率(保底 +20),自身暴击率 +10% 持续 5s
|
||||
- **lv12 致命本能**:AP +50%
|
||||
|
||||
### 5005 出血贼(中程·刺客)
|
||||
|
||||
**lv1:HP 560 / AP 52 / DEF 10** | 攻速 0.90s | 大招 CD 6s(极频)
|
||||
> 定位:出血 DoT 持续消耗
|
||||
|
||||
- **普攻 6002 强化普攻** → lv2:50% 概率使目标出血(每 0.5s 微量掉血,每层使目标被暴击率 +1%,可叠 5 层)
|
||||
- **大招 6150 血刃风暴**:200% 伤害 + 100% 出血(-20/5s)→ lv8 250%/-30/7s → lv16 300%/-50/9s(lv16 文案附带 50% 麻痹但配置未注入,见待确认点 ②)
|
||||
- **攻击触发 6401 攻击强化**(lv4):普攻 3 次后 50% 概率(保底 +20),全队攻击 +2%
|
||||
- **lv12 致命本能**:AP +50%
|
||||
|
||||
### 5010 火箭哥(中程·射手)
|
||||
|
||||
**lv1:HP 280 / AP 32 / DEF 10** | 攻速 0.90s | 大招 CD 6s(极频)
|
||||
> 定位:弹幕减攻
|
||||
|
||||
- **普攻 6006 射击** → lv2:暴击率 +10%
|
||||
- **大招 6151 万箭齐发·火**:5 支箭各 70% 伤害,必挂减攻 -10%/5s → lv8 80%/-20% → lv16 100%/-30%
|
||||
- **攻击触发 6008 红箭**(lv4):普攻 3 次后 50% 概率(保底 +20),追加 120% 伤害射击
|
||||
- **lv12 鹰眼锁定**:AP +50%
|
||||
|
||||
### 5016 连珠神射(中程·射手)
|
||||
|
||||
**lv1:HP 280 / AP 42 / DEF 10** | 攻速 **0.75s(极速,全英雄最快)** | 大招 CD 6s(极频)
|
||||
> 定位:极速连射暴击爆发主C
|
||||
|
||||
- **普攻 6008 连珠箭** → lv2:20% 概率额外射击,造成 80% 伤害
|
||||
- **大招 6151 裂空箭雨**:5 箭各 150% 伤害,30% 暴击 → lv8 200%/50% → lv16 250%/70%/6 箭
|
||||
- **光环 7004 暴击光环(lv4)**:全队暴击率提升(词条值 0.1)
|
||||
- **lv12 猎杀直觉**:AP +50%
|
||||
|
||||
---
|
||||
|
||||
## 三、远程法师 / 射手 / 辅助
|
||||
|
||||
### 5006 冻箭哥(远程·射手)
|
||||
|
||||
**lv1:HP 280 / AP 62 / DEF 10** | 攻速 1.05s | 大招 CD 8s
|
||||
> 定位:冰冻控场 + 多重箭
|
||||
|
||||
- **普攻 6006 强化普攻** → lv2:10% 概率冰冻
|
||||
- **大招 6150 寒冰箭**:6 支箭各 150% 伤害,每支 30% 概率冰冻 → lv8 200% → lv16 250%(冰冻率恒 30%)
|
||||
- **攻击触发 6401 攻击强化**(lv4):普攻 3 次后 50% 概率(保底 +20),**全队永久攻击 +2%**
|
||||
- **lv12 鹰眼锁定**:AP +50%
|
||||
- 备注:手动增量 ap+30,lv1 AP 62 为全英雄最高普攻基数(见待确认点 ③)
|
||||
|
||||
### 5015 毒箭猎手(远程·射手)
|
||||
|
||||
**lv1:HP 290 / AP 32 / DEF 10** | 攻速 1.05s | 大招 CD 8s
|
||||
> 定位:毒伤 DoT 持续消耗型远程C
|
||||
|
||||
- **普攻 6007 淬毒箭** → lv2:10% 概率击退
|
||||
- **大招 6150 万箭齐发·毒**:150% 伤害,50% 中毒(每 0.5s 30 伤害/5s,可叠 5 层)→ lv8 200%/100%/60/5s → lv16 250%/100%/100/7s
|
||||
- **攻击触发 6007 蚀骨毒箭**(lv4):普攻 3 次后 50% 概率(保底 +20),追加 80% 伤害且 **必定中毒**(40/跳)
|
||||
- **lv12 剧毒淬炼**:AP +50%
|
||||
|
||||
### 5007 击退火女(远程·法师)
|
||||
|
||||
**lv1:HP 275 / AP 42 / DEF 10** | 攻速 1.20s | 大招 CD 8s
|
||||
> 定位:烈焰击退 + 攻击光环,法系压场
|
||||
|
||||
- **普攻 6010 紫珠** → lv2:20% 概率击退
|
||||
- **大招 6200 火焰喷射**:250% 伤害 + 50% 暴击 + 击退 → lv8 350% → lv16 450%/4 个火球(升档后暴击加成降为 0,见待确认点 ④)
|
||||
- **光环 7002 攻击光环(lv4)**:全队攻击 +15%
|
||||
- **lv12 奥术共鸣**:AP +50%
|
||||
|
||||
### 5012 疾风法师(远程·法师)
|
||||
|
||||
**lv1:HP 280 / AP 32 / DEF 10** | 攻速 1.05s | 大招 CD 8s
|
||||
> 定位:攻速 buff + 攻速光环,加速大师
|
||||
|
||||
- **普攻 6009 蓝珠** → lv2:10% 概率自身攻速 +30%(6507 攻速爆发)
|
||||
- **大招 6201 飓风**:2 道飓风各 100% 伤害,每道 20% 概率全队攻速 +20% → lv8 3 道/25%/+25% → lv16 4 道/30%/+30%
|
||||
- **光环 7006 攻速光环(lv4)**:全队攻速 +10%
|
||||
- **lv12 奥术共鸣**:AP +50%
|
||||
|
||||
### 5017 碎甲术士(远程·法师)
|
||||
|
||||
**lv1:HP 280 / AP 42 / DEF 10** | 攻速 1.05s | 大招 CD 8s
|
||||
> 定位:破甲 + 穿透光环,物理队核心增伤位
|
||||
|
||||
- **普攻 6012 蚀甲酸瓶** → lv2:30% 概率破甲(防御 -50%/5s)
|
||||
- **大招 6203 碎甲能量波**:150% 伤害 + 必破甲 -30%/5s → lv8 200%/-50% → lv16 250%/-70%/7s
|
||||
- **光环 7009 穿透光环(lv4)**:全队穿透概率 +10%
|
||||
- **lv12 奥术共鸣**:AP +50%
|
||||
|
||||
### 5018 雷暴领主(远程·法师)
|
||||
|
||||
**lv1:HP 270 / AP 52 / DEF 10** | 攻速 0.90s(快速)| 大招 CD 8s
|
||||
> 定位:雷暴 AOE + 麻痹控场,法系主C
|
||||
|
||||
- **普攻 6013 召唤闪电** → lv2:15% 概率麻痹(技能 CD 增长减慢 50%,持续 5s)
|
||||
- **大招 6204 雷霆裂空**:150% 伤害,50% 麻痹 → lv8 200%/70% → lv16 250%/100% 可多次命中
|
||||
- **光环 7024 命中光环(lv4)**:全队命中 +10%
|
||||
- **lv12 奥术共鸣**:AP +50%
|
||||
|
||||
### 5008 团疗奶爸(远程·辅助)
|
||||
|
||||
**lv1:HP 290 / AP 52 / DEF 15** | 攻速 1.05s | 大招 CD 10s(稀有)
|
||||
> 定位:全体治疗 + 团队续航
|
||||
|
||||
- **普攻 6011 绿瓶** → lv2:120% 伤害
|
||||
- **大招 6302 治疗**:全队回复攻击 30% 生命 → lv8 40% → lv16 50% + 全队防御 +20/5s(7024 防御爆发词条)
|
||||
- **光环 7001 战后恢复(lv4)**:回合结束休整回血 +20%
|
||||
- **lv12 守护之心**:HP +50%
|
||||
|
||||
### 5013 生命哥(远程·辅助)
|
||||
|
||||
**lv1:HP 280 / AP 32 / DEF 15** | 攻速 1.05s | 大招 CD 10s(稀有)
|
||||
> 定位:全体生命上限 + 反伤护盾
|
||||
|
||||
- **普攻 6004** → lv2:120% 伤害
|
||||
- **大招 6517 生命护盾**:全队最大生命 +20%/5s(3 人)→ lv8 +25%/10s → lv16 +30%/10s
|
||||
- **攻击触发 6414 反伤比例**(lv4):普攻 3 次后 30% 概率(保底 +15),全队比例反伤 +10%
|
||||
- **lv12 守护之心**:HP +50%
|
||||
|
||||
---
|
||||
|
||||
## 四、英雄速查总表(lv1 基础值)
|
||||
|
||||
| UUID | 名称 | 定位 | 职业 | HP | AP | DEF | 普攻间隔 | 大招CD | 核心机制 |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 5001 | 叠甲骑士 | 近战 | 坦克 | 620 | 20 | 60 | 1.20s | 10s | 自身护盾/受击叠防 |
|
||||
| 5002 | 易伤剑仙 | 近战 | 战士 | 565 | 42 | 35 | 1.20s | 8s | 易伤放大 |
|
||||
| 5003 | 眩晕暴击哥 | 近战 | 战士 | 565 | 42 | 35 | 1.20s | 8s | 必晕+暴击 |
|
||||
| 5004 | 刀刀暴击 | 中程 | 刺客 | 560 | 52 | 10 | 0.90s | 6s | 暴击流主C |
|
||||
| 5005 | 出血贼 | 中程 | 刺客 | 560 | 52 | 10 | 0.90s | 6s | 出血DoT |
|
||||
| 5006 | 冻箭哥 | 远程 | 射手 | 280 | 62 | 10 | 1.05s | 8s | 冰冻控场 |
|
||||
| 5007 | 击退火女 | 远程 | 法师 | 275 | 42 | 10 | 1.20s | 8s | 击退+攻击光环 |
|
||||
| 5008 | 团疗奶爸 | 远程 | 辅助 | 290 | 52 | 15 | 1.05s | 10s | 全体治疗 |
|
||||
| 5009 | 甲虫领主 | 近战 | 坦克 | 600 | 20 | 50 | 1.20s | 8s | 受击自疗 |
|
||||
| 5010 | 火箭哥 | 中程 | 射手 | 280 | 32 | 10 | 0.90s | 6s | 弹幕减攻 |
|
||||
| 5011 | 控血大师 | 近战 | 坦克 | 600 | 20 | 50 | 1.05s | 10s | 生命上限成长 |
|
||||
| 5012 | 疾风法师 | 远程 | 法师 | 280 | 32 | 10 | 1.05s | 8s | 攻速buff+光环 |
|
||||
| 5013 | 生命哥 | 远程 | 辅助 | 280 | 32 | 15 | 1.05s | 10s | 生命上限+反伤 |
|
||||
| 5014 | 闪避哥 | 近战 | 战士 | 560 | 22 | 30 | 1.05s | 8s | 强闪避 |
|
||||
| 5015 | 毒箭猎手 | 远程 | 射手 | 290 | 32 | 10 | 1.05s | 8s | 毒伤DoT |
|
||||
| 5016 | 连珠神射 | 中程 | 射手 | 280 | 42 | 10 | 0.75s | 6s | 极速暴击爆发 |
|
||||
| 5017 | 碎甲术士 | 远程 | 法师 | 280 | 42 | 10 | 1.05s | 8s | 破甲+穿透光环 |
|
||||
| 5018 | 雷暴领主 | 远程 | 法师 | 270 | 52 | 10 | 0.90s | 8s | 雷暴+麻痹 |
|
||||
|
||||
---
|
||||
|
||||
## 五、官方推荐阵容(HeroComboSet)
|
||||
|
||||
| ID | 阵容 | 英雄 | 配合思路 |
|
||||
|---|---|---|---|
|
||||
| 8001 | 暴击爆发流 | 5001 + 5016 + 5004 | 叠甲骑士前排抗伤,连珠神射暴击光环覆盖全队+极速箭雨压场,刀刀暴击近战收割 |
|
||||
| 8002 | 冰雷控制流 | 5003 + 5006 + 5018 | 开山斧必晕 + 寒冰箭冰冻链 + 雷霆麻痹,三控交替 |
|
||||
| 8003 | 铁桶续航流 | 5009 + 5011 + 5008 | 受击自疗前排 + 全队永久生命成长 + 全体治疗,持久战 |
|
||||
| 8004 | 破甲压制流 | 5001 + 5017 + 5010 | 双通道破甲+穿透光环放大物理伤害,五箭弹幕挂减攻 |
|
||||
| 8005 | 光环覆盖流 | 5014 + 5007 + 5012 | 闪避/攻击/攻速三光环全覆盖 |
|
||||
| 8006 | 易伤消耗流 | 5002 + 5005 + 5008 | 易伤放大出血 DoT,治疗保前排磨血 |
|
||||
|
||||
---
|
||||
|
||||
## 六、待数值设计师确认的配置口径疑问
|
||||
|
||||
1. **5014 光环语义不一致**:词条 uuid=7023,FieldSkillSet 7023 名称为"闪避光环",但 BuffSet 7023 为"防御强化"(防御 +50/10s)。需确认实际生效语义。
|
||||
2. **5005 大招 lv16 文案与配置不符**:desc 写"附带 50% 概率麻痹",但 overrides 未注入 `para` 字段。
|
||||
3. **5006 冻箭哥 AP 基数偏高**:手动增量 ap+30(lv1 AP 62),显著高于其他远程英雄(32~52),需确认是否刻意。
|
||||
4. **5007 大招升档暴击消失**:lv1 档 crt:50,lv8/lv16 档 crt:0,升档后暴击加成消失,需确认是否刻意。
|
||||
5. **触发技解锁等级**:统一 **lv4** 激活(atking/atked/fstart/dead/field 触发类无品质门槛)。heroSet 头注释原为 lv6,已修正为 lv4 与实际配置对齐。
|
||||
6. **品质体系已停用**:全部英雄 rarity 统一为 1,不再区分品质;`getHeroQualityCoef` 保留但恒为 1.00。卡池费用统一 5、抽卡权重统一 40。若未来重启品质体系,仅影响 lv1 初始基础属性(相邻差 5%),不影响技能解锁/光环/属性补偿。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A、技能底座表(SkillSet)
|
||||
|
||||
> 数值说明:`ap` 对伤害/治疗技能为攻击力百分比,对护盾(6301)为生命最大值百分比,对永久强化(64xx)为修饰数值;`hit_count` 对伤害技能为弹道穿透数,对辅助技能为作用目标数。
|
||||
|
||||
### A1. 单体普攻底座(6001~6013)
|
||||
|
||||
| UUID | 名称 | 弹道 | ap% | 穿透 | 附加语义 | 使用英雄 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 6001 | 飞剑 | 近战贝塞尔 | 100 | 1 | 可附加易伤 | 5002(触发技) |
|
||||
| 6002 | 匕首 | 近战贝塞尔 | 100 | 1 | 可附加出血/暴击 | 5004、5005 |
|
||||
| 6004 | 攻击 | 近战碰撞 | 100 | 1 | 默认 100% 击退(bck_chance=100) | 5001/5002/5003/5009/5011/5013/5014 |
|
||||
| 6006 | 射击 | 远程箭 | 100 | 1 | 可附加冰冻/暴击 | 5006、5010 |
|
||||
| 6007 | 蓝箭 | 远程箭 | 100 | 1 | 可附加击退/中毒 | 5015 |
|
||||
| 6008 | 红箭 | 远程箭 | 100 | 1 | 可附加追射/暴击 | 5010(触发技)、5016 |
|
||||
| 6009 | 蓝珠 | 近战弹道 | 100 | 1 | 可附加攻速 proc | 5012 |
|
||||
| 6010 | 紫珠 | 近战弹道 | 100 | 1 | 可附加击退 | 5007 |
|
||||
| 6011 | 绿瓶 | 近战弹道 | 100 | 1 | — | 5008 |
|
||||
| 6012 | 红瓶 | 近战弹道 | 100 | 1 | 可附加破甲 | 5017 |
|
||||
| 6013 | 闪电 | 固定终点落雷 | 100 | 1 | 可附加麻痹 | 5018 |
|
||||
|
||||
### A2. 群体大招底座(6100~6204)
|
||||
|
||||
| UUID | 名称 | 类型 | ap% | 穿透/弹道 | 附加语义 | 使用英雄 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 6100 | 寒冰斩 | 近战 | 150 | 5 | 可叠出血、自增益 | 5014 |
|
||||
| 6101 | 裂气斩 | 近战 | 150 | 5 | 可叠击退、易伤 | 5002 |
|
||||
| 6102 | 火焰斩 | 近战 | 150 | 5 | 可叠暴击、proc 治疗 | 5009 |
|
||||
| 6103 | 闪电斩 | 近战 | 150 | 5 | 可叠自身增益 | 5011 |
|
||||
| 6104 | 开天斩 | 远程范围 | 300 | 6 | 可叠击晕增强 | 5003 |
|
||||
| 6150 | 万箭齐发·冰 | 远程箭 | 100 | 1 | 6 支蓝箭底座 | 5004/5005/5006/5015 |
|
||||
| 6151 | 万箭齐发·火 | 远程箭 | 100 | 1 | 6 支红箭底座 | 5010、5016 |
|
||||
| 6200 | 火球术 | 远程 | 100 | 1 | 单发大火球 | 5007 |
|
||||
| 6201 | 飓风 | 远程直线 | 100 | 99 | 穿透飓风,可挂攻速 proc | 5012 |
|
||||
| 6202 | 毒气弹 | 远程直线 | 100 | 1 | 穿透毒气 | 未启用 |
|
||||
| 6203 | 能量波 | 远程直线 | 100 | 1 | 穿透能量波 | 5017 |
|
||||
| 6204 | 雷暴 | 远程直线 | 100 | 99 | 雷暴多段穿透,可附加麻痹 | 5018 |
|
||||
|
||||
### A3. 治疗/护盾底座(6301~6302)
|
||||
|
||||
| UUID | 名称 | kind | ap 语义 | 默认数值 | 使用英雄 |
|
||||
|---|---|---|---|---|---|
|
||||
| 6301 | 护盾 | Shield | 生命最大值 % | 30%、持续 7s | 5001 |
|
||||
| 6302 | 治疗 | Heal | 目标最大生命 × ap% | 300%(底座值,实际由 overrides 覆写) | 5008(大招)、5009(proc/受击触发) |
|
||||
|
||||
### A4. 永久强化底座(6401~6416,buff_type + ap,单次施加)
|
||||
|
||||
| UUID | 名称 | buff_type | 底座 ap | 语义 | 使用英雄 |
|
||||
|---|---|---|---|---|---|
|
||||
| 6401 | 攻击强化 | ap | 5 | 全体攻击永久 +N% | 5005、5006(触发技) |
|
||||
| 6402 | 生命强化 | hp_max | 20 | 全体最大生命永久 +N% | 5011(触发技) |
|
||||
| 6403 | 暴击强化 | critical | 1 | 全体暴击率永久 +N% | 未启用 |
|
||||
| 6404 | 暴伤强化 | critical_damage | 1 | 全体暴伤永久 +N% | 未启用 |
|
||||
| 6405 | 击晕强化 | stun_chance | 1 | 全体击晕概率永久 +N% | 未启用 |
|
||||
| 6406 | 击退强化 | knockback_chance | 10 | 全体击退距离永久 +N% | 未启用 |
|
||||
| 6408 | 穿刺强化 | puncture_chance | 20 | 全体穿刺概率永久 +N% | 未启用 |
|
||||
| 6410 | 冰冻强化 | freeze_chance | 5 | 全体冰冻概率永久 +N% | 未启用 |
|
||||
| 6411 | 溅射强化 | puncture_dmg_bonus | 10 | 全体穿透伤害永久 +N% | 未启用 |
|
||||
| 6412 | 防御强化 | defense | 50 | 全体防御永久 +N 点 | 5001(触发技) |
|
||||
| 6413 | 反伤强化 | thorns_dmg | 10 | 全体固定反伤永久 +N 点 | 未启用 |
|
||||
| 6414 | 反伤比例强化 | thorns_rate | 10 | 全体比例反伤永久 +N% | 5013(触发技) |
|
||||
| 6415 | 闪避强化 | dodge | 5 | 全体闪避永久 +N% | 未启用 |
|
||||
| 6416 | 命中强化 | accuracy | 5 | 全体命中永久 +N% | 未启用 |
|
||||
|
||||
### A5. 计时强化底座(6501~6519,走 timed_buff_id → BuffList,默认 5s)
|
||||
|
||||
| UUID | 名称 | 关联 Buff | buff_type | 底座语义 | 使用英雄 |
|
||||
|---|---|---|---|---|---|
|
||||
| 6501 | 复活 | — | — | ap=复活生命百分比(dead 触发用) | 未启用 |
|
||||
| 6502 | 计时回血 | 7010 再生 | hp | 全体每秒回血 5s | 5011(普攻 proc) |
|
||||
| 6503 | 攻击爆发 | 7001 攻击强化 | ap | 全体攻击 +N% 5s | 未启用 |
|
||||
| 6504 | 暴击爆发 | 7012 暴击爆发 | critical | 全体暴击 +N% 5s | 5003、5004(触发技) |
|
||||
| 6505 | 击退爆发 | 7013 击退爆发 | knockback_chance | 全体击退距离 +N% 5s | 未启用 |
|
||||
| 6506 | 穿透爆发 | 7014 穿透爆发 | puncture_chance | 全体穿透 +N% 5s | 未启用 |
|
||||
| 6507 | 攻速爆发 | 7015 攻速爆发 | speed | 全体攻速 +N% 5s | 5012(普攻/大招 proc) |
|
||||
| 6509 | 击晕爆发 | 7017 击晕爆发 | stun_chance | 全体击晕概率 +N% 5s | 未启用 |
|
||||
| 6510 | 击晕抗性 | 7018 击晕抗性 | stun_res | 全体击晕抗性 +N% 5s | 未启用 |
|
||||
| 6511 | 冰冻抗性 | 7019 冰冻抗性 | freeze_res | 全体冰冻抗性 +N% 5s | 未启用 |
|
||||
| 6512 | 溅射爆发 | 7022 穿透伤害爆发 | puncture_dmg_bonus | 全体穿透伤害 +N% 5s | 未启用 |
|
||||
| 6513 | 防御爆发 | 7024 防御爆发 | defense | 全体防御 +N 点 5s | 未启用 |
|
||||
| 6514 | 反伤护盾 | 7027 荆棘 | thorns_dmg | 全体固定反伤 +N 点 5s | 未启用 |
|
||||
| 6515 | 反伤比例 | 7028 荆棘比例 | thorns_rate | 全体比例反伤 +N% 5s | 未启用 |
|
||||
| 6516 | 次级反伤 | — | — | 空底座,效果由引用处 overrides 注入 | 未启用 |
|
||||
| 6517 | 生命爆发 | 7029 生命爆发 | hp_max | 全体最大生命 +N% 5s | 5013(大招) |
|
||||
| 6518 | 闪避爆发 | 7030 闪避爆发 | dodge | 全体闪避 +N% 5s | 5014(大招经 7030) |
|
||||
| 6519 | 命中爆发 | 7031 命中强化 | accuracy | 全体命中 +N% 5s | 未启用 |
|
||||
|
||||
## 附录 B、Buff/Debuff 底座表(BuffList)
|
||||
|
||||
### B1. 敌方减益(Debuff/Control)
|
||||
|
||||
| ID | 名称 | 类别 | 默认效果 | 持续 | 叠层 | 使用英雄 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 7002 | 易伤 | Debuff | 受伤附加 10 点固定伤害 | 5s | 5 | 5002 |
|
||||
| 7003 | 冰冻 | Control | 无法行动 | 2s | 1 | 5006 |
|
||||
| 7004 | 击晕 | Control | 无法行动 1s,CD 削减 20% 并停滞 | 1s | 1 | 5003 |
|
||||
| 7005 | 中毒 | Debuff | 每 0.5s 10 点 + AP 30% 伤害 | 5s | 5 | 5015 |
|
||||
| 7006 | 减攻 | Debuff | 攻击 -10% | 5s | 1 | 5010 |
|
||||
| 7007 | 出血 | Debuff | 每 0.5s 微量伤害,每层被暴击率 +1% | 5s | 5 | 5005 |
|
||||
| 7008 | 破甲 | Debuff | 防御 -50% | 5s | 1 | 5017 |
|
||||
| 7025 | 麻痹 | Control | 技能 CD 增长减慢 50% | 5s | 1 | 5018 |
|
||||
|
||||
### B2. 友方增益(Buff,计时类默认 5s)
|
||||
|
||||
| ID | 名称 | 效果 | 使用英雄 |
|
||||
|---|---|---|---|
|
||||
| 7001 | 攻击强化 | 攻击 +5%,10s | 6503 底座 |
|
||||
| 7010 | 再生 | 每 0.5s 回 10 血,5s | 5011(6502) |
|
||||
| 7012 | 暴击爆发 | 暴击率 +30% | 5003/5004(6504) |
|
||||
| 7013 | 击退爆发 | 击退距离 +30% | 6505 底座 |
|
||||
| 7014 | 穿透爆发 | 穿透概率 +30% | 6506 底座 |
|
||||
| 7015 | 攻速爆发 | 攻速 +40% | 5012(6507) |
|
||||
| 7017 | 击晕爆发 | 击晕概率 +30% | 6509 底座 |
|
||||
| 7018 | 击晕抗性 | 击晕抗性 +50% | 6510 底座 |
|
||||
| 7019 | 冰冻抗性 | 冰冻抗性 +50% | 6511 底座 |
|
||||
| 7020 | 暴伤爆发 | 暴伤 +30% | 6404/未启用 |
|
||||
| 7021 | 冰冻概率爆发 | 冰冻概率 +5% | 未启用 |
|
||||
| 7022 | 穿透伤害爆发 | 穿透伤害 +30% | 6512 底座 |
|
||||
| 7023 | 防御强化 | 防御 +50,10s | 5014 光环词条(语义存疑①) |
|
||||
| 7024 | 防御爆发 | 防御 +100 | 5008 大招 lv16 |
|
||||
| 7027 | 荆棘 | 固定反伤 10 点,可叠 5 层 | 6514 底座 |
|
||||
| 7028 | 荆棘比例 | 比例反伤 10%,可叠 3 层 | 6515 底座 |
|
||||
| 7029 | 生命爆发 | 最大生命 +30% 并等量回血 | 5013(6517)、5011(6103) |
|
||||
| 7030 | 闪避爆发 | 闪避 +20% | 5014(6100) |
|
||||
| 7031 | 命中强化 | 命中 +20% | 6519 底座 |
|
||||
|
||||
## 附录 C、驻场光环底座表(FieldSkillSet)
|
||||
|
||||
| UUID | 名称 | 类型 | 数值口径 | 使用英雄 |
|
||||
|---|---|---|---|---|
|
||||
| 7001 | 战后恢复 | WaveHeal | 比例(0.2=+20%) | 5008 |
|
||||
| 7002 | 攻击光环 | HeroAtk | 比例(0.15=+15%) | 5007 |
|
||||
| 7003 | 击晕光环 | HeroStun | 比例 | 未启用 |
|
||||
| 7004 | 暴击光环 | HeroCrit | 比例(0.1=+10%) | 5016 |
|
||||
| 7005 | 暴伤光环 | HeroCritDamage | 比例 | 未启用 |
|
||||
| 7006 | 攻速光环 | HeroSpeed | 比例(0.1=+10%) | 5012 |
|
||||
| 7007 | 生命光环 | HeroHp | 比例 | 未启用 |
|
||||
| 7009 | 穿透光环 | HeroPuncture | 比例(0.1=+10%) | 5017 |
|
||||
| 7014 | 召唤光环 | SummonCount | 整数次数 | 未启用 |
|
||||
| 7015 | 死亡光环 | DeadCount | 整数次数 | 未启用 |
|
||||
| 7016 | 鼓舞光环 | StartCount | 整数次数 | 未启用 |
|
||||
| 7018 | 连击光环 | AtkCount | 整数次数 | 未启用 |
|
||||
| 7019 | 受击光环 | BeAtkCount | 整数次数 | 未启用 |
|
||||
| 7020 | 溅射光环 | HeroPunctureDmg | 百分点 | 未启用 |
|
||||
| 7021 | 反伤光环 | HeroThornsDmg | 整数 | 未启用 |
|
||||
| 7022 | 反伤比例光环 | HeroThornsRate | 百分点 | 未启用 |
|
||||
| 7023 | 闪避光环 | HeroDodge | 百分点(10=+10%) | 5014(语义存疑①) |
|
||||
| 7024 | 命中光环 | HeroHit | 百分点(10=+10%) | 5018 |
|
||||
|
||||
> 注:BuffList 与 FieldSkillSet 共用 7000 号段但语义独立——BuffList 是计时/战斗内修饰,FieldSkillSet 是驻场光环元数据;同一 uuid(如 7023/7024)在两表含义不同,配置时需按引用位置区分。
|
||||
6
put/.gitignore
vendored
6
put/.gitignore
vendored
@@ -1,4 +1,8 @@
|
||||
node_modules
|
||||
dist
|
||||
*.local
|
||||
.DS_Store
|
||||
# 环境变量含数据库连接串,禁止提交(参考 server/.env.example)
|
||||
.env
|
||||
# 线上设计版部署所需的产物需提交:web/dist、server/data/meta.json(勿忽略)
|
||||
!web/dist
|
||||
!web/dist/**
|
||||
|
||||
89
put/README.md
Normal file
89
put/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# hero_set — 英雄配置编辑器
|
||||
|
||||
英雄数值/技能/触发/光环的可视化配置工具。数据存储在 **MongoDB**,编辑器产出可写回游戏工程的 `heroSet.ts` 与 `英雄设计表.csv`。
|
||||
|
||||
## 双版本架构
|
||||
|
||||
| | 项目开发版(local) | 线上设计版(remote) |
|
||||
|---|---|---|
|
||||
| 使用人 | 开发者 | 数值设计人员 |
|
||||
| 数据 | 同一个线上 MongoDB | 同一个线上 MongoDB |
|
||||
| meta 底座(技能/Buff) | 从游戏工程 TS 实时编译 | `server/data/meta.json` 快照(需随部署发布) |
|
||||
| 英雄/预设 增删改 | ✅ | ✅(库结构版本更高时只读) |
|
||||
| 写 heroSet.ts / 导出 CSV | ✅ | ❌(接口返回 403,按钮隐藏) |
|
||||
|
||||
**版本闸门**:库中 `meta` 集合存 `schemaVersion`(只升不降)。线上版启动版本低于库版本时,保存接口返回 409、前端禁用保存——防止旧结构覆盖新数据。结构有破坏性变更时:本地 `SCHEMA_VERSION` +1 → `npm run push-meta` → 重新部署线上版。
|
||||
|
||||
## 目录
|
||||
|
||||
```
|
||||
put/
|
||||
├── server/ # Express API(单服务托管 API + 前端静态页)
|
||||
│ ├── index.js # 接口与模式/版本闸门
|
||||
│ ├── db.js # MongoDB 存储层
|
||||
│ ├── data/ # meta.json 快照(remote 模式必需)
|
||||
│ ├── scripts/
|
||||
│ │ ├── migrate-to-mongo.js # 一次性:JSON 文件 → Mongo
|
||||
│ │ └── push-meta.js # 开发版:生成 meta 快照 + 抬升库版本
|
||||
│ └── .env.example
|
||||
└── web/ # Vue3 + Element Plus 前端
|
||||
└── dist/ # 构建产物(remote 模式由 server 托管,需提交)
|
||||
```
|
||||
|
||||
## 部署线上设计版(给 AI 部署智能体)
|
||||
|
||||
前置:Node.js ≥ 18;可访问的 MongoDB 连接串。
|
||||
|
||||
```bash
|
||||
# 1. 拉代码
|
||||
git clone git@git.eoxnet.com:pan/hero_set.git && cd hero_set
|
||||
|
||||
# 2. 配置环境变量
|
||||
cp server/.env.example server/.env
|
||||
# 编辑 server/.env:
|
||||
# MODE=remote
|
||||
# MONGO_URL=mongodb://user:pass@host:27017
|
||||
# MONGO_DB=heroconfig
|
||||
# PORT=3001
|
||||
|
||||
# 3. 构建前端
|
||||
cd web && npm ci && npm run build && cd ..
|
||||
# 注意:web/dist 与 server/data/meta.json 必须存在(均已随仓库提交,若仓库有更新请重新构建)
|
||||
|
||||
# 4. 启动(server 自动托管 web/dist,单服务即可)
|
||||
cd server && npm ci && npm start
|
||||
# 访问 http://<host>:3001
|
||||
```
|
||||
|
||||
进程守护(可选):`pm2 start index.js --name hero-set --cwd server`
|
||||
|
||||
## 开发版工作流(本地)
|
||||
|
||||
```bash
|
||||
# server/.env:MODE=local + 同一个 MONGO_URL
|
||||
cd server && npm ci && npm run dev # API :3001
|
||||
cd web && npm ci && npm run dev # 前端 :4004(已代理 /api → :3001)
|
||||
```
|
||||
|
||||
- **保存到数据库** → 全量覆盖 MongoDB(按 uuid upsert + 删除多余)
|
||||
- **写入 heroSet.ts** → 渲染进 `assets/script/game/common/config/heroSet.ts` 的 `@hero-setup:heroes-begin/end` 标记区域
|
||||
- **技能/底座结构变更后**:`SCHEMA_VERSION` +1 → `npm run push-meta`(需 dev 服务已启动)→ 提交并重新部署线上版
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `MODE` | `local` | `local` 开发版 / `remote` 线上设计版 |
|
||||
| `MONGO_URL` | — | 必填,MongoDB 连接串 |
|
||||
| `MONGO_DB` | `heroconfig` | 库名 |
|
||||
| `PORT` | `3001` | 服务端口 |
|
||||
| `HOST` | `0.0.0.0` | 监听地址;容器部署必须保持 0.0.0.0 并映射端口(如 `docker run -p 3001:3001`) |
|
||||
| `SCHEMA_VERSION` | `1` | 数据结构版本,破坏性变更时 +1 |
|
||||
|
||||
前端单独部署(不经 server 托管)时,`web/.env` 配 `VITE_API_BASE=http://<host>:3001`。
|
||||
|
||||
## 数据库结构(MongoDB · heroconfig)
|
||||
|
||||
- `heroes`:每个英雄一条文档,`uuid` 唯一索引
|
||||
- `templates`:`_id='presets'` 单文档(triggerPresets / fieldPresets)
|
||||
- `meta`:`_id='version'` 单文档(schemaVersion)
|
||||
166
put/package-lock.json
generated
166
put/package-lock.json
generated
@@ -527,6 +527,15 @@
|
||||
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.5.5.tgz",
|
||||
"integrity": "sha512-0PV4mvjGd2Q2/viDvXuoSNzTjQngHvtuWUry1vqvcK4Q/CTeO1zEKMf9set01DMocJnogl/pyNAdvyk+Pe0kQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
|
||||
@@ -975,6 +984,21 @@
|
||||
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/webidl-conversions": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/whatwg-url": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/webidl-conversions": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
@@ -1253,6 +1277,15 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bson": {
|
||||
"version": "6.10.4",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
|
||||
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -1495,6 +1528,21 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "18.0.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.5.tgz",
|
||||
"integrity": "sha512-aBrGvt6KhjxbEnatqMWOMidftwsrCiCvqh1yNIGB9QyF0J+VcWkKDMsvYOEm7ZS82oiV+BT1sTOtIlAmUlTpwQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"dotenv": "dist/index.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -2102,6 +2150,12 @@
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/memory-pager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
@@ -2153,6 +2207,62 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz",
|
||||
"integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mongodb-js/saslprep": "^1.3.0",
|
||||
"bson": "^6.10.4",
|
||||
"mongodb-connection-string-url": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/credential-providers": "^3.188.0",
|
||||
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
|
||||
"gcp-metadata": "^5.2.0",
|
||||
"kerberos": "^2.0.1",
|
||||
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||
"snappy": "^7.3.2",
|
||||
"socks": "^2.7.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/credential-providers": {
|
||||
"optional": true
|
||||
},
|
||||
"@mongodb-js/zstd": {
|
||||
"optional": true
|
||||
},
|
||||
"gcp-metadata": {
|
||||
"optional": true
|
||||
},
|
||||
"kerberos": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-client-encryption": {
|
||||
"optional": true
|
||||
},
|
||||
"snappy": {
|
||||
"optional": true
|
||||
},
|
||||
"socks": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
||||
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/whatwg-url": "^11.0.2",
|
||||
"whatwg-url": "^14.1.0 || ^13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
@@ -2309,6 +2419,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
@@ -2590,6 +2709,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"memory-pager": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
@@ -2669,6 +2797,18 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
@@ -3299,6 +3439,28 @@
|
||||
"resolved": "web",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^5.1.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
@@ -3360,8 +3522,10 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^18.0.5",
|
||||
"esbuild": "^0.24.0",
|
||||
"express": "^4.21.2"
|
||||
"express": "^4.21.2",
|
||||
"mongodb": "^6.12.0"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
|
||||
24
put/server/.env.example
Normal file
24
put/server/.env.example
Normal file
@@ -0,0 +1,24 @@
|
||||
# ---- 运行模式 ----
|
||||
# local = 项目开发版(本地,全功能:写 heroSet.ts / 导出 CSV / push-meta)
|
||||
# remote = 线上设计版(VPS 部署,仅英雄/预设编辑,版本落后时只读)
|
||||
MODE=local
|
||||
|
||||
# ---- MongoDB(两边都要配置,连同一个库)----
|
||||
# 本地 MongoDB 示例:mongodb://127.0.0.1:27017
|
||||
# 带账号密码示例: mongodb://user:password@your-vps-ip:27017
|
||||
# Atlas 示例: mongodb+srv://user:password@cluster0.xxxx.mongodb.net
|
||||
MONGO_URL=mongodb://user:password@your-vps-ip:27017
|
||||
MONGO_DB=heroconfig
|
||||
|
||||
# ---- 服务端口 ----
|
||||
PORT=3001
|
||||
# 监听地址:默认 0.0.0.0(容器/局域网部署必须);仅本机访问可改 127.0.0.1
|
||||
HOST=0.0.0.0
|
||||
|
||||
# ---- 数据结构版本 ----
|
||||
# 英雄/底座结构有破坏性变更时 +1,并执行 npm run push-meta 推送到库中
|
||||
# 线上设计版低于库中版本时将进入只读,需更新部署
|
||||
SCHEMA_VERSION=1
|
||||
|
||||
# ---- 前端(web/.env,仅静态部署时需要;server 托管 dist 时不用配)----
|
||||
# VITE_API_BASE=http://your-vps-ip:3001
|
||||
314
put/server/data/api-skill-guide.md
Normal file
314
put/server/data/api-skill-guide.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# 英雄配置 API 智能体调用规范
|
||||
|
||||
> 本文档面向 **Hermes / OpenClaw** 等智能体 Skill,说明如何通过 HTTP API 配置与修改英雄。
|
||||
> 数据源头为 `heroes.json` / MongoDB,技能/光环/buff 底座定义请阅读 `heroes.json编辑指引.md`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 服务信息
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 开发版地址 | `http://localhost:3001`(`MODE=local`) |
|
||||
| 线上设计版地址 | 部署地址(`MODE=remote`,静态托管 `web/dist`) |
|
||||
| 内容类型 | `application/json` |
|
||||
| 认证 | 无(内网/本地环境) |
|
||||
|
||||
启动服务(在 `put` 目录):
|
||||
```bash
|
||||
npm run dev # 开发版
|
||||
npm run dev:remote # 线上设计版(本地模拟 remote)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心接口一览
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/meta` | 获取底座快照(技能/光环/buff/职业模板/版本) |
|
||||
| `GET` | `/api/heroes` | 获取全部英雄列表 |
|
||||
| `GET` | `/api/heroes/:uuid` | 获取单个英雄完整配置 |
|
||||
| `POST` | `/api/heroes/:uuid` | **新增或覆盖**单个英雄(body 为完整英雄对象) |
|
||||
| `PUT` | `/api/heroes/:uuid` | **局部更新**单个英雄(body 为需修改的字段,顶层浅合并) |
|
||||
| `PATCH` | `/api/heroes/:uuid/skills/:skillUuid` | **技能级局部更新**:支持按 `lv` 合并 `lv_entries`,避免整表替换 `skills` |
|
||||
| `DELETE` | `/api/heroes/:uuid` | 删除单个英雄 |
|
||||
| `POST` | `/api/refresh` | 刷新底座缓存(修改 SkillSet.ts 等后调用) |
|
||||
|
||||
> **版本闸门**:线上设计版(remote)在库中 `schemaVersion` 高于服务内置版本时,写操作返回 `409`,需更新部署。
|
||||
|
||||
---
|
||||
|
||||
## 3. 英雄对象结构(Hero Schema)
|
||||
|
||||
智能体在构造或修改英雄时,必须遵循以下字段规范。
|
||||
|
||||
### 3.1 顶层字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `uuid` | number | 是 | 英雄唯一 ID,5000 段,新增从 5011 起(先 `GET /api/heroes` 查重) |
|
||||
| `name` | string | 是 | 显示名 |
|
||||
| `comment` | string | 否 | 设计说明/机制主题 |
|
||||
| `path` | string | 是 | 美术资源名,需存在 `assets/resources/game/heros/{path}.prefab` |
|
||||
| `rarity` | 1~5 | 是 | 稀有度,影响 lv1 基础属性缩放与卡池费用 |
|
||||
| `roleKey` | string | 是 | 职业:`Tank`/`Warrior`/`Assassin`/`Archer`/`Mage`/`Support` |
|
||||
| `growKey` | string | 是 | 成长曲线,通常与 `roleKey` 一致 |
|
||||
| `typeKey` | string | 是 | 攻击定位:`Melee`/`Mid`/`Long` |
|
||||
| `statBase` | `{hp,ap,def}` | 是 | 相对职业模板的偏移量,惯例 ±5/10/20/30 |
|
||||
| `dis` / `speed` / `dodge` / `accuracy` | number\|null | 否 | 英雄一般 `null`,走默认 |
|
||||
| `skills` | array | 是 | **恰好 2 个**:`[普攻, 大招]`,见 §3.2 |
|
||||
| `atking`/`atked`/`fstart`/`fend`/`dead` | object | 是 | 触发组,见 §3.3;无则写 `{}` |
|
||||
| `field` | array | 是 | 驻场光环档位,见 §3.4;无则写 `[]` |
|
||||
| `bonus` | array | 是 | 额外属性加成,见 §3.5;常规英雄写 `[]` |
|
||||
| `info` | string | 否 | 描述文案 |
|
||||
|
||||
### 3.2 skills 数组(恰好 2 项)
|
||||
|
||||
每项结构:`{uuid, name, cd, cdExpr, overrides, desc, lv_entries}`
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `uuid` | 技能底座 uuid,普攻必须 ∈ `ROLE_ATK_SKILL[roleKey]`,大招 ∈ `ROLE_ULT_SKILL[roleKey]` 或 `SUPPORT_ULT` |
|
||||
| `name` | 留空回退底座名;建议起特色名 |
|
||||
| `cd` | 数值冷却(普攻 0.75/0.9/1.05/1.2/1.5;大招 6/7/8/9/10) |
|
||||
| `cdExpr` | 表达式,普攻=`AtkSpeedSet[AtkSpeedLv.X].cd`,大招=`UltCdSet[UltCdLv.X].cd` |
|
||||
| `overrides` | 覆写键值对,见 §3.6 |
|
||||
| `desc` | 描述模板,支持 `{target}` `{ap}` `{dur}` 等占位符 |
|
||||
| `lv_entries` | 升级档位:普攻 1 档 `{lv:2,s_lv:2,...}`;大招 2 档 `{lv:8,s_lv:2,...}`、`{lv:16,s_lv:3,...}` |
|
||||
|
||||
### 3.3 触发组(atking / atked / fstart / fend / dead)
|
||||
|
||||
结构:`{ "<技能uuid字符串>": [条目, ...] }`
|
||||
|
||||
条目:`{lv, s_lv, name, t_num, t_chance?, pity_add?, overrides, desc}`
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `lv` | 解锁等级,惯例 **4** |
|
||||
| `s_lv` | 技能等级,惯例 **1** |
|
||||
| `t_num` | 触发间隔:atking/atked 为每 n 次;fstart/fend 固定 **1** |
|
||||
| `t_chance` | 触发概率(0~100),仅 atking/atked 生效,缺省=100 |
|
||||
| `pity_add` | 保底叠加,仅 atking/atked 生效,触发后清零 |
|
||||
| `overrides` | 同技能覆写 |
|
||||
| `desc` | 描述模板,支持 `{t_chance}` `{pity_add}` 占位符 |
|
||||
|
||||
**复活**:在 `dead` 组条目 `overrides` 中配置 `revive_hp`(回血%)+ `revive_count`(次数上限)。
|
||||
|
||||
### 3.4 field 光环
|
||||
|
||||
结构:`[{lv:4, uuids:[{uuid, value, name}], desc?}]`
|
||||
|
||||
- `uuid` 必须 ∈ `FieldSkillSet`(7001~7022 段)
|
||||
- `value` 按光环类型:比例类为小数(如 0.15),次数/固定值类为整数
|
||||
- 解锁等级惯例 **lv4**
|
||||
|
||||
### 3.5 bonus 额外属性
|
||||
|
||||
结构:`[{lv, attrs:{...}, name?, icon?, info?}]`
|
||||
|
||||
- `attrs` 键:`hp`/`ap`/`def`(百分比,基数=基础×品质+成长)、`crt`/`frz`/`stun`/`para`(固定百分点)
|
||||
- 运行时累加所有 `lv ≤ 英雄等级` 的档位
|
||||
- 配 `icon` 的档位在图鉴技能列表最末展示
|
||||
|
||||
### 3.6 overrides 白名单
|
||||
|
||||
可用键:
|
||||
`ap, hit_count, hitcd, crt, frz, stun, para, bck, bck_chance, TGroup, buff_type, timed_buff_id, buff_value, buff_duration, buff_chance, buff_target, timed_debuff_id, debuff_value, debuff_duration, debuff_chance, call_hero, summon_count, is_accel, num, revive_hp, revive_count, proc_skill, icon`
|
||||
|
||||
特殊格式:
|
||||
- `TGroup`:字符串 `"Self"`/`"Ally"`/`"Team"`/`"Enemy"`,导出还原枚举
|
||||
- `buff_target`:数字 0~3(Hit/Self/Ally/Enemy)
|
||||
- `proc_skill`:对象 `{s_uuid, chance?, overrides?}`
|
||||
- `icon`:字符串图标帧名
|
||||
|
||||
**buff/debuff 语义**:
|
||||
- `timed_buff_id`:增益,缺省挂施法者自身(伤害技能)或友方目标(辅助技能)
|
||||
- `timed_debuff_id`:减益,命中后挂被命中者,目标不可覆写
|
||||
- 对敌挂减益必须用 `timed_debuff_id`,误用 `timed_buff_id` 会把减益挂到自己身上
|
||||
|
||||
---
|
||||
|
||||
## 4. 调用示例
|
||||
|
||||
### 4.1 获取底座数据(修改前必读)
|
||||
|
||||
```http
|
||||
GET /api/meta
|
||||
```
|
||||
|
||||
返回包含 `SkillSet`、`FieldSkillSet`、`BuffList`、`HERO_ROLE_BASE`、`HERO_GROW_COEF`、`AtkSpeedSet`、`HeroDisVal` 等。
|
||||
|
||||
### 4.2 查询全部英雄
|
||||
|
||||
```http
|
||||
GET /api/heroes
|
||||
```
|
||||
|
||||
### 4.3 查询单个英雄
|
||||
|
||||
```http
|
||||
GET /api/heroes/5001
|
||||
```
|
||||
|
||||
### 4.4 新增英雄(完整对象)
|
||||
|
||||
```http
|
||||
POST /api/heroes/5011
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"uuid": 5011,
|
||||
"name": "烈焰法师",
|
||||
"comment": "群体火伤+燃烧",
|
||||
"path": "mage_fire",
|
||||
"rarity": 4,
|
||||
"roleKey": "Mage",
|
||||
"growKey": "Mage",
|
||||
"typeKey": "Long",
|
||||
"statBase": { "hp": -5, "ap": 30, "def": 0 },
|
||||
"dis": null,
|
||||
"speed": null,
|
||||
"dodge": null,
|
||||
"accuracy": null,
|
||||
"skills": [
|
||||
{
|
||||
"uuid": 6009,
|
||||
"name": "火球术",
|
||||
"cd": 1.2,
|
||||
"cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd",
|
||||
"overrides": { "ap": 120 },
|
||||
"desc": "对{target}造成{ap}%伤害",
|
||||
"lv_entries": [
|
||||
{ "lv": 2, "s_lv": 2, "overrides": { "ap": 150 }, "desc": "造成{ap}%伤害" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"uuid": 6200,
|
||||
"name": "烈焰风暴",
|
||||
"cd": 8,
|
||||
"cdExpr": "UltCdSet[UltCdLv.Normal].cd",
|
||||
"overrides": { "ap": 200, "timed_debuff_id": 7002, "debuff_value": 30, "debuff_chance": 50 },
|
||||
"desc": "对全体敌人造成{ap}%伤害,{debuff_chance}%附加燃烧",
|
||||
"lv_entries": [
|
||||
{ "lv": 8, "s_lv": 2, "overrides": { "ap": 250 }, "desc": "造成{ap}%伤害" },
|
||||
{ "lv": 16, "s_lv": 3, "overrides": { "ap": 350 }, "desc": "造成{ap}%伤害" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"atking": {},
|
||||
"atked": {},
|
||||
"fstart": {},
|
||||
"fend": {},
|
||||
"dead": {},
|
||||
"field": [],
|
||||
"bonus": [],
|
||||
"info": "掌控火焰的法师"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 局部修改英雄(如只改大招数值)
|
||||
|
||||
```http
|
||||
PUT /api/heroes/5011
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"skills": [
|
||||
{ "uuid": 6009, "name": "火球术", "cd": 1.2, "cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd", "overrides": { "ap": 120 }, "desc": "对{target}造成{ap}%伤害", "lv_entries": [{ "lv": 2, "s_lv": 2, "overrides": { "ap": 150 }, "desc": "造成{ap}%伤害" }] },
|
||||
{ "uuid": 6200, "name": "烈焰风暴", "cd": 8, "cdExpr": "UltCdSet[UltCdLv.Normal].cd", "overrides": { "ap": 250, "timed_debuff_id": 7002, "debuff_value": 50, "debuff_chance": 80 }, "desc": "对全体敌人造成{ap}%伤害,{debuff_chance}%附加燃烧", "lv_entries": [{ "lv": 8, "s_lv": 2, "overrides": { "ap": 300 }, "desc": "造成{ap}%伤害" }, { "lv": 16, "s_lv": 3, "overrides": { "ap": 400 }, "desc": "造成{ap}%伤害" }] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:`PUT` 是**顶层字段浅合并**。若修改 `skills` 数组,必须传入**完整的新数组**(不能只传被修改的技能对象)。若只需改某个技能的局部字段,请用 `PATCH /api/heroes/:uuid/skills/:skillUuid`(见 §4.7)。
|
||||
|
||||
### 4.6 删除英雄
|
||||
|
||||
```http
|
||||
DELETE /api/heroes/5011
|
||||
```
|
||||
|
||||
### 4.7 技能级局部更新(推荐智能体使用)
|
||||
|
||||
当只需要修改某个技能的局部字段(如调整 lv8 档位的 ap 值)时,使用技能级 PATCH,避免整表替换 `skills` 数组。
|
||||
|
||||
**合并规则**:
|
||||
- `name`/`cd`/`cdExpr`/`desc`:存在则直接覆盖
|
||||
- `overrides`:与现有 `overrides` **浅合并**(不是替换)
|
||||
- `lv_entries`:按 `lv` 合并——同 `lv` 更新该档(`overrides` 浅合并),不同 `lv` 新增
|
||||
|
||||
```http
|
||||
PATCH /api/heroes/5011/skills/6200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"overrides": { "ap": 280 },
|
||||
"lv_entries": [
|
||||
{ "lv": 8, "overrides": { "ap": 320 } },
|
||||
{ "lv": 20, "s_lv": 4, "overrides": { "ap": 500 }, "desc": "造成{ap}%伤害" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**效果**:
|
||||
- 基础 `overrides.ap` 从 250 变为 280
|
||||
- `lv_entries` 中 `lv:8` 档的 `overrides.ap` 从 300 变为 320(其他字段保留)
|
||||
- `lv_entries` 中新增 `lv:20` 档
|
||||
|
||||
**对比:三种修改方式的选择**
|
||||
|
||||
| 场景 | 推荐接口 | 原因 |
|
||||
|---|---|---|
|
||||
| 只改英雄名/稀有度/statBase 等顶层字段 | `PUT /api/heroes/:uuid` | 简单直接,不涉及 skills |
|
||||
| 改某个技能的 name/cd/desc/overrides 或某一档 lv_entry | `PATCH /api/heroes/:uuid/skills/:skillUuid` | 避免整表替换 skills,最安全 |
|
||||
| 新增/删除技能、改技能 uuid、大规模重构 | `POST /api/heroes/:uuid`(全量覆盖) | 结构变动大,整表替换更清晰 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 智能体操作流程(推荐)
|
||||
|
||||
1. **读取底座**:`GET /api/meta`,确认技能/光环/buff 可用值与职业模板
|
||||
2. **查询现有英雄**:`GET /api/heroes`,确定新 uuid 不冲突(当前已用 5001~5010)
|
||||
3. **构造英雄对象**:严格遵循 §3 结构,空组写 `{}`,空数组写 `[]`
|
||||
4. **提交**:
|
||||
- 新增/覆盖:`POST /api/heroes/:uuid`
|
||||
- 顶层字段局部修改:`PUT /api/heroes/:uuid`
|
||||
- 技能级局部修改:`PATCH /api/heroes/:uuid/skills/:skillUuid`
|
||||
5. **验证**:`GET /api/heroes/:uuid` 确认数据已写入
|
||||
6. **(开发版)应用**:`POST /api/apply` 将 heroes.json 渲染进 `heroSet.ts`(线上设计版无此权限)
|
||||
|
||||
---
|
||||
|
||||
## 6. 错误码与处理
|
||||
|
||||
| 状态码 | 场景 | 处理 |
|
||||
|---|---|---|
|
||||
| 400 | 参数错误(uuid 非数字、body 非对象/数组、heroes 为空等) | 检查请求格式 |
|
||||
| 403 | 线上设计版调用本地专属接口(如 `/api/apply`) | 切换至开发版或放弃该操作 |
|
||||
| 404 | 英雄不存在 | 先 `GET /api/heroes` 确认 uuid |
|
||||
| 409 | 线上设计版结构版本落后 | 更新部署后再试 |
|
||||
| 500 | 服务端错误(MongoDB 连接失败、文件读写失败等) | 查看服务日志 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见陷阱(智能体必读)
|
||||
|
||||
- **不要手改 `heroSet.ts` 英雄区**:`POST /api/apply` 会整体覆盖标记区内容
|
||||
- **新增英雄后必须手动把 uuid 加入 `HeroList`**(`heroSet.ts` 标记区之外),否则英雄不进卡池
|
||||
- **触发组解锁等级是 lv4,大招档位是 lv8+lv16**,不要写 lv6/lv9
|
||||
- **普攻 cdExpr 用 `AtkSpeedSet`,大招 cdExpr 用 `UltCdSet`**,两套体系不可混用
|
||||
- **档位 overrides 与基础 overrides 浅合并**,档位只写变化键,不要全量复制
|
||||
- **desc 用占位符 `{ap}`**,不要硬编码数字,避免与 overrides 数值脱节
|
||||
- **对敌 debuff 用 `timed_debuff_id`,吸血/自强化用 `timed_buff_id`**,两者目标语义相反
|
||||
- **复活已废弃独立 `revive` 字段**,改在 `dead` 组 overrides 配 `revive_hp`/`revive_count`
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考文档
|
||||
|
||||
- 详细字段规则与数据源:`put/server/data/heroes.json编辑指引.md`
|
||||
- 编辑器枚举与默认值:`put/web/src/constants.js`
|
||||
- 技能底座定义:`assets/script/game/common/config/SkillSet.ts`
|
||||
- Buff 底座定义:`assets/script/game/common/config/BuffSet.ts`
|
||||
- 职业模板与成长:`assets/script/game/common/config/heroSet.ts`
|
||||
1
put/server/data/meta.json
Normal file
1
put/server/data/meta.json
Normal file
File diff suppressed because one or more lines are too long
75
put/server/db.js
Normal file
75
put/server/db.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 英雄/预设存储层:开发版与线上设计版统一读写线上 MongoDB。
|
||||
* - heroes 集合:每个英雄一条文档,uuid 唯一索引
|
||||
* - templates 集合:预设库(_id='presets' 单文档)
|
||||
* - meta 集合:版本闸门(_id='version' 单文档,schemaVersion 由开发版推送 meta 时写入)
|
||||
* 技能/Buff 底座不进数据库,开发版走 TS 编译,线上版走 meta.json 快照。
|
||||
*/
|
||||
import { MongoClient } from 'mongodb'
|
||||
|
||||
const MONGO_URL = process.env.MONGO_URL || ''
|
||||
const DB_NAME = process.env.MONGO_DB || 'heroconfig'
|
||||
|
||||
let client = null
|
||||
let db = null
|
||||
|
||||
/** 惰性连接 MongoDB(单例复用) */
|
||||
async function mongo() {
|
||||
if (db) return db
|
||||
if (!MONGO_URL) throw new Error('未配置 MONGO_URL 环境变量')
|
||||
client = new MongoClient(MONGO_URL)
|
||||
await client.connect()
|
||||
db = client.db(DB_NAME)
|
||||
// uuid 唯一索引:保证英雄按 uuid 幂等覆盖
|
||||
await db.collection('heroes').createIndex({ uuid: 1 }, { unique: true })
|
||||
return db
|
||||
}
|
||||
|
||||
/** 读取全部英雄(按 uuid 升序,剥离 Mongo 内部 _id) */
|
||||
export async function getHeroes() {
|
||||
const docs = await (await mongo()).collection('heroes').find().sort({ uuid: 1 }).toArray()
|
||||
return docs.map(({ _id, ...h }) => h)
|
||||
}
|
||||
|
||||
/** 全量覆盖:upsert 传入英雄,并删除库中多余的 uuid */
|
||||
export async function saveHeroes(heroes) {
|
||||
const col = (await mongo()).collection('heroes')
|
||||
for (const h of heroes) {
|
||||
await col.replaceOne({ uuid: h.uuid }, h, { upsert: true })
|
||||
}
|
||||
await col.deleteMany({ uuid: { $nin: heroes.map((h) => h.uuid) } })
|
||||
}
|
||||
|
||||
/** 读取预设库(无文档时返回空池) */
|
||||
export async function getTemplates() {
|
||||
const doc = await (await mongo()).collection('templates').findOne({ _id: 'presets' })
|
||||
return {
|
||||
triggerPresets: Array.isArray(doc?.triggerPresets) ? doc.triggerPresets : [],
|
||||
fieldPresets: Array.isArray(doc?.fieldPresets) ? doc.fieldPresets : [],
|
||||
}
|
||||
}
|
||||
|
||||
/** 全量覆盖预设库 */
|
||||
export async function saveTemplates(triggerPresets, fieldPresets) {
|
||||
await (await mongo())
|
||||
.collection('templates')
|
||||
.replaceOne({ _id: 'presets' }, { _id: 'presets', triggerPresets, fieldPresets }, { upsert: true })
|
||||
}
|
||||
|
||||
/** 读取库中数据结构版本(首次为 0) */
|
||||
export async function getSchemaVersion() {
|
||||
const doc = await (await mongo()).collection('meta').findOne({ _id: 'version' })
|
||||
return doc?.schemaVersion ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送数据结构版本:只允许升不允许降,避免旧部署误把版本刷低导致线上版解锁写入。
|
||||
* @returns {Promise<number>} 写入后的实际版本
|
||||
*/
|
||||
export async function bumpSchemaVersion(version) {
|
||||
const col = (await mongo()).collection('meta')
|
||||
const cur = await getSchemaVersion()
|
||||
const next = Math.max(cur, version)
|
||||
await col.replaceOne({ _id: 'version' }, { _id: 'version', schemaVersion: next, updatedAt: new Date() }, { upsert: true })
|
||||
return next
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
/**
|
||||
* 英雄配置工具服务(双模式)
|
||||
* - 项目开发版(MODE=local):全功能,meta 从游戏工程 TS 编译,可写 heroSet.ts / 导出 CSV / 推送快照
|
||||
* - 线上设计版(MODE=remote):meta 从 data/meta.json 快照加载,仅英雄/预设的增删改查;
|
||||
* 库中 schemaVersion 高于本服务内置版本时进入只读(保存接口返回 409),需更新部署后才能修改
|
||||
*/
|
||||
import 'dotenv/config'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import esbuild from 'esbuild'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import url from 'node:url'
|
||||
import * as store from './db.js'
|
||||
|
||||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(__dirname, '..', '..')
|
||||
@@ -14,7 +22,11 @@ const CSV_PATH = path.join(CONFIG_DIR, '英雄设计表.csv')
|
||||
const HEROES_BEGIN = '// @hero-setup:heroes-begin'
|
||||
const HEROES_END = '// @hero-setup:heroes-end'
|
||||
const DATA_DIR = path.join(__dirname, 'data')
|
||||
const HEROES_PATH = path.join(DATA_DIR, 'heroes.json')
|
||||
const META_SNAPSHOT_PATH = path.join(DATA_DIR, 'meta.json')
|
||||
|
||||
const MODE = process.env.MODE || 'local'
|
||||
/** 数据结构版本:英雄/底座结构有破坏性变更时 +1,并随 push-meta 推送到库中 */
|
||||
const SCHEMA_VERSION = Number(process.env.SCHEMA_VERSION || 1)
|
||||
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
@@ -43,6 +55,14 @@ async function loadConfigModule(file) {
|
||||
let cache = null
|
||||
|
||||
async function buildCache() {
|
||||
// 线上设计版:从快照文件加载(快照由开发版 scripts/push-meta.js 生成并随部署发布)
|
||||
if (MODE === 'remote') {
|
||||
if (!fs.existsSync(META_SNAPSHOT_PATH)) {
|
||||
throw new Error('缺少 data/meta.json 快照,请先在开发版执行 npm run push-meta 并重新部署')
|
||||
}
|
||||
cache = JSON.parse(fs.readFileSync(META_SNAPSHOT_PATH, 'utf8'))
|
||||
return cache
|
||||
}
|
||||
const [skill, buff, hero] = await Promise.all([
|
||||
loadConfigModule('SkillSet.ts'),
|
||||
loadConfigModule('BuffSet.ts'),
|
||||
@@ -63,7 +83,12 @@ async function buildCache() {
|
||||
app.get('/api/meta', async (_req, res) => {
|
||||
try {
|
||||
if (!cache) await buildCache()
|
||||
res.json(cache)
|
||||
res.json({
|
||||
...cache,
|
||||
mode: MODE,
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
dbSchemaVersion: await store.getSchemaVersion(),
|
||||
})
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
@@ -78,51 +103,209 @@ app.post('/api/refresh', async (_req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
/** 英雄数据持久化:GET 读取(无文件时返回空列表) */
|
||||
app.get('/api/heroes', (_req, res) => {
|
||||
/** 版本闸门:线上设计版落后于库中结构版本时,写操作返回 409 强制只读 */
|
||||
async function guardVersion(res) {
|
||||
if (MODE !== 'remote') return true
|
||||
const dbVer = await store.getSchemaVersion()
|
||||
if (dbVer > SCHEMA_VERSION) {
|
||||
res.status(409).json({
|
||||
error: `线上设计版结构版本(v${SCHEMA_VERSION})低于数据库(v${dbVer}),请更新部署后再修改`,
|
||||
dbSchemaVersion: dbVer,
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 英雄数据:GET 从线上库读取 */
|
||||
app.get('/api/heroes', async (_req, res) => {
|
||||
try {
|
||||
if (!fs.existsSync(HEROES_PATH)) return res.json({ heroes: [] })
|
||||
res.json(JSON.parse(fs.readFileSync(HEROES_PATH, 'utf8')))
|
||||
res.json({ heroes: await store.getHeroes() })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** 英雄数据持久化:POST 全量覆盖保存 */
|
||||
app.post('/api/heroes', (req, res) => {
|
||||
/** 英雄数据:POST 全量覆盖写入线上库 */
|
||||
app.post('/api/heroes', async (req, res) => {
|
||||
const heroes = req.body?.heroes
|
||||
if (!Array.isArray(heroes)) {
|
||||
return res.status(400).json({ error: 'heroes 必须为数组' })
|
||||
}
|
||||
try {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true })
|
||||
fs.writeFileSync(HEROES_PATH, JSON.stringify({ heroes }, null, 2), 'utf8')
|
||||
res.json({ ok: true, path: HEROES_PATH, count: heroes.length })
|
||||
if (!(await guardVersion(res))) return
|
||||
await store.saveHeroes(heroes)
|
||||
res.json({ ok: true, count: heroes.length })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
const TEMPLATES_PATH = path.join(DATA_DIR, 'templates.json')
|
||||
|
||||
/** 预设库持久化:GET 读取(无文件时返回空池) */
|
||||
app.get('/api/templates', (_req, res) => {
|
||||
/** 单英雄读取:GET /api/heroes/:uuid */
|
||||
app.get('/api/heroes/:uuid', async (req, res) => {
|
||||
try {
|
||||
if (!fs.existsSync(TEMPLATES_PATH)) return res.json({ triggerPresets: [], fieldPresets: [] })
|
||||
const data = JSON.parse(fs.readFileSync(TEMPLATES_PATH, 'utf8'))
|
||||
// 兼容旧格式:若根上是 templates 数组,返回空池让前端走默认
|
||||
if (Array.isArray(data.templates)) return res.json({ triggerPresets: [], fieldPresets: [] })
|
||||
res.json({
|
||||
triggerPresets: Array.isArray(data.triggerPresets) ? data.triggerPresets : [],
|
||||
fieldPresets: Array.isArray(data.fieldPresets) ? data.fieldPresets : [],
|
||||
})
|
||||
const uuid = Number(req.params.uuid)
|
||||
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
|
||||
const heroes = await store.getHeroes()
|
||||
const hero = heroes.find((h) => h.uuid === uuid)
|
||||
if (!hero) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
|
||||
res.json(hero)
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** 职业模板持久化:POST 全量覆盖保存(结构:{ triggerPresets: [], fieldPresets: [] }) */
|
||||
app.post('/api/templates', (req, res) => {
|
||||
/** 单英雄新增/覆盖:POST /api/heroes/:uuid(body 为完整英雄对象,uuid 以 URL 为准) */
|
||||
app.post('/api/heroes/:uuid', async (req, res) => {
|
||||
try {
|
||||
if (!(await guardVersion(res))) return
|
||||
const uuid = Number(req.params.uuid)
|
||||
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
|
||||
const hero = req.body
|
||||
if (!hero || typeof hero !== 'object') return res.status(400).json({ error: 'body 必须为英雄对象' })
|
||||
hero.uuid = uuid
|
||||
const heroes = await store.getHeroes()
|
||||
const idx = heroes.findIndex((h) => h.uuid === uuid)
|
||||
if (idx >= 0) heroes[idx] = hero
|
||||
else heroes.push(hero)
|
||||
await store.saveHeroes(heroes)
|
||||
res.json({ ok: true, uuid, action: idx >= 0 ? 'updated' : 'created' })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** 单英雄局部更新:PUT /api/heroes/:uuid(body 为部分字段,与现有英雄浅合并) */
|
||||
app.put('/api/heroes/:uuid', async (req, res) => {
|
||||
try {
|
||||
if (!(await guardVersion(res))) return
|
||||
const uuid = Number(req.params.uuid)
|
||||
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
|
||||
const patch = req.body
|
||||
if (!patch || typeof patch !== 'object') return res.status(400).json({ error: 'body 必须为部分字段对象' })
|
||||
const heroes = await store.getHeroes()
|
||||
const idx = heroes.findIndex((h) => h.uuid === uuid)
|
||||
if (idx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
|
||||
// uuid 不可变,防止 patch 覆盖
|
||||
delete patch.uuid
|
||||
heroes[idx] = { ...heroes[idx], ...patch }
|
||||
await store.saveHeroes(heroes)
|
||||
res.json({ ok: true, uuid, action: 'patched' })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** 单英雄删除:DELETE /api/heroes/:uuid */
|
||||
app.delete('/api/heroes/:uuid', async (req, res) => {
|
||||
try {
|
||||
if (!(await guardVersion(res))) return
|
||||
const uuid = Number(req.params.uuid)
|
||||
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
|
||||
const heroes = await store.getHeroes()
|
||||
const idx = heroes.findIndex((h) => h.uuid === uuid)
|
||||
if (idx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
|
||||
heroes.splice(idx, 1)
|
||||
await store.saveHeroes(heroes)
|
||||
res.json({ ok: true, uuid, action: 'deleted' })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 技能级局部更新:PATCH /api/heroes/:uuid/skills/:skillUuid
|
||||
* 支持对技能字段及 lv_entries 按 lv 合并,避免整表替换 skills 数组。
|
||||
*
|
||||
* body 格式:
|
||||
* {
|
||||
* name?, cd?, cdExpr?, desc?, // 技能基础字段,存在则覆盖
|
||||
* overrides?, // 与现有 overrides 浅合并(不是替换)
|
||||
* lv_entries?: [ // 按 lv 合并:存在则更新该档,不存在则新增
|
||||
* { lv, s_lv?, overrides?, desc? }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
app.patch('/api/heroes/:uuid/skills/:skillUuid', async (req, res) => {
|
||||
try {
|
||||
if (!(await guardVersion(res))) return
|
||||
const uuid = Number(req.params.uuid)
|
||||
const skillUuid = Number(req.params.skillUuid)
|
||||
if (Number.isNaN(uuid) || Number.isNaN(skillUuid)) {
|
||||
return res.status(400).json({ error: 'uuid 与 skillUuid 必须为数字' })
|
||||
}
|
||||
const patch = req.body
|
||||
if (!patch || typeof patch !== 'object') {
|
||||
return res.status(400).json({ error: 'body 必须为技能补丁对象' })
|
||||
}
|
||||
|
||||
const heroes = await store.getHeroes()
|
||||
const hIdx = heroes.findIndex((h) => h.uuid === uuid)
|
||||
if (hIdx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
|
||||
|
||||
const hero = heroes[hIdx]
|
||||
const sIdx = hero.skills.findIndex((s) => s.uuid === skillUuid)
|
||||
if (sIdx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在技能 ${skillUuid}` })
|
||||
|
||||
const skill = hero.skills[sIdx]
|
||||
|
||||
// 基础字段直接覆盖
|
||||
if (patch.name !== undefined) skill.name = patch.name
|
||||
if (patch.cd !== undefined) skill.cd = patch.cd
|
||||
if (patch.cdExpr !== undefined) skill.cdExpr = patch.cdExpr
|
||||
if (patch.desc !== undefined) skill.desc = patch.desc
|
||||
|
||||
// overrides 浅合并(不是替换)
|
||||
if (patch.overrides && typeof patch.overrides === 'object') {
|
||||
skill.overrides = { ...skill.overrides, ...patch.overrides }
|
||||
}
|
||||
|
||||
// lv_entries 按 lv 合并:同 lv 更新,不同 lv 新增
|
||||
if (Array.isArray(patch.lv_entries)) {
|
||||
if (!Array.isArray(skill.lv_entries)) skill.lv_entries = []
|
||||
for (const entry of patch.lv_entries) {
|
||||
if (!entry || typeof entry !== 'object') continue
|
||||
const lv = entry.lv
|
||||
if (lv === undefined) continue
|
||||
const eIdx = skill.lv_entries.findIndex((e) => e.lv === lv)
|
||||
if (eIdx >= 0) {
|
||||
// 同 lv:浅合并 overrides,覆盖其他字段
|
||||
const existing = skill.lv_entries[eIdx]
|
||||
skill.lv_entries[eIdx] = {
|
||||
...existing,
|
||||
...entry,
|
||||
overrides: entry.overrides
|
||||
? { ...existing.overrides, ...entry.overrides }
|
||||
: existing.overrides,
|
||||
}
|
||||
} else {
|
||||
// 新 lv:直接插入
|
||||
skill.lv_entries.push(entry)
|
||||
}
|
||||
}
|
||||
// 按 lv 升序排序,保持数据整洁
|
||||
skill.lv_entries.sort((a, b) => a.lv - b.lv)
|
||||
}
|
||||
|
||||
await store.saveHeroes(heroes)
|
||||
res.json({ ok: true, uuid, skillUuid, action: 'skill_patched' })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** 预设库:GET 从线上库读取 */
|
||||
app.get('/api/templates', async (_req, res) => {
|
||||
try {
|
||||
res.json(await store.getTemplates())
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** 预设库:POST 全量覆盖写入线上库(结构:{ triggerPresets: [], fieldPresets: [] }) */
|
||||
app.post('/api/templates', async (req, res) => {
|
||||
const body = req.body || {}
|
||||
const triggerPresets = body.triggerPresets
|
||||
const fieldPresets = body.fieldPresets
|
||||
@@ -136,9 +319,9 @@ app.post('/api/templates', (req, res) => {
|
||||
return res.status(400).json({ error: `触发预设 ${bad.id || bad.label} 的 group 非法:${bad.group}` })
|
||||
}
|
||||
try {
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true })
|
||||
fs.writeFileSync(TEMPLATES_PATH, JSON.stringify({ triggerPresets, fieldPresets }, null, 2), 'utf8')
|
||||
res.json({ ok: true, path: TEMPLATES_PATH, triggerCount: triggerPresets.length, fieldCount: fieldPresets.length })
|
||||
if (!(await guardVersion(res))) return
|
||||
await store.saveTemplates(triggerPresets, fieldPresets)
|
||||
res.json({ ok: true, triggerCount: triggerPresets.length, fieldCount: fieldPresets.length })
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
@@ -425,8 +608,16 @@ function renderHeroCsv(heroes) {
|
||||
return rows.map((r) => r.map(csvCell).join(',')).join('\n') + '\n'
|
||||
}
|
||||
|
||||
/** 仅开发版可用的本地文件写操作(heroSet.ts / CSV),线上设计版调用返回 403 */
|
||||
function localOnly(res) {
|
||||
if (MODE === 'local') return true
|
||||
res.status(403).json({ error: '线上设计版不支持此操作(写 heroSet.ts / 导出 CSV 请在项目开发版执行)' })
|
||||
return false
|
||||
}
|
||||
|
||||
app.post('/api/export-csv', async (req, res) => {
|
||||
try {
|
||||
if (!localOnly(res)) return
|
||||
const heroes = req.body?.heroes
|
||||
if (!Array.isArray(heroes) || !heroes.length) {
|
||||
return res.status(400).json({ error: 'heroes 不能为空' })
|
||||
@@ -440,12 +631,25 @@ app.post('/api/export-csv', async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/export-csv', (_req, res) => {
|
||||
app.get('/api/export-csv', (req, res) => {
|
||||
if (!localOnly(res)) return
|
||||
if (!fs.existsSync(CSV_PATH)) return res.status(404).json({ error: '尚未生成' })
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8')
|
||||
res.send(fs.readFileSync(CSV_PATH, 'utf8'))
|
||||
})
|
||||
|
||||
/** 导出 meta 快照(开发版专用):供 push-meta 脚本发布到线上设计版 */
|
||||
app.get('/api/meta-snapshot', async (_req, res) => {
|
||||
try {
|
||||
if (!localOnly(res)) return
|
||||
if (!cache) await buildCache()
|
||||
const { mode: _m, schemaVersion: _v, dbSchemaVersion: _d, ...snapshot } = cache
|
||||
res.json(snapshot)
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: String(e?.message || e) })
|
||||
}
|
||||
})
|
||||
|
||||
/** HeroList 定义匹配(用于导出时同步图鉴/卡池数据源) */
|
||||
const HERO_LIST_RE = /export const HeroList: number\[\] = \[[\s\S]*?\n\];/
|
||||
|
||||
@@ -470,6 +674,7 @@ function syncHeroList(src, heroes) {
|
||||
|
||||
app.post('/api/apply', (req, res) => {
|
||||
try {
|
||||
if (!localOnly(res)) return
|
||||
const heroes = req.body?.heroes
|
||||
if (!Array.isArray(heroes) || !heroes.length) {
|
||||
return res.status(400).json({ error: 'heroes 不能为空' })
|
||||
@@ -491,7 +696,19 @@ app.post('/api/apply', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
const PORT = 3001
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[hero-config-tool] server listening http://localhost:${PORT}`)
|
||||
const PORT = Number(process.env.PORT || 3001)
|
||||
// 默认监听所有网卡,保证容器/局域网端口映射可用;可用 HOST=127.0.0.1 限制为本机
|
||||
const HOST = process.env.HOST || '0.0.0.0'
|
||||
|
||||
// 线上设计版:托管前端构建产物(web/dist),单个服务即可对外提供完整站点
|
||||
if (MODE === 'remote') {
|
||||
const DIST_DIR = path.resolve(__dirname, '..', 'web', 'dist')
|
||||
if (fs.existsSync(DIST_DIR)) {
|
||||
app.use(express.static(DIST_DIR))
|
||||
app.get(/^\/(?!api\/).*/, (_req, res) => res.sendFile(path.join(DIST_DIR, 'index.html')))
|
||||
}
|
||||
}
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
console.log(`[hero-config-tool] mode=${MODE} schemaVersion=${SCHEMA_VERSION} listening http://${HOST}:${PORT}`)
|
||||
})
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node --watch index.js"
|
||||
"dev": "node --watch index.js",
|
||||
"start": "node index.js",
|
||||
"push-meta": "node scripts/push-meta.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^18.0.5",
|
||||
"esbuild": "^0.24.0",
|
||||
"express": "^4.21.2"
|
||||
"express": "^4.21.2",
|
||||
"mongodb": "^6.12.0"
|
||||
}
|
||||
}
|
||||
|
||||
35
put/server/scripts/migrate-to-mongo.js
Normal file
35
put/server/scripts/migrate-to-mongo.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 一次性迁移:把本地 data/heroes.json、data/templates.json 导入线上 MongoDB
|
||||
* 用法:配置 MONGO_URL 后执行 node scripts/migrate-to-mongo.js
|
||||
* 已在库中的数据会按 uuid 幂等覆盖,可重复执行。
|
||||
*/
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import url from 'node:url'
|
||||
import { saveHeroes, saveTemplates, getHeroes } from '../db.js'
|
||||
|
||||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
|
||||
const DATA_DIR = path.join(__dirname, '..', 'data')
|
||||
|
||||
const readJson = (file, fallback) => {
|
||||
const p = path.join(DATA_DIR, file)
|
||||
return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : fallback
|
||||
}
|
||||
|
||||
const heroes = readJson('heroes.json', { heroes: [] }).heroes || []
|
||||
const tpl = readJson('templates.json', { triggerPresets: [], fieldPresets: [] })
|
||||
|
||||
if (heroes.length) {
|
||||
await saveHeroes(heroes)
|
||||
console.log(`[migrate] heroes 导入 ${heroes.length} 条`)
|
||||
} else {
|
||||
console.log('[migrate] 无本地 heroes.json 或为空,跳过英雄导入')
|
||||
}
|
||||
if ((tpl.triggerPresets?.length || 0) + (tpl.fieldPresets?.length || 0) > 0) {
|
||||
await saveTemplates(tpl.triggerPresets || [], tpl.fieldPresets || [])
|
||||
console.log(`[migrate] templates 导入 trigger=${tpl.triggerPresets?.length || 0} field=${tpl.fieldPresets?.length || 0}`)
|
||||
} else {
|
||||
console.log('[migrate] 无本地 templates.json 或为空,跳过预设导入')
|
||||
}
|
||||
console.log(`[migrate] 库中现有英雄 ${(await getHeroes()).length} 条`)
|
||||
process.exit(0)
|
||||
29
put/server/scripts/push-meta.js
Normal file
29
put/server/scripts/push-meta.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 推送 meta 快照 + 结构版本到线上(项目开发版使用)
|
||||
* 用法:先启动本地 dev 服务,再执行 npm run push-meta
|
||||
* 产物:server/data/meta.json —— 需随线上设计版一起部署(git 提交或拷贝到 VPS)
|
||||
*/
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import url from 'node:url'
|
||||
import { bumpSchemaVersion } from '../db.js'
|
||||
|
||||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
|
||||
const SNAPSHOT_PATH = path.join(__dirname, '..', 'data', 'meta.json')
|
||||
const LOCAL_API = process.env.LOCAL_API || 'http://localhost:3001'
|
||||
const SCHEMA_VERSION = Number(process.env.SCHEMA_VERSION || 1)
|
||||
|
||||
const res = await fetch(`${LOCAL_API}/api/meta-snapshot`)
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => '')
|
||||
throw new Error(`拉取 meta 快照失败(请确认本地 dev 服务已启动): ${res.status} ${body}`)
|
||||
}
|
||||
const snapshot = await res.json()
|
||||
fs.mkdirSync(path.dirname(SNAPSHOT_PATH), { recursive: true })
|
||||
fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(snapshot), 'utf8')
|
||||
|
||||
const dbVersion = await bumpSchemaVersion(SCHEMA_VERSION)
|
||||
console.log(`[push-meta] meta.json 已生成(${Buffer.byteLength(JSON.stringify(snapshot))} bytes)`)
|
||||
console.log(`[push-meta] 数据库结构版本: v${dbVersion}(本服务 v${SCHEMA_VERSION})`)
|
||||
console.log('[push-meta] 请将 meta.json 与最新代码一起部署到线上设计版')
|
||||
process.exit(0)
|
||||
88
put/web/dist/assets/index-CZTxENXL.js
vendored
Normal file
88
put/web/dist/assets/index-CZTxENXL.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
put/web/dist/assets/index-Dzv_EDk3.css
vendored
Normal file
1
put/web/dist/assets/index-Dzv_EDk3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
13
put/web/dist/index.html
vendored
Normal file
13
put/web/dist/index.html
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>英雄配置编辑器</title>
|
||||
<script type="module" crossorigin src="/assets/index-CZTxENXL.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Dzv_EDk3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,18 +1,33 @@
|
||||
<template>
|
||||
<div class="hero-app">
|
||||
<header class="hero-header">
|
||||
<h1>英雄配置编辑器</h1>
|
||||
<h1>
|
||||
英雄配置编辑器
|
||||
<el-tag size="small" :type="isDev ? 'success' : 'warning'" effect="plain">
|
||||
{{ isDev ? '项目开发版' : '线上设计版' }} v{{ meta?.schemaVersion ?? '-' }}
|
||||
</el-tag>
|
||||
</h1>
|
||||
<div class="actions">
|
||||
<el-radio-group v-model="view" size="small">
|
||||
<el-radio-button value="heroes">英雄列表</el-radio-button>
|
||||
<el-radio-button value="templates">预设库</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button size="small" @click="refreshMeta" :loading="loadingMeta">刷新配置数据</el-button>
|
||||
<el-button size="small" type="success" @click="doSave" :disabled="!heroes.length">保存到服务器</el-button>
|
||||
<el-button size="small" type="success" @click="doSave" :disabled="!heroes.length || outdated">保存到数据库</el-button>
|
||||
<template v-if="isDev">
|
||||
<el-button size="small" type="primary" @click="doApply" :disabled="!heroes.length">写入 heroSet.ts</el-button>
|
||||
<el-button size="small" type="danger" @click="doExportCsv" :disabled="!heroes.length">导出英雄设计表.csv</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
<el-alert
|
||||
v-if="outdated"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="`数据库结构已升级到 v${meta.dbSchemaVersion},当前线上设计版(v${meta.schemaVersion})已切换为只读,请更新部署后再修改`"
|
||||
style="margin-bottom: 8px"
|
||||
/>
|
||||
<div class="hero-main">
|
||||
<!-- 预设库视图 -->
|
||||
<TemplateManager
|
||||
@@ -252,6 +267,10 @@ const editVisible = ref(false)
|
||||
|
||||
const current = computed(() => heroes.value[activeIdx.value])
|
||||
|
||||
// 版本/模式:isDev 区分开发版全功能;outdated 时线上设计版只读
|
||||
const isDev = computed(() => meta.value?.mode === 'local')
|
||||
const outdated = computed(() => (meta.value?.dbSchemaVersion ?? 0) > (meta.value?.schemaVersion ?? 0))
|
||||
|
||||
// 首页列表排序:none 原始顺序 / role 按职业枚举序(坦克→辅助)/ rarity 按星级
|
||||
const sortKey = ref('none')
|
||||
const sortOrder = ref('desc')
|
||||
@@ -510,7 +529,7 @@ async function loadTemplates() {
|
||||
async function doSave() {
|
||||
try {
|
||||
const r = await api.saveHeroes(heroes.value)
|
||||
ElMessage.success(`已保存 ${r.count} 个英雄 → ${r.path}`)
|
||||
ElMessage.success(`已保存 ${r.count} 个英雄到数据库`)
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败: ' + (e?.response?.data?.error || e.message))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// 开发环境走 vite 代理(留空即可);部署静态站点时用 VITE_API_BASE 指向后端服务
|
||||
const baseURL = import.meta.env.VITE_API_BASE || ''
|
||||
const http = axios.create({ baseURL })
|
||||
|
||||
export const api = {
|
||||
meta: () => axios.get('/api/meta').then((r) => r.data),
|
||||
refresh: () => axios.post('/api/refresh').then((r) => r.data),
|
||||
fetchHeroes: () => axios.get('/api/heroes').then((r) => r.data),
|
||||
saveHeroes: (heroes) => axios.post('/api/heroes', { heroes }).then((r) => r.data),
|
||||
applyHeroes: (heroes) => axios.post('/api/apply', { heroes }).then((r) => r.data),
|
||||
exportCsv: (heroes) => axios.post('/api/export-csv', { heroes }).then((r) => r.data),
|
||||
fetchTemplates: () => axios.get('/api/templates').then((r) => r.data),
|
||||
saveTemplates: (templates) => axios.post('/api/templates', { templates }).then((r) => r.data),
|
||||
meta: () => http.get('/api/meta').then((r) => r.data),
|
||||
refresh: () => http.post('/api/refresh').then((r) => r.data),
|
||||
fetchHeroes: () => http.get('/api/heroes').then((r) => r.data),
|
||||
saveHeroes: (heroes) => http.post('/api/heroes', { heroes }).then((r) => r.data),
|
||||
applyHeroes: (heroes) => http.post('/api/apply', { heroes }).then((r) => r.data),
|
||||
exportCsv: (heroes) => http.post('/api/export-csv', { heroes }).then((r) => r.data),
|
||||
fetchTemplates: () => http.get('/api/templates').then((r) => r.data),
|
||||
saveTemplates: (templates) => http.post('/api/templates', { templates }).then((r) => r.data),
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import vue from '@vitejs/plugin-vue'
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
// 监听所有网卡,容器/局域网内可直接访问开发服务器
|
||||
host: '0.0.0.0',
|
||||
port: 4004,
|
||||
strictPort: true,
|
||||
proxy: {
|
||||
|
||||
Reference in New Issue
Block a user