Compare commits
62 Commits
b3c41a294d
...
card0614
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c2f1defa9 | ||
|
|
46fa481607 | ||
|
|
72cdf32a75 | ||
|
|
a8642cb788 | ||
|
|
d60b66350a | ||
|
|
bfa434634c | ||
|
|
fb65fa79c8 | ||
|
|
24b5c49891 | ||
|
|
e3102c63ff | ||
|
|
6a81630f6f | ||
|
|
4a5659b7ec | ||
|
|
4df88c1c90 | ||
|
|
0d28ad7a5e | ||
|
|
0a960b737c | ||
|
|
7bb5f8bacc | ||
|
|
c0755b3b8d | ||
|
|
88c1a28c80 | ||
|
|
acb038a70a | ||
|
|
315a1a6af9 | ||
|
|
5170b2d0dc | ||
|
|
d8f02b568b | ||
|
|
b7388615ed | ||
|
|
3056b61ced | ||
|
|
b634cf5383 | ||
|
|
57d2805761 | ||
|
|
62af155ce8 | ||
|
|
735bf205fd | ||
|
|
4d6403e362 | ||
|
|
0a281a95d1 | ||
|
|
107e7fde96 | ||
|
|
d456b2d61f | ||
|
|
f61c4a506f | ||
|
|
4247299a86 | ||
|
|
48a174902d | ||
|
|
b9a3c704c7 | ||
|
|
ea34367d7b | ||
|
|
1eaaf4ccc5 | ||
|
|
e422844717 | ||
|
|
5d244e8091 | ||
|
|
dc8391847b | ||
|
|
a866cba8d1 | ||
|
|
25346c44a2 | ||
|
|
17452167c3 | ||
|
|
c30900e508 | ||
|
|
3d7c9bfe54 | ||
|
|
9220254c56 | ||
|
|
2d4bc1fd05 | ||
|
|
18c873999b | ||
|
|
40c27e04f2 | ||
|
|
875d2d68b5 | ||
|
|
9f738ab881 | ||
|
|
e0c6622bec | ||
|
|
40cc9ed0f9 | ||
|
|
4d51249b61 | ||
|
|
7165fe60d9 | ||
|
|
8cb81e2db6 | ||
|
|
6c6efb640e | ||
|
|
c35d14b5b5 | ||
|
|
eec455cbd9 | ||
|
|
e6395ba018 | ||
|
|
575b9cf4d3 | ||
|
|
3d13b6be46 |
@@ -1,9 +1,5 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "bash .claude/statusline.sh"
|
||||
},
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git status*)",
|
||||
@@ -15,7 +11,11 @@
|
||||
"Bash(dir *)",
|
||||
"Bash(python -m json.tool*)",
|
||||
"Bash(python -m pytest*)",
|
||||
"Bash(py -m pytest*)"
|
||||
"Bash(py -m pytest*)",
|
||||
"WebSearch",
|
||||
"Bash(git add *)",
|
||||
"Bash(git commit *)",
|
||||
"Bash(npm install *)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(rm -rf *)",
|
||||
@@ -155,5 +155,9 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "bash .claude/statusline.sh"
|
||||
}
|
||||
}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -20,7 +20,8 @@ native
|
||||
# WebStorm
|
||||
#//////////////////////////
|
||||
.idea/
|
||||
extensions/
|
||||
extensions/*
|
||||
!extensions/pixelhero-config-editor/
|
||||
extensions/oops-plugin-framework
|
||||
# === IDE and Editor ===
|
||||
.vs/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "2b7fa972-969e-4bf6-903c-2e8fa9403c01",
|
||||
"uuid": "2a95d196-6be3-4be8-a810-c179f3125ff5",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
|
||||
@@ -164,12 +164,12 @@
|
||||
"a": 206
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "2423272e-e63b-4736-b15b-30b40cf98a23@b0413",
|
||||
"__uuid__": "2423272e-e63b-4736-b15b-30b40cf98a23@586a7",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 1,
|
||||
"_fillType": 1,
|
||||
"_sizeMode": 1,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -50,19 +50,22 @@
|
||||
},
|
||||
{
|
||||
"__id__": 295
|
||||
},
|
||||
{
|
||||
"__id__": 307
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 307
|
||||
"__id__": 323
|
||||
},
|
||||
{
|
||||
"__id__": 309
|
||||
"__id__": 325
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 311
|
||||
"__id__": 327
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -2599,8 +2602,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -1.378,
|
||||
"y": 1.378,
|
||||
"x": 2.438,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -2640,8 +2643,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 17.90380859375,
|
||||
"height": 35.5
|
||||
"width": 34.748356206795606,
|
||||
"height": 67
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -2679,16 +2682,16 @@
|
||||
"_string": "3",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 25,
|
||||
"_fontSize": 25,
|
||||
"_actualFontSize": 40,
|
||||
"_fontSize": 40,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 25,
|
||||
"_lineHeight": 50,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
"_isSystemFontUsed": true,
|
||||
"_spacingX": 0,
|
||||
"_isItalic": false,
|
||||
"_isItalic": true,
|
||||
"_isBold": true,
|
||||
"_isUnderline": false,
|
||||
"_underlineHeight": 2,
|
||||
@@ -5390,7 +5393,7 @@
|
||||
"__id__": 296
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 302
|
||||
@@ -5667,6 +5670,396 @@
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "call",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 308
|
||||
},
|
||||
{
|
||||
"__id__": 314
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 320
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 322
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -23.213,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "btn_yellow",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 307
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 309
|
||||
},
|
||||
{
|
||||
"__id__": 311
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 313
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.8,
|
||||
"y": 0.8,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 308
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 310
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 185,
|
||||
"height": 81
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "f5+WQKeepDpI7jXqgvmtiO"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 308
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 312
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "6165ffc9-a838-4a33-b569-bdbaaab0e6b4@b2501",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "08MkVcRXlJh64uV97FC5zt"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "979t7Rjp5IGpxx6E++HMV4",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Label",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 307
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 315
|
||||
},
|
||||
{
|
||||
"__id__": 317
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 319
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 314
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 316
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 63.5999755859375,
|
||||
"height": 54.4
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "feOuVKuYxJK6z+M3tV8Ey3"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 314
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 318
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "召唤",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 29.8,
|
||||
"_fontSize": 29.8,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 40,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
"_isSystemFontUsed": true,
|
||||
"_spacingX": 0,
|
||||
"_isItalic": false,
|
||||
"_isBold": true,
|
||||
"_isUnderline": false,
|
||||
"_underlineHeight": 2,
|
||||
"_cacheMode": 0,
|
||||
"_enableOutline": true,
|
||||
"_outlineColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_outlineWidth": 2,
|
||||
"_enableShadow": false,
|
||||
"_shadowColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_shadowOffset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 2,
|
||||
"y": 2
|
||||
},
|
||||
"_shadowBlur": 2,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "00BfCWkAJL/p90pcsuwUDH"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "83KbPaQKpJ3p+hfLZ1PBTs",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 307
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 321
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 148,
|
||||
"height": 64.8
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "94GKNoSM1MwIqd0zy183DD"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "57KHqoGQRBmIdHCIXwuEoC",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
@@ -5677,7 +6070,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 308
|
||||
"__id__": 324
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -5705,10 +6098,13 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 310
|
||||
"__id__": 326
|
||||
},
|
||||
"Lock": null,
|
||||
"unLock": null,
|
||||
"call_btn": {
|
||||
"__id__": 307
|
||||
},
|
||||
"name_node": {
|
||||
"__id__": 257
|
||||
},
|
||||
@@ -5725,11 +6121,9 @@
|
||||
"__id__": 10
|
||||
},
|
||||
"info_node": {
|
||||
"__id__": 295
|
||||
},
|
||||
"lvl_node": {
|
||||
"__id__": 254
|
||||
"__id__": 296
|
||||
},
|
||||
"lvl_node": null,
|
||||
"ap_node": {
|
||||
"__id__": 195
|
||||
},
|
||||
|
||||
@@ -1928,7 +1928,7 @@
|
||||
"__id__": 129
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 135
|
||||
@@ -1945,7 +1945,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 69.648,
|
||||
"x": 64.648,
|
||||
"y": 75.307,
|
||||
"z": 0
|
||||
},
|
||||
@@ -2073,7 +2073,7 @@
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4b5bf",
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@23e64",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
@@ -2134,8 +2134,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -1.378,
|
||||
"y": 1.378,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -2175,8 +2175,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 17.90380859375,
|
||||
"height": 35.5
|
||||
"width": 34.748356206795606,
|
||||
"height": 67
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -2214,16 +2214,16 @@
|
||||
"_string": "3",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 25,
|
||||
"_fontSize": 25,
|
||||
"_actualFontSize": 40,
|
||||
"_fontSize": 40,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 25,
|
||||
"_lineHeight": 50,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
"_isSystemFontUsed": true,
|
||||
"_spacingX": 0,
|
||||
"_isItalic": false,
|
||||
"_isItalic": true,
|
||||
"_isBold": true,
|
||||
"_isUnderline": false,
|
||||
"_underlineHeight": 2,
|
||||
|
||||
@@ -28,17 +28,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 51
|
||||
"__id__": 60
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
"__id__": 62
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
"__id__": 64
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 57
|
||||
"__id__": 66
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -385,28 +385,28 @@
|
||||
"__id__": 17
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
{
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 55
|
||||
},
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 57
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 50
|
||||
"__id__": 59
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"y": -71.244,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -434,178 +434,218 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "popupb",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 16
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 18
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 17
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "d3jYvT7whFS5scQFZeO5AK",
|
||||
"instance": {
|
||||
"__id__": 19
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "14YFVIWXtFxKTjZkEPn9Vm",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 22
|
||||
},
|
||||
{
|
||||
"__id__": 23
|
||||
},
|
||||
{
|
||||
"__id__": 24
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
},
|
||||
{
|
||||
"__id__": 30
|
||||
},
|
||||
{
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 24
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lpos": {
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "smalltip"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"d3jYvT7whFS5scQFZeO5AK"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lrot": {
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 19
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 670,
|
||||
"height": 200
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 27
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"a3o8h0yV1IRpWNvk7tyKLF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 29
|
||||
},
|
||||
"_type": 1,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 600,
|
||||
"height": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"3aTUQpkypKh5v6YnH99gAD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 31
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
"y": -75.679,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"ceD+T7pqtNKrlNgs4enPQc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 33
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 23
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
"_left": 25,
|
||||
"_right": 25,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 480,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -0.557,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "43t47xyhJOxrZNbgyVjchG",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"06aFK/VuNKJJkB5vy5eTap"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
@@ -619,14 +659,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 37
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 30
|
||||
"__id__": 39
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -663,11 +703,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 27
|
||||
"__id__": 36
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -691,11 +731,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 29
|
||||
"__id__": 38
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -776,23 +816,23 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 51
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 45
|
||||
"__id__": 54
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -829,20 +869,20 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 42
|
||||
},
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 37
|
||||
"__id__": 46
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -879,11 +919,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 34
|
||||
"__id__": 43
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -907,11 +947,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 36
|
||||
"__id__": 45
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -988,11 +1028,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 39
|
||||
"__id__": 48
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1016,11 +1056,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 41
|
||||
"__id__": 50
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -1061,15 +1101,15 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 43
|
||||
"__id__": 52
|
||||
},
|
||||
"clickEvents": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 53
|
||||
}
|
||||
],
|
||||
"_interactable": true,
|
||||
@@ -1148,7 +1188,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 47
|
||||
"__id__": 56
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1176,7 +1216,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 49
|
||||
"__id__": 58
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
@@ -1225,7 +1265,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 52
|
||||
"__id__": 61
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1253,7 +1293,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 54
|
||||
"__id__": 63
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -1289,7 +1329,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 56
|
||||
"__id__": 65
|
||||
},
|
||||
"guide_id": 1,
|
||||
"_id": ""
|
||||
@@ -1308,6 +1348,11 @@
|
||||
},
|
||||
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": [
|
||||
{
|
||||
"__id__": 17
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -28,17 +28,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 51
|
||||
"__id__": 60
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
"__id__": 62
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
"__id__": 64
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 57
|
||||
"__id__": 66
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -385,23 +385,23 @@
|
||||
"__id__": 17
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
{
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 55
|
||||
},
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 57
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 50
|
||||
"__id__": 59
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -434,178 +434,218 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "popupb",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 16
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 18
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 17
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "d3jYvT7whFS5scQFZeO5AK",
|
||||
"instance": {
|
||||
"__id__": 19
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "26atbF+JFPsKsIxnYa2ikH",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 22
|
||||
},
|
||||
{
|
||||
"__id__": 23
|
||||
},
|
||||
{
|
||||
"__id__": 24
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
},
|
||||
{
|
||||
"__id__": 30
|
||||
},
|
||||
{
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 24
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lpos": {
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "smalltip"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"d3jYvT7whFS5scQFZeO5AK"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lrot": {
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 19
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 670,
|
||||
"height": 200
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 27
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"a3o8h0yV1IRpWNvk7tyKLF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 29
|
||||
},
|
||||
"_type": 1,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 600,
|
||||
"height": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"3aTUQpkypKh5v6YnH99gAD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 31
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
"y": -75.679,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"ceD+T7pqtNKrlNgs4enPQc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 33
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 23
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
"_left": 25,
|
||||
"_right": 25,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 480,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -0.557,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "43t47xyhJOxrZNbgyVjchG",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"06aFK/VuNKJJkB5vy5eTap"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
@@ -619,14 +659,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 37
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 30
|
||||
"__id__": 39
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -663,11 +703,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 27
|
||||
"__id__": 36
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -691,11 +731,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 29
|
||||
"__id__": 38
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -707,7 +747,7 @@
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "选择一个战斗技能",
|
||||
"_string": "选择一个战斗「天赋」",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 46,
|
||||
@@ -776,23 +816,23 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 51
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 45
|
||||
"__id__": 54
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -829,20 +869,20 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 42
|
||||
},
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 37
|
||||
"__id__": 46
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -879,11 +919,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 34
|
||||
"__id__": 43
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -907,11 +947,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 36
|
||||
"__id__": 45
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -988,11 +1028,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 39
|
||||
"__id__": 48
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1016,11 +1056,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 41
|
||||
"__id__": 50
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -1061,15 +1101,15 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 43
|
||||
"__id__": 52
|
||||
},
|
||||
"clickEvents": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 53
|
||||
}
|
||||
],
|
||||
"_interactable": true,
|
||||
@@ -1148,7 +1188,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 47
|
||||
"__id__": 56
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1176,7 +1216,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 49
|
||||
"__id__": 58
|
||||
},
|
||||
"_alignFlags": 42,
|
||||
"_target": null,
|
||||
@@ -1225,7 +1265,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 52
|
||||
"__id__": 61
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1253,7 +1293,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 54
|
||||
"__id__": 63
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -1289,7 +1329,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 56
|
||||
"__id__": 65
|
||||
},
|
||||
"guide_id": 2,
|
||||
"_id": ""
|
||||
@@ -1308,6 +1348,11 @@
|
||||
},
|
||||
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": [
|
||||
{
|
||||
"__id__": 17
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -28,17 +28,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 51
|
||||
"__id__": 60
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
"__id__": 62
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
"__id__": 64
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 57
|
||||
"__id__": 66
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -381,28 +381,28 @@
|
||||
"__id__": 17
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
{
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 55
|
||||
},
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 57
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 50
|
||||
"__id__": 59
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 248.35000000000002,
|
||||
"y": -216.83000000000004,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -430,178 +430,218 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "popupb",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 16
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 18
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 17
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "d3jYvT7whFS5scQFZeO5AK",
|
||||
"instance": {
|
||||
"__id__": 19
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "10lq435ItJs7YGjSiwNDfo",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 22
|
||||
},
|
||||
{
|
||||
"__id__": 23
|
||||
},
|
||||
{
|
||||
"__id__": 24
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
},
|
||||
{
|
||||
"__id__": 30
|
||||
},
|
||||
{
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 24
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lpos": {
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "smalltip"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"d3jYvT7whFS5scQFZeO5AK"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"y": -2.664,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lrot": {
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 19
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 670,
|
||||
"height": 200
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 27
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"a3o8h0yV1IRpWNvk7tyKLF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 29
|
||||
},
|
||||
"_type": 1,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 600,
|
||||
"height": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"3aTUQpkypKh5v6YnH99gAD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 31
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
"y": -75.679,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"ceD+T7pqtNKrlNgs4enPQc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 33
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 23
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
"_left": 25,
|
||||
"_right": 25,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 480,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -0.557,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "43t47xyhJOxrZNbgyVjchG",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"06aFK/VuNKJJkB5vy5eTap"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
@@ -615,14 +655,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 37
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 30
|
||||
"__id__": 39
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -659,11 +699,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 27
|
||||
"__id__": 36
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -687,11 +727,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 29
|
||||
"__id__": 38
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -703,7 +743,7 @@
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "选中卡牌向上滑动,召唤英雄",
|
||||
"_string": "上划或点击召唤英雄",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 41,
|
||||
@@ -772,23 +812,23 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 51
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 45
|
||||
"__id__": 54
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -825,20 +865,20 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 42
|
||||
},
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 37
|
||||
"__id__": 46
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -875,11 +915,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 34
|
||||
"__id__": 43
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -903,11 +943,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 36
|
||||
"__id__": 45
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -984,11 +1024,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 39
|
||||
"__id__": 48
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1012,11 +1052,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 41
|
||||
"__id__": 50
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -1057,15 +1097,15 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 43
|
||||
"__id__": 52
|
||||
},
|
||||
"clickEvents": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 53
|
||||
}
|
||||
],
|
||||
"_interactable": true,
|
||||
@@ -1144,7 +1184,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 47
|
||||
"__id__": 56
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1172,14 +1212,14 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 49
|
||||
"__id__": 58
|
||||
},
|
||||
"_alignFlags": 44,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 590,
|
||||
"_bottom": 788.35,
|
||||
"_bottom": 323.16999999999996,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
@@ -1221,7 +1261,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 52
|
||||
"__id__": 61
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1249,7 +1289,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 54
|
||||
"__id__": 63
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -1285,7 +1325,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 56
|
||||
"__id__": 65
|
||||
},
|
||||
"guide_id": 3,
|
||||
"_id": ""
|
||||
@@ -1304,6 +1344,10 @@
|
||||
},
|
||||
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
"nestedPrefabInstanceRoots": [
|
||||
{
|
||||
"__id__": 17
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -28,17 +28,17 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 51
|
||||
"__id__": 60
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
"__id__": 62
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
"__id__": 64
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 57
|
||||
"__id__": 66
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -385,28 +385,28 @@
|
||||
"__id__": 17
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
{
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 46
|
||||
"__id__": 55
|
||||
},
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 57
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 50
|
||||
"__id__": 59
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"y": -307.501,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -434,178 +434,218 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "popupb",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 16
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 18
|
||||
},
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"__editorExtras__": {}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 17
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "d3jYvT7whFS5scQFZeO5AK",
|
||||
"instance": {
|
||||
"__id__": 19
|
||||
},
|
||||
"targetOverrides": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "94hfqwQnBNSJgHYVjgX7oO",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
"mountedChildren": [],
|
||||
"mountedComponents": [],
|
||||
"propertyOverrides": [
|
||||
{
|
||||
"__id__": 20
|
||||
},
|
||||
{
|
||||
"__id__": 22
|
||||
},
|
||||
{
|
||||
"__id__": 23
|
||||
},
|
||||
{
|
||||
"__id__": 24
|
||||
},
|
||||
{
|
||||
"__id__": 25
|
||||
},
|
||||
{
|
||||
"__id__": 26
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
},
|
||||
{
|
||||
"__id__": 30
|
||||
},
|
||||
{
|
||||
"__id__": 32
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 24
|
||||
"removedComponents": []
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lpos": {
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "smalltip"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"d3jYvT7whFS5scQFZeO5AK"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lrot": {
|
||||
"propertyPath": [
|
||||
"_lrot"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"propertyPath": [
|
||||
"_euler"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 19
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 670,
|
||||
"height": 200
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 27
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"a3o8h0yV1IRpWNvk7tyKLF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 29
|
||||
},
|
||||
"_type": 1,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 600,
|
||||
"height": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"3aTUQpkypKh5v6YnH99gAD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 31
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
"y": -75.679,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"ceD+T7pqtNKrlNgs4enPQc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 17
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 33
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 23
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
"_left": 25,
|
||||
"_right": 25,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 480,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -0.557,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "43t47xyhJOxrZNbgyVjchG",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"06aFK/VuNKJJkB5vy5eTap"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
@@ -619,14 +659,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 26
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 28
|
||||
"__id__": 37
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 30
|
||||
"__id__": 39
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -663,11 +703,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 27
|
||||
"__id__": 36
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -691,11 +731,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 25
|
||||
"__id__": 34
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 29
|
||||
"__id__": 38
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -776,23 +816,23 @@
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 40
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 51
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 45
|
||||
"__id__": 54
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -829,20 +869,20 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 33
|
||||
"__id__": 42
|
||||
},
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 44
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 37
|
||||
"__id__": 46
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -879,11 +919,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 34
|
||||
"__id__": 43
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -907,11 +947,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 32
|
||||
"__id__": 41
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 36
|
||||
"__id__": 45
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -988,11 +1028,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 39
|
||||
"__id__": 48
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1016,11 +1056,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 41
|
||||
"__id__": 50
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -1061,15 +1101,15 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 31
|
||||
"__id__": 40
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 43
|
||||
"__id__": 52
|
||||
},
|
||||
"clickEvents": [
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 53
|
||||
}
|
||||
],
|
||||
"_interactable": true,
|
||||
@@ -1148,7 +1188,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 47
|
||||
"__id__": 56
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1176,7 +1216,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 49
|
||||
"__id__": 58
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
@@ -1225,7 +1265,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 52
|
||||
"__id__": 61
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1253,7 +1293,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 54
|
||||
"__id__": 63
|
||||
},
|
||||
"_alignFlags": 45,
|
||||
"_target": null,
|
||||
@@ -1289,7 +1329,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 56
|
||||
"__id__": 65
|
||||
},
|
||||
"guide_id": 4,
|
||||
"_id": ""
|
||||
@@ -1308,6 +1348,10 @@
|
||||
},
|
||||
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
"nestedPrefabInstanceRoots": [
|
||||
{
|
||||
"__id__": 17
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -7265,7 +7265,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 656.792,
|
||||
"y": 641.191,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -7277,8 +7277,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1.9,
|
||||
"y": 1.9,
|
||||
"x": 1.8,
|
||||
"y": 1.8,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -7342,12 +7342,12 @@
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@3c27d",
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4dd1c",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
@@ -7357,7 +7357,10 @@
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_atlas": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -7380,7 +7383,7 @@
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": -119.39200000000005,
|
||||
"_top": -98.39100000000008,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1226
assets/resources/gui/element/list-me.prefab
Normal file
1226
assets/resources/gui/element/list-me.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,12 @@
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "0f2aeee0-d590-4c36-9f20-a93b058d5b91",
|
||||
"uuid": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "mskills"
|
||||
"syncNodeName": "list-me"
|
||||
}
|
||||
}
|
||||
@@ -185,10 +185,10 @@
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d1468",
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d5229",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_type": 1,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
@@ -200,7 +200,10 @@
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_atlas": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -279,8 +282,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -297.949,
|
||||
"y": 0,
|
||||
"x": -276.372,
|
||||
"y": 8.188,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -418,8 +421,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -299.563,
|
||||
"y": 0,
|
||||
"x": -277.986,
|
||||
"y": 8.188,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -577,8 +580,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -217.727,
|
||||
"y": 0,
|
||||
"x": -214.902,
|
||||
"y": 8.188,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -590,8 +593,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.35,
|
||||
"y": 0.35,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -713,8 +716,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -139.467,
|
||||
"y": 0,
|
||||
"x": -136.642,
|
||||
"y": 8.188,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -872,8 +875,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 225.016,
|
||||
"y": 0,
|
||||
"x": 167.228,
|
||||
"y": 8.1,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -952,8 +955,8 @@
|
||||
"_string": "999999",
|
||||
"_horizontalAlign": 0,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 26,
|
||||
"_fontSize": 25,
|
||||
"_actualFontSize": 31,
|
||||
"_fontSize": 30,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 40,
|
||||
"_overflow": 2,
|
||||
@@ -1031,8 +1034,8 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 181.966,
|
||||
"y": 0,
|
||||
"x": 140.032,
|
||||
"y": 8.1,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1044,8 +1047,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,203 +0,0 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "mskills",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "mskills",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 2
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 10
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -180,
|
||||
"y": 990,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 360,
|
||||
"height": 100
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "4dNsDtN3ZGiZ+om9xELLFQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 5
|
||||
},
|
||||
"_alignFlags": 10,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 350,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 0,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "baKsIzXktAyqqNBZKVQXO+"
|
||||
},
|
||||
{
|
||||
"__type__": "68387wnH45AVo6Nco+pXtG5",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"skill_box": {
|
||||
"__uuid__": "d19cde30-f5d0-47de-a0d5-3a272b696343",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "d4AyZdLXNB07iSaFvEU6BB"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Layout",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_resizeMode": 0,
|
||||
"_layoutType": 1,
|
||||
"_cellSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
"_startAxis": 0,
|
||||
"_paddingLeft": 10,
|
||||
"_paddingRight": 0,
|
||||
"_paddingTop": 0,
|
||||
"_paddingBottom": 0,
|
||||
"_spacingX": 10,
|
||||
"_spacingY": 0,
|
||||
"_verticalDirection": 1,
|
||||
"_horizontalDirection": 0,
|
||||
"_constraint": 0,
|
||||
"_constraintNum": 2,
|
||||
"_affectedByScale": false,
|
||||
"_isAlign": false,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "786tQWlIxKHLEpV0oUK9dj"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "5622mxbS1PNqMFP0FH5Mir",
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
@@ -22,32 +22,32 @@
|
||||
"__id__": 2
|
||||
},
|
||||
{
|
||||
"__id__": 35
|
||||
"__id__": 63
|
||||
},
|
||||
{
|
||||
"__id__": 41
|
||||
"__id__": 69
|
||||
},
|
||||
{
|
||||
"__id__": 47
|
||||
"__id__": 75
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
"__id__": 81
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 59
|
||||
"__id__": 87
|
||||
},
|
||||
{
|
||||
"__id__": 61
|
||||
"__id__": 89
|
||||
},
|
||||
{
|
||||
"__id__": 63
|
||||
"__id__": 91
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 65
|
||||
"__id__": 93
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -169,6 +169,48 @@
|
||||
},
|
||||
{
|
||||
"__id__": 34
|
||||
},
|
||||
{
|
||||
"__id__": 35
|
||||
},
|
||||
{
|
||||
"__id__": 37
|
||||
},
|
||||
{
|
||||
"__id__": 39
|
||||
},
|
||||
{
|
||||
"__id__": 41
|
||||
},
|
||||
{
|
||||
"__id__": 43
|
||||
},
|
||||
{
|
||||
"__id__": 45
|
||||
},
|
||||
{
|
||||
"__id__": 47
|
||||
},
|
||||
{
|
||||
"__id__": 49
|
||||
},
|
||||
{
|
||||
"__id__": 51
|
||||
},
|
||||
{
|
||||
"__id__": 53
|
||||
},
|
||||
{
|
||||
"__id__": 55
|
||||
},
|
||||
{
|
||||
"__id__": 57
|
||||
},
|
||||
{
|
||||
"__id__": 59
|
||||
},
|
||||
{
|
||||
"__id__": 61
|
||||
}
|
||||
],
|
||||
"removedComponents": []
|
||||
@@ -474,6 +516,280 @@
|
||||
],
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 36
|
||||
},
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"4aTybcwdBL1IFgubnxRuUh"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 38
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 220,
|
||||
"height": 270
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"eedc9qvxRPQpepecxrSorR"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 40
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 440,
|
||||
"height": 540
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"deY6hTveBKBIzSn0k9oD+7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 42
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 432,
|
||||
"height": 528
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"b7HYchf9tMK7WtemLDPjlE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 44
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 190,
|
||||
"height": 140
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"25j0n7apFBratTNBweGTa7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 46
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 50,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"93O2t0s3dBQYpkATdTKBPZ"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 48
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 380,
|
||||
"height": 280
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"1fWMnHXs1IPZv24wLXcOc2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 50
|
||||
},
|
||||
"propertyPath": [
|
||||
"_active"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"34+q4uHrVMer4aH0jqs5JM"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 52
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 220,
|
||||
"height": 270
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"2bJbEaLWxKYIZXGRHTBM1m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 54
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 440,
|
||||
"height": 540
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"8b8xEuZsBB+KDupKZRRki9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 56
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 432,
|
||||
"height": 528
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"30uXpT+kpFI5rT0dm/gN9k"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 58
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 190,
|
||||
"height": 140
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"c6lDSuSgFMJaDP3gZWwz54"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 60
|
||||
},
|
||||
"propertyPath": [
|
||||
"_lpos"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 50,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"24Xd/896JIz4KMqBZHi94n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "CCPropertyOverrideInfo",
|
||||
"targetInfo": {
|
||||
"__id__": 62
|
||||
},
|
||||
"propertyPath": [
|
||||
"_contentSize"
|
||||
],
|
||||
"value": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 380,
|
||||
"height": 280
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"1dy2U4eNdI6KgBqcoGyOOb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "icon",
|
||||
@@ -486,14 +802,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 36
|
||||
"__id__": 64
|
||||
},
|
||||
{
|
||||
"__id__": 38
|
||||
"__id__": 66
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 40
|
||||
"__id__": 68
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -530,11 +846,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 35
|
||||
"__id__": 63
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 37
|
||||
"__id__": 65
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -558,11 +874,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 35
|
||||
"__id__": 63
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 39
|
||||
"__id__": 67
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -625,14 +941,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 42
|
||||
"__id__": 70
|
||||
},
|
||||
{
|
||||
"__id__": 44
|
||||
"__id__": 72
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 46
|
||||
"__id__": 74
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -669,11 +985,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 41
|
||||
"__id__": 69
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 43
|
||||
"__id__": 71
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -697,11 +1013,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 41
|
||||
"__id__": 69
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 45
|
||||
"__id__": 73
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -784,14 +1100,14 @@
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 48
|
||||
"__id__": 76
|
||||
},
|
||||
{
|
||||
"__id__": 50
|
||||
"__id__": 78
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 52
|
||||
"__id__": 80
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -828,11 +1144,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 47
|
||||
"__id__": 75
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 49
|
||||
"__id__": 77
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -856,11 +1172,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 47
|
||||
"__id__": 75
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 51
|
||||
"__id__": 79
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -943,14 +1259,14 @@
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 54
|
||||
"__id__": 82
|
||||
},
|
||||
{
|
||||
"__id__": 56
|
||||
"__id__": 84
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 58
|
||||
"__id__": 86
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -987,11 +1303,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 53
|
||||
"__id__": 81
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 55
|
||||
"__id__": 83
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1015,11 +1331,11 @@
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 53
|
||||
"__id__": 81
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 57
|
||||
"__id__": 85
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
@@ -1100,7 +1416,7 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 60
|
||||
"__id__": 88
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
@@ -1128,7 +1444,7 @@
|
||||
},
|
||||
"_enabled": false,
|
||||
"__prefab": {
|
||||
"__id__": 62
|
||||
"__id__": 90
|
||||
},
|
||||
"_alignFlags": 40,
|
||||
"_target": null,
|
||||
@@ -1164,16 +1480,16 @@
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 64
|
||||
"__id__": 92
|
||||
},
|
||||
"lbl_name": {
|
||||
"__id__": 44
|
||||
"__id__": 72
|
||||
},
|
||||
"lbl_info": {
|
||||
"__id__": 50
|
||||
"__id__": 78
|
||||
},
|
||||
"icon": {
|
||||
"__id__": 38
|
||||
"__id__": 66
|
||||
},
|
||||
"bg": null,
|
||||
"_id": ""
|
||||
@@ -1194,7 +1510,7 @@
|
||||
"instance": null,
|
||||
"targetOverrides": [
|
||||
{
|
||||
"__id__": 66
|
||||
"__id__": 94
|
||||
}
|
||||
],
|
||||
"nestedPrefabInstanceRoots": [
|
||||
@@ -1206,7 +1522,7 @@
|
||||
{
|
||||
"__type__": "cc.TargetOverrideInfo",
|
||||
"source": {
|
||||
"__id__": 63
|
||||
"__id__": 91
|
||||
},
|
||||
"sourceInfo": null,
|
||||
"propertyPath": [
|
||||
@@ -1216,7 +1532,7 @@
|
||||
"__id__": 2
|
||||
},
|
||||
"targetInfo": {
|
||||
"__id__": 67
|
||||
"__id__": 95
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3292,7 +3292,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 617.5,
|
||||
"y": 657.577,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -3304,8 +3304,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"x": 1.5,
|
||||
"y": 1.5,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -3332,8 +3332,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 740,
|
||||
"height": 55
|
||||
"width": 540,
|
||||
"height": 117
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -3369,12 +3369,12 @@
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@98637",
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4dd1c",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
@@ -3384,7 +3384,10 @@
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_atlas": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -3399,15 +3402,15 @@
|
||||
"node": {
|
||||
"__id__": 134
|
||||
},
|
||||
"_enabled": true,
|
||||
"_enabled": false,
|
||||
"__prefab": {
|
||||
"__id__": 140
|
||||
},
|
||||
"_alignFlags": 41,
|
||||
"_target": null,
|
||||
"_left": -10,
|
||||
"_right": -10,
|
||||
"_top": -5,
|
||||
"_left": 90,
|
||||
"_right": 0,
|
||||
"_top": -89.59100000000001,
|
||||
"_bottom": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
@@ -3629,10 +3632,10 @@
|
||||
"__id__": 151
|
||||
},
|
||||
"asset": {
|
||||
"__uuid__": "0c80c911-642f-423a-8ebb-98aebbbe2ef0",
|
||||
"__uuid__": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"fileId": "70V66T2DlAP7ksTOaOBDja",
|
||||
"fileId": "43WU05gfVAPr5YF69YW+M4",
|
||||
"instance": {
|
||||
"__id__": 153
|
||||
},
|
||||
@@ -3640,7 +3643,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInstance",
|
||||
"fileId": "50AgFB9kROhppT8Q5wAY6S",
|
||||
"fileId": "e722VC+kdPfqeaFe1joI7B",
|
||||
"prefabRootNode": {
|
||||
"__id__": 1
|
||||
},
|
||||
@@ -3670,12 +3673,12 @@
|
||||
"propertyPath": [
|
||||
"_name"
|
||||
],
|
||||
"value": "melist"
|
||||
"value": "list-me"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.TargetInfo",
|
||||
"localID": [
|
||||
"70V66T2DlAP7ksTOaOBDja"
|
||||
"43WU05gfVAPr5YF69YW+M4"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -4318,7 +4321,7 @@
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"melist_prefab": {
|
||||
"__uuid__": "0c80c911-642f-423a-8ebb-98aebbbe2ef0",
|
||||
"__uuid__": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
|
||||
"__expectedType__": "cc.Prefab"
|
||||
},
|
||||
"_id": ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
583
assets/resources/gui/element/smalltip.prefab
Normal file
583
assets/resources/gui/element/smalltip.prefab
Normal file
@@ -0,0 +1,583 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "smalltip",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "smalltip",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 22
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 24
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "box",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 11
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 17
|
||||
},
|
||||
{
|
||||
"__id__": 19
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 21
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 63.35300000000001,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Arrow",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
{
|
||||
"__id__": 6
|
||||
},
|
||||
{
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 10
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -35.679,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 5
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 38,
|
||||
"height": 35
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "e3LajWvQdNHo2ofzBHiAy7"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4bcea",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "517oJrn2hJXKJIupkBAN7f"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Widget",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 3
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_alignFlags": 20,
|
||||
"_target": null,
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": -18.179000000000002,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
"_isAbsBottom": true,
|
||||
"_isAbsHorizontalCenter": true,
|
||||
"_isAbsVerticalCenter": true,
|
||||
"_originalWidth": 0,
|
||||
"_originalHeight": 0,
|
||||
"_alignMode": 2,
|
||||
"_lockFlags": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "82QThq5MNOsYFImPKMW3Re"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "ceD+T7pqtNKrlNgs4enPQc",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Label",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 16
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 2.877,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 13
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 84,
|
||||
"height": 54.4
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "11pqUo9t9IWYWj/LjK3fNo"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Label",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 11
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 15
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_string": "卡成升级",
|
||||
"_horizontalAlign": 1,
|
||||
"_verticalAlign": 1,
|
||||
"_actualFontSize": 20,
|
||||
"_fontSize": 20,
|
||||
"_fontFamily": "Arial",
|
||||
"_lineHeight": 40,
|
||||
"_overflow": 0,
|
||||
"_enableWrapText": true,
|
||||
"_font": null,
|
||||
"_isSystemFontUsed": true,
|
||||
"_spacingX": 0,
|
||||
"_isItalic": false,
|
||||
"_isBold": true,
|
||||
"_isUnderline": false,
|
||||
"_underlineHeight": 2,
|
||||
"_cacheMode": 0,
|
||||
"_enableOutline": true,
|
||||
"_outlineColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_outlineWidth": 2,
|
||||
"_enableShadow": false,
|
||||
"_shadowColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 0,
|
||||
"g": 0,
|
||||
"b": 0,
|
||||
"a": 255
|
||||
},
|
||||
"_shadowOffset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 2,
|
||||
"y": 2
|
||||
},
|
||||
"_shadowBlur": 2,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "52GlHcXJZDJqYfTH62X5lp"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "a3o8h0yV1IRpWNvk7tyKLF",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 150,
|
||||
"height": 70
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "3aTUQpkypKh5v6YnH99gAD"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 20
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d552a",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 1,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "a6LRCKiMBA7pI2xOs2dxQb"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "06aFK/VuNKJJkB5vy5eTap",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 23
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "1ctkO0TStCjK1FILIEJzwK"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "d3jYvT7whFS5scQFZeO5AK",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/gui/element/smalltip.prefab.meta
Normal file
13
assets/resources/gui/element/smalltip.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "smalltip"
|
||||
}
|
||||
}
|
||||
@@ -1519,12 +1519,12 @@
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@3c27d",
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4dd1c",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_sizeMode": 0,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
@@ -1534,7 +1534,10 @@
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": null,
|
||||
"_atlas": {
|
||||
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.5 MiB |
@@ -21,12 +21,12 @@
|
||||
"role_power": "Power",
|
||||
"role_physical": "Physical",
|
||||
"role_agile": "Agile",
|
||||
"fskill_name_7009": "Frost Domain",
|
||||
"fskill_name_7010": "Deadly Focus",
|
||||
"fskill_name_7011": "Rending Strike",
|
||||
"fskill_name_7012": "Gale March",
|
||||
"fskill_info_7009": "Increase all allied heroes' freeze chance by {0}%",
|
||||
"fskill_info_7010": "Increase all allied heroes' critical chance by {0}%",
|
||||
"fskill_info_7011": "Increase all allied heroes' critical damage by {0}%",
|
||||
"fskill_info_7012": "Increase all allied heroes' attack speed by {0}%"
|
||||
"fskill_name_7003": "Frost Domain",
|
||||
"fskill_name_7004": "Deadly Focus",
|
||||
"fskill_name_7005": "Rending Strike",
|
||||
"fskill_name_7006": "Gale March",
|
||||
"fskill_info_7003": "Increase all allied heroes' freeze chance by {0}%",
|
||||
"fskill_info_7004": "Increase all allied heroes' critical chance by {0}%",
|
||||
"fskill_info_7005": "Increase all allied heroes' critical damage by {0}%",
|
||||
"fskill_info_7006": "Increase all allied heroes' attack speed by {0}%"
|
||||
}
|
||||
|
||||
@@ -144,31 +144,31 @@
|
||||
"scard_info_7102": "刷新卡池,都是远程英雄",
|
||||
"scard_info_7103": "刷新卡池,都是3级卡池等级英雄",
|
||||
|
||||
"fskill_name_7001": "召唤精通",
|
||||
"fskill_name_7002": "亡灵统御",
|
||||
"fskill_name_7003": "先发制人",
|
||||
"fskill_name_7004": "余音绕梁",
|
||||
"fskill_name_7005": "理财专家",
|
||||
"fskill_name_7006": "商业大亨",
|
||||
"fskill_name_7007": "神圣恢复",
|
||||
"fskill_name_7008": "战鼓激昂",
|
||||
"fskill_name_7009": "寒霜领域",
|
||||
"fskill_name_7010": "致命专注",
|
||||
"fskill_name_7011": "裂伤打击",
|
||||
"fskill_name_7012": "疾风战歌",
|
||||
"fskill_name_7014": "召唤精通",
|
||||
"fskill_name_7015": "亡灵统御",
|
||||
"fskill_name_7016": "先发制人",
|
||||
"fskill_name_7017": "余音绕梁",
|
||||
"fskill_name_7010": "理财专家",
|
||||
"fskill_name_7011": "商业大亨",
|
||||
"fskill_name_7001": "神圣恢复",
|
||||
"fskill_name_7002": "战鼓激昂",
|
||||
"fskill_name_7003": "寒霜领域",
|
||||
"fskill_name_7004": "致命专注",
|
||||
"fskill_name_7005": "裂伤打击",
|
||||
"fskill_name_7006": "疾风战歌",
|
||||
|
||||
"fskill_info_7001": "场上所有友方召唤触发技能触发次数+{0}",
|
||||
"fskill_info_7002": "场上所有友方死亡触发技能触发次数+{0}",
|
||||
"fskill_info_7003": "场上所有友方战斗开始触发技能触发次数+{0}",
|
||||
"fskill_info_7004": "场上所有友方战斗结束触发技能触发次数+{0}",
|
||||
"fskill_info_7005": "每回合结束时金币收益提升{0}",
|
||||
"fskill_info_7006": "卖出英雄时金币收益提升{0}",
|
||||
"fskill_info_7007": "战斗结束时全队恢复效果+{0}%",
|
||||
"fskill_info_7008": "场上所有友方攻击力提升{0}%",
|
||||
"fskill_info_7009": "场上所有友方冰冻概率提升{0}%",
|
||||
"fskill_info_7010": "场上所有友方暴击率提升{0}%",
|
||||
"fskill_info_7011": "场上所有友方暴击伤害提升{0}%",
|
||||
"fskill_info_7012": "场上所有友方攻击速度提升{0}%",
|
||||
"fskill_info_7014": "场上所有友方召唤触发技能触发次数+{0}",
|
||||
"fskill_info_7015": "场上所有友方死亡触发技能触发次数+{0}",
|
||||
"fskill_info_7016": "场上所有友方战斗开始触发技能触发次数+{0}",
|
||||
"fskill_info_7017": "场上所有友方战斗结束触发技能触发次数+{0}",
|
||||
"fskill_info_7010": "每回合结束时金币收益提升{0}",
|
||||
"fskill_info_7011": "卖出英雄时金币收益提升{0}",
|
||||
"fskill_info_7001": "战斗结束时全队恢复效果+{0}%",
|
||||
"fskill_info_7002": "场上所有友方攻击力提升{0}%",
|
||||
"fskill_info_7003": "场上所有友方冰冻概率提升{0}%",
|
||||
"fskill_info_7004": "场上所有友方暴击率提升{0}%",
|
||||
"fskill_info_7005": "场上所有友方暴击伤害提升{0}%",
|
||||
"fskill_info_7006": "场上所有友方攻击速度提升{0}%",
|
||||
|
||||
"hl_title_CritMaster_1": "初级暴击者",
|
||||
"hl_title_CritMaster_2": "暴击大师",
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* | mon_name | 怪物名称 | mon_name_6001 | 兽人战士 |
|
||||
* | skill_name | 技能名称 | skill_name_6001 | 攻击 |
|
||||
* | skill_info | 技能描述 | skill_info_6001 | 对单个目标攻击... |
|
||||
* | fskill_name | 驻场技能名称 | fskill_name_7001 | 召唤精通 |
|
||||
* | fskill_info | 驻场技能描述 | fskill_info_7001 | 召唤触发... |
|
||||
* | fskill_name | 驻场技能名称 | fskill_name_7014 | 召唤精通 |
|
||||
* | fskill_info | 驻场技能描述 | fskill_info_7014 | 召唤触发... |
|
||||
* | scard_name | 特殊卡牌名称 | scard_name_7001 | 战术晋升 |
|
||||
* | scard_info | 特殊卡牌描述 | scard_info_7001 | 升级场上随机... |
|
||||
* | hl_name | 亮点成就名称 | hl_name_9001 | 暴击大师 |
|
||||
@@ -30,8 +30,8 @@
|
||||
* langf(LangPrefix.skill_info, 6001, 1, 100) // → "对单个目标攻击1次,每次造成100%攻击的伤害"
|
||||
*
|
||||
* // 驻场技能
|
||||
* lang(LangPrefix.fskill_name, 7001) // → "召唤精通"
|
||||
* langf(LangPrefix.fskill_info, 7001, 1) // → "场上所有友方召唤触发技能触发次数+1"
|
||||
* lang(LangPrefix.fskill_name, 7014) // → "召唤精通"
|
||||
* langf(LangPrefix.fskill_info, 7014, 1) // → "场上所有友方召唤触发技能触发次数+1"
|
||||
*
|
||||
* // 亮点成就(与英雄/技能完全一致的调用方式)
|
||||
* lang(LangPrefix.hl_title, 9011) // → "初级暴击者"
|
||||
|
||||
@@ -283,13 +283,13 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
* 在游戏载入早期调用,预加载常用图集
|
||||
*/
|
||||
preloadCommonAssets() {
|
||||
resources.load("gui/uicons", SpriteAtlas, (err, atlas) => {
|
||||
resources.load("gui/ui3", SpriteAtlas, (err, atlas) => {
|
||||
if (!err && atlas) {
|
||||
// 增加引用计数,防止图集被引擎自动垃圾回收(GC)导致底层 spriteFrames 为 null
|
||||
atlas.addRef();
|
||||
this.uiconsAtlas = atlas;
|
||||
} else {
|
||||
mLogger.error(this.debugMode, 'SMC', "预加载 gui/uicons 图集失败:", err);
|
||||
mLogger.error(this.debugMode, 'SMC', "预加载 gui/ui3 图集失败:", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as exp from "constants"
|
||||
import { HeroInfo, HeroList, HType } from "./heroSet"
|
||||
import { FightSet } from "./GameSet"
|
||||
import { FightSet, SKILL_CARD_WAVES } from "./GameSet"
|
||||
import { oops } from "db://oops-framework/core/Oops"
|
||||
import { SkillOverrides, TGroup } from "./SkillSet"
|
||||
|
||||
@@ -37,6 +37,16 @@ export enum CKind {
|
||||
Potion = 4, //药水
|
||||
}
|
||||
|
||||
/** 技能卡触发类型 */
|
||||
export enum CardSkillType {
|
||||
Interval = 1, // 间隔定时触发 (战斗中每隔N秒执行)
|
||||
Field = 2, // 驻场技能 (被动光环)
|
||||
BattleStart = 3, // 战斗开始时触发一次
|
||||
BattleEnd = 4, // 战斗结束时触发一次
|
||||
HeroDead = 5, // 场上己方英雄死亡时触发
|
||||
HeroCall = 6, // 场上己方英雄召唤上场时触发
|
||||
}
|
||||
|
||||
/** 卡池等级定义 */
|
||||
export enum CardLV {
|
||||
LV1 = 1,
|
||||
@@ -46,6 +56,21 @@ export enum CardLV {
|
||||
LV5 = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡牌技能触发类型
|
||||
* - 命名对齐英雄侧 SkillTriggerType,便于跨模块认知统一
|
||||
* - 枚举值从 1 开始,避免 0 的 falsy 坑(if (trigger_type) 判断出错)
|
||||
*/
|
||||
export enum CardTriggerType {
|
||||
Instant = 1, // 即时触发:使用后立即生效一次
|
||||
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
|
||||
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
|
||||
FightStart = 4, // 战斗开始时触发
|
||||
FightEnd = 5, // 战斗结束时触发(每波结束)
|
||||
HeroDead = 6, // 场上己方英雄死亡时触发
|
||||
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
|
||||
}
|
||||
|
||||
/** 通用卡牌配置 */
|
||||
export interface CardConfig {
|
||||
uuid: number
|
||||
@@ -61,6 +86,7 @@ export interface CardConfig {
|
||||
|
||||
// 技能卡扩展属性
|
||||
skill?: number // 关联的技能 UUID
|
||||
icon?: string // 图标ID(可选,优先使用;未设置时按 trigger_type 从 SkillSet/FieldSkillSet 自动取)
|
||||
name?: string // 卡牌名称
|
||||
info?: string // 卡牌描述信息
|
||||
is_inst?: boolean // 是否即时起效
|
||||
@@ -69,6 +95,15 @@ export interface CardConfig {
|
||||
keep_waves?: number // 维持的波次数(-1表示持续到战斗结束,0或undefined表示仅本波次)
|
||||
overrides?: SkillOverrides // 技能参数覆写(如自定义伤害ap、buff值、金币数等)
|
||||
field?: number[] // 驻场技能 UUID 数组,表示该卡牌提供驻场属性加成
|
||||
|
||||
/** 触发类型(必填,技能卡专用;功能卡/英雄卡可缺省) */
|
||||
trigger_type?: CardTriggerType;
|
||||
/**
|
||||
* 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
|
||||
* 默认 Infinity;达到上限后销毁节点
|
||||
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数
|
||||
*/
|
||||
trigger_limit?: number;
|
||||
}
|
||||
export const CardsUpSet: Record<number, number> = {
|
||||
1: 50,
|
||||
@@ -78,14 +113,12 @@ export const CardsUpSet: Record<number, number> = {
|
||||
5: 250,
|
||||
}
|
||||
|
||||
/**初始coin数 */
|
||||
export const CardInitCoins = 4
|
||||
/** 卡池升级每波减免金额 */
|
||||
export const CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE = 10
|
||||
/** 卡池默认初始等级 */
|
||||
export const CARD_POOL_INIT_LEVEL = CardLV.LV1
|
||||
/** 卡池等级上限 */
|
||||
export const CARD_POOL_MAX_LEVEL = CardLV.LV5
|
||||
/** 卡池等级上限(统一由 FightSet.MAX_CARD_POOL_LEVEL 设定,保持单一数据源) */
|
||||
export const CARD_POOL_MAX_LEVEL = FightSet.MAX_CARD_POOL_LEVEL as unknown as CardLV
|
||||
/** 英雄最高等级限制 */
|
||||
export const CARD_HERO_MAX_LEVEL = 1
|
||||
/** 基础卡池(英雄、技能、功能) */
|
||||
@@ -140,58 +173,82 @@ HeroList.forEach(uuid => {
|
||||
});
|
||||
|
||||
// 添加非英雄卡牌 (技能、功能卡)
|
||||
const waveToPoolLv: Record<number, number> = {
|
||||
1: 1,
|
||||
5: 2,
|
||||
10: 3,
|
||||
15: 4,
|
||||
20: 5
|
||||
};
|
||||
// 体系:wave 由 SKILL_CARD_WAVES 统一配置,每档强度递增(Field 靠 field uuid 区分数值,Interval 靠 overrides 覆写)
|
||||
// wave→pool_lv 映射由 SKILL_CARD_WAVES 索引+1 自动生成(wave 1→lv1, wave 5→lv2, wave 8→lv3)
|
||||
const waveToPoolLv: Record<number, number> = {};
|
||||
SKILL_CARD_WAVES.forEach((w, i) => { waveToPoolLv[w] = i + 1; });
|
||||
|
||||
const SkillCardData: any[] = [
|
||||
// === 1波技能 ===
|
||||
{ uuid: 8301, skill: 6301, wave: 1, name: "护盾", info: "为伙伴/自己添加护盾,可抵挡3次伤害", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8302, skill: 6302, wave: 1, name: "治疗", info: "治疗伙伴/自己", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8705, skill: 0, wave: 1, name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7005] },
|
||||
{ uuid: 8706, skill: 0, wave: 1, name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7006] },
|
||||
{ uuid: 8707, skill: 0, wave: 1, name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7007] },
|
||||
// ==================== wave 1 档(基础强度) ====================
|
||||
// --- 驻场卡(Field) ---
|
||||
|
||||
// === 5波技能 ===
|
||||
{ uuid: 8303, skill: 6303, wave: 5, name: "获取金币", info: "增加一定数量的金币", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8401, skill: 6401, wave: 5, name: "攻击强化", info: "全体友方攻击力提升5点,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8402, skill: 6402, wave: 5, name: "生命强化", info: "全体友方最大生命值提升20点,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8403, skill: 6403, wave: 5, name: "暴击强化", info: "全体友方暴击率提升10%,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8404, skill: 6404, wave: 5, name: "暴伤强化", info: "全体友方暴击伤害提升20%,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8405, skill: 6405, wave: 5, name: "击晕强化", info: "全体友方击晕概率提升10%,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8408, skill: 6408, wave: 5, name: "穿刺强化", info: "全体友方穿透概率提升20%,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8409, skill: 6409, wave: 5, name: "风怒强化", info: "全体友方风怒次数提升1次,持续1次", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8501, skill: 6501, wave: 5, name: "复活", info: "ap 代表复活的生命值百分比", is_inst: true, keep_waves: 15 },
|
||||
{ uuid: 8701, skill: 0, wave: SKILL_CARD_WAVES[0], name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7001], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8702, skill: 0, wave: SKILL_CARD_WAVES[0], name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7002], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8703, skill: 0, wave: SKILL_CARD_WAVES[0], name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7003], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8704, skill: 0, wave: SKILL_CARD_WAVES[0], name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7004], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8705, skill: 0, wave: SKILL_CARD_WAVES[0], name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7005], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8706, skill: 0, wave: SKILL_CARD_WAVES[0], name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7006], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8707, skill: 0, wave: SKILL_CARD_WAVES[0], name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7007], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8708, skill: 0, wave: SKILL_CARD_WAVES[0], name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7008], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8709, skill: 0, wave: SKILL_CARD_WAVES[0], name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7009], trigger_type: CardTriggerType.Field },
|
||||
|
||||
// === 10波技能 ===
|
||||
{ uuid: 8708, skill: 0, wave: 10, name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7008] },
|
||||
{ uuid: 8709, skill: 0, wave: 10, name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7009] },
|
||||
{ uuid: 8710, skill: 0, wave: 10, name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7010] },
|
||||
{ uuid: 8711, skill: 0, wave: 10, name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7011] },
|
||||
{ uuid: 8712, skill: 0, wave: 10, name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7012] },
|
||||
{ uuid: 8713, skill: 0, wave: 10, name: "购买优惠", info: "购买卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7013] },
|
||||
{ uuid: 8714, skill: 0, wave: 10, name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7014] },
|
||||
{ uuid: 8716, skill: 0, wave: 10, name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7016] },
|
||||
{ uuid: 8717, skill: 0, wave: 10, name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7017] },
|
||||
{ uuid: 8718, skill: 0, wave: 10, name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7018] },
|
||||
{ uuid: 8710, skill: 0, wave: SKILL_CARD_WAVES[0], name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7010], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8711, skill: 0, wave: SKILL_CARD_WAVES[0], name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7011], trigger_type: CardTriggerType.Field },
|
||||
|
||||
// === 15波技能 ===
|
||||
{ uuid: 8701, skill: 0, wave: 15, name: "召唤强化", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7001] },
|
||||
{ uuid: 8702, skill: 0, wave: 15, name: "死亡强化", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7002] },
|
||||
{ uuid: 8703, skill: 0, wave: 15, name: "开场强化", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7003] },
|
||||
{ uuid: 8704, skill: 0, wave: 15, name: "结束强化", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7004] },
|
||||
// ==================== wave 5 档(强度 ×2) ====================
|
||||
// --- 驻场卡(field uuid +200,对应 FieldSkillSet 72xx 段) ---
|
||||
{ uuid: 8751, skill: 0, wave: SKILL_CARD_WAVES[1], name: "战后恢复+", info: "战斗结束生命回复量+20%", is_inst: false, keep_waves: -1, field: [7201], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8752, skill: 0, wave: SKILL_CARD_WAVES[1], name: "攻击加成+", info: "英雄攻击力+20%", is_inst: false, keep_waves: -1, field: [7202], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8753, skill: 0, wave: SKILL_CARD_WAVES[1], name: "击晕加成+", info: "英雄击晕概率+20%", is_inst: false, keep_waves: -1, field: [7203], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8754, skill: 0, wave: SKILL_CARD_WAVES[1], name: "暴击加成+", info: "英雄暴击率+20%", is_inst: false, keep_waves: -1, field: [7204], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8755, skill: 0, wave: SKILL_CARD_WAVES[1], name: "暴伤加成+", info: "英雄暴击伤害+40%", is_inst: false, keep_waves: -1, field: [7205], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8756, skill: 0, wave: SKILL_CARD_WAVES[1], name: "攻速加成+", info: "英雄攻击速度+20%", is_inst: false, keep_waves: -1, field: [7206], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8757, skill: 0, wave: SKILL_CARD_WAVES[1], name: "生命加成+", info: "英雄最大生命+20%", is_inst: false, keep_waves: -1, field: [7207], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8758, skill: 0, wave: SKILL_CARD_WAVES[1], name: "风怒加成+", info: "英雄风怒概率+20%", is_inst: false, keep_waves: -1, field: [7208], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8759, skill: 0, wave: SKILL_CARD_WAVES[1], name: "穿刺加成+", info: "英雄穿刺概率+20%", is_inst: false, keep_waves: -1, field: [7209], trigger_type: CardTriggerType.Field },
|
||||
// --- 范围攻击卡(ap 递增,间隔缩短) ---
|
||||
{ uuid: 8261, skill: 6201, wave: SKILL_CARD_WAVES[1], name: "雷墙+", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
|
||||
{ uuid: 8262, skill: 6202, wave: SKILL_CARD_WAVES[1], name: "火墙+", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
|
||||
{ uuid: 8263, skill: 6203, wave: SKILL_CARD_WAVES[1], name: "飓风+", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
|
||||
{ uuid: 8264, skill: 6204, wave: SKILL_CARD_WAVES[1], name: "水墙+", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
|
||||
{ uuid: 8265, skill: 6205, wave: SKILL_CARD_WAVES[1], name: "风墙+", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
|
||||
{ uuid: 8266, skill: 6206, wave: SKILL_CARD_WAVES[1], name: "陨石术+", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
|
||||
|
||||
// === 20波技能 ===
|
||||
{ uuid: 8201, skill: 6201, wave: 20, name: "雷墙", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8202, skill: 6202, wave: 20, name: "火墙", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8203, skill: 6203, wave: 20, name: "飓风", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8204, skill: 6204, wave: 20, name: "水墙", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8205, skill: 6205, wave: 20, name: "风墙", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
{ uuid: 8206, skill: 6206, wave: 20, name: "陨石术", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
|
||||
|
||||
{ uuid: 8760, skill: 0, wave: SKILL_CARD_WAVES[1], name: "金币收益+", info: "每回合金币收益+2", is_inst: false, keep_waves: -1, field: [7210], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8761, skill: 0, wave: SKILL_CARD_WAVES[1], name: "购买优惠+", info: "购买卡牌费用-2金币", is_inst: false, keep_waves: -1, field: [7212], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8762, skill: 0, wave: SKILL_CARD_WAVES[1], name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7213], trigger_type: CardTriggerType.Field },
|
||||
|
||||
|
||||
// ==================== wave 8 档(强度 ×3) ====================
|
||||
// --- 驻场卡(field uuid +400,对应 FieldSkillSet 74xx 段) ---
|
||||
{ uuid: 8801, skill: 0, wave: SKILL_CARD_WAVES[2], name: "战后恢复++", info: "战斗结束生命回复量+30%", is_inst: false, keep_waves: -1, field: [7401], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8802, skill: 0, wave: SKILL_CARD_WAVES[2], name: "攻击加成++", info: "英雄攻击力+30%", is_inst: false, keep_waves: -1, field: [7402], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8803, skill: 0, wave: SKILL_CARD_WAVES[2], name: "击晕加成++", info: "英雄击晕概率+30%", is_inst: false, keep_waves: -1, field: [7403], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8804, skill: 0, wave: SKILL_CARD_WAVES[2], name: "暴击加成++", info: "英雄暴击率+30%", is_inst: false, keep_waves: -1, field: [7404], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8805, skill: 0, wave: SKILL_CARD_WAVES[2], name: "暴伤加成++", info: "英雄暴击伤害+60%", is_inst: false, keep_waves: -1, field: [7405], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8806, skill: 0, wave: SKILL_CARD_WAVES[2], name: "攻速加成++", info: "英雄攻击速度+30%", is_inst: false, keep_waves: -1, field: [7406], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8807, skill: 0, wave: SKILL_CARD_WAVES[2], name: "生命加成++", info: "英雄最大生命+30%", is_inst: false, keep_waves: -1, field: [7407], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8808, skill: 0, wave: SKILL_CARD_WAVES[2], name: "风怒加成++", info: "英雄风怒概率+30%", is_inst: false, keep_waves: -1, field: [7408], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8809, skill: 0, wave: SKILL_CARD_WAVES[2], name: "穿刺加成++", info: "英雄穿刺概率+30%", is_inst: false, keep_waves: -1, field: [7409], trigger_type: CardTriggerType.Field },
|
||||
|
||||
{ uuid: 8361, skill: 6201, wave: SKILL_CARD_WAVES[2], name: "雷墙++", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
|
||||
{ uuid: 8362, skill: 6202, wave: SKILL_CARD_WAVES[2], name: "火墙++", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
|
||||
{ uuid: 8363, skill: 6203, wave: SKILL_CARD_WAVES[2], name: "飓风++", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
|
||||
{ uuid: 8364, skill: 6204, wave: SKILL_CARD_WAVES[2], name: "水墙++", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
|
||||
{ uuid: 8365, skill: 6205, wave: SKILL_CARD_WAVES[2], name: "风墙++", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
|
||||
{ uuid: 8366, skill: 6206, wave: SKILL_CARD_WAVES[2], name: "陨石术++", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
|
||||
|
||||
|
||||
{ uuid: 8810, skill: 0, wave: SKILL_CARD_WAVES[2], name: "金币收益++", info: "每回合金币收益+3", is_inst: false, keep_waves: -1, field: [7410], trigger_type: CardTriggerType.Field },
|
||||
|
||||
{ uuid: 8811, skill: 0, wave: SKILL_CARD_WAVES[2], name: "召唤强化++", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7014], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8812, skill: 0, wave: SKILL_CARD_WAVES[2], name: "死亡强化++", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7015], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8813, skill: 0, wave: SKILL_CARD_WAVES[2], name: "开场强化++", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7016], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8814, skill: 0, wave: SKILL_CARD_WAVES[2], name: "结束强化++", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7017], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8815, skill: 0, wave: SKILL_CARD_WAVES[2], name: "攻击强化++", info: "攻击触发技能次数+1", is_inst: false, keep_waves: -1, field: [7018], trigger_type: CardTriggerType.Field },
|
||||
{ uuid: 8816, skill: 0, wave: SKILL_CARD_WAVES[2], name: "受击强化++", info: "被攻击触发技能次数+1", is_inst: false, keep_waves: -1, field: [7019], trigger_type: CardTriggerType.Field },
|
||||
// --- 范围攻击卡(ap 最高,间隔最短) ---
|
||||
];
|
||||
|
||||
SkillCardData.forEach(data => {
|
||||
@@ -204,14 +261,18 @@ SkillCardData.forEach(data => {
|
||||
pool_lv: waveToPoolLv[data.wave] as CardLV,
|
||||
wave: data.wave,
|
||||
kind: CKind.Skill,
|
||||
card_lv: 1,
|
||||
card_lv: waveToPoolLv[data.wave], // wave 1→1, 5→2, 8→3
|
||||
name: data.name,
|
||||
info: data.info,
|
||||
icon: data.icon, // 【新增】透传自定义图标ID(优先级最高)
|
||||
is_inst: data.is_inst,
|
||||
t_times: data.t_times || (data.is_inst ? 1 : 999),
|
||||
t_inv: data.t_inv || 0,
|
||||
keep_waves: data.keep_waves,
|
||||
field: data.field
|
||||
field: data.field,
|
||||
overrides: data.overrides, // 【修复】原遗漏
|
||||
trigger_type: data.trigger_type, // 【新增】显式触发类型
|
||||
trigger_limit: data.trigger_limit, // 【新增】事件型触发次数上限
|
||||
});
|
||||
});
|
||||
|
||||
@@ -290,14 +351,19 @@ const weightedPick = (cards: CardConfig[]): CardConfig | null => {
|
||||
return cards[cards.length - 1]
|
||||
}
|
||||
|
||||
/** 连续抽取 count 张卡,允许重复 */
|
||||
const pickCards = (cards: CardConfig[], count: number): CardConfig[] => {
|
||||
/** 连续抽取 count 张卡,允许重复或通过 unique 剔除重复 */
|
||||
const pickCards = (cards: CardConfig[], count: number, unique: boolean = false): CardConfig[] => {
|
||||
if (cards.length === 0 || count <= 0) return []
|
||||
const selected: CardConfig[] = []
|
||||
let available = [...cards]
|
||||
while (selected.length < count) {
|
||||
const pick = weightedPick(cards)
|
||||
if (available.length === 0) break
|
||||
const pick = weightedPick(available)
|
||||
if (!pick) break
|
||||
selected.push(pick)
|
||||
if (unique) {
|
||||
available = available.filter(c => c.uuid !== pick.uuid)
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
@@ -345,6 +411,7 @@ export const drawCardsByRule = (
|
||||
heroLv?: number
|
||||
targetPoolLv?: number
|
||||
wave?: number
|
||||
unique?: boolean
|
||||
} = {}
|
||||
): CardConfig[] => {
|
||||
const count = Math.max(0, Math.floor(options.count ?? 4))
|
||||
@@ -394,6 +461,6 @@ export const drawCardsByRule = (
|
||||
})
|
||||
}
|
||||
|
||||
const picked = pickCards(pool, count)
|
||||
const picked = pickCards(pool, count, options.unique)
|
||||
return picked
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ export enum GameEvent {
|
||||
CastSkill = "CastSkill",
|
||||
CardsClose = "CardsClose",
|
||||
CardRefresh = "CardRefresh",
|
||||
/** 单张卡牌被点击选中(payload 为被点击的 CardComp 实例,用于其他卡牌联动隐藏 call_btn) */
|
||||
CardSelected = "CardSelected",
|
||||
UseHeroCard = "UseHeroCard",
|
||||
UseSkillCard = "UseSkillCard",
|
||||
UseSpecialCard = "UseSpecialCard",
|
||||
@@ -74,6 +76,7 @@ export enum GameEvent {
|
||||
UpdateMissionGet = "UpdateMissionGet",
|
||||
GlobalAttrChange = "GlobalAttrChange",
|
||||
CoinAdd = "CoinAdd",
|
||||
ShowSmallTip = "ShowSmallTip",
|
||||
CardPoolUpgrade = "CardPoolUpgrade",
|
||||
TriggerSkill = "TriggerSkill", // 瞬间触发施法事件
|
||||
RemoveSkillBox = "RemoveSkillBox", // 技能盒销毁事件
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum BoxSet {
|
||||
LETF_END = -360,
|
||||
RIGHT_END = 360,
|
||||
//游戏地平线
|
||||
GAME_LINE = -100,
|
||||
GAME_LINE = -90,
|
||||
}
|
||||
|
||||
export enum FacSet {
|
||||
@@ -21,14 +21,14 @@ export enum FacSet {
|
||||
MON = 1,
|
||||
}
|
||||
export enum FightSet {
|
||||
WAVE_COIN_BASE = 4, // 波次金币基础奖励
|
||||
WAVE_COIN_GROW = 1, // 波次金币递增值
|
||||
WAVE_COIN_MAX = 10, // 波次金币最大基础奖励
|
||||
WAVE_COIN_BASE = 7, // 波次金币基础奖励
|
||||
WAVE_COIN_GROW = 2, // 波次金币递增值
|
||||
WAVE_COIN_MAX = 17, // 波次金币最大基础奖励
|
||||
CRIT_DAMAGE = 50,//暴击伤害
|
||||
MORE_RC = 10,//更多次数 广告获取的次数
|
||||
HEARTPOS = -320,//基地位置
|
||||
HERO_MAX_NUM = 6,//英雄最大数量
|
||||
MERGE_MAX = 3, //英雄最大等级
|
||||
MERGE_MAX = 2, //英雄最大等级
|
||||
MERGE_NEED = 3, //英雄升级需要的英雄数
|
||||
// BACK_RANG=30,//后退范围
|
||||
BACK_RANG = 30,//后退范围
|
||||
@@ -42,11 +42,33 @@ export enum FightSet {
|
||||
SHIELD_MAX = 5,
|
||||
WAVE_HEAL_RATE = 0.5, // 回合结束时所有英雄恢复最大生命值的比例
|
||||
PUNCTURE_DOWN = 50,
|
||||
REFRESH_COST = 1,
|
||||
BASE_COST=3
|
||||
REFRESH_COST = 2,
|
||||
BASE_COST = 5,
|
||||
INIT_COIN = 7, // 初始金币数
|
||||
// 刷新成本
|
||||
/** 卡池等级上限(对应 CardLV 最大值) */
|
||||
MAX_CARD_POOL_LEVEL = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡池升级波次配置(单一数据源)。
|
||||
* 索引 i 对应目标等级 = i + 2,即:
|
||||
* - 第 1 个波次 → 升至 LV2
|
||||
* - 第 2 个波次 → 升至 LV3
|
||||
* - 依此类推,上限为 FightSet.MAX_CARD_POOL_LEVEL
|
||||
*/
|
||||
export const CARD_POOL_UPGRADE_WAVES: number[] = [4, 7, 10, 13];
|
||||
|
||||
/**
|
||||
* 技能卡牌出现的波次配置(单一数据源)。
|
||||
* 数组索引 i 对应卡牌档位 card_lv = i + 1:
|
||||
* - 第 1 个波次 → LV1 档(基础强度)
|
||||
* - 第 2 个波次 → LV2 档(强度 ×2)
|
||||
* - 第 3 个波次 → LV3 档(强度 ×3)
|
||||
* 需与 CardSet.SkillCardData 的 wave 字段严格对齐,否则抽卡时池子为空。
|
||||
*/
|
||||
export const SKILL_CARD_WAVES: number[] = [1, 5, 8];
|
||||
|
||||
export const laneIdx = {
|
||||
2: [-180, 90],
|
||||
1: [-180, 0],
|
||||
|
||||
@@ -155,6 +155,7 @@ export interface SkillConfig {
|
||||
bck?: number, // 额外击退概率
|
||||
buff_type?: Attrs, // Buff 类型 (单一职责)
|
||||
call_hero?: number, // 召唤技能召唤英雄id(可选)
|
||||
is_accel?: boolean, // 是否逐渐加速飞行
|
||||
info: string, // 技能描述
|
||||
}
|
||||
|
||||
@@ -170,6 +171,7 @@ export interface SkillOverrides {
|
||||
bck?: number;
|
||||
buff_type?: Attrs;
|
||||
call_hero?: number;
|
||||
is_accel?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,39 +210,33 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
* 6010 箭矢黄 击晕取向
|
||||
**/
|
||||
6001: {
|
||||
uuid: 6001, name: "火球", sp_name: "atk_1", icon: "1026", 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,
|
||||
uuid: 6001, name: "火球", sp_name: "atk_1", icon: "Stat_Attack_01", 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.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6002: {
|
||||
uuid: 6002, name: "紫烟", sp_name: "atk_2", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6002, name: "紫烟", sp_name: "atk_2", icon: "Stat_Attack_01", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "近战普通攻击技能",
|
||||
},
|
||||
6003: {
|
||||
uuid: 6003, name: "白球", sp_name: "atk_3", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6003, name: "白球", sp_name: "atk_3", icon: "Stat_Attack_01", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "一定几率暴击",
|
||||
},
|
||||
6004: {
|
||||
uuid: 6004, name: "水球", sp_name: "atk_4", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "普通远程攻击",
|
||||
},
|
||||
|
||||
|
||||
6008: {
|
||||
uuid: 6008, name: "箭矢", sp_name: "arrow", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6008, name: "箭矢", sp_name: "arrow", icon: "Stat_RangedAttack", 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.remote,
|
||||
RType: RType.bezier, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6009: {
|
||||
uuid: 6009, name: "箭矢蓝", sp_name: "arrow_blue", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6009, name: "箭矢蓝", sp_name: "arrow_blue", icon: "Stat_RangedAttack", 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.remote,
|
||||
RType: RType.bezier, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6010: {
|
||||
uuid: 6010, name: "箭矢红", sp_name: "arrow_red", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6010, name: "箭矢红", sp_name: "arrow_red", icon: "Stat_RangedAttack", 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.remote,
|
||||
RType: RType.bezier, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
@@ -252,27 +248,22 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
* 6104 水球 溅射:分裂多个
|
||||
**/
|
||||
6101: {
|
||||
uuid: 6101, name: "大火球", sp_name: "line_1", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6101, name: "大火球", sp_name: "line_1", icon: "Stat_FireDamage", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, stun: 0, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害,一定几率暴击,高阶技能",
|
||||
},
|
||||
//怪物法师统一使用 暗影球
|
||||
6102: {
|
||||
uuid: 6102, name: "大紫球", sp_name: "line_2", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6102, name: "大紫球", sp_name: "line_2", icon: "Stat_Mana", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害,高阶技能",
|
||||
},
|
||||
|
||||
6103: {
|
||||
uuid: 6103, name: "大白球", sp_name: "line_3", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
uuid: 6103, name: "大白球", sp_name: "line_3", icon: "Stat_WaterDamage", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害,一定几率上毒(后期加入),高阶技能 ",
|
||||
},
|
||||
6104: {
|
||||
uuid: 6104, name: "大水球", sp_name: "line_4", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "yellow", endAnm: "", act: "max",
|
||||
DTType: DTType.single, crt: 20, ap: 100, hit_count: 2, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "射出强力箭矢,最多穿透6个敌人,附带20%额外暴击率",
|
||||
},
|
||||
/*** ======高阶范围攻击技能 ====
|
||||
* 都是3*3 范围攻击 不是英雄技能,是技能卡20波技能,
|
||||
* 6201 雷墙 击晕向
|
||||
@@ -283,105 +274,105 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
* 6206 陨石术 暴击向
|
||||
**/
|
||||
6201: {
|
||||
uuid: 6201, name: "雷墙", sp_name: "box_1", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
uuid: 6201, name: "雷墙", sp_name: "box_1", icon: "Stat_LightningDamag", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
|
||||
},
|
||||
6202: {
|
||||
uuid: 6202, name: "火墙", sp_name: "box_2", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
uuid: 6202, name: "火墙", sp_name: "box_2", icon: "Stat_FireDamage", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
|
||||
},
|
||||
|
||||
6203: {
|
||||
uuid: 6203, name: "飓风", sp_name: "box_3", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
uuid: 6203, name: "飓风", sp_name: "box_3", icon: "Stat_Stun_01", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
|
||||
},
|
||||
|
||||
6204: {
|
||||
uuid: 6204, name: "水墙", sp_name: "box_4", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
uuid: 6204, name: "水墙", sp_name: "box_4", icon: "Stat_Mana", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
|
||||
},
|
||||
|
||||
6205: {
|
||||
uuid: 6205, name: "风墙", sp_name: "box_5", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
uuid: 6205, name: "风墙", sp_name: "box_5", icon: "Stat_Stun_01", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "召唤风墙困住敌人,有概率击晕",
|
||||
},
|
||||
|
||||
6206: {
|
||||
uuid: 6206, name: "陨石术", sp_name: "box_6", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
uuid: 6206, name: "陨石术", sp_name: "box_6", icon: "Stat_Tripleshot", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
|
||||
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.fixed, EType: EType.animationEnd, info: "召唤陨石范围攻击敌人,有概率击晕",
|
||||
},
|
||||
|
||||
//============================= ====== 辅助技能 技能卡牌 1 波技能 ====== ==========================
|
||||
6301: {
|
||||
uuid: 6301, name: "护盾", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
uuid: 6301, name: "护盾", sp_name: "buff_wind", icon: "Stat_Defense", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Shield, ap: 3, 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: "为伙伴/自己添加护盾,可抵挡3次伤害",
|
||||
},
|
||||
6302: {
|
||||
uuid: 6302, name: "治疗", sp_name: "buff_wind", icon: "1292", TGroup: TGroup.Team, readyAnm: "up_green", endAnm: "", act: "atk",
|
||||
uuid: 6302, name: "治疗", sp_name: "buff_wind", icon: "Stat_Hp_01", TGroup: TGroup.Team, readyAnm: "up_green", endAnm: "", act: "atk",
|
||||
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: "治疗伙伴/自己",
|
||||
},
|
||||
//==========================buff 技能 也是 技能卡牌 5 波技能
|
||||
6303: {
|
||||
uuid: 6303, name: "获取金币", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "gold", act: "atk",
|
||||
uuid: 6303, name: "获取金币", sp_name: "buff_wind", icon: "Stat_GoldGainIncrease_01", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "gold", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Gold, ap: 0, gold: 1, 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: "增加一定数量的金币",
|
||||
},
|
||||
//==========================buff 技能=====================
|
||||
6401: {
|
||||
uuid: 6401, name: "攻击强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
uuid: 6401, name: "攻击强化", sp_name: "buff_wind", icon: "Stat_Attack_03", 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次",
|
||||
},
|
||||
6402: {
|
||||
uuid: 6402, name: "生命强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_hp", endAnm: "", act: "atk",
|
||||
uuid: 6402, name: "生命强化", sp_name: "buff_wind", icon: "Stat_Hp_02", 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次",
|
||||
},
|
||||
6403: {
|
||||
uuid: 6403, name: "暴击强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
uuid: 6403, name: "暴击强化", sp_name: "buff_wind", icon: "Stat_CriticalChance_02", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, 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.critical, info: "全体友方暴击率提升10%,持续1次",
|
||||
},
|
||||
6404: {
|
||||
uuid: 6404, name: "暴伤强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
uuid: 6404, name: "暴伤强化", sp_name: "buff_wind", icon: "Stat_Critical_01", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, 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.critical_damage, info: "全体友方暴击伤害提升20%,持续1次",
|
||||
},
|
||||
6405: {
|
||||
uuid: 6405, name: "击晕强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
uuid: 6405, name: "击晕强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, 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.stun_chance, info: "全体友方击晕概率提升10%,持续1次",
|
||||
},
|
||||
6406: {
|
||||
uuid: 6406, name: "击退强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
uuid: 6406, name: "击退强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, 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: "暂未使用",
|
||||
},
|
||||
6407: {
|
||||
uuid: 6407, name: "距推强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
uuid: 6407, name: "距推强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, 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: "暂未使用",
|
||||
},
|
||||
6408: {
|
||||
uuid: 6408, name: "穿刺强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
uuid: 6408, name: "穿刺强化", sp_name: "buff_wind", icon: "Stat_Tripleshot", TGroup: TGroup.Team, readyAnm: "up_ap", 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.puncture_chance, info: "全体友方穿透概率提升20%,持续1次",
|
||||
},
|
||||
6409: {
|
||||
uuid: 6409, name: "风怒强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
uuid: 6409, name: "风怒强化", sp_name: "buff_wind", icon: "Stat_CriticalComboChance", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, 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.wfuny, info: "全体友方风怒次数提升1次,持续1次",
|
||||
},
|
||||
6501: {
|
||||
uuid: 6501, name: "复活", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Self, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
uuid: 6501, name: "复活", sp_name: "buff_wind", icon: "Stat_HolyDamage", TGroup: TGroup.Self, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 50, 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, info: "ap 代表复活的生命值百分比",
|
||||
}
|
||||
@@ -408,34 +399,77 @@ export enum FieldSkillType {
|
||||
HeroHp = 16, // 英雄最大生命加成
|
||||
HeroWindFury = 17, // 英雄风怒概率加成
|
||||
HeroPuncture = 18, // 英雄穿刺概率加成
|
||||
AtkCount = 19, // 攻击触发技能次数提升
|
||||
BeAtkCount = 20, // 被攻击触发技能次数提升
|
||||
}
|
||||
|
||||
export interface FieldSkillConfig {
|
||||
uuid: number;
|
||||
name: string;
|
||||
icon: string; // 新增图标字段
|
||||
type: FieldSkillType;
|
||||
value: number; // 提升的数值
|
||||
info: string;
|
||||
}
|
||||
|
||||
export const FieldSkillSet: Record<number, FieldSkillConfig> = {
|
||||
7001: { uuid: 7001, name: "召唤强化", type: FieldSkillType.SummonCount, value: 1, info: "召唤触发技能次数+1" }, //15 波技能
|
||||
7002: { uuid: 7002, name: "死亡强化", type: FieldSkillType.DeadCount, value: 1, info: "死亡触发技能次数+1" }, //15 波技能
|
||||
7003: { uuid: 7003, name: "开场强化", type: FieldSkillType.StartCount, value: 1, info: "战斗开始触发技能次数+1" }, //15 波技能
|
||||
7004: { uuid: 7004, name: "结束强化", type: FieldSkillType.EndCount, value: 1, info: "战斗结束触发技能次数+1" }, //15 波技能
|
||||
7005: { uuid: 7005, name: "金币收益", type: FieldSkillType.WaveGold, value: 1, info: "每回合金币收益+1" }, //1 波技能
|
||||
7006: { uuid: 7006, name: "出售强化", type: FieldSkillType.SellGold, value: 1, info: "卖出英雄金币+1" }, //1 波技能
|
||||
7007: { uuid: 7007, name: "战后恢复", type: FieldSkillType.WaveHeal, value: 0.1, info: "战斗结束生命回复量+10%" }, //1 波技能
|
||||
7008: { uuid: 7008, name: "攻击加成", type: FieldSkillType.HeroAtk, value: 0.1, info: "英雄攻击力+10%" }, //10 波技能
|
||||
7009: { uuid: 7009, name: "击晕加成", type: FieldSkillType.HeroStun, value: 0.1, info: "英雄击晕概率+10%" }, //10 波技能
|
||||
7010: { uuid: 7010, name: "暴击加成", type: FieldSkillType.HeroCrit, value: 0.1, info: "英雄暴击率+10%" }, //10 波技能
|
||||
7011: { uuid: 7011, name: "暴伤加成", type: FieldSkillType.HeroCritDamage, value: 0.2, info: "英雄暴击伤害+20%" }, //10 波技能
|
||||
7012: { uuid: 7012, name: "攻速加成", type: FieldSkillType.HeroSpeed, value: 0.1, info: "英雄攻击速度+10%" }, //10 波技能
|
||||
// ---- 13~18 来自原 TalentSet,统一为驻场百分比 / 绝对值口径 ----
|
||||
// 出售返还由原生 SellGold 承担,SellBonus 不再单独配置
|
||||
7013: { uuid: 7013, name: "购买优惠", type: FieldSkillType.BuyDiscount, value: 1, info: "购买卡牌费用-1金币" }, //10 波技能
|
||||
7014: { uuid: 7014, name: "刷新优惠", type: FieldSkillType.RefreshDiscount, value: 1, info: "刷新卡牌费用-1金币" }, //10 波技能
|
||||
7016: { uuid: 7016, name: "生命加成", type: FieldSkillType.HeroHp, value: 0.1, info: "英雄最大生命+10%" }, //10 波技能
|
||||
7017: { uuid: 7017, name: "风怒加成", type: FieldSkillType.HeroWindFury, value: 0.1, info: "英雄风怒概率+10%" }, //10 波技能
|
||||
7018: { uuid: 7018, name: "穿刺加成", type: FieldSkillType.HeroPuncture, value: 0.1, info: "英雄穿刺概率+10%" }, //10 波技能
|
||||
|
||||
|
||||
7001: { uuid: 7001, name: "战后恢复", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, value: 0.1, info: "战斗结束生命回复量+10%" },
|
||||
7002: { uuid: 7002, name: "攻击加成", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, value: 0.1, info: "英雄攻击力+10%" },
|
||||
7003: { uuid: 7003, name: "击晕加成", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, value: 0.1, info: "英雄击晕概率+10%" },
|
||||
7004: { uuid: 7004, name: "暴击加成", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, value: 0.1, info: "英雄暴击率+10%" },
|
||||
7005: { uuid: 7005, name: "暴伤加成", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, value: 0.2, info: "英雄暴击伤害+20%" },
|
||||
7006: { uuid: 7006, name: "攻速加成", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, value: 0.1, info: "英雄攻击速度+10%" },
|
||||
7007: { uuid: 7007, name: "生命加成", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, value: 0.1, info: "英雄最大生命+10%" },
|
||||
7008: { uuid: 7008, name: "风怒加成", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, value: 0.1, info: "英雄风怒概率+10%" },
|
||||
7009: { uuid: 7009, name: "穿刺加成", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, value: 0.1, info: "英雄穿刺概率+10%" },
|
||||
|
||||
7010: { uuid: 7010, name: "金币收益", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, value: 1, info: "每回合金币收益+1" },
|
||||
7011: { uuid: 7011, name: "出售强化", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, value: 1, info: "卖出英雄金币+1" },
|
||||
7012: { uuid: 7012, name: "购买优惠", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, value: 1, info: "购买卡牌费用-1金币" },
|
||||
7013: { uuid: 7013, name: "刷新优惠", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, value: 1, info: "刷新卡牌费用-1金币" },
|
||||
|
||||
7014: { uuid: 7014, name: "召唤强化", icon: "Stat_UnitSummonIncrease_02", type: FieldSkillType.SummonCount, value: 1, info: "召唤触发技能次数+1" },
|
||||
7015: { uuid: 7015, name: "死亡强化", icon: "Stat_PoisonChanceIncrease", type: FieldSkillType.DeadCount, value: 1, info: "死亡触发技能次数+1" },
|
||||
7016: { uuid: 7016, name: "开场强化", icon: "Stat_AttackRangeIncrease_01", type: FieldSkillType.StartCount, value: 1, info: "战斗开始触发技能次数+1" },
|
||||
7017: { uuid: 7017, name: "结束强化", icon: "Stat_UnitSummonIncrease_01", type: FieldSkillType.EndCount, value: 1, info: "战斗结束触发技能次数+1" },
|
||||
7018: { uuid: 7018, name: "攻击强化", icon: "Stat_Attack_03", type: FieldSkillType.AtkCount, value: 1, info: "攻击触发技能次数+1" },
|
||||
7019: { uuid: 7019, name: "受击强化", icon: "Stat_Armor_01", type: FieldSkillType.BeAtkCount, value: 1, info: "被攻击触发技能次数+1" },
|
||||
|
||||
// ============ wave5 档(原值 ×2) ============
|
||||
|
||||
7201: { uuid: 7201, name: "战后恢复+", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, value: 0.2, info: "战斗结束生命回复量+20%" },
|
||||
7202: { uuid: 7202, name: "攻击加成+", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, value: 0.2, info: "英雄攻击力+20%" },
|
||||
7203: { uuid: 7203, name: "击晕加成+", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, value: 0.2, info: "英雄击晕概率+20%" },
|
||||
7204: { uuid: 7204, name: "暴击加成+", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, value: 0.2, info: "英雄暴击率+20%" },
|
||||
7205: { uuid: 7205, name: "暴伤加成+", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, value: 0.4, info: "英雄暴击伤害+40%" },
|
||||
7206: { uuid: 7206, name: "攻速加成+", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, value: 0.2, info: "英雄攻击速度+20%" },
|
||||
7207: { uuid: 7207, name: "生命加成+", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, value: 0.2, info: "英雄最大生命+20%" },
|
||||
7208: { uuid: 7208, name: "风怒加成+", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, value: 0.2, info: "英雄风怒概率+20%" },
|
||||
7209: { uuid: 7209, name: "穿刺加成+", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, value: 0.2, info: "英雄穿刺概率+20%" },
|
||||
|
||||
|
||||
7210: { uuid: 7210, name: "金币收益+", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, value: 2, info: "每回合金币收益+2" },
|
||||
7211: { uuid: 7211, name: "出售强化+", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, value: 2, info: "卖出英雄金币+2" },
|
||||
7212: { uuid: 7212, name: "购买优惠+", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, value: 2, info: "购买卡牌费用-2金币" },
|
||||
7213: { uuid: 7213, name: "刷新优惠+", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, value: 2, info: "刷新卡牌费用-2金币" },
|
||||
|
||||
|
||||
|
||||
7401: { uuid: 7401, name: "战后恢复++", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, value: 0.3, info: "战斗结束生命回复量+30%" },
|
||||
7402: { uuid: 7402, name: "攻击加成++", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, value: 0.3, info: "英雄攻击力+30%" },
|
||||
7403: { uuid: 7403, name: "击晕加成++", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, value: 0.3, info: "英雄击晕概率+30%" },
|
||||
7404: { uuid: 7404, name: "暴击加成++", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, value: 0.3, info: "英雄暴击率+30%" },
|
||||
7405: { uuid: 7405, name: "暴伤加成++", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, value: 0.6, info: "英雄暴击伤害+60%" },
|
||||
7406: { uuid: 7406, name: "攻速加成++", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, value: 0.3, info: "英雄攻击速度+30%" },
|
||||
7407: { uuid: 7407, name: "生命加成++", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, value: 0.3, info: "英雄最大生命+30%" },
|
||||
7408: { uuid: 7408, name: "风怒加成++", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, value: 0.3, info: "英雄风怒概率+30%" },
|
||||
7409: { uuid: 7409, name: "穿刺加成++", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, value: 0.3, info: "英雄穿刺概率+30%" },
|
||||
|
||||
|
||||
7410: { uuid: 7410, name: "金币收益++", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, value: 3, info: "每回合金币收益+3" },
|
||||
7411: { uuid: 7411, name: "出售强化++", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, value: 3, info: "卖出英雄金币+3" },
|
||||
7412: { uuid: 7412, name: "购买优惠++", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, value: 3, info: "购买卡牌费用-3金币" },
|
||||
7413: { uuid: 7413, name: "刷新优惠++", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, value: 3, info: "刷新卡牌费用-3金币" },
|
||||
};
|
||||
|
||||
@@ -100,14 +100,14 @@ export enum SkillTriggerType {
|
||||
}
|
||||
|
||||
export const SkillTriggerName = {
|
||||
[SkillTriggerType.Call]: "降临",
|
||||
[SkillTriggerType.Dead]: "遗志",
|
||||
[SkillTriggerType.FStart]: "先手",
|
||||
[SkillTriggerType.FEnd]: "终战",
|
||||
[SkillTriggerType.Call]: "召唤",
|
||||
[SkillTriggerType.Dead]: "亡语",
|
||||
[SkillTriggerType.FStart]: "起手",
|
||||
[SkillTriggerType.FEnd]: "生息",
|
||||
[SkillTriggerType.Field]: "光环",
|
||||
[SkillTriggerType.Atking]: "追击",
|
||||
[SkillTriggerType.Atked]: "反击",
|
||||
[SkillTriggerType.Revive]: "涅槃",
|
||||
[SkillTriggerType.Atking]: "进击",
|
||||
[SkillTriggerType.Atked]: "受击",
|
||||
[SkillTriggerType.Revive]: "复活",
|
||||
}
|
||||
|
||||
export const SkillTriggerDesc = {
|
||||
@@ -180,63 +180,193 @@ export interface HeroEvolve {
|
||||
}
|
||||
/*
|
||||
*=============英雄配置列表================
|
||||
* 职业触发规则 (v3)
|
||||
* 战士 专注 atked(受击) + dead(死亡) 触发 — 承伤坦克定位
|
||||
* 刺客 专注 atking(攻击) + dead(死亡) 触发 — 高风险高回报近战
|
||||
* 射手 专注 atking(攻击) + fstart(战前) 触发 — 稳定远程输出
|
||||
* 法师 专注 atking(攻击) + field(驻场) 触发 — 魔法输出+被动光环
|
||||
* 辅助 专注 atking(攻击) + revive(复活) 触发 — 战斗支援
|
||||
* v4 触发类型底座版 — 每个英雄专注单一效果,流派协同由玩家自行发现
|
||||
*
|
||||
* atked 战士 受击触发,效果作用于自身,越挨打越强
|
||||
* atking 刺客 攻击触发自身强化;射手 攻击触发队友强化(hit_count 控制目标数 1→3→6)
|
||||
* dead 战士/刺客 死亡触发,一次性全队遗产
|
||||
* fstart 法师 战斗开始前触发一次,必然生效的全队增益
|
||||
* field 法师 驻场光环,存活期间持续生效,死亡消失
|
||||
* fend 辅助 每波结束触发,跨波次累积增益
|
||||
*
|
||||
* 触发节奏:atked 3次/5次,atking 5次/7次,fstart/fend 1次/波
|
||||
* 废弃属性:frz(冰冻)、bck(击退) — 怪物固定不移动,无效
|
||||
*
|
||||
* 设定中的英雄都是1级,最高可以升级到3级(不在列表内提现,升级在游戏内进行)
|
||||
* skills[0]是普通攻击技能
|
||||
* skills[1]是等级2时的技能,skills[2]是等级3时的技能
|
||||
*
|
||||
* 属性基准(pool_lv:1,lv:1) : SPEED:800, AP:30 | HP:300 | skills[0].cd = 1.0 (普通)
|
||||
* 坦克(pool_lv:1,lv:1) : SPEED:800, AP:25 | HP:450 | skills[0].cd = 2.3 或 2.8 (很慢+/很慢) - 突出沉重感与承伤定位
|
||||
* 近战dps(pool_lv:1,lv:1) : SPEED:800, AP:50 | HP:250 | skills[0].cd = 0.3 或 0.5 (极速/快速+) - 强化割草连击爽感
|
||||
* 远程dps(pool_lv:1,lv:1) : SPEED:800, AP:60 | HP:150 | skills[0].cd = 0.7 或 0.9 (快速/普通+) - 稳定持续的物理输出节奏
|
||||
*远程法dps(pool_lv:1,lv:1) : SPEED:800, AP:60 | HP:150 | skills[0].cd = 1.5 或 1.8 (慢+/慢) - 强调施法前摇与单发爆发
|
||||
* 远程辅助(pool_lv:1,lv:1) : SPEED:800, AP:20 | HP:150 | skills[0].cd = 1.1 (普通) - 贴近基准,动作不急不躁,侧重技能
|
||||
*/
|
||||
|
||||
export const HeroInfo: Record<number, heroInfo> = {
|
||||
// ========== 基础九大英雄 ==========
|
||||
5001:{uuid:5001,name:"见习战士",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:30,
|
||||
// ========== atked 类(战士 · 自身强化) ==========
|
||||
5011:{uuid:5011,name:"小铁卫",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
|
||||
info:"每受击3次为自身添加4层护盾"},
|
||||
5012:{uuid:5012,name:"不死小强",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[{s_uuid:6302,t_num:3,overrides:{TGroup:TGroup.Self,ap:250}}],
|
||||
info:"每受击3次为自身回复攻击力250%的生命值"},
|
||||
5013:{uuid:5013,name:"铁骨头",path:"hk3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[{s_uuid:6402,t_num:5,overrides:{TGroup:TGroup.Self,ap:100}}],
|
||||
info:"每受击5次永久提升自身最大生命值100点"},
|
||||
5014:{uuid:5014,name:"怒火武者",path:"hk4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
|
||||
atked:[{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:12}}],
|
||||
info:"每受击3次永久提升自身攻击力12点"},
|
||||
5015:{uuid:5015,name:"血刃武者",path:"hk5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[
|
||||
{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:3}},
|
||||
{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
|
||||
],
|
||||
info:"每受击3次加3层护盾,每受击5次永久+15攻击力"},
|
||||
5016:{uuid:5016,name:"狂血战士",path:"hc1", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[
|
||||
{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:10}},
|
||||
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
|
||||
],
|
||||
info:"每受击3次永久+10攻击力,每受击5次永久+15%暴击率"},
|
||||
|
||||
// ========== atking 类 — 刺客(自身强化) ==========
|
||||
5021:{uuid:5021,name:"小刺客",path:"hc1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast2].cd,ccd:0}},
|
||||
atking:[{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:8}}],
|
||||
info:"每攻击5次永久提升自身攻击力8点"},
|
||||
5022:{uuid:5022,name:"嗜血剑客",path:"hc2", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast2].cd,ccd:0}},
|
||||
atking:[
|
||||
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:10}},
|
||||
{s_uuid:6401,t_num:7,overrides:{TGroup:TGroup.Self,ap:12}}
|
||||
],
|
||||
info:"每攻击5次永久+10%暴击率,每攻击7次永久+12攻击力"},
|
||||
5023:{uuid:5023,name:"暗影杀手",path:"hc3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast1].cd,ccd:0}},
|
||||
atking:[
|
||||
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:10}},
|
||||
{s_uuid:6404,t_num:7,overrides:{TGroup:TGroup.Self,ap:15}}
|
||||
],
|
||||
info:"每攻击5次永久+10%暴击率,每攻击7次永久+15%暴伤"},
|
||||
|
||||
// ========== atking 类 — 射手(队友强化,hit_count 控制目标数) ==========
|
||||
5031:{uuid:5031,name:"援护弓手",path:"ha1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
|
||||
atking:[{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:1,ap:8}}],
|
||||
info:"每攻击5次为随机1名队友永久提升攻击力8点"},
|
||||
5032:{uuid:5032,name:"战术弓手",path:"ha2", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal1].cd,ccd:0}},
|
||||
atked:[{s_uuid:6301,t_num:3}],
|
||||
info:"基础近战,受击3次加护盾"},
|
||||
5002:{uuid:5002,name:"见习骑士",path:"hk2", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:350,ap:25,
|
||||
skills:{6003:{uuid:6003,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
|
||||
atked:[{s_uuid:6402,t_num:3}],
|
||||
info:"基础近战,受击3次提升生命"},
|
||||
5003:{uuid:5003,name:"见习刺客",path:"hk3", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:250,ap:40,
|
||||
skills:{6004:{uuid:6004,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6401,t_num:3}],
|
||||
info:"基础近战,攻击3次提升攻击"},
|
||||
5004:{uuid:5004,name:"见习法师",path:"hm1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Mid,hp:200,ap:45,
|
||||
skills:{6101:{uuid:6101,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
|
||||
atking:[{s_uuid:6403,t_num:3}],
|
||||
info:"基础中程,攻击3次提升暴击"},
|
||||
5005:{uuid:5005,name:"见习术士",path:"hm2", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Mid,hp:200,ap:45,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow2].cd,ccd:0}},
|
||||
atking:[{s_uuid:6404,t_num:3}],
|
||||
info:"基础中程,攻击3次提升暴伤"},
|
||||
5006:{uuid:5006,name:"见习巫师",path:"hm3", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Mid,hp:200,ap:45,
|
||||
skills:{6104:{uuid:6104,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
|
||||
atking:[{s_uuid:6405,t_num:3}],
|
||||
info:"基础中程,攻击3次提升击晕"},
|
||||
5007:{uuid:5007,name:"见习射手",path:"ha1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:180,ap:50,
|
||||
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6303,t_num:3}],
|
||||
info:"基础远程,攻击3次获得金币"},
|
||||
5008:{uuid:5008,name:"见习游侠",path:"ha2", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:180,ap:50,
|
||||
skills:{6009:{uuid:6009,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6302,t_num:3}],
|
||||
info:"基础远程,攻击3次治疗"},
|
||||
5009:{uuid:5009,name:"见习弩手",path:"ha3", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:180,ap:50,
|
||||
skills:{6010:{uuid:6010,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6401,t_num:3}],
|
||||
info:"基础远程,攻击3次提升攻击"},
|
||||
atking:[{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:3,ap:10}}],
|
||||
info:"每攻击5次为随机3名队友永久提升暴击率10%"},
|
||||
5033:{uuid:5033,name:"鹰眼弓将",path:"ha3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
|
||||
atking:[
|
||||
{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:6,ap:8}},
|
||||
{s_uuid:6404,t_num:7,overrides:{TGroup:TGroup.Team,hit_count:6,ap:12}}
|
||||
],
|
||||
info:"每攻击5次为随机6名队友永久+8攻击力,每攻击7次永久+12%暴伤"},
|
||||
|
||||
// ========== dead 类(战士+刺客 · 死亡遗产) ==========
|
||||
5041:{uuid:5041,name:"殉道卫士",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
dead:[{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:3}}],
|
||||
info:"死亡时为全队添加3层护盾"},
|
||||
5042:{uuid:5042,name:"遗志将军",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
dead:[
|
||||
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}},
|
||||
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:80}}
|
||||
],
|
||||
revive:{s_uuid:6501,r_num:1,upr:0.3},
|
||||
info:"死亡时全队永久+20攻击力、+80最大生命值,死后复活一次"},
|
||||
5043:{uuid:5043,name:"亡魂刺客",path:"hc1", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal1].cd,ccd:0}},
|
||||
dead:[{s_uuid:6405,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}}],
|
||||
info:"死亡时全队永久提升击晕概率15%"},
|
||||
5044:{uuid:5044,name:"血誓剑客",path:"hc2", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
|
||||
dead:[
|
||||
{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}},
|
||||
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
|
||||
],
|
||||
info:"死亡时全队永久+15%暴击率、+20%暴伤"},
|
||||
5045:{uuid:5045,name:"不灭战魂",path:"hk3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
dead:[
|
||||
{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:5}},
|
||||
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:30}},
|
||||
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:120}}
|
||||
],
|
||||
revive:{s_uuid:6501,r_num:1,upr:0.5},
|
||||
info:"死亡时全队获得5层护盾、永久+30攻击力、永久+120最大生命值,死后复活一次"},
|
||||
|
||||
// ========== fstart 类(法师 · 战前增益) ==========
|
||||
5051:{uuid:5051,name:"占卜师",path:"hm1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid3].cd,ccd:0}},
|
||||
fstart:[{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}}],
|
||||
info:"战斗开始时为全队永久提升攻击力15点"},
|
||||
5052:{uuid:5052,name:"护盾牧师",path:"hm2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
|
||||
fstart:[{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:2}}],
|
||||
info:"战斗开始时为全队添加2层护盾"},
|
||||
5053:{uuid:5053,name:"血盟法师",path:"hm3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid2].cd,ccd:0}},
|
||||
fstart:[{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:100}}],
|
||||
info:"战斗开始时为全队永久提升最大生命值100点"},
|
||||
5054:{uuid:5054,name:"暴击法师",path:"hm4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid3].cd,ccd:0}},
|
||||
fstart:[{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}],
|
||||
info:"战斗开始时为全队永久提升暴击率20%"},
|
||||
5055:{uuid:5055,name:"毁灭法师",path:"hm5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Long,hp:573,ap:160,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid1].cd,ccd:0}},
|
||||
fstart:[
|
||||
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:25}},
|
||||
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
|
||||
],
|
||||
info:"战斗开始时为全队永久+25%暴伤、+20攻击力"},
|
||||
5056:{uuid:5056,name:"预言法师",path:"hm6", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
fstart:[
|
||||
{s_uuid:6405,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}},
|
||||
{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}},
|
||||
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
|
||||
],
|
||||
info:"战斗开始时为全队永久+20%击晕概率、+15%暴击率、+20%暴伤"},
|
||||
|
||||
// ========== field 类(法师 · 驻场光环) ==========
|
||||
5061:{uuid:5061,name:"亡语法师",path:"hm1", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid2].cd,ccd:0}},
|
||||
field:[7015],
|
||||
info:"驻场期间全队死亡触发技能次数+1,死亡后光环消失"},
|
||||
|
||||
// ========== fend + atking 类(辅助 · 治疗续航 + 波次增益) ==========
|
||||
5071:{uuid:5071,name:"治愈牧师",path:"hh1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:200}}],
|
||||
info:"每攻击5次治疗全队200%AP"},
|
||||
5072:{uuid:5072,name:"小金库",path:"hh2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid1].cd,ccd:0}},
|
||||
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:200}}],
|
||||
fend:[{s_uuid:6303,t_num:1,overrides:{gold:1}}],
|
||||
info:"每攻击5次治疗全队,每波结束获得1金币"},
|
||||
5073:{uuid:5073,name:"强化牧师",path:"hh3", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:250}}],
|
||||
fend:[{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:10}}],
|
||||
info:"每攻击5次治疗全队,每波结束全队永久+10攻击力"},
|
||||
5074:{uuid:5074,name:"生命牧师",path:"hh4", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Long,hp:573,ap:160,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
|
||||
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:250}}],
|
||||
fend:[{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:80}}],
|
||||
info:"每攻击5次治疗全队,每波结束全队永久+80最大生命值"},
|
||||
5075:{uuid:5075,name:"全能牧师",path:"hh5", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
|
||||
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:300}}],
|
||||
fend:[
|
||||
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:12}},
|
||||
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:60}}
|
||||
],
|
||||
info:"每攻击5次治疗全队300%AP,每波结束全队永久+12攻击力、+60最大生命值"},
|
||||
|
||||
|
||||
|
||||
@@ -251,61 +381,59 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
*/
|
||||
|
||||
|
||||
// 基础怪物 (全部远程攻击,HType仅决定站位)
|
||||
// 近战位怪物 (站在前排,承受更多伤害) — v5: TD节奏CD,多而弱爽感设计
|
||||
// 基础怪物 (全部固定点位站桩攻击,HType仅决定是前排还是后排)
|
||||
// 前排怪物 (站在前排,承受更多伤害) — v5: TD节奏CD,多而弱爽感设计
|
||||
6001:{uuid:6001,name:"兽人战士",path:"m1", fac:FacSet.MON,lv:1,type:HType.Melee,hp:220,ap:10,speed:70,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础近战位怪"},
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础前排怪"},
|
||||
6002:{uuid:6002,name:"兽人精锐战士",path:"m2", fac:FacSet.MON,lv:1,type:HType.Melee,hp:300,ap:14,speed:110,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"进阶近战位怪,更快更痛"},
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"进阶前排怪,更快更痛"},
|
||||
6003:{uuid:6003,name:"兽人重装兵",path:"m3", fac:FacSet.MON,lv:1,type:HType.Melee,hp:850,ap:20,speed:50,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"重型坦克怪,高HP慢攻"},
|
||||
// 远程位怪物 (站在后排,输出更高)
|
||||
// 后排怪物 (站在后排,输出更高)
|
||||
6004:{uuid:6004,name:"兽人射手",path:"m4", fac:FacSet.MON,lv:1,type:HType.Long,hp:190,ap:35,speed:70,
|
||||
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"远程高DPS怪"},
|
||||
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排高DPS怪"},
|
||||
6005:{uuid:6005,name:"兽人刺客",path:"m5", fac:FacSet.MON,lv:1,type:HType.Long,hp:210,ap:38,speed:130,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"高AP快速攻击刺客"},
|
||||
// 特殊位怪物
|
||||
6006:{uuid:6006,name:"骷髅领主",path:"m6", fac:FacSet.MON,lv:1,type:HType.Melee,hp:5000,ap:20,speed:60,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"MiniBoss级坦克"},
|
||||
6007:{uuid:6007,name:"兽人术士",path:"m7", fac:FacSet.MON,lv:1,type:HType.Melee,hp:300,ap:24,speed:70,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"法师怪,远程魔法攻击"},
|
||||
6008:{uuid:6008,name:"兽人火法",path:"m8", fac:FacSet.MON,lv:1,type:HType.Melee,hp:270,ap:32,speed:70,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"高输出法师怪"},
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排MiniBoss级坦克"},
|
||||
6007:{uuid:6007,name:"兽人术士",path:"m7", fac:FacSet.MON,lv:1,type:HType.Long,hp:300,ap:24,speed:70,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排法师怪,魔法攻击"},
|
||||
6008:{uuid:6008,name:"兽人火法",path:"m8", fac:FacSet.MON,lv:1,type:HType.Long,hp:270,ap:32,speed:70,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"后排高输出法师怪"},
|
||||
|
||||
// BOSS怪物 — Boss节奏1.2-1.5s,删除不存在的6206技能
|
||||
6101:{uuid:6101,name:"兽人首领-双刀战士",path:"mb1", fac:FacSet.MON,lv:6,type:HType.Long,hp:1900,ap:30,speed:120,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"远程Boss,高攻速"},
|
||||
6101:{uuid:6101,name:"兽人首领-双刀战士",path:"mb1", fac:FacSet.MON,lv:6,type:HType.Melee,hp:1900,ap:30,speed:120,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排Boss,高攻速"},
|
||||
6102:{uuid:6102,name:"兽人首领-斧头战士",path:"mb2", fac:FacSet.MON,lv:6,type:HType.Melee,hp:7500,ap:26,speed:60,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"近战Boss,超高HP"},
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"前排Boss,超高HP"},
|
||||
6103:{uuid:6103,name:"兽人首领-魔法师",path:"mb3", fac:FacSet.MON,lv:6,type:HType.Long,hp:2250,ap:38,speed:110,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"远程法系Boss,高AP"},
|
||||
6104:{uuid:6104,name:"兽人首领-射手",path:"mb4", fac:FacSet.MON,lv:6,type:HType.Melee,hp:6800,ap:30,speed:70,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"近战位Boss,均衡型"},
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"后排法系Boss,高AP"},
|
||||
6104:{uuid:6104,name:"兽人首领-射手",path:"mb4", fac:FacSet.MON,lv:6,type:HType.Long,hp:6800,ap:30,speed:70,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"后排位Boss,均衡型"},
|
||||
6105:{uuid:6105,name:"亡灵首领-法师",path:"mb5", fac:FacSet.MON,lv:6,type:HType.Long,hp:2600,ap:42,speed:110,
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"远程高AP Boss"},
|
||||
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排高AP Boss"},
|
||||
6106:{uuid:6106,name:"亡灵首领-骑马战士",path:"mb6", fac:FacSet.MON,lv:6,type:HType.Melee,hp:9000,ap:26,speed:130,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"终极Boss,最高HP+高速"},
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排终极Boss,最高HP+高速"},
|
||||
|
||||
|
||||
};
|
||||
|
||||
export const HeroList: number[] = [
|
||||
// 铁壁反伤流
|
||||
5001, 5002, 5007, 5008, 5009,
|
||||
// 攻速叠伤流
|
||||
5006, 5205, 5403,
|
||||
// 冰冻控制流
|
||||
5101, 5106, 5107, 5108,
|
||||
// 治疗续航流
|
||||
5301, 5302, 5303, 5304, 5305,
|
||||
// 穿刺风怒流
|
||||
5209, 5210, 5404,
|
||||
// 暴击爆发流
|
||||
5211, 5212, 5405,
|
||||
// 经济滚雪球流
|
||||
5109, 5306, 5213,
|
||||
// 献祭亡语流
|
||||
5010, 5110, 5111,
|
||||
// atked 类(战士 · 自身强化)
|
||||
5011, 5012, 5013, 5014, 5015, 5016,
|
||||
// atking 刺客(自身强化)
|
||||
5021, 5022, 5023,
|
||||
// atking 射手(队友强化)
|
||||
5031, 5032, 5033,
|
||||
// dead 类(死亡遗产)
|
||||
5041, 5042, 5043, 5044, 5045,
|
||||
// fstart 类(法师 · 战前增益)
|
||||
5051, 5052, 5053, 5054, 5055, 5056,
|
||||
// field 类(法师 · 驻场光环)
|
||||
5061,
|
||||
// fend + atking 类(辅助 · 治疗 + 波次增益)
|
||||
5071, 5072, 5073, 5074, 5075,
|
||||
];
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -249,7 +249,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
|
||||
// 触发复活动画
|
||||
if (targetView && reviveSkillConf) {
|
||||
targetView.playReady(reviveSkillConf.readyAnm);
|
||||
targetView.skill_name('', reviveSkillConf.uuid);
|
||||
targetView.skill_name('', reviveSkillConf.uuid, SkillTriggerType.Revive);
|
||||
// 延迟 0.5 秒后恢复状态,让特效有时间播放,同时也能体现“正在复活”的过程
|
||||
targetView.scheduleOnce(() => {
|
||||
if (targetView.node && targetView.node.isValid) {
|
||||
@@ -327,6 +327,10 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
|
||||
const view = entity.get(HeroViewComp);
|
||||
if (view) {
|
||||
SkillTriggerHelper.trigger(SkillTriggerType.Dead, TAttrsComp, view);
|
||||
// 【新增】仅英雄阵营派发全局死亡事件(怪物死亡会误触发海量卡牌效果)
|
||||
if (TAttrsComp.fac === FacSet.HERO) {
|
||||
oops.message.dispatchEvent(GameEvent.HeroDead, { eid: entity.eid });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SkillSet,} from "../common/config/SkillSet";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { Tooltip } from "../skill/Tooltip";
|
||||
import { timedCom } from "../skill/timedCom";
|
||||
@@ -321,10 +322,10 @@ export class HeroViewComp extends CCComp {
|
||||
Tooltip.load(pos, type, value, s_uuid, this.node);
|
||||
}
|
||||
/** 技能提示 */
|
||||
public skill_name(value: string = "", s_uuid: number = 1001, y: number = 50) {
|
||||
public skill_name(value: string = "", s_uuid: number = 1001, triggerType: string = "", y: number = 50) {
|
||||
let pos = v3(0, 60);
|
||||
pos.y = pos.y + y;
|
||||
Tooltip.load(pos, TooltipTypes.skill, value, s_uuid, this.node);
|
||||
Tooltip.load(pos, TooltipTypes.skill, value, s_uuid, this.node, 1, this.model?.fac ?? FacSet.MON, triggerType);
|
||||
}
|
||||
/** 血量提示(伤害数字) */
|
||||
private hp_tip(type: number = 1, value: string = "", s_uuid: number = 1001, y: number = 0) {
|
||||
@@ -454,6 +455,12 @@ export class HeroViewComp extends CCComp {
|
||||
this.top_node.active = true;
|
||||
|
||||
this.status_change("idle");
|
||||
|
||||
// 【新增】仅英雄阵营派发复活成功事件,供卡牌技能(HeroCall 类型)监听
|
||||
// 统一在此派发可覆盖两条复活路径:复活技能触发 + 关卡战斗准备阶段恢复
|
||||
if (this.model && this.model.fac === FacSet.HERO && this.ent) {
|
||||
oops.message.dispatchEvent(GameEvent.ReviveSuccess, { eid: this.ent.eid });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
if(castTimes >1){
|
||||
val = "*"+castTimes.toString
|
||||
}
|
||||
heroView.skill_name(val,s_uuid)
|
||||
heroView.skill_name(val,s_uuid,triggerType)
|
||||
for (let i = 0; i < castTimes; i++) {
|
||||
if (!heroView.node || !heroView.node.isValid) return;
|
||||
if (isFriendly) {
|
||||
|
||||
@@ -101,10 +101,19 @@ export class SkillTriggerHelper {
|
||||
*/
|
||||
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
|
||||
if (!model.atking || model.atking.length === 0) return;
|
||||
|
||||
let triggerCount = 1;
|
||||
if (model.fac === FacSet.HERO) {
|
||||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
|
||||
}
|
||||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||||
|
||||
model.atking.forEach(atkConfig => {
|
||||
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
|
||||
if (model.atk_count > 0 && model.atk_count % atkConfig.t_num === 0) {
|
||||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
|
||||
for (let i = 0; i < triggerCount; i++) {
|
||||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -115,10 +124,19 @@ export class SkillTriggerHelper {
|
||||
*/
|
||||
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
|
||||
if (!model.atked || model.atked.length === 0) return;
|
||||
|
||||
let triggerCount = 1;
|
||||
if (model.fac === FacSet.HERO) {
|
||||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
|
||||
}
|
||||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||||
|
||||
model.atked.forEach(atkConfig => {
|
||||
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
|
||||
if (model.atked_count > 0 && model.atked_count % atkConfig.t_num === 0) {
|
||||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
|
||||
for (let i = 0; i < triggerCount; i++) {
|
||||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* - smc.vmdata.mission_data —— 读写局内金币
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, Light, UITransform, Widget } from "cc";
|
||||
import { _decorator, Animation, AnimationClip, EventTouch, Input, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, input, resources, Light, UITransform, Widget } 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 { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, CardPoolList } from "../common/config/CardSet";
|
||||
@@ -62,6 +62,9 @@ export class CardComp extends CCComp {
|
||||
/** 解锁态图标节点(显示时表示本槽位未锁定,可点击上锁) */
|
||||
@property(Node)
|
||||
unLock: Node = null!
|
||||
|
||||
@property(Node)
|
||||
call_btn: Node = null!
|
||||
/** 英雄卡信息面板(显示 AP / HP) 废弃,改用直接绑定 */
|
||||
// @property(Node)
|
||||
// info_node=null!
|
||||
@@ -132,10 +135,8 @@ export class CardComp extends CCComp {
|
||||
* 防止快速切卡时旧回调错误覆盖新图标。
|
||||
*/
|
||||
private iconVisualToken: number = 0;
|
||||
/** 是否触发了长按 */
|
||||
private isLongPressed: boolean = false;
|
||||
/** 长按触发时间(秒) */
|
||||
private readonly LONG_PRESS_DURATION: number = 0.5;
|
||||
/** 视为"点击"的最大位移阈值(像素),小于该值视为点击而非拖拽 */
|
||||
private readonly tapMoveThreshold: number = 10;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
@@ -151,7 +152,8 @@ export class CardComp extends CCComp {
|
||||
this.opacityComp.opacity = 255;
|
||||
this.updateLockUI();
|
||||
this.applyEmptyUI();
|
||||
|
||||
// call_btn 默认隐藏,仅在点击本卡时显示
|
||||
this.hideCallBtn();
|
||||
}
|
||||
|
||||
/** 组件销毁时解绑所有事件,防止残留回调 */
|
||||
@@ -310,7 +312,7 @@ export class CardComp extends CCComp {
|
||||
// 使用统一经济管理入口消费金币
|
||||
const success = MissionEconomy.spendCoin(cardCost);
|
||||
if (!success) {
|
||||
oops.gui.toast(`金币不足,召唤需要${cardCost}`);
|
||||
oops.message.dispatchEvent(GameEvent.ShowSmallTip, "buy_coin");
|
||||
this.playReboundAnim();
|
||||
mLogger.log(this.debugMode, "CardComp", "use card coin not enough", {
|
||||
uuid: this.cardData.uuid,
|
||||
@@ -445,6 +447,7 @@ export class CardComp extends CCComp {
|
||||
this.node.setScale(new Vec3(1, 1, 1));
|
||||
this.updateLockUI();
|
||||
this.applyEmptyUI();
|
||||
this.hideCallBtn();
|
||||
this.node.active = false;
|
||||
}
|
||||
|
||||
@@ -458,6 +461,10 @@ export class CardComp extends CCComp {
|
||||
this.node.on(NodeEventType.TOUCH_CANCEL, this.onCardTouchCancel, this);
|
||||
this.Lock?.on(NodeEventType.TOUCH_END, this.onToggleLock, this);
|
||||
this.unLock?.on(NodeEventType.TOUCH_END, this.onToggleLock, this);
|
||||
// 召唤按钮点击 = 使用卡牌
|
||||
this.call_btn?.on(NodeEventType.TOUCH_END, this.onCallBtnClick, this);
|
||||
// 监听跨卡联动:其他卡牌被点击时隐藏本卡 call_btn
|
||||
oops.message.on(GameEvent.CardSelected, this.onOtherCardSelected, this);
|
||||
}
|
||||
|
||||
/** 解绑触控,防止节点销毁后残留回调 */
|
||||
@@ -474,19 +481,22 @@ export class CardComp extends CCComp {
|
||||
if (this.unLock && this.unLock.isValid) {
|
||||
this.unLock.off(NodeEventType.TOUCH_END, this.onToggleLock, this);
|
||||
}
|
||||
if (this.call_btn && this.call_btn.isValid) {
|
||||
this.call_btn.off(NodeEventType.TOUCH_END, this.onCallBtnClick, this);
|
||||
}
|
||||
oops.message.off(GameEvent.CardSelected, this.onOtherCardSelected, this);
|
||||
// 兜底注销全局触摸监听(showCallBtn 后组件销毁的场景)
|
||||
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
|
||||
}
|
||||
|
||||
// ======================== 触摸交互 ========================
|
||||
|
||||
/** 触摸开始:记录起点 Y,进入拖拽状态 */
|
||||
/** 触摸开始:记录起点坐标,进入拖拽状态 */
|
||||
private onCardTouchStart(event: EventTouch) {
|
||||
if (!this.cardData || this.isUsing) return;
|
||||
this.touchStartY = event.getUILocation().y;
|
||||
this.touchStartX = event.getUILocation().x;
|
||||
this.isDragging = true;
|
||||
this.isLongPressed = false;
|
||||
this.unschedule(this.onLongPress);
|
||||
this.scheduleOnce(this.onLongPress, this.LONG_PRESS_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -496,25 +506,15 @@ export class CardComp extends CCComp {
|
||||
if (!this.isDragging || !this.cardData || this.isUsing) return;
|
||||
const currentY = event.getUILocation().y;
|
||||
const deltaY = Math.max(0, currentY - this.touchStartY);
|
||||
if (deltaY > 10) {
|
||||
this.unschedule(this.onLongPress);
|
||||
if (this.isLongPressed) {
|
||||
this.isLongPressed = false;
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
}
|
||||
}
|
||||
|
||||
this.node.setPosition(this.restPosition.x, this.restPosition.y + deltaY, this.restPosition.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触摸结束:
|
||||
* - 技能卡:点击即可使用
|
||||
* - 英雄卡/其他:上拉距离 >= dragUseThreshold → 视为"使用卡牌"
|
||||
* - 否则视为"点击"或者"长按结束"
|
||||
* - 上拉距离 >= dragUseThreshold → 视为"使用卡牌"
|
||||
* - 位移很小(点击)→ 触发 onCardTap:显示 call_btn / 打开信息面板 / 通知其他卡牌联动
|
||||
*/
|
||||
private onCardTouchEnd(event: EventTouch) {
|
||||
this.unschedule(this.onLongPress);
|
||||
if (!this.isDragging || !this.cardData || this.isUsing) return;
|
||||
const endY = event.getUILocation().y;
|
||||
const endX = event.getUILocation().x;
|
||||
@@ -522,11 +522,6 @@ export class CardComp extends CCComp {
|
||||
const deltaX = endX - this.touchStartX;
|
||||
this.isDragging = false;
|
||||
|
||||
if (this.isLongPressed) {
|
||||
this.isLongPressed = false;
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
}
|
||||
|
||||
// 英雄卡保持上划使用
|
||||
if (deltaY >= this.dragUseThreshold) {
|
||||
const used = this.useCard();
|
||||
@@ -536,31 +531,93 @@ export class CardComp extends CCComp {
|
||||
return;
|
||||
}
|
||||
|
||||
// 位移小于阈值视为"点击"
|
||||
if (Math.abs(deltaY) < this.tapMoveThreshold && Math.abs(deltaX) < this.tapMoveThreshold) {
|
||||
this.onCardTap();
|
||||
return;
|
||||
}
|
||||
|
||||
this.playReboundAnim();
|
||||
}
|
||||
|
||||
/** 触摸取消:回弹至原位 */
|
||||
private onCardTouchCancel() {
|
||||
this.unschedule(this.onLongPress);
|
||||
if (!this.isDragging || this.isUsing) return;
|
||||
this.isDragging = false;
|
||||
if (this.isLongPressed) {
|
||||
this.isLongPressed = false;
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
}
|
||||
this.playReboundAnim();
|
||||
}
|
||||
|
||||
/** 长按触发:英雄卡打开英雄信息预览面板 */
|
||||
private onLongPress() {
|
||||
if (!this.isDragging || this.isUsing) return;
|
||||
if (!this.cardData || this.card_type !== CardType.Hero) return;
|
||||
this.isLongPressed = true;
|
||||
const heroUuid = this.card_uuid;
|
||||
const heroLv = Math.max(1, this.cardData.hero_lv ?? 1);
|
||||
const poolLv = Math.max(1, this.cardData.base_pool_lv ?? this.cardData.pool_lv ?? 1);
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv });
|
||||
/**
|
||||
* 卡牌被点击(非拖拽):
|
||||
* 1. 显示本卡的召唤按钮 call_btn。
|
||||
* 2. 派发 CardSelected 事件,其他卡牌收到后隐藏各自的 call_btn。
|
||||
* 3. 英雄卡打开 HInfo 信息面板(点击查看,替代原长按)。
|
||||
*/
|
||||
private onCardTap() {
|
||||
this.playReboundAnim();
|
||||
this.showCallBtn();
|
||||
oops.message.dispatchEvent(GameEvent.CardSelected, this);
|
||||
// 英雄卡打开信息面板(点击替代长按)
|
||||
if (this.card_type === CardType.Hero) {
|
||||
const heroUuid = this.card_uuid;
|
||||
const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1);
|
||||
const poolLv = Math.max(1, this.cardData?.base_pool_lv ?? this.cardData?.pool_lv ?? 1);
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他卡牌被点击时联动隐藏本卡 call_btn。
|
||||
* @param event 事件名(ListenerFunc 约定的第一个参数)
|
||||
* @param source 被点击的 CardComp 实例
|
||||
*/
|
||||
private onOtherCardSelected(event: string, source: CardComp) {
|
||||
if (source === this) return;
|
||||
this.hideCallBtn();
|
||||
}
|
||||
|
||||
/** 显示召唤按钮 */
|
||||
private showCallBtn() {
|
||||
if (this.call_btn && this.call_btn.isValid) {
|
||||
this.call_btn.active = true;
|
||||
// 按需监听全局触摸:点击本体以外区域时隐藏召唤按钮
|
||||
input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
|
||||
}
|
||||
}
|
||||
|
||||
/** 隐藏召唤按钮 */
|
||||
private hideCallBtn() {
|
||||
if (this.call_btn && this.call_btn.isValid) {
|
||||
this.call_btn.active = false;
|
||||
}
|
||||
// 注销全局监听(无论是否曾注册,off 均安全)
|
||||
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局触摸结束:点击落点不在召唤按钮 / 卡牌本体包围盒内时隐藏召唤按钮。
|
||||
* Why: 复用 HInfoComp 的"点击外部关闭"交互模式,避免 call_btn 长期滞留。
|
||||
* 排除卡牌本体是防止与 onCardTap 的"点击卡牌显示 call_btn"交互相互冲突。
|
||||
* @param event 全局触摸事件
|
||||
*/
|
||||
private onGlobalTouchEnd(event: EventTouch) {
|
||||
if (!this.call_btn || !this.call_btn.active) return;
|
||||
const uiPos = event.getUILocation();
|
||||
// 命中召唤按钮本体 → 保留
|
||||
const btnTrans = this.call_btn.getComponent(UITransform);
|
||||
if (btnTrans && btnTrans.getBoundingBoxToWorld().contains(uiPos)) return;
|
||||
// 命中卡牌本体 → 保留(由 onCardTap 自行管理显隐)
|
||||
const cardTrans = this.node.getComponent(UITransform);
|
||||
if (cardTrans && cardTrans.getBoundingBoxToWorld().contains(uiPos)) return;
|
||||
// 其余区域 → 隐藏
|
||||
this.hideCallBtn();
|
||||
}
|
||||
|
||||
/** 召唤按钮点击回调:阻止冒泡后触发使用卡牌 */
|
||||
private onCallBtnClick(event: EventTouch) {
|
||||
event.propagationStopped = true;
|
||||
this.useCard();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
* - Hero —— 英雄 ECS 实体类(用于出售删除)
|
||||
* - UIID.IBox —— 英雄详情弹窗 ID
|
||||
*/
|
||||
import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, CCInteger, SpriteFrame } from "cc";
|
||||
import { _decorator, Animation, AnimationClip, Button, Event, EventTouch, Input, Label, Node, NodeEventType, Sprite, UITransform, input, resources, CCInteger, SpriteFrame } 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 { HeroInfo } from "../common/config/heroSet";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { Hero } from "../hero/Hero";
|
||||
import { FieldSkillType } from "../common/config/SkillSet";
|
||||
import { FieldSkillType, FieldSkillSet } from "../common/config/SkillSet";
|
||||
import { buildSkillDesc } from "../common/config/HeroSkillDesc";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
@@ -35,7 +35,7 @@ import { mLogger } from "../common/Logger";
|
||||
import { MissionHeroComp } from "./MissionHeroComp";
|
||||
import { MoveComp } from "../hero/MoveComp";
|
||||
import { FacSet, getLvColor } from "../common/config/GameSet";
|
||||
import { CKind, CardPoolList } from "../common/config/CardSet";
|
||||
import { CKind, CardPoolList, CardConfig, CardTriggerType } from "../common/config/CardSet";
|
||||
import { SkillSet } from "../common/config/SkillSet";
|
||||
import { CardBgComp } from "./CardBgComp";
|
||||
import { MissionEconomy } from "./MissionEconomy";
|
||||
@@ -54,6 +54,9 @@ export class HInfoComp extends CCComp {
|
||||
/** 英雄 idle 动画图标节点 */
|
||||
@property(Node)
|
||||
icon_node=null!
|
||||
/** 技能图标节点 */
|
||||
@property(Node)
|
||||
skill_icon_node=null!
|
||||
/** 出售按钮节点 */
|
||||
@property(Node)
|
||||
sell_node=null!
|
||||
@@ -153,9 +156,15 @@ export class HInfoComp extends CCComp {
|
||||
const heroUuid = this.previewUuid;
|
||||
const heroLv = Math.max(1, this.previewLv);
|
||||
|
||||
// 技能卡无等级概念,隐藏等级节点;英雄卡显示等级
|
||||
if (this.lv_node) {
|
||||
this.lv_node.string = `Lv.${heroLv}`;
|
||||
this.lv_node.color = getLvColor(heroLv);
|
||||
if (this.isSkillCard) {
|
||||
this.lv_node.node.active = false;
|
||||
} else {
|
||||
this.lv_node.node.active = true;
|
||||
this.lv_node.string = `Lv.${heroLv}`;
|
||||
this.lv_node.color = getLvColor(heroLv);
|
||||
}
|
||||
}
|
||||
|
||||
const kindName = this.isSkillCard ? CKind[CKind.Skill] : CKind[CKind.Hero];
|
||||
@@ -169,6 +178,10 @@ export class HInfoComp extends CCComp {
|
||||
|
||||
if (this.isSkillCard) {
|
||||
// ================= 技能卡预览 =================
|
||||
// 互斥:技能卡只显示技能图标,隐藏英雄图标
|
||||
if (this.icon_node) this.icon_node.active = false;
|
||||
if (this.skill_icon_node) this.skill_icon_node.active = true;
|
||||
|
||||
const config = CardPoolList.find(c => c.uuid === heroUuid);
|
||||
if (!config) return;
|
||||
|
||||
@@ -192,10 +205,14 @@ export class HInfoComp extends CCComp {
|
||||
if (heroUuid !== this.iconHeroUuid) {
|
||||
this.iconHeroUuid = heroUuid;
|
||||
this.iconVisualToken += 1;
|
||||
this.updateSkillAnimation(this.icon_node, config.skill ?? heroUuid, this.iconVisualToken);
|
||||
this.updateSkillAnimation(this.skill_icon_node, config, this.iconVisualToken);
|
||||
}
|
||||
} else {
|
||||
// ================= 英雄卡预览 =================
|
||||
// 互斥:英雄卡只显示英雄图标,隐藏技能图标
|
||||
if (this.icon_node) this.icon_node.active = true;
|
||||
if (this.skill_icon_node) this.skill_icon_node.active = false;
|
||||
|
||||
const hero = HeroInfo[heroUuid];
|
||||
if (!hero) return;
|
||||
|
||||
@@ -461,31 +478,35 @@ export class HInfoComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 为技能图标加载静态图片。
|
||||
* @param node 图标节点
|
||||
* @param skillUuid 技能 UUID
|
||||
* @param token 视觉令牌
|
||||
* 图标来源优先级:config.icon > 按 trigger_type 自动取(Interval→SkillSet,Field→FieldSkillSet)。
|
||||
* @param node 图标节点
|
||||
* @param config 卡牌配置
|
||||
* @param token 视觉令牌
|
||||
*/
|
||||
private updateSkillAnimation(node: Node, skillUuid: number, token: number) {
|
||||
private updateSkillAnimation(node: Node, config: CardConfig, token: number) {
|
||||
if (!node) return;
|
||||
this.clearIconAnimation(node); // 停止之前的动画
|
||||
this.clearIconAnimation(node);
|
||||
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
|
||||
if (!sprite) return;
|
||||
|
||||
const skillData = SkillSet[skillUuid];
|
||||
if (!skillData || !skillData.icon) {
|
||||
sprite.spriteFrame = null;
|
||||
return;
|
||||
}
|
||||
sprite.spriteFrame = null;
|
||||
|
||||
if (smc.uiconsAtlas) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(skillData.icon);
|
||||
if (frame && token === this.iconVisualToken) {
|
||||
sprite.spriteFrame = frame;
|
||||
// 优先使用卡牌自定义 icon;未定义则按 trigger_type 自动取
|
||||
let iconId: string | undefined = config.icon;
|
||||
if (!iconId) {
|
||||
if (config.trigger_type === CardTriggerType.Interval) {
|
||||
iconId = config.skill ? SkillSet[config.skill]?.icon : undefined;
|
||||
} else if (config.trigger_type === CardTriggerType.Field) {
|
||||
const fieldUuid = config.field?.[0];
|
||||
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
|
||||
}
|
||||
} else {
|
||||
const sf = oops.res.get("game/heros/cards/" + skillData.icon, SpriteFrame) as SpriteFrame;
|
||||
if (sf && token === this.iconVisualToken) {
|
||||
sprite.spriteFrame = sf;
|
||||
}
|
||||
if (!iconId) return;
|
||||
|
||||
// 与 CardComp / SCardComp 一致:仅信任全局缓存的 uiconsAtlas
|
||||
if (smc.uiconsAtlas) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
||||
if (token === this.iconVisualToken) {
|
||||
sprite.spriteFrame = frame || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,6 +532,8 @@ export class HInfoComp extends CCComp {
|
||||
this.sell_node?.on(Button.EventType.CLICK, this.onSellHero, this);
|
||||
this.close_node?.on(Button.EventType.CLICK, this.onClosePanel, this);
|
||||
// this.node.on(NodeEventType.TOUCH_END, this.onOpenIBox, this);
|
||||
// 监听全局触摸结束:点击本体节点以外区域时关闭面板
|
||||
input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
|
||||
}
|
||||
|
||||
private unbindEvents() {
|
||||
@@ -523,6 +546,28 @@ export class HInfoComp extends CCComp {
|
||||
// if (this.node && this.node.isValid) {
|
||||
// this.node.off(NodeEventType.TOUCH_END, this.onOpenIBox, this);
|
||||
// }
|
||||
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局触摸结束处理:点击落点不在本体节点包围盒内时关闭面板。
|
||||
* Why: 弹窗常见的"点击空白处关闭"交互,用全局 input + 包围盒命中检测实现,
|
||||
* 无需额外遮罩节点,也不会误关面板内部(含子按钮)的点击。
|
||||
* @param event 全局触摸事件
|
||||
*/
|
||||
private onGlobalTouchEnd(event: EventTouch) {
|
||||
if (this.isClosing) return;
|
||||
const transform = this.node.getComponent(UITransform);
|
||||
if (!transform) {
|
||||
// 缺少 UITransform 时保守处理为关闭
|
||||
this.onClosePanel();
|
||||
return;
|
||||
}
|
||||
const worldRect = transform.getBoundingBoxToWorld();
|
||||
const uiPos = event.getUILocation();
|
||||
if (!worldRect.contains(uiPos)) {
|
||||
this.onClosePanel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,16 +63,16 @@ export class MissSkillsComp extends CCComp {
|
||||
* 第 2 行 y=320:x = -320, -240, -160, -80, 0
|
||||
*/
|
||||
private slots: SkillBoxSlot[] = [
|
||||
{ x: -320, y: 200, used: false, node: null },
|
||||
{ x: -240, y: 200, used: false, node: null },
|
||||
{ x: -160, y: 200, used: false, node: null },
|
||||
{ x: -80, y: 200, used: false, node: null },
|
||||
{ x: 0, y: 200, used: false, node: null },
|
||||
{ x: -320, y: 300, used: false, node: null },
|
||||
{ x: -240, y: 300, used: false, node: null },
|
||||
{ x: -160, y: 300, used: false, node: null },
|
||||
{ x: -80, y: 300, used: false, node: null },
|
||||
{ x: 0, y: 300, used: false, node: null },
|
||||
{ x: -310, y: 200, used: false, node: null },
|
||||
{ x: -220, y: 200, used: false, node: null },
|
||||
{ x: -130, y: 200, used: false, node: null },
|
||||
{ x: -40, y: 200, used: false, node: null },
|
||||
{ x: 50, y: 200, used: false, node: null },
|
||||
{ x: -310, y: 300, used: false, node: null },
|
||||
{ x: -220, y: 300, used: false, node: null },
|
||||
{ x: -130, y: 300, used: false, node: null },
|
||||
{ x: -40, y: 300, used: false, node: null },
|
||||
{ x: 50, y: 300, used: false, node: null },
|
||||
];
|
||||
|
||||
/** 注册事件监听 */
|
||||
|
||||
@@ -45,10 +45,11 @@ import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { HeroInfo, HType } from "../common/config/heroSet";
|
||||
import { HeroViewComp } from "../hero/HeroViewComp";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES, SKILL_CARD_WAVES } from "../common/config/GameSet";
|
||||
import { MoveComp } from "../hero/MoveComp";
|
||||
import { MissionHeroComp } from "./MissionHeroComp";
|
||||
import { MissionEconomy } from "./MissionEconomy";
|
||||
import { MissionComp } from "./MissionComp";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
@@ -234,7 +235,7 @@ export class MissionCardComp extends CCComp {
|
||||
this.dispatchCardsToSlots(cards);
|
||||
|
||||
const wave = this.getCurrentWave();
|
||||
if ([1, 5, 10, 15, 20].includes(wave)) {
|
||||
if (SKILL_CARD_WAVES.includes(wave)) {
|
||||
this.showSkillCardPopup();
|
||||
}
|
||||
|
||||
@@ -292,6 +293,7 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
|
||||
oops.message.on(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
|
||||
oops.message.on(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
|
||||
oops.message.on(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
|
||||
|
||||
/** 按钮触控事件:抽卡与卡池升级 */
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
@@ -358,12 +360,58 @@ export class MissionCardComp extends CCComp {
|
||||
});
|
||||
|
||||
// 提示卡池升级
|
||||
oops.gui.toast(`卡池已升至${this.poolLv}级`);
|
||||
this.showSmallTip("pool_upgrade");
|
||||
|
||||
// 更新UI
|
||||
this.updatePoolLvUI();
|
||||
}
|
||||
|
||||
private onShowSmallTip(event: string, args: any) {
|
||||
const type = args as string;
|
||||
this.showSmallTip(type as any);
|
||||
}
|
||||
|
||||
public showSmallTip(type: "refresh_coin" | "pool_upgrade" | "buy_coin" | "hero_full") {
|
||||
let targetNode: Node | null = null;
|
||||
switch (type) {
|
||||
case "refresh_coin":
|
||||
targetNode = this.cards_chou;
|
||||
break;
|
||||
case "pool_upgrade":
|
||||
targetNode = this.pool_lv_node;
|
||||
break;
|
||||
case "buy_coin":
|
||||
targetNode = this.coins_node;
|
||||
break;
|
||||
case "hero_full":
|
||||
targetNode = this.hero_num_node;
|
||||
break;
|
||||
}
|
||||
if (targetNode && targetNode.isValid) {
|
||||
const tipNode = targetNode.getChildByName("smalltip");
|
||||
if (tipNode) {
|
||||
tipNode.active = true;
|
||||
Tween.stopAllByTarget(tipNode);
|
||||
|
||||
// 设置初始状态:缩放为 0
|
||||
tipNode.setScale(new Vec3(0, 0, 1));
|
||||
|
||||
tween(tipNode)
|
||||
// 1. 弹出动画(微放大再回弹)
|
||||
.to(0.15, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadOut' })
|
||||
.to(0.05, { scale: new Vec3(1, 1, 1) })
|
||||
// 2. 停留 1 秒
|
||||
.delay(1)
|
||||
// 3. 缩小消失动画
|
||||
.to(0.15, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
|
||||
.call(() => {
|
||||
if (tipNode && tipNode.isValid) tipNode.active = false;
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onPhasePrepareStart() {
|
||||
this.updateHeroNumUI(true, true);
|
||||
}
|
||||
@@ -378,7 +426,7 @@ export class MissionCardComp extends CCComp {
|
||||
this.dispatchCardsToSlots(cards);
|
||||
|
||||
const wave = this.getCurrentWave();
|
||||
if ([1, 5, 10, 15, 20].includes(wave)) {
|
||||
if (SKILL_CARD_WAVES.includes(wave)) {
|
||||
this.showSkillCardPopup();
|
||||
}
|
||||
}
|
||||
@@ -457,6 +505,10 @@ export class MissionCardComp extends CCComp {
|
||||
if (!smc.finish_guides.includes(3)) {
|
||||
oops.gui.open(UIID.Guide3);
|
||||
}
|
||||
|
||||
// 驻场技能可能影响刷新费用(如"刷新优惠"),延迟到下一帧刷新费用 UI
|
||||
// 确保 MissSkillsComp 已创建 SkillBoxComp 并注册驻场效果
|
||||
this.scheduleOnce(() => this.updateCoinAndCostUI(), 0);
|
||||
}
|
||||
|
||||
/** 解除按钮监听,避免节点销毁后回调泄漏 */
|
||||
@@ -469,6 +521,7 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
|
||||
oops.message.off(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
|
||||
oops.message.off(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
|
||||
oops.message.off(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
|
||||
if (this.cards_chou && this.cards_chou.isValid) {
|
||||
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_chou.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
@@ -558,7 +611,7 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
payload.cancel = true;
|
||||
payload.reason = "hero_limit";
|
||||
oops.gui.toast(`英雄已满 (${current}/${heroMax})`);
|
||||
this.showSmallTip("hero_full");
|
||||
this.playHeroNumDeniedAnim();
|
||||
}
|
||||
}
|
||||
@@ -678,7 +731,7 @@ export class MissionCardComp extends CCComp {
|
||||
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
const success = MissionEconomy.executeRefresh(this.refreshCost);
|
||||
if (!success) {
|
||||
oops.gui.toast(`金币不足,刷新需要${cost}`);
|
||||
this.showSmallTip("refresh_coin");
|
||||
return;
|
||||
}
|
||||
const cards = this.buildSkillDrawCards();
|
||||
@@ -735,7 +788,7 @@ export class MissionCardComp extends CCComp {
|
||||
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
const success = MissionEconomy.executeRefresh(this.refreshCost);
|
||||
if (!success) {
|
||||
oops.gui.toast(`金币不足,刷新需要${cost}`);
|
||||
this.showSmallTip("refresh_coin");
|
||||
this.updateCoinAndCostUI();
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "draw coin not enough", {
|
||||
currentCoin: MissionEconomy.getCoin(),
|
||||
@@ -857,7 +910,8 @@ export class MissionCardComp extends CCComp {
|
||||
const cards = drawCardsByRule(this.poolLv, {
|
||||
count: 3,
|
||||
type: targetType,
|
||||
wave: currentWave
|
||||
wave: currentWave,
|
||||
unique: true // 保证技能牌不重复
|
||||
});
|
||||
|
||||
if (cards.length >= 3) return cards.slice(0, 3);
|
||||
@@ -866,10 +920,18 @@ export class MissionCardComp extends CCComp {
|
||||
const fallback = drawCardsByRule(this.poolLv, {
|
||||
count: 3,
|
||||
type: targetType,
|
||||
wave: currentWave
|
||||
wave: currentWave,
|
||||
unique: true
|
||||
});
|
||||
if (fallback.length === 0) break;
|
||||
filled.push(fallback[filled.length % fallback.length]);
|
||||
|
||||
// 如果池子数量不足,只能被迫允许重复,但尽量拿没被抽到的
|
||||
const fPick = fallback.find(c => !filled.some(fc => fc.uuid === c.uuid));
|
||||
if (fPick) {
|
||||
filled.push(fPick);
|
||||
} else {
|
||||
filled.push(fallback[filled.length % fallback.length]);
|
||||
}
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
@@ -974,23 +1036,6 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
/** 更新升级按钮上的等级文案,反馈当前卡池层级 */
|
||||
private updatePoolLvUI() {
|
||||
// if (this.cards_up) {
|
||||
// const nobg = this.cards_up.getChildByName("nobg");
|
||||
// if (nobg) {
|
||||
// nobg.active = !this.canUpPool();
|
||||
// }
|
||||
// const coinNode = this.cards_up.getChildByName("coin");
|
||||
// const label = coinNode?.getChildByName("num")?.getComponent(Label);
|
||||
// if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
|
||||
// if (label) {
|
||||
// label.string = `0`;
|
||||
// }
|
||||
// } else {
|
||||
// if (label) {
|
||||
// label.string = `${this.getUpgradeCost(this.poolLv)}`;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if (this.pool_lv_node) {
|
||||
this.pool_lv_node.active = true;
|
||||
const lv = Math.max(CARD_POOL_INIT_LEVEL, Math.min(CARD_POOL_MAX_LEVEL, Math.floor(this.poolLv)));
|
||||
@@ -1001,12 +1046,48 @@ export class MissionCardComp extends CCComp {
|
||||
label.string = `lv.${lv}`;
|
||||
}
|
||||
}
|
||||
// const widget = this.pool_lv_node.getComponent(Widget);
|
||||
// if (widget) widget.updateAlignment();
|
||||
// this.pool_lv_node.children.forEach(child => {
|
||||
// const childWidget = child.getComponent(Widget);
|
||||
// if (childWidget) childWidget.updateAlignment();
|
||||
// });
|
||||
|
||||
const nextNode = this.pool_lv_node.getChildByName("next");
|
||||
if (nextNode) {
|
||||
const nextLabel = nextNode.getComponent(Label);
|
||||
if (nextLabel) {
|
||||
if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
|
||||
nextLabel.string = `已满级`;
|
||||
} else {
|
||||
// 优先取 MissionComp 运行时配置,缺失时回退到全局常量
|
||||
let upgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
|
||||
ecs.query(ecs.allOf(MissionComp)).forEach((entity) => {
|
||||
const mission = entity.get(MissionComp);
|
||||
if (mission && mission.cardPoolUpgradeWaves && mission.cardPoolUpgradeWaves.length > 0) {
|
||||
upgradeWaves = mission.cardPoolUpgradeWaves;
|
||||
}
|
||||
});
|
||||
|
||||
// 已完成的升级次数 = 当前等级 - 初始等级
|
||||
// 例:poolLv=2(INIT=1)→ 已升 1 次 → 下一升级对应 upgradeWaves[1]
|
||||
const upgradedCount = Math.max(0, Math.floor(this.poolLv) - CARD_POOL_INIT_LEVEL);
|
||||
const currentWave = this.getCurrentWave();
|
||||
|
||||
if (upgradedCount >= upgradeWaves.length) {
|
||||
// 配置已耗尽但等级未到上限(配置缺陷)
|
||||
nextLabel.string = `已满级`;
|
||||
} else {
|
||||
const nextWave = upgradeWaves[upgradedCount];
|
||||
if (nextWave > currentWave) {
|
||||
const remain = nextWave - currentWave;
|
||||
nextLabel.string = `${remain} 回合后升级`;
|
||||
} else if (nextWave === currentWave) {
|
||||
// 当前波次正好是升级波次(事件可能即将触发或刚刚触发)
|
||||
nextLabel.string = `本回合升级`;
|
||||
} else {
|
||||
// nextWave < currentWave:异常状态,升级事件未按时触发
|
||||
nextLabel.string = `即将升级`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const peak = 1.2
|
||||
this.playHeroNumNodePop(this.pool_lv_node, peak);
|
||||
}
|
||||
@@ -1043,7 +1124,7 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
private updateCoinAndCostUI() {
|
||||
// this.updatePoolLvUI();
|
||||
this.updatePoolLvUI();
|
||||
this.updateDrawCostUI();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,13 +39,12 @@ import { HeroViewComp } from "../hero/HeroViewComp";
|
||||
import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { SkillView } from "../skill/SkillView";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES } 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";
|
||||
import { Tooltip } from "../skill/Tooltip";
|
||||
import { CardInitCoins } from "../common/config/CardSet";
|
||||
import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
||||
import { FieldSkillType } from "../common/config/SkillSet";
|
||||
import { FieldSkillHelper } from "../hero/FieldSkillHelper";
|
||||
@@ -88,9 +87,8 @@ export class MissionComp extends CCComp {
|
||||
private maxMonsterCount: number = 80;
|
||||
/** 怪物数量恢复阈值(降至此值以下恢复刷怪) */
|
||||
private resumeMonsterCount: number = 45;
|
||||
/** 卡池升级波次配置:达到对应波次时,推送卡池升级事件 */
|
||||
@property({ type: [CCInteger], tooltip: "卡池升级波次配置,例如 [10, 20] 表示第10波升到2级,第20波升到3级" })
|
||||
cardPoolUpgradeWaves: number[] = [5, 10];
|
||||
/** 卡池升级波次配置(默认值来自 GameSet.CARD_POOL_UPGRADE_WAVES,保持全局统一) */
|
||||
public cardPoolUpgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
|
||||
|
||||
// ======================== 编辑器绑定节点 ========================
|
||||
|
||||
@@ -198,10 +196,12 @@ export class MissionComp extends CCComp {
|
||||
this.mission_start();
|
||||
}, 0);
|
||||
smc.map.MapView.scene.mapLayer.stopAnimations();
|
||||
smc.map.MapView.scene.mapLayer.node.getChildByName("fight").getChildByName("fbox").active = true;
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
smc.map.MapView.scene.mapLayer.playAnimations()
|
||||
smc.map.MapView.scene.mapLayer.node.getChildByName("fight").getChildByName("fbox").active = false;
|
||||
super.onDestroy();
|
||||
if (this.start_btn && this.start_btn.isValid) {
|
||||
this.start_btn.off(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this);
|
||||
@@ -495,6 +495,9 @@ export class MissionComp extends CCComp {
|
||||
|
||||
// 战斗结束阶段,给予所有英雄恢复70%血量的技能效果
|
||||
this.healAllHeroes();
|
||||
|
||||
// 【新增】派发每波战斗结束事件,供卡牌技能监听(区别于整局结束的 MissionEnd)
|
||||
oops.message.dispatchEvent(GameEvent.FightEnd);
|
||||
break;
|
||||
|
||||
case MissionPhase.Settle:
|
||||
@@ -732,7 +735,7 @@ export class MissionComp extends CCComp {
|
||||
// 重置所有的战局得分数据,防止上一局的数据污染
|
||||
smc.resetScores();
|
||||
|
||||
smc.vmdata.mission_data.coin = Math.max(0, Math.floor(CardInitCoins));
|
||||
smc.vmdata.mission_data.coin = Math.max(0, Math.floor(FightSet.INIT_COIN));
|
||||
// 【评分系统 - 效率分】记录初始获得的金币收入
|
||||
smc.vmdata.scores.gold_earned += smc.vmdata.mission_data.coin;
|
||||
}
|
||||
@@ -853,11 +856,6 @@ export class MissionComp extends CCComp {
|
||||
// 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,且待刷新的怪物队列也为空,直接结束战斗进入下一波的准备阶段
|
||||
const pendingCount = smc.vmdata.mission_data.pending_mon_num || 0;
|
||||
if (monsterCount === 0 && pendingCount === 0 && smc.mission.play && !smc.mission.pause && this.currentPhase === MissionPhase.Battle) {
|
||||
let heroesAliveRatio = heroCount / 6.0; // 假设最大 6 个站位,或者直接基于存活数算比例
|
||||
// 如果能获取当前已部署英雄数最好,这里简化处理,大于 4 个就算高存活
|
||||
heroesAliveRatio = Math.min(1.0, heroCount / 4.0);
|
||||
spawningEngine.updateAdaptive(heroesAliveRatio, this.clearTime);
|
||||
|
||||
if (this.currentWave >= 15) {
|
||||
// 15 波通关
|
||||
this.open_Victory(null, false);
|
||||
|
||||
@@ -55,12 +55,12 @@ export class MissionHeroComp extends CCComp {
|
||||
|
||||
/** 硬编码的6个英雄占位点 */
|
||||
public static readonly HERO_POSITIONS: Vec3[] = [
|
||||
v3(-210, BoxSet.GAME_LINE + 90, 0), // index 0 (node_index 1): Top Front
|
||||
v3(-160, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
|
||||
v3(-210, BoxSet.GAME_LINE - 90, 0), // index 2 (node_index 3): Bot Front
|
||||
v3(-300, BoxSet.GAME_LINE + 90, 0), // index 3 (node_index 4): Top Back
|
||||
v3(-300, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
|
||||
v3(-300, BoxSet.GAME_LINE - 90, 0), // index 5 (node_index 6): Bot Back
|
||||
v3(-175, BoxSet.GAME_LINE + 100, 0), // index 0 (node_index 1): Top Front
|
||||
v3(-170, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
|
||||
v3(-175, BoxSet.GAME_LINE - 100, 0), // index 2 (node_index 3): Bot Front
|
||||
v3(-280, BoxSet.GAME_LINE + 100, 0), // index 3 (node_index 4): Top Back
|
||||
v3(-280, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
|
||||
v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back
|
||||
];
|
||||
|
||||
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */
|
||||
|
||||
@@ -3,27 +3,13 @@
|
||||
* @description 怪物(Monster)波次刷新管理组件(逻辑层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 管理每一波怪物的 **生成计划**:根据 WaveSlotConfig 生成怪物。
|
||||
* 2. 处理特殊插队刷怪请求(MonQueue),优先于常规刷新。
|
||||
* 3. 自动推进波次:当前波所有怪物被清除后自动进入下一波。
|
||||
* 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。
|
||||
* 2. 自动推进波次:在准备阶段结束时(PhasePrepareEnd)统一刷出怪物。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 突破 5 槽限制,怪物按刷怪顺序依次从 X=280 开始向右(每隔 50)排布。
|
||||
* - 6 条刷怪线:在三路 Y 轴范围内随机偏移,实现 6 路进军。
|
||||
* - resetSlotSpawnData(wave) 在每波开始时读取配置,分配并立即生成所有怪物。
|
||||
* - 去除跨波 HP 继承,上一波残留怪在波次结束/开始时销毁。
|
||||
*
|
||||
* 怪物属性计算公式:
|
||||
* ap = floor((base_ap + stage × grow_ap) × SpawnPowerBias)
|
||||
* hp = floor((base_hp + stage × grow_hp) × SpawnPowerBias)
|
||||
* 其中 stage = currentWave - 1
|
||||
*
|
||||
* 依赖:
|
||||
* - RogueConfig —— 怪物类型、成长值、波次配置
|
||||
* - Monster(hero/Mon.ts)—— 怪物 ECS 实体类
|
||||
* - HeroInfo(heroSet)—— 怪物基础属性配置(与英雄共用配置)
|
||||
* - HeroAttrsComp / MonMoveComp —— 怪物属性和移动组件
|
||||
* - BoxSet.GAME_LINE —— 地面基准 Y 坐标
|
||||
* - 采用 12 个硬编码的网格位置点 (MON_POSITIONS,3行x4列)
|
||||
* - 每次生成最多 12 个怪物,固定在位置点。
|
||||
* - 上一波残留怪在波次结束/开始时统一清理。
|
||||
*/
|
||||
import { _decorator, v3, Vec3 } from "cc";
|
||||
import { mLogger } from "../common/Logger";
|
||||
@@ -31,21 +17,15 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { Monster } from "../hero/Mon";
|
||||
import { HeroInfo, HType } from "../common/config/heroSet";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import {BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { spawningEngine, GeneratedMonster, AffixType, MonType, MonList, TestModeConfig } from "./RogueConfig";
|
||||
import { BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { spawningEngine, GeneratedMonster, TestModeConfig } from "./RogueConfig";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { MonMoveComp } from "../hero/MonMoveComp";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* MissionMonCompComp —— 怪物波次刷新管理器
|
||||
*
|
||||
* 每波开始时根据 WaveSlotConfig 配置生成怪物,
|
||||
* 战斗中监控数量,所有怪物消灭后自动推进到下一波。
|
||||
*/
|
||||
@ccclass('MissionMonCompComp')
|
||||
@ecs.register('MissionMonComp', false)
|
||||
export class MissionMonCompComp extends CCComp {
|
||||
@@ -59,21 +39,21 @@ export class MissionMonCompComp extends CCComp {
|
||||
/** 硬编码的 12 个怪物占位点 (3行4列) */
|
||||
public static readonly MON_POSITIONS: Vec3[] = [
|
||||
// 第 1 列 (X=60)
|
||||
v3(60, BoxSet.GAME_LINE + 90, 0), // index 0: Top
|
||||
v3(60, BoxSet.GAME_LINE, 0), // index 1: Mid
|
||||
v3(60, BoxSet.GAME_LINE - 90, 0), // index 2: Bot
|
||||
v3(0, BoxSet.GAME_LINE + 100, 0), // index 0: Top
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 1: Mid
|
||||
v3(0, BoxSet.GAME_LINE - 100, 0), // index 2: Bot
|
||||
// 第 2 列 (X=140)
|
||||
v3(140, BoxSet.GAME_LINE + 90, 0), // index 3: Top
|
||||
v3(140, BoxSet.GAME_LINE, 0), // index 4: Mid
|
||||
v3(140, BoxSet.GAME_LINE - 90, 0), // index 5: Bot
|
||||
v3(90, BoxSet.GAME_LINE + 100, 0), // index 3: Top
|
||||
v3(90, BoxSet.GAME_LINE, 0), // index 4: Mid
|
||||
v3(90, BoxSet.GAME_LINE - 100, 0), // index 5: Bot
|
||||
// 第 3 列 (X=220)
|
||||
v3(220, BoxSet.GAME_LINE + 90, 0), // index 6: Top
|
||||
v3(220, BoxSet.GAME_LINE, 0), // index 7: Mid
|
||||
v3(220, BoxSet.GAME_LINE - 90, 0), // index 8: Bot
|
||||
v3(180, BoxSet.GAME_LINE + 100, 0), // index 6: Top
|
||||
v3(180, BoxSet.GAME_LINE, 0), // index 7: Mid
|
||||
v3(180, BoxSet.GAME_LINE - 100, 0), // index 8: Bot
|
||||
// 第 4 列 (X=300)
|
||||
v3(300, BoxSet.GAME_LINE + 90, 0), // index 9: Top
|
||||
v3(300, BoxSet.GAME_LINE, 0), // index 10: Mid
|
||||
v3(300, BoxSet.GAME_LINE - 90, 0), // index 11: Bot
|
||||
v3(270, BoxSet.GAME_LINE + 100, 0), // index 9: Top
|
||||
v3(270, BoxSet.GAME_LINE, 0), // index 10: Mid
|
||||
v3(270, BoxSet.GAME_LINE - 100, 0), // index 11: Bot
|
||||
];
|
||||
|
||||
// ======================== 编辑器属性 ========================
|
||||
@@ -81,83 +61,32 @@ export class MissionMonCompComp extends CCComp {
|
||||
@property({ tooltip: "是否启用调试日志" })
|
||||
private debugMode: boolean = false;
|
||||
|
||||
// ======================== 插队刷怪队列 ========================
|
||||
|
||||
/**
|
||||
* 刷怪队列(优先于常规配置处理):
|
||||
* 用于插队生成(如运营活动怪、技能召唤怪、剧情强制怪)。
|
||||
*/
|
||||
private MonQueue: Array<{
|
||||
/** 怪物 UUID */
|
||||
uuid: number,
|
||||
/** 怪物等级 */
|
||||
level: number,
|
||||
/** 飞行层 */
|
||||
flyLane: number,
|
||||
}> = [];
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 全局生成顺序计数器(用于渲染层级排序) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 插队刷怪处理计时器 */
|
||||
private queueTimer: number = 0;
|
||||
/** 当前波数 */
|
||||
private currentWave: number = 0;
|
||||
/** 当前波的目标怪物总数 */
|
||||
private waveTargetCount: number = 0;
|
||||
/** 当前波已生成的怪物数量 */
|
||||
private waveSpawnedCount: number = 0;
|
||||
/** 等待生成的怪物队列(由新肉鸽引擎提供) */
|
||||
/** 等待生成的怪物队列 */
|
||||
private pendingMonsters: GeneratedMonster[] = [];
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
onLoad(){
|
||||
this.on(GameEvent.FightReady,this.fight_ready,this)
|
||||
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
|
||||
onLoad() {
|
||||
this.on(GameEvent.FightReady, this.fight_ready, this);
|
||||
this.on("PhasePrepareEnd", this.onPhasePrepareEnd, this);
|
||||
this.on("TimeUpAdvanceWave", this.onTimeUpAdvanceWave, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 帧更新:
|
||||
* 1. 检查游戏是否运行中。
|
||||
* 2. 处理插队刷怪队列。
|
||||
* 3. 逐步从 pendingMonsters 队列中生成怪物(受 stop_spawn_mon 限制)。
|
||||
*/
|
||||
protected update(dt: number): void {
|
||||
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length;
|
||||
|
||||
if(!smc.mission.play) return
|
||||
if(smc.mission.pause) return
|
||||
if(smc.mission.stop_mon_action) return;
|
||||
if(!smc.mission.in_fight) return;
|
||||
|
||||
this.updateSpecialQueue(dt);
|
||||
}
|
||||
|
||||
// ======================== 事件处理 ========================
|
||||
|
||||
/**
|
||||
* 接收特殊刷怪事件并入队。
|
||||
* @param event 事件名
|
||||
* @param args { uuid: number, level: number, flyLane?: number }
|
||||
*/
|
||||
private onSpawnSpecialMonster(event: string, args: any) {
|
||||
if (!args) return;
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 收到特殊刷怪指令:`, args);
|
||||
this.MonQueue.push({
|
||||
uuid: args.uuid,
|
||||
level: args.level,
|
||||
flyLane: args.flyLane || 0
|
||||
});
|
||||
// 加速队列消费
|
||||
this.queueTimer = 1.0;
|
||||
}
|
||||
|
||||
start() {
|
||||
}
|
||||
start() {}
|
||||
|
||||
private setupWaveData(monsters: GeneratedMonster[]) {
|
||||
this.pendingMonsters = monsters.slice(0, MissionMonCompComp.MAX_MONSTERS);
|
||||
@@ -166,9 +95,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
let hasBoss = monsters.some(m => m.isBoss);
|
||||
|
||||
console.log(`[MissionMonComp] 波次 ${this.currentWave} 生成怪物总数: ${this.waveTargetCount}`);
|
||||
const uuids = monsters.map(m => m.uuid);
|
||||
console.log(`[MissionMonComp] 波次 ${this.currentWave} 怪物 UUID 列表:`, uuids);
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 波次 ${this.currentWave} 生成怪物总数: ${this.waveTargetCount}`);
|
||||
|
||||
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||
wave: this.currentWave,
|
||||
@@ -180,81 +107,41 @@ export class MissionMonCompComp extends CCComp {
|
||||
/**
|
||||
* 战斗准备:重置所有运行时状态并开始第一波。
|
||||
*/
|
||||
fight_ready(){
|
||||
smc.vmdata.mission_data.mon_num=0
|
||||
smc.mission.stop_spawn_mon = false
|
||||
this.globalSpawnOrder = 0
|
||||
this.queueTimer = 0
|
||||
this.currentWave = 1
|
||||
this.waveTargetCount = 0
|
||||
this.waveSpawnedCount = 0
|
||||
this.MonQueue = []
|
||||
this.pendingMonsters = []
|
||||
fight_ready() {
|
||||
smc.vmdata.mission_data.mon_num = 0;
|
||||
smc.mission.stop_spawn_mon = false;
|
||||
this.globalSpawnOrder = 0;
|
||||
this.currentWave = 1;
|
||||
this.waveTargetCount = 0;
|
||||
this.waveSpawnedCount = 0;
|
||||
this.pendingMonsters = [];
|
||||
|
||||
// 预生成第一波数据以获取数量和 Boss 信息
|
||||
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||
this.setupWaveData(monsters);
|
||||
|
||||
// 如果处于测试模式,英雄也需要限制为只产出一个,这部分通知可以配合使用
|
||||
if (TestModeConfig.enable) {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启:每波仅生成1只基准怪物");
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启");
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
|
||||
}
|
||||
|
||||
// ======================== 插队刷怪 ========================
|
||||
|
||||
/**
|
||||
* 处理插队刷怪队列(每 0.15 秒尝试消费一个):
|
||||
* 1. 找到后从队列中移除并生成怪物。
|
||||
*/
|
||||
private updateSpecialQueue(dt: number) {
|
||||
if (this.MonQueue.length <= 0) return;
|
||||
this.queueTimer += dt;
|
||||
if (this.queueTimer < 0.15) return;
|
||||
|
||||
const item = this.MonQueue.shift()!;
|
||||
this.queueTimer = 0;
|
||||
|
||||
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) ||
|
||||
MonList[MonType.LongBoss].includes(item.uuid);
|
||||
|
||||
const spawnIndex = this.waveSpawnedCount++;
|
||||
const targetPosIndex = spawnIndex % MissionMonCompComp.MAX_MONSTERS;
|
||||
|
||||
// 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAtGrid
|
||||
const base = HeroInfo[item.uuid];
|
||||
const monData: GeneratedMonster = {
|
||||
uuid: item.uuid,
|
||||
type: MonType.Melee, // 简化的兜底,真实逻辑依赖 heroSet 配置
|
||||
hp: base ? base.hp : 100,
|
||||
ap: base ? base.ap : 10,
|
||||
affixes: [],
|
||||
isBoss: isBoss,
|
||||
spawnIndex: 0
|
||||
};
|
||||
this.addMonsterAtGrid(targetPosIndex, monData, item.level);
|
||||
}
|
||||
|
||||
// ======================== 波次管理 ========================
|
||||
|
||||
/**
|
||||
* 开始下一波:
|
||||
* 1. 波数 +1 并更新全局数据。
|
||||
* 2. 分发 NewWave 事件(实际的生成在 resetSlotSpawnData 中触发)。
|
||||
* 开始下一波:波数 +1 并预生成数据
|
||||
*/
|
||||
private onTimeUpAdvanceWave() {
|
||||
this.currentWave += 1;
|
||||
smc.vmdata.mission_data.level = this.currentWave;
|
||||
|
||||
// 预生成新一波数据以获取数量和 Boss 信息
|
||||
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||
this.setupWaveData(monsters);
|
||||
}
|
||||
|
||||
private onPhasePrepareEnd() {
|
||||
this.resetSlotSpawnData(this.currentWave);
|
||||
this.resetSlotSpawnData();
|
||||
|
||||
// 准备结束阶段,立即刷出本波所有怪物
|
||||
if (this.pendingMonsters.length > 0) {
|
||||
@@ -262,11 +149,9 @@ export class MissionMonCompComp extends CCComp {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const monData = this.pendingMonsters.shift()!;
|
||||
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS;
|
||||
console.log(`[MissionMonComp] [PhasePrepareEnd] 准备生成怪物 UUID=${monData.uuid}, 当前已生成数量=${this.waveSpawnedCount}`);
|
||||
this.addMonsterAtGrid(targetPosIndex, monData);
|
||||
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
|
||||
this.waveSpawnedCount++;
|
||||
}
|
||||
// 生成完毕后清空 pendingMonsters
|
||||
this.pendingMonsters = [];
|
||||
}
|
||||
}
|
||||
@@ -274,14 +159,9 @@ export class MissionMonCompComp extends CCComp {
|
||||
// ======================== 槽位管理 ========================
|
||||
|
||||
/**
|
||||
* 重新分配本波所有怪物状态:
|
||||
* 1. 清理上一波残留怪物。
|
||||
* 2. pendingMonsters 已在 onTimeUpAdvanceWave / fight_ready 中准备好。
|
||||
*
|
||||
* @param wave 当前波数
|
||||
* 清理上一波残留怪物,并重置生成计数
|
||||
*/
|
||||
private resetSlotSpawnData(wave: number = 1) {
|
||||
// 1. 清理上一波残留怪物
|
||||
private resetSlotSpawnData() {
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
if (attrs && attrs.fac === FacSet.MON && !attrs.is_dead) {
|
||||
@@ -289,18 +169,13 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 重置排号索引
|
||||
this.waveSpawnedCount = 0;
|
||||
}
|
||||
|
||||
// ======================== 怪物生成 ========================
|
||||
|
||||
/**
|
||||
* 在指定位置索引处生成一个怪物:
|
||||
*
|
||||
* @param posIndex 位置索引 (0-11)
|
||||
* @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等)
|
||||
* @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定)
|
||||
* 在指定位置索引处生成一个怪物
|
||||
*/
|
||||
private addMonsterAtGrid(
|
||||
posIndex: number,
|
||||
@@ -310,51 +185,32 @@ export class MissionMonCompComp extends CCComp {
|
||||
let mon = ecs.getEntity<Monster>(Monster);
|
||||
let scale = -1;
|
||||
|
||||
// 获取硬编码的占位点坐标,不再使用随机偏移
|
||||
const basePos = MissionMonCompComp.MON_POSITIONS[posIndex % MissionMonCompComp.MON_POSITIONS.length];
|
||||
const spawnX = basePos.x;
|
||||
const landingY = basePos.y + (monData.isBoss ? 6 : 0);
|
||||
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||
|
||||
// 如果存在测试技能覆盖,则传递下去(修改 mon.load 逻辑或者通过预存)
|
||||
// 为了避免侵入 Mon.ts 的原有逻辑,我们先预存
|
||||
(mon as any)._testSkills = monData.testSkills;
|
||||
if (monData.testSkills) {
|
||||
(mon as any)._testSkills = monData.testSkills;
|
||||
}
|
||||
|
||||
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, posIndex);
|
||||
|
||||
// 设置渲染排序
|
||||
const move = mon.get(MonMoveComp);
|
||||
if (move) {
|
||||
move.spawnOrder = this.globalSpawnOrder;
|
||||
}
|
||||
|
||||
// 应用新引擎计算好的最终属性和词缀
|
||||
// 应用新引擎计算好的最终属性
|
||||
const model = mon.get(HeroAttrsComp);
|
||||
if (!model) return;
|
||||
model.ap = monData.ap;
|
||||
model.hp_max = monData.hp;
|
||||
model.hp = model.hp_max;
|
||||
|
||||
// 将词缀记录到属性组件上,供战斗层使用
|
||||
(model as any).affixes = monData.affixes || [];
|
||||
|
||||
// 解析特定的抗性词缀
|
||||
if (monData.affixes) {
|
||||
if (monData.affixes.includes(AffixType.CritRes)) {
|
||||
model.critical_res = 50;
|
||||
}
|
||||
if (monData.affixes.includes(AffixType.FreezeRes)) {
|
||||
model.freeze_res = 50;
|
||||
}
|
||||
if (monData.affixes.includes(AffixType.KnockbackRes)) {
|
||||
model.knockback_res = 50;
|
||||
}
|
||||
if (model) {
|
||||
model.ap = monData.ap;
|
||||
model.hp_max = monData.hp;
|
||||
model.hp = model.hp_max;
|
||||
}
|
||||
}
|
||||
|
||||
/** ECS 组件移除时触发(当前不销毁节点) */
|
||||
reset() {
|
||||
// this.node.destroy();
|
||||
}
|
||||
/** ECS 组件移除时触发 */
|
||||
reset() {}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { CardConfig, CardType, CKind, CardPoolList } from "../common/config/CardSet";
|
||||
import { CardBgComp } from "./CardBgComp";
|
||||
import { SkillSet } from "../common/config/SkillSet";
|
||||
import { FieldSkillSet, SkillSet } from "../common/config/SkillSet";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -96,7 +96,7 @@ export class SCardComp extends CCComp {
|
||||
|
||||
const success = MissionEconomy.spendCoin(cardCost);
|
||||
if (!success) {
|
||||
oops.gui.toast(`金币不足,需要${cardCost}`);
|
||||
oops.message.dispatchEvent(GameEvent.ShowSmallTip, "buy_coin");
|
||||
this.playReboundAnim();
|
||||
return null;
|
||||
}
|
||||
@@ -258,7 +258,22 @@ export class SCardComp extends CCComp {
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
|
||||
|
||||
if (this.info_node) this.info_node.active = true;
|
||||
if (this.info_node) {
|
||||
this.info_node.active = true;
|
||||
// 驻场技能卡描述取 FieldSkillSet;其他卡牌取卡牌/技能配置
|
||||
let desc = "";
|
||||
if (this.cardData.field && this.cardData.field.length > 0) {
|
||||
desc = FieldSkillSet[this.cardData.field[0]]?.info || "";
|
||||
}
|
||||
if (!desc) {
|
||||
desc = skillCard?.info || skill?.info || this.cardData?.info || "";
|
||||
}
|
||||
// 与 HlistComp 保持一致:优先查找名为 "info" 的子节点上的 Label
|
||||
const infoLabel = this.info_node.getChildByName("info")?.getComponent(Label)
|
||||
|| this.info_node.getComponent(Label)
|
||||
|| this.info_node.getComponentInChildren(Label);
|
||||
if (infoLabel) infoLabel.string = desc;
|
||||
}
|
||||
|
||||
if (this.cost_node) {
|
||||
this.cost_node.active = true;
|
||||
@@ -272,7 +287,14 @@ export class SCardComp extends CCComp {
|
||||
if (iconNode) {
|
||||
iconNode.setScale(new Vec3(1, 1, 1));
|
||||
this.clearIconAnimation(iconNode);
|
||||
const iconId = skill?.icon || `${s_uuid}`;
|
||||
// 驻场技能卡(skill=undefined 但有 field)使用 FieldSkillSet 中的图标
|
||||
let iconId: string;
|
||||
if (!this.cardData.skill && this.cardData.field && this.cardData.field.length > 0) {
|
||||
const fieldUuid = this.cardData.field[0];
|
||||
iconId = FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
|
||||
} else {
|
||||
iconId = skill?.icon || `${s_uuid}`;
|
||||
}
|
||||
this.updateIcon(iconNode, iconId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,27 @@
|
||||
*
|
||||
* 职责:
|
||||
* 1. 表示一张已使用的技能卡在战场上的 **可视化实体**。
|
||||
* 2. 管理技能的 **触发逻辑**:即时触发 vs 定时触发(战斗中按间隔触发)。
|
||||
* 2. 按 trigger_type 类型化分发触发逻辑(即时 / 定时 / 驻场 / 事件型)。
|
||||
* 3. 显示技能图标和剩余触发次数。
|
||||
* 4. 触发结束后自动销毁。
|
||||
*
|
||||
* 关键设计:
|
||||
* - is_instant=true(即时技能):init 时立即触发一次,播放后延迟销毁。
|
||||
* - is_instant=false(持续技能):战斗中每隔 trigger_interval 秒触发一次,
|
||||
* 共触发 trigger_times 次后销毁。
|
||||
* - 新一波(NewWave)时如果持续技能的次数已用完则销毁。
|
||||
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位。
|
||||
* 触发类型(CardTriggerType):
|
||||
* - Instant (1):init 时立即触发一次(按 t_times 控制次数,跨波次 NewWave 时再次触发)
|
||||
* - Interval (2):监听 FightStart → update 帧驱动按 t_inv 间隔重复触发(按 t_times 控制每波次数)
|
||||
* - Field (3):被动生效,不主动施法(实际由 FieldSkillSet 处理)
|
||||
* - FightStart (4):监听 FightStart 事件,按 trigger_limit 全局累计上限
|
||||
* - FightEnd (5):监听 FightEnd 事件(每波结束派发),按 trigger_limit 全局累计上限
|
||||
* - HeroDead (6):监听 HeroDead 事件(仅英雄阵营派发,怪物死亡不触发)
|
||||
* - HeroCall (7):监听 MasterCalled(主角/技能召唤)+ ReviveSuccess(复活)
|
||||
*
|
||||
* 触发技能的方式:
|
||||
* - 通过 GameEvent.TriggerSkill 事件,将技能 UUID、卡牌等级、
|
||||
* 触发位置等信息分发给技能系统。
|
||||
* 关键设计:
|
||||
* - 事件型(4-7)统一走 onEventTrigger 入口,仅作触发信号,不读取 payload
|
||||
* - 触发上限:Instant/Interval 按 t_times(每波内),事件型按 trigger_limit(全局)
|
||||
* - 跨波次:keep_waves 控制存活;事件型 trigger_count 不随波次重置
|
||||
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位
|
||||
*
|
||||
* 依赖:
|
||||
* - CardPoolList(CardSet)—— 查询技能卡的触发配置(t_times / t_inv / is_inst)
|
||||
* - CardPoolList / CardTriggerType(CardSet)—— 查询技能卡的触发配置
|
||||
* - SkillSet —— 技能静态配置(icon 字段)
|
||||
* - GameEvent —— 各类游戏事件
|
||||
* - smc.mission —— 游戏运行状态
|
||||
@@ -29,8 +33,8 @@ import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } 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 { CardPoolList } from "../common/config/CardSet";
|
||||
import { SkillSet, SkillOverrides } from "../common/config/SkillSet";
|
||||
import { CardPoolList, CardTriggerType } from "../common/config/CardSet";
|
||||
import { SkillSet, SkillOverrides, FieldSkillSet } from "../common/config/SkillSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -53,6 +57,10 @@ export class SkillBoxComp extends CCComp {
|
||||
@property({ type: Node })
|
||||
private icon_node: Node = null;
|
||||
|
||||
/** 技能图标节点:下面有 green blue purple red yellow 5个不同颜色节点*/
|
||||
@property({ type: Node })
|
||||
private bg_node: Node = null;
|
||||
|
||||
/** 剩余次数标签 */
|
||||
@property(Label)
|
||||
private info_label: Label = null;
|
||||
@@ -78,6 +86,17 @@ export class SkillBoxComp extends CCComp {
|
||||
private overrides?: SkillOverrides;
|
||||
/** 驻场技能 UUID 列表 */
|
||||
public field: number[] = [];
|
||||
/** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */
|
||||
private card_icon?: string;
|
||||
|
||||
// ======================== 触发类型化扩展 ========================
|
||||
|
||||
/** 触发类型(默认即时,保持向后兼容) */
|
||||
private trigger_type: CardTriggerType = CardTriggerType.Instant;
|
||||
/** 事件型触发的全局次数上限(Infinity 表示无上限) */
|
||||
private trigger_limit: number = Infinity;
|
||||
/** 事件型已触发次数 */
|
||||
private trigger_count: number = 0;
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
@@ -92,9 +111,13 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
/** 注册战斗开始、任务结束、新一波等事件 */
|
||||
/**
|
||||
* 注册全局事件:
|
||||
* - MissionEnd:所有类型都需要监听,任务结束时强制销毁
|
||||
* - NewWave:处理 keep_waves 跨波次逻辑(所有类型统一)
|
||||
* - 其它触发事件由 registerTrigger 按 trigger_type 动态注册
|
||||
*/
|
||||
onLoad() {
|
||||
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
|
||||
oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this);
|
||||
this.node.on(GameEvent.NewWave, this.onNewWave, this);
|
||||
oops.message.on(GameEvent.NewWave, this.onNewWaveGlobal, this);
|
||||
@@ -104,8 +127,15 @@ export class SkillBoxComp extends CCComp {
|
||||
/** 销毁时移除所有事件监听并通知槽位管理器回收 */
|
||||
onDestroy() {
|
||||
super.onDestroy();
|
||||
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
|
||||
// 统一 off 所有可能订阅的事件(即使未订阅也无副作用)
|
||||
// 注意:FightStart 可能由两种回调订阅(Interval→onFightStart / FightStart触发型→onEventTrigger),都需要 off
|
||||
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
|
||||
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
|
||||
oops.message.off(GameEvent.FightStart, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.FightEnd, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.HeroDead, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.MasterCalled, this.onEventTrigger, this);
|
||||
oops.message.off(GameEvent.ReviveSuccess, this.onEventTrigger, this);
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.off(GameEvent.NewWave, this.onNewWave, this);
|
||||
this.node.off(NodeEventType.TOUCH_END, this.onNodeClicked, this);
|
||||
@@ -128,9 +158,9 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 初始化技能卡效果:
|
||||
* 1. 从 CardPoolList 查询技能卡的触发配置。
|
||||
* 1. 从 CardPoolList 查询技能卡的触发配置(含 trigger_type)。
|
||||
* 2. 更新 UI 显示(图标 + 次数)。
|
||||
* 3. 即时技能立即触发一次;若次数已满则延迟销毁。
|
||||
* 3. 按 trigger_type 注册对应事件监听并执行首次触发。
|
||||
*
|
||||
* @param uuid 卡牌 UUID
|
||||
* @param card_lv 技能卡等级
|
||||
@@ -148,58 +178,208 @@ export class SkillBoxComp extends CCComp {
|
||||
this.keep_waves = config.keep_waves ?? 0;
|
||||
this.overrides = config.overrides;
|
||||
this.field = config.field || [];
|
||||
this.card_icon = config.icon; // 保存卡牌自定义图标(优先级最高)
|
||||
// 读取触发类型与上限(兜底默认值,避免 undefined)
|
||||
this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
|
||||
this.trigger_limit = config.trigger_limit ?? Infinity;
|
||||
} else {
|
||||
this.s_uuid = uuid;
|
||||
}
|
||||
|
||||
this.current_trigger_times = 0;
|
||||
this.trigger_count = 0;
|
||||
this.timer = 0;
|
||||
this.initialized = true;
|
||||
|
||||
this.updateUI();
|
||||
|
||||
if (this.is_instant) {
|
||||
// 即时技能:立即触发
|
||||
this.triggerSkill();
|
||||
this.current_trigger_times++;
|
||||
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
|
||||
// 次数已满且不跨波次维持 → 延迟 1 秒后销毁(保留短暂视觉反馈)
|
||||
this.scheduleOnce(() => {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}, 1.0);
|
||||
}
|
||||
// 按 trigger_type 注册事件监听 + 执行首次触发
|
||||
this.registerTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 trigger_type 注册对应事件监听:
|
||||
* - Instant: init 时立即触发一次(保持旧行为)
|
||||
* - Interval: 监听 FightStart,进入战斗后由 update 帧驱动计时
|
||||
* - Field: 不主动施法(实际生效由 FieldSkillSet 处理)
|
||||
* - FightStart: 监听 FightStart 事件
|
||||
* - FightEnd: 监听 FightEnd 事件
|
||||
* - HeroDead: 监听 HeroDead 事件(已在派发处做阵营过滤)
|
||||
* - HeroCall: 监听 MasterCalled(主角/技能召唤)+ ReviveSuccess(复活)
|
||||
*
|
||||
* 注意:MasterCalled 各派发点 payload 不一致,onEventTrigger 仅作触发信号使用。
|
||||
*/
|
||||
private registerTrigger(): void {
|
||||
switch (this.trigger_type) {
|
||||
case CardTriggerType.Instant:
|
||||
// 即时技能:立即触发一次
|
||||
this.onEventTrigger();
|
||||
break;
|
||||
|
||||
case CardTriggerType.Interval:
|
||||
// 定时循环:监听 FightStart 进入战斗后启动计时
|
||||
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.Field:
|
||||
// 驻场光环:不主动施法,由 FieldSkillSet 处理
|
||||
break;
|
||||
|
||||
case CardTriggerType.FightStart:
|
||||
oops.message.on(GameEvent.FightStart, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.FightEnd:
|
||||
oops.message.on(GameEvent.FightEnd, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.HeroDead:
|
||||
oops.message.on(GameEvent.HeroDead, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
case CardTriggerType.HeroCall:
|
||||
// 同时监听召唤和复活两类英雄上场事件
|
||||
oops.message.on(GameEvent.MasterCalled, this.onEventTrigger, this);
|
||||
oops.message.on(GameEvent.ReviveSuccess, this.onEventTrigger, this);
|
||||
break;
|
||||
|
||||
default:
|
||||
mLogger.warn(true, 'SkillBoxComp', `[registerTrigger] unknown trigger_type: ${this.trigger_type}, fallback to Instant`);
|
||||
this.onEventTrigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件型触发的统一入口:
|
||||
* - Instant 类型:按 trigger_times 上限判定,复用 current_trigger_times 跟踪
|
||||
* (保持与原 is_instant 行为一致,且 NewWave 中也用 current_trigger_times)
|
||||
* - 事件型(FightStart/FightEnd/HeroDead/HeroCall):按 trigger_limit 上限判定,使用 trigger_count 跟踪
|
||||
*
|
||||
* 注意:本方法不读取事件 payload,仅作触发信号使用(避免 MasterCalled 不同 payload 字段引发的兼容问题)。
|
||||
*/
|
||||
private onEventTrigger(): void {
|
||||
if (!this.initialized) return;
|
||||
|
||||
if (this.trigger_type === CardTriggerType.Instant) {
|
||||
// 即时触发:上限由 trigger_times 控制(保持旧行为)
|
||||
if (this.current_trigger_times >= this.trigger_times) {
|
||||
this.destroySelf();
|
||||
return;
|
||||
}
|
||||
this.triggerSkill();
|
||||
this.current_trigger_times++;
|
||||
this.updateUI();
|
||||
|
||||
// 单次触发 + 不跨波次维持 → 延迟销毁(保留短暂视觉反馈)
|
||||
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
|
||||
this.scheduleOnce(() => this.destroySelf(), 1.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 事件型:上限由 trigger_limit 控制(全局累计,跨波次不重置)
|
||||
if (this.trigger_count >= this.trigger_limit) {
|
||||
this.destroySelf();
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerSkill();
|
||||
this.trigger_count++;
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的节点销毁封装:
|
||||
* 优先通过 ECS 实体销毁;否则直接销毁节点。
|
||||
*/
|
||||
private destroySelf(): void {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据技能等级切换 bg_node 下的颜色子节点显示。
|
||||
* 等级与颜色对应关系(技能卡 wave 档位):
|
||||
* card_lv 1 (wave 1) → green
|
||||
* card_lv 2 (wave 5) → blue
|
||||
* card_lv 3 (wave 8) → purple
|
||||
*/
|
||||
private updateBgNode() {
|
||||
if (!this.bg_node) return;
|
||||
const lvToColor: Record<number, string> = {
|
||||
1: "green",
|
||||
2: "blue",
|
||||
3: "purple",
|
||||
};
|
||||
const targetColor = lvToColor[this.card_lv];
|
||||
this.bg_node.children.forEach(child => {
|
||||
child.active = (child.name === targetColor);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 UI:
|
||||
* - 图标:从 uicons 图集获取。
|
||||
* - 剩余次数:持续技能显示剩余数字,即时技能不显示。
|
||||
* - 背景:根据技能等级切换对应颜色子节点显示
|
||||
* - 图标:从 uicons 图集获取
|
||||
* - 剩余次数标签:
|
||||
* * Interval / 事件型:显示剩余次数(按各自上限计算)
|
||||
* * Instant / Field:不显示
|
||||
* - CD 遮罩:仅 Interval 类型展示冷却进度
|
||||
*/
|
||||
updateUI() {
|
||||
// 按技能等级切换背景颜色节点(与 getLvColor 等级配色一致)
|
||||
this.updateBgNode();
|
||||
|
||||
// 加载技能图标
|
||||
if (this.icon_node) {
|
||||
const iconId = SkillSet[this.s_uuid]?.icon || `${this.s_uuid}`;
|
||||
if (smc.uiconsAtlas) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
||||
if (frame && this.icon_node && this.icon_node.isValid) {
|
||||
let sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite);
|
||||
sprite.spriteFrame = frame;
|
||||
// 优先使用卡牌自定义 icon;未定义则按 trigger_type 自动取
|
||||
let iconId: string | undefined = this.card_icon;
|
||||
if (!iconId) {
|
||||
if (this.trigger_type === CardTriggerType.Interval) {
|
||||
iconId = this.s_uuid ? SkillSet[this.s_uuid]?.icon : undefined;
|
||||
} else if (this.trigger_type === CardTriggerType.Field) {
|
||||
const fieldUuid = this.field?.[0];
|
||||
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
|
||||
}
|
||||
}
|
||||
if (this.icon_node && this.icon_node.isValid) {
|
||||
let sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite);
|
||||
if (smc.uiconsAtlas && iconId) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
||||
sprite.spriteFrame = frame || null; // 取不到时清空,避免残留
|
||||
} else {
|
||||
sprite.spriteFrame = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新剩余次数标签
|
||||
// 是否需要展示剩余次数
|
||||
const showRemainCount =
|
||||
this.trigger_type === CardTriggerType.Interval ||
|
||||
this.trigger_type === CardTriggerType.FightStart ||
|
||||
this.trigger_type === CardTriggerType.FightEnd ||
|
||||
this.trigger_type === CardTriggerType.HeroDead ||
|
||||
this.trigger_type === CardTriggerType.HeroCall;
|
||||
|
||||
if (this.info_label) {
|
||||
if (!this.is_instant) {
|
||||
if (this.trigger_interval <= 0 && this.field && this.field.length > 0) {
|
||||
this.info_label.string = ""; // 纯驻场技能不显示剩余次数
|
||||
if (showRemainCount) {
|
||||
// 事件型按 trigger_limit;Interval 按 t_times
|
||||
const isEvent =
|
||||
this.trigger_type === CardTriggerType.FightStart ||
|
||||
this.trigger_type === CardTriggerType.FightEnd ||
|
||||
this.trigger_type === CardTriggerType.HeroDead ||
|
||||
this.trigger_type === CardTriggerType.HeroCall;
|
||||
const used = isEvent ? this.trigger_count : this.current_trigger_times;
|
||||
const total = isEvent ? this.trigger_limit : this.trigger_times;
|
||||
if (isEvent && !isFinite(total)) {
|
||||
// 无上限:显示已触发次数
|
||||
this.info_label.string = `${used}`;
|
||||
} else {
|
||||
const remain = Math.max(0, this.trigger_times - this.current_trigger_times);
|
||||
const remain = Math.max(0, Math.floor(total) - used);
|
||||
this.info_label.string = `${remain}`;
|
||||
}
|
||||
} else {
|
||||
@@ -207,14 +387,14 @@ export class SkillBoxComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化或重置 CD 遮罩表现
|
||||
// 初始化或重置 CD 遮罩表现(仅 Interval 类型有冷却进度)
|
||||
if (this.cd_mask && this.cd_mask.isValid) {
|
||||
let sprite = this.cd_mask.getComponent(Sprite);
|
||||
if (sprite) {
|
||||
if (this.is_instant || this.trigger_interval <= 0) {
|
||||
sprite.fillRange = 0; // 无需冷却(包括驻场光环卡),直接归 0
|
||||
} else {
|
||||
if (this.trigger_type === CardTriggerType.Interval && this.trigger_interval > 0) {
|
||||
sprite.fillRange = Math.max(0, 1 - (this.timer / this.trigger_interval));
|
||||
} else {
|
||||
sprite.fillRange = 0; // 非冷却类型直接归 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,14 +402,17 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
// ======================== 战斗状态事件 ========================
|
||||
|
||||
/** 战斗开始:标记进入战斗状态,持续技能开始计时 */
|
||||
/**
|
||||
* 战斗开始回调:
|
||||
* - 仅 Interval 类型在 registerTrigger 中订阅此事件
|
||||
* - 标记进入战斗状态,启动计时器(实际触发由 update 帧驱动)
|
||||
*
|
||||
* 注意:FightStart 触发型(CardTriggerType.FightStart)的事件回调是 onEventTrigger,不是本方法。
|
||||
*/
|
||||
private onFightStart() {
|
||||
if (!this.initialized) return;
|
||||
this.in_combat = true;
|
||||
|
||||
if (!this.is_instant) {
|
||||
this.timer = 0; // 重置计时器
|
||||
}
|
||||
this.timer = 0; // 重置计时器
|
||||
}
|
||||
|
||||
/** 节点级新一波事件处理 */
|
||||
@@ -245,76 +428,79 @@ export class SkillBoxComp extends CCComp {
|
||||
/**
|
||||
* 新一波:退出战斗状态。
|
||||
* 处理维持波次逻辑:递减剩余波次,或者重置触发次数。
|
||||
*
|
||||
* 各类型在新一波的行为:
|
||||
* - Instant/Interval/FightStart/FightEnd:按 keep_waves 决定维持/销毁,并在新一波开始时重置本地计数
|
||||
* - Field:被动生效,跟随 keep_waves 决定存活
|
||||
* - HeroDead/HeroCall:跨波次触发的事件型,trigger_count(全局)不重置,仅 keep_waves 控制存活
|
||||
*/
|
||||
private handleNewWave() {
|
||||
if (!this.initialized) return;
|
||||
this.in_combat = false;
|
||||
|
||||
// 事件型触发(HeroDead / HeroCall):trigger_count 全局累计,不随波次重置
|
||||
const isGlobalEventType =
|
||||
this.trigger_type === CardTriggerType.HeroDead ||
|
||||
this.trigger_type === CardTriggerType.HeroCall;
|
||||
|
||||
if (this.keep_waves !== 0) {
|
||||
if (this.keep_waves > 0) {
|
||||
this.keep_waves--;
|
||||
if (this.keep_waves <= 0) {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
this.destroySelf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 能够跨波次维持,重置触发次数和计时器,以便新一波继续触发
|
||||
this.current_trigger_times = 0;
|
||||
// 跨波次维持:重置本地计数与计时器(事件型 trigger_count 不重置)
|
||||
if (!isGlobalEventType) {
|
||||
this.current_trigger_times = 0;
|
||||
this.trigger_count = 0;
|
||||
}
|
||||
this.timer = 0;
|
||||
|
||||
// 即时技能在新一波开始时立即触发一次
|
||||
if (this.is_instant) {
|
||||
|
||||
// 即时/事件型触发一次(保持旧行为:Instant 在新一波开始立即触发一次)
|
||||
if (this.trigger_type === CardTriggerType.Instant) {
|
||||
this.triggerSkill();
|
||||
this.current_trigger_times++;
|
||||
}
|
||||
this.updateUI();
|
||||
} else {
|
||||
// 默认逻辑:不跨波次维持
|
||||
if (!this.is_instant) {
|
||||
if (this.current_trigger_times >= this.trigger_times) {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
// 不跨波次维持:达到上限即销毁
|
||||
// - Interval / Instant:按 t_times 判定
|
||||
// - 事件型:按 trigger_limit 判定
|
||||
const reachedLimit = isGlobalEventType
|
||||
? this.trigger_count >= this.trigger_limit
|
||||
: this.current_trigger_times >= this.trigger_times;
|
||||
|
||||
if (reachedLimit) {
|
||||
this.destroySelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 任务结束:强制销毁 */
|
||||
private onMissionEnd() {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
this.destroySelf();
|
||||
}
|
||||
|
||||
// ======================== 帧更新 ========================
|
||||
|
||||
/**
|
||||
* 每帧更新(仅对持续技能生效):
|
||||
* - 累加计时器,达到 trigger_interval 时触发一次技能。
|
||||
* - 触发后重置计时器并更新 UI。
|
||||
* - 总次数用完后延迟销毁。
|
||||
* 每帧更新:
|
||||
* - 仅 Interval 类型走帧驱动计时逻辑(其它类型提前 return)
|
||||
* - 累加计时器,达到 trigger_interval 时触发一次技能
|
||||
* - 触发后重置计时器并更新 UI
|
||||
* - 总次数用完后延迟销毁
|
||||
*/
|
||||
update(dt: number) {
|
||||
if (!this.initialized || !this.in_combat || this.is_instant) return;
|
||||
// 收窄:仅 Interval 类型走帧驱动
|
||||
if (this.trigger_type !== CardTriggerType.Interval) return;
|
||||
if (!this.initialized || !this.in_combat) return;
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
|
||||
// 如果是纯驻场光环技能且无触发间隔,则不执行定期触发逻辑
|
||||
if (this.trigger_interval <= 0 && this.field && this.field.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.current_trigger_times < this.trigger_times) {
|
||||
this.timer += dt;
|
||||
|
||||
|
||||
// 更新 CD 遮罩 (fillRange 从 1 降到 0)
|
||||
if (this.cd_mask && this.cd_mask.isValid && this.trigger_interval > 0) {
|
||||
let sprite = this.cd_mask.getComponent(Sprite);
|
||||
@@ -331,13 +517,7 @@ export class SkillBoxComp extends CCComp {
|
||||
|
||||
// 次数用完且不跨波次维持 → 延迟销毁
|
||||
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
|
||||
this.scheduleOnce(() => {
|
||||
if (this.ent) {
|
||||
(this.ent as ecs.Entity).destroy();
|
||||
} else if (this.node && this.node.isValid) {
|
||||
this.node.destroy();
|
||||
}
|
||||
}, 0.5);
|
||||
this.scheduleOnce(() => this.destroySelf(), 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { _decorator, Label, Sprite, SpriteFrame } 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 { CardConfig } from "../common/config/CardSet";
|
||||
import { SkillSet } from "../common/config/SkillSet";
|
||||
import { FieldSkillSet, SkillSet } from "../common/config/SkillSet";
|
||||
import { CardBgComp } from "./CardBgComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
@@ -45,38 +45,35 @@ export class TalentItemComp extends CCComp {
|
||||
*/
|
||||
public updateItem(config: CardConfig): void {
|
||||
if (!config) return;
|
||||
|
||||
|
||||
if (this.lbl_name) this.lbl_name.string = config.name ?? "";
|
||||
if (this.lbl_info) this.lbl_info.string = config.info ?? "";
|
||||
|
||||
// 根据 wave 映射背景颜色
|
||||
// 1=绿色(poolLv=1) 5=蓝色(poolLv=2) 10=紫色(poolLv=3) 15=黄色(poolLv=4) 20=红色(poolLv=5)
|
||||
|
||||
// 直接复用 CardSet 中已映射好的 pool_lv,保证与技能卡牌背景一致
|
||||
// CardSet 通过 waveToPoolLv[wave] 由 SKILL_CARD_WAVES 索引推导(wave 1→1, 5→2, 8→3)
|
||||
if (this.bg) {
|
||||
let poolLv = 1;
|
||||
const wave = config.wave || 1;
|
||||
if (wave >= 20) poolLv = 5;
|
||||
else if (wave >= 15) poolLv = 4;
|
||||
else if (wave >= 10) poolLv = 3;
|
||||
else if (wave >= 5) poolLv = 2;
|
||||
else poolLv = 1;
|
||||
|
||||
this.bg.apply(poolLv);
|
||||
this.bg.apply(config.pool_lv || 1);
|
||||
}
|
||||
|
||||
// 设置图标
|
||||
if (this.icon && config.skill) {
|
||||
const skillData = SkillSet[config.skill];
|
||||
if (skillData && skillData.icon) {
|
||||
// 设置图标:驻场卡(skill=undefined 但有 field)走 FieldSkillSet,否则走 SkillSet
|
||||
// 与 SCardComp 保持一致,避免驻场卡无 icon 显示
|
||||
if (this.icon) {
|
||||
let iconId: string | undefined;
|
||||
if (!config.skill && config.field && config.field.length > 0) {
|
||||
// 驻场卡:用 FieldSkillSet[field[0]].icon
|
||||
const fieldUuid = config.field[0];
|
||||
iconId = FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
|
||||
} else if (config.skill) {
|
||||
// 技能卡:用 SkillSet[skill].icon
|
||||
iconId = SkillSet[config.skill]?.icon;
|
||||
}
|
||||
if (iconId) {
|
||||
if (smc.uiconsAtlas) {
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(skillData.icon);
|
||||
if (frame) {
|
||||
this.icon.spriteFrame = frame;
|
||||
}
|
||||
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
|
||||
if (frame) this.icon.spriteFrame = frame;
|
||||
} else {
|
||||
const sf = oops.res.get("game/heros/cards/" + skillData.icon, SpriteFrame) as SpriteFrame;
|
||||
if (sf) {
|
||||
this.icon.spriteFrame = sf;
|
||||
}
|
||||
const sf = oops.res.get("game/heros/cards/" + iconId, SpriteFrame) as SpriteFrame;
|
||||
if (sf) this.icon.spriteFrame = sf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
bezierMidHeight: number = 200;
|
||||
bezierArc: number = 1;
|
||||
|
||||
/** 是否逐渐加速 (ease-in) */
|
||||
isAccelerate: boolean = false;
|
||||
|
||||
/** 是否自动销毁(到达目标后) */
|
||||
autoDestroy: boolean = true;
|
||||
|
||||
@@ -80,6 +83,7 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
this.bezierStartHeight = 30;
|
||||
this.bezierMidHeight = 200;
|
||||
this.bezierArc = 1;
|
||||
this.isAccelerate = false;
|
||||
this.speed = 500;
|
||||
this.progress = 0;
|
||||
this.scale = 1;
|
||||
@@ -181,14 +185,20 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
* 根据移动类型计算当前位置
|
||||
*/
|
||||
private calculateCurrentPosition() {
|
||||
// 如果开启了逐渐加速,混合线性与二次方曲线 (如 0.7 * t^2 + 0.3 * t)
|
||||
// 这样起步拥有 30% 的基础速度,不会显得完全静止,随后逐渐加速
|
||||
const t = this.isAccelerate ?
|
||||
(this.progress * this.progress * 0.7 + this.progress * 0.3) :
|
||||
this.progress;
|
||||
|
||||
switch (this.runType) {
|
||||
case RType.linear:
|
||||
// 直线运动
|
||||
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, this.progress);
|
||||
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, t);
|
||||
break;
|
||||
|
||||
case RType.bezier:
|
||||
this.calculateBezierPosition(this.progress);
|
||||
this.calculateBezierPosition(t);
|
||||
break;
|
||||
|
||||
case RType.fixed:
|
||||
@@ -198,7 +208,7 @@ export class SMoveDataComp extends ecs.Comp {
|
||||
break;
|
||||
|
||||
default:
|
||||
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, this.progress);
|
||||
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,13 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据配置设置移动速度
|
||||
// 根据配置设置移动速度与加速度
|
||||
if (skillConfig.speed > 0) {
|
||||
moveComp.speed = skillConfig.speed;
|
||||
}
|
||||
if (skillConfig.is_accel) {
|
||||
moveComp.isAccelerate = true;
|
||||
}
|
||||
|
||||
// 根据runType设置初始位置
|
||||
this.initializePosition(moveComp, skillView);
|
||||
@@ -190,10 +193,12 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
if (moveComp.progress < 1) {
|
||||
// 计算下一帧的位置来确定方向
|
||||
const nextProgress = Math.min(moveComp.progress + 0.01, 1);
|
||||
const t = moveComp.isAccelerate ?
|
||||
(nextProgress * nextProgress * 0.7 + nextProgress * 0.3) :
|
||||
nextProgress;
|
||||
const nextPos = v3(0, 0, 0);
|
||||
|
||||
// 计算下一个位置
|
||||
const t = nextProgress;
|
||||
const oneMinusT = 1 - t;
|
||||
const oneMinusTSquared = oneMinusT * oneMinusT;
|
||||
const tSquared = t * t;
|
||||
@@ -245,6 +250,7 @@ export class SMoveHelper {
|
||||
if (skillConfig) {
|
||||
moveComp.runType = skillConfig.RType || RType.linear;
|
||||
moveComp.speed = skillConfig.speed || 500;
|
||||
if (skillConfig.is_accel) moveComp.isAccelerate = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export class Tooltip extends ecs.Entity {
|
||||
this.remove(TooltipCom);
|
||||
super.destroy();
|
||||
}
|
||||
static load(pos: Vec3 = Vec3.ZERO,type:number=1,vaule:string="",s_uuid:number=1001,parent:any=null,cd:number=1,fac:number=FacSet.MON) {
|
||||
static load(pos: Vec3 = Vec3.ZERO,type:number=1,vaule:string="",s_uuid:number=1001,parent:any=null,cd:number=1,fac:number=FacSet.MON,triggerType:string="") {
|
||||
let node: Node;
|
||||
if (Tooltip.pool.size() > 0) {
|
||||
node = Tooltip.pool.get()!;
|
||||
@@ -75,7 +75,7 @@ export class Tooltip extends ecs.Entity {
|
||||
node.active = true;
|
||||
|
||||
var sv = node.getComponent(TooltipCom)!;
|
||||
sv.init(type, vaule, s_uuid, fac);
|
||||
sv.init(type, vaule, s_uuid, fac, triggerType);
|
||||
|
||||
// this.add(sv); // 不要添加到单例实体上,否则会覆盖或导致单例被销毁
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { _decorator, Collider2D, Contact2DType, v3, IPhysics2DContact, Vec3, tween, Label, resources, SpriteFrame, Sprite, UIOpacity, Color, math, Tween } from "cc";
|
||||
import { _decorator, Collider2D, Contact2DType, v3, IPhysics2DContact, Vec3, tween, Label, resources, SpriteFrame, Sprite, UIOpacity, Color, math, Tween,Node } 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 { SkillSet } from "../common/config/SkillSet";
|
||||
import { FacSet, TooltipTypes } from "../common/config/GameSet";
|
||||
import { SkillTriggerType } from "../common/config/heroSet";
|
||||
import { Tooltip } from "./Tooltip";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
@@ -11,11 +12,26 @@ const { ccclass, property } = _decorator;
|
||||
@ccclass('TooltipCom')
|
||||
@ecs.register('TooltipView', false)
|
||||
export class TooltipCom extends CCComp {
|
||||
@property(Node)
|
||||
atking_bg: Node = null;
|
||||
@property(Node)
|
||||
atked_bg: Node = null;
|
||||
@property(Node)
|
||||
fstart_bg: Node = null;
|
||||
@property(Node)
|
||||
fend_bg: Node = null;
|
||||
@property(Node)
|
||||
dead_bg: Node = null;
|
||||
@property(Node)
|
||||
revive_bg: Node = null;
|
||||
|
||||
stype: number = 1; // 1:减少生命值,2:增加生命值,3:技能图标
|
||||
value: string = "";
|
||||
s_uuid: number = 1001;
|
||||
fac: number = FacSet.MON;
|
||||
|
||||
/** 当前技能喊话对应的触发类型(空字符串表示普通主动技能) */
|
||||
triggerType: string = "";
|
||||
|
||||
// 动画参数配置
|
||||
private readonly popDuration = 0.15;
|
||||
private readonly driftDuration = 0.5;
|
||||
@@ -29,11 +45,12 @@ export class TooltipCom extends CCComp {
|
||||
}
|
||||
|
||||
/** 初始化并播放动画 */
|
||||
init(type: number, value: string, uuid: number, fac: number = FacSet.MON) {
|
||||
init(type: number, value: string, uuid: number, fac: number = FacSet.MON, triggerType: string = "") {
|
||||
this.stype = type;
|
||||
this.value = value;
|
||||
this.s_uuid = uuid;
|
||||
this.fac = fac;
|
||||
this.triggerType = triggerType;
|
||||
|
||||
// 初始化或获取 UIOpacity 组件
|
||||
this._uiOpacity = this.node.getComponent(UIOpacity);
|
||||
@@ -106,6 +123,8 @@ export class TooltipCom extends CCComp {
|
||||
this.setupLabel("skill", "name", skillName+this.value);
|
||||
// this.node.setPosition(v3(this.node.position.x, currentY));
|
||||
this.node.setSiblingIndex(topSiblingIndex);
|
||||
// 根据触发类型激活对应的背景标识(追击/反击/起手/生息/亡语/复活)
|
||||
this.setupTriggerBg(this.triggerType);
|
||||
break;
|
||||
case TooltipTypes.uskill:
|
||||
this.setupLabel("uskill", "name", this.value);
|
||||
@@ -146,6 +165,34 @@ export class TooltipCom extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据技能触发类型激活对应的背景标识节点
|
||||
* 仅 TooltipTypes.skill 走此分支,其他飘字类型不受影响
|
||||
* 对象池复用场景下先统一关闭所有 _bg,避免上一次的状态残留
|
||||
*/
|
||||
private setupTriggerBg(triggerType: string) {
|
||||
// 先关闭所有触发类型背景,防止节点池复用时残留
|
||||
this.atking_bg && (this.atking_bg.active = false);
|
||||
this.atked_bg && (this.atked_bg.active = false);
|
||||
this.fstart_bg && (this.fstart_bg.active = false);
|
||||
this.fend_bg && (this.fend_bg.active = false);
|
||||
this.dead_bg && (this.dead_bg.active = false);
|
||||
this.revive_bg && (this.revive_bg.active = false);
|
||||
|
||||
if (!triggerType) return;
|
||||
|
||||
const bgMap: Record<string, Node | null> = {
|
||||
[SkillTriggerType.Atking]: this.atking_bg,
|
||||
[SkillTriggerType.Atked]: this.atked_bg,
|
||||
[SkillTriggerType.FStart]: this.fstart_bg,
|
||||
[SkillTriggerType.FEnd]: this.fend_bg,
|
||||
[SkillTriggerType.Dead]: this.dead_bg,
|
||||
[SkillTriggerType.Revive]: this.revive_bg,
|
||||
};
|
||||
const bg = bgMap[triggerType];
|
||||
if (bg) bg.active = true;
|
||||
}
|
||||
|
||||
playAnimation(scaleMax: number, isCrit: boolean, isHeal: boolean, sx: number = 1) {
|
||||
// 随机 X 轴偏移 (防止重叠)
|
||||
const randomX = (Math.random() - 0.5) * 60;
|
||||
|
||||
1741
docs/superpowers/plans/2026-06-20-config-editor-foundation.md
Normal file
1741
docs/superpowers/plans/2026-06-20-config-editor-foundation.md
Normal file
File diff suppressed because it is too large
Load Diff
59
docs/superpowers/skill_card_trigger_refactor.md
Normal file
59
docs/superpowers/skill_card_trigger_refactor.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 技能卡触发机制重构方案(已废弃)
|
||||
|
||||
> ⚠️ **本文档已废弃**,被以下执行计划取代:
|
||||
> [`2026-06-19-card-skill-trigger-type-refactor-plan.md`](./2026-06-19-card-skill-trigger-type-refactor-plan.md)
|
||||
>
|
||||
> 废弃原因:本草案存在 3 处关键错误,已在新计划中修正:
|
||||
> 1. 缺少 `Instant` 类型,导致现有即时卡牌无法归类
|
||||
> 2. `BattleEnd` 错误映射到 `MissionEnd`(整局结束),应为 `FightEnd`(每波结束)
|
||||
> 3. `HeroDead` 未提阵营过滤,会导致怪物死亡误触发
|
||||
>
|
||||
> 保留本文档仅作历史记录,请勿参考。
|
||||
|
||||
---
|
||||
|
||||
## 旧草案内容(仅供参考)
|
||||
|
||||
### 原始需求背景
|
||||
当前技能卡(SkillCardData)在 `CardSet.ts` 中仅通过 `is_inst`(是否即时)和 `t_inv`(触发间隔)隐式区分类型。随着技能丰富,需要:
|
||||
1. 明确定义卡牌技能的触发类型(如:驻场、定时)。
|
||||
2. 新增类似于英雄生命周期的触发时机:战斗开始、战斗结束、场上英雄死亡、英雄召唤上场。
|
||||
|
||||
### 原始修改方案
|
||||
|
||||
#### 1. 明确技能触发类型 (`CardSet.ts`)
|
||||
新增枚举 `CardSkillType`,用于明确区分卡牌技能的触发时机:
|
||||
```typescript
|
||||
export enum CardSkillType {
|
||||
Interval = 1, // 间隔定时触发 (战斗中每隔N秒执行)
|
||||
Field = 2, // 驻场技能 (被动光环)
|
||||
BattleStart = 3, // 战斗开始时触发一次
|
||||
BattleEnd = 4, // 战斗结束时触发一次
|
||||
HeroDead = 5, // 场上己方英雄死亡时触发
|
||||
HeroCall = 6, // 场上己方英雄召唤上场时触发
|
||||
}
|
||||
```
|
||||
|
||||
> ❌ **错误 1**:缺少 `Instant` 类型,现有 `is_inst: true` 的卡牌(8301护盾、8302治疗等)无法归类。
|
||||
|
||||
#### 2. 完善事件派发机制
|
||||
为支持新的触发类型,确保相关事件被正确派发:
|
||||
- **英雄召唤上场 (`GameEvent.MasterCalled`)**:已在 `Hero.ts` 中实现。
|
||||
- **英雄死亡 (`GameEvent.HeroDead`)**:需在 `HeroAtkSystem.ts` 中的英雄死亡逻辑里,补充派发 `GameEvent.HeroDead` 事件,供技能盒子监听。
|
||||
- **战斗开始/结束 (`GameEvent.FightStart` / `GameEvent.MissionEnd`)**:已支持。
|
||||
|
||||
> ❌ **错误 2**:`BattleEnd` 映射到 `MissionEnd` 是错的——MissionEnd 是整局任务结束,不是每波战斗结束。
|
||||
> ❌ **错误 3**:HeroDead 未提阵营过滤,怪物死亡会误触发。
|
||||
|
||||
#### 3. 重构技能盒子逻辑 (`SkillBoxComp.ts`)
|
||||
修改 `SkillBoxComp`,使其根据 `trigger_type` 进行不同的监听与触发:
|
||||
|
||||
- **属性定义**:新增解析并保存 `trigger_type`。
|
||||
- **事件监听**:在 `onLoad` 或 `init` 后根据 `trigger_type` 注册相应的监听:
|
||||
- `CardSkillType.BattleStart`: 监听 `GameEvent.FightStart`。
|
||||
- `CardSkillType.BattleEnd`: 监听 `GameEvent.MissionEnd`。
|
||||
- `CardSkillType.HeroDead`: 监听 `GameEvent.HeroDead`。
|
||||
- `CardSkillType.HeroCall`: 监听 `GameEvent.MasterCalled`。
|
||||
- **触发处理**:
|
||||
- 每当监听到对应事件,调用 `triggerSkill()` 释放技能,并累加触发次数。
|
||||
- 若已达最大触发次数,则销毁节点。
|
||||
@@ -0,0 +1,332 @@
|
||||
# 卡牌技能触发类型化改造执行计划
|
||||
|
||||
> 状态:Accepted
|
||||
> 日期:2026-06-19
|
||||
> 关联文档:`skill_card_trigger_refactor.md`(旧草案,已废弃,被本计划取代)
|
||||
> 关联设计:`2026-05-22-skill-template-refactor-design.md`(技能 overrides 机制,本计划复用)
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
### 1.1 现状问题
|
||||
|
||||
当前技能卡([SkillCardData](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts#L151))通过 `is_inst` / `t_inv` / `field` 三个字段**隐式组合**推断触发模式:
|
||||
|
||||
| 隐式模式 | 判定条件 | 痛点 |
|
||||
|---------|---------|------|
|
||||
| 即时一次性 | `is_inst: true` | 类型不直观,新人需交叉对比 3 个字段 |
|
||||
| 战斗中定时 | `is_inst: false && t_inv > 0` | 同上 |
|
||||
| 纯驻场光环 | `field.length > 0 && t_inv <= 0` | 同上 |
|
||||
|
||||
且**无法表达**事件驱动型触发(战斗开始/结束、英雄死亡/召唤)。
|
||||
|
||||
### 1.2 改造目标
|
||||
|
||||
1. **显式类型化**:新增 `trigger_type` 字段,一张卡一个类型,强制必填
|
||||
2. **事件驱动扩展**:新增 4 种事件触发类型,对齐英雄侧 [SkillTriggerType](file:///d:/game/pixelheros/assets/script/game/common/config/heroSet.ts#L91-L101)
|
||||
3. **复用现有事件**:直接监听 `GameEvent.FightStart` / `FightEnd` / `HeroDead` / `MasterCalled` / `ReviveSuccess`
|
||||
4. **零破坏迁移**:一次性批改所有 SkillCardData 配置,不保留向后兼容推断逻辑
|
||||
|
||||
### 1.3 关键决策(已确认)
|
||||
|
||||
| 决策点 | 选择 | 理由 |
|
||||
|--------|------|------|
|
||||
| 向后兼容策略 | **强制显式声明** | 一次性迁移到位,避免推断逻辑长期残留 |
|
||||
| FightEnd 事件 | **新增 FightEnd 派发** | MissionEnd 是整局结束,语义不符;FightEnd 才是每波战斗结束 |
|
||||
| HeroCall 覆盖范围 | **所有英雄上场** | MasterCalled(主角+技能召唤)+ ReviveSuccess(复活) |
|
||||
| Field 类型改造 | **仅显式分类** | 实际生效仍由 FieldSkillSet 处理,SkillBoxComp 不主动施法 |
|
||||
|
||||
---
|
||||
|
||||
## 二、最终设计(融合修正版)
|
||||
|
||||
### 2.1 CardTriggerType 枚举定义
|
||||
|
||||
> 融合说明:吸收旧草案的命名规范(Field/Interval/从1开始),规避其事件映射错误
|
||||
|
||||
```typescript
|
||||
/** 卡牌技能触发类型 */
|
||||
export enum CardTriggerType {
|
||||
Instant = 1, // 即时触发:使用后立即生效一次
|
||||
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
|
||||
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
|
||||
FightStart = 4, // 战斗开始时触发
|
||||
FightEnd = 5, // 战斗结束时触发(每波结束)
|
||||
HeroDead = 6, // 场上己方英雄死亡时触发
|
||||
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
|
||||
}
|
||||
```
|
||||
|
||||
**命名对齐说明**:
|
||||
- `Field` 对齐英雄侧 [SkillTriggerType.Field](file:///d:/game/pixelheros/assets/script/game/common/config/heroSet.ts#L96)
|
||||
- `Interval` 对齐现有 `t_inv`(interval)命名
|
||||
- 枚举值从 1 开始,避免 `0` 的 falsy 坑(`if (trigger_type)` 判断出错)
|
||||
|
||||
### 2.2 事件映射表(核心设计)
|
||||
|
||||
| trigger_type | 监听事件 | 派发点现状 | 需补派发 |
|
||||
|--------------|---------|-----------|---------|
|
||||
| `Instant` | 无(init 时立即触发) | — | — |
|
||||
| `Interval` | `FightStart`(启动计时) | ✅ [MissionComp.ts:458](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts#L458) | — |
|
||||
| `Field` | 无(不主动施法) | — | — |
|
||||
| `FightStart` | `FightStart` | ✅ 已派发 | — |
|
||||
| `FightEnd` | `FightEnd` | ❌ **未派发** | ✅ [MissionComp.ts:494](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts#L494) 之后 |
|
||||
| `HeroDead` | `HeroDead` | ❌ **未派发**(死代码) | ✅ [HeroAtkSystem.ts:329](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts#L329) 内(带阵营过滤) |
|
||||
| `HeroCall` | `MasterCalled` + `ReviveSuccess` | MasterCalled ✅ 已派发;ReviveSuccess ❌ 未派发 | ✅ 复活成功处补 ReviveSuccess |
|
||||
|
||||
### 2.3 CardConfig 接口扩展
|
||||
|
||||
```typescript
|
||||
export interface CardConfig {
|
||||
// ... 既有字段 ...
|
||||
|
||||
/** 触发类型(必填) */
|
||||
trigger_type: CardTriggerType;
|
||||
|
||||
/** 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
|
||||
* 默认 Infinity;达到上限后销毁节点
|
||||
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数 */
|
||||
trigger_limit?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 t_times vs trigger_limit 语义区分
|
||||
|
||||
| 字段 | 适用类型 | 含义 | 重置时机 |
|
||||
|------|---------|------|---------|
|
||||
| `t_times` | `Interval` | 每波内的触发次数上限 | 每波 NewWave 时重置 |
|
||||
| `trigger_limit` | `FightStart/FightEnd/HeroDead/HeroCall` | 整局全局触发总次数 | 不重置,达上限销毁 |
|
||||
|
||||
---
|
||||
|
||||
## 三、分阶段执行计划
|
||||
|
||||
### 阶段 1:补齐事件派发缺口(基础设施)
|
||||
|
||||
**目标**:确保所有新触发类型依赖的事件都能正确派发
|
||||
|
||||
#### 任务 1.1:MissionComp 补派发 FightEnd
|
||||
|
||||
- **文件**:[MissionComp.ts](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts)
|
||||
- **位置**:`BattleEnd` case,`triggerHeroBattleSkills(false)` + `healAllHeroes()` 之后
|
||||
- **改动**:
|
||||
```typescript
|
||||
case MissionPhase.BattleEnd:
|
||||
// ... 既有评分逻辑 ...
|
||||
this.triggerHeroBattleSkills(false);
|
||||
this.healAllHeroes();
|
||||
// 【新增】派发战斗结束事件,供卡牌技能监听
|
||||
oops.message.dispatchEvent(GameEvent.FightEnd);
|
||||
break;
|
||||
```
|
||||
|
||||
#### 任务 1.2:HeroAtkSystem 补派发 HeroDead(带阵营过滤)
|
||||
|
||||
- **文件**:[HeroAtkSystem.ts](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts)
|
||||
- **位置**:`triggerDeadSkills` 方法(L329 附近)
|
||||
- **改动**:
|
||||
```typescript
|
||||
private triggerDeadSkills(entity: ecs.Entity): void {
|
||||
const TAttrsComp = entity.get(HeroAttrsComp);
|
||||
if (!TAttrsComp) return;
|
||||
const view = entity.get(HeroViewComp);
|
||||
if (view) {
|
||||
SkillTriggerHelper.trigger(SkillTriggerType.Dead, TAttrsComp, view);
|
||||
// 【新增】仅英雄阵营派发全局死亡事件(怪物死亡不触发卡牌效果)
|
||||
if (TAttrsComp.fac === FacSet.HERO) {
|
||||
oops.message.dispatchEvent(GameEvent.HeroDead, { eid: entity.eid });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 任务 1.3:复活逻辑补派发 ReviveSuccess
|
||||
|
||||
- **文件**:需先定位复活成功处理点(搜索 `is_reviving` 置 false 的位置)
|
||||
- **改动**:复活成功时派发 `oops.message.dispatchEvent(GameEvent.ReviveSuccess, { eid })`
|
||||
- **注意**:需先执行任务:全局搜索复活成功逻辑位置
|
||||
|
||||
---
|
||||
|
||||
### 阶段 2:CardSet 配置层改造
|
||||
|
||||
**目标**:定义枚举 + 扩展接口 + 修复字段透传 + 批量迁移配置
|
||||
|
||||
#### 任务 2.1:新增 CardTriggerType 枚举
|
||||
|
||||
- **文件**:[CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts)
|
||||
- **位置**:`CardLV` 枚举之后
|
||||
- **内容**:见 [2.1 节](#21-cardtriggertype-枚举定义)
|
||||
|
||||
#### 任务 2.2:CardConfig 接口扩展
|
||||
|
||||
- **文件**:同上
|
||||
- **位置**:`CardConfig` 接口
|
||||
- **内容**:见 [2.3 节](#23-cardconfig-接口扩展)
|
||||
|
||||
#### 任务 2.3:修复 SkillCardData.forEach 字段透传断点
|
||||
|
||||
- **文件**:同上
|
||||
- **位置**:[L220-L240](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts#L220-L240) `SkillCardData.forEach`
|
||||
- **改动**:补充 `overrides` 和 `trigger_type` 透传:
|
||||
```typescript
|
||||
SkillCardData.forEach(data => {
|
||||
CardPoolList.push({
|
||||
// ... 既有字段 ...
|
||||
keep_waves: data.keep_waves,
|
||||
field: data.field,
|
||||
overrides: data.overrides, // 【修复】原遗漏
|
||||
trigger_type: data.trigger_type, // 【新增】
|
||||
trigger_limit: data.trigger_limit, // 【新增】
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### 任务 2.4:SkillCardData 批量补 trigger_type(30 张卡牌)
|
||||
|
||||
- **文件**:同上
|
||||
- **迁移对照表**:
|
||||
|
||||
| 卡牌区间 | 旧字段特征 | 新增 trigger_type |
|
||||
|---------|-----------|------------------|
|
||||
| 8301, 8302, 8303, 8401-8409, 8501(`is_inst: true`) | 即时技能 | `CardTriggerType.Instant` |
|
||||
| 8705, 8706, 8707, 8708-8718, 8701-8704(有 `field`) | 驻场光环 | `CardTriggerType.Field` |
|
||||
| 8201-8206(`is_inst: false, t_inv: 5`) | 定时循环 | `CardTriggerType.Interval` |
|
||||
|
||||
---
|
||||
|
||||
### 阶段 3:SkillBoxComp 核心重构
|
||||
|
||||
**目标**:按 trigger_type 分发事件监听与触发
|
||||
|
||||
#### 任务 3.1:新增成员变量
|
||||
|
||||
- **文件**:[SkillBoxComp.ts](file:///d:/game/pixelheros/assets/script/game/map/SkillBoxComp.ts)
|
||||
- **位置**:`// ======================== 技能配置 ========================` 区块
|
||||
```typescript
|
||||
/** 触发类型 */
|
||||
private trigger_type: CardTriggerType = CardTriggerType.Instant;
|
||||
/** 事件型触发次数上限 */
|
||||
private trigger_limit: number = Infinity;
|
||||
/** 事件型已触发次数 */
|
||||
private trigger_count: number = 0;
|
||||
```
|
||||
|
||||
#### 任务 3.2:init 读取 trigger_type
|
||||
|
||||
- **位置**:`init()` 方法内,读取 config 之后
|
||||
```typescript
|
||||
this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
|
||||
this.trigger_limit = config.trigger_limit ?? Infinity;
|
||||
```
|
||||
|
||||
#### 任务 3.3:新增 registerTrigger 方法
|
||||
|
||||
按 trigger_type 注册对应事件监听,见下方完整代码。
|
||||
|
||||
#### 任务 3.4:新增 onEventTrigger 统一入口
|
||||
|
||||
事件型触发的统一处理:检查 trigger_limit → triggerSkill → 累加计数 → 检查销毁。
|
||||
|
||||
#### 任务 3.5:onLoad / onDestroy 调整
|
||||
|
||||
- `onLoad`:移除原 FightStart / NewWave 硬编码监听,改为 `init` 后调用 `registerTrigger` 动态注册
|
||||
- `onDestroy`:统一 off 所有可能订阅的事件(即使没订阅也无副作用)
|
||||
|
||||
#### 任务 3.6:update 方法收窄
|
||||
|
||||
仅 `Interval` 类型走帧驱动计时逻辑,其他类型提前 return。
|
||||
|
||||
---
|
||||
|
||||
### 阶段 4:验证与回归
|
||||
|
||||
#### 任务 4.1:编译检查
|
||||
- 确认所有 CardTriggerType 引用正确
|
||||
- 确认无 TS 类型错误
|
||||
|
||||
#### 任务 4.2:功能验证清单
|
||||
- [ ] 即时卡(8301 护盾):使用后立即触发,每波重置
|
||||
- [ ] 定时卡(8201 雷墙):战斗中每 5 秒触发,跨波次维持
|
||||
- [ ] 驻场卡(8705 金币收益):被动生效,不主动施法
|
||||
- [ ] 新增 FightStart 卡:每波战斗开始时触发
|
||||
- [ ] 新增 FightEnd 卡:每波战斗结束时触发
|
||||
- [ ] 新增 HeroDead 卡:英雄死亡时触发,怪物死亡不触发
|
||||
- [ ] 新增 HeroCall 卡:主角召唤/技能召唤/复活都触发
|
||||
|
||||
#### 任务 4.3:边界场景
|
||||
- [ ] trigger_limit 达上限后节点正确销毁
|
||||
- [ ] keep_waves 与 trigger_type 的组合行为正确
|
||||
- [ ] 节点销毁时所有事件监听正确注销(无内存泄漏)
|
||||
|
||||
---
|
||||
|
||||
## 四、风险与注意事项
|
||||
|
||||
### 4.1 高风险点
|
||||
|
||||
1. **HeroDead 必须加阵营过滤**
|
||||
- [HeroAtkSystem.triggerDeadSkills](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts#L319) 是英雄和怪物共用
|
||||
- 不加 `fac === FacSet.HERO` 过滤 → 每波几百只怪物死亡 = 海量误触发
|
||||
|
||||
2. **FightEnd vs MissionEnd 不可混淆**
|
||||
- MissionEnd = 整局任务结束(通关/失败)
|
||||
- FightEnd = 每波战斗结束(BattleEnd 阶段)
|
||||
- 文档草案错误地把 BattleEnd 映射到 MissionEnd,本计划已修正
|
||||
|
||||
3. **MasterCalled 携带数据不一致**
|
||||
- 3 个派发点([Hero.ts:206](file:///d:/game/pixelheros/assets/script/game/hero/Hero.ts#L206)、[MissionHeroComp.ts:223](file:///d:/game/pixelheros/assets/script/game/map/MissionHeroComp.ts#L223)、[273](file:///d:/game/pixelheros/assets/script/game/map/MissionHeroComp.ts#L273))payload 字段不同
|
||||
- SkillBoxComp 的 `onEventTrigger` **不要读 payload 字段**,仅作触发信号
|
||||
|
||||
### 4.2 不破坏的现有逻辑
|
||||
|
||||
- ✅ Field 类型完全复用现有 [FieldSkillSet](file:///d:/game/pixelheros/assets/script/game/common/config/SkillSet.ts#L414-L421) 机制
|
||||
- ✅ Interval 类型完全复用现有 `update` 帧驱动 + cd_mask 表现
|
||||
- ✅ [forceCastCardSkill](file:///d:/game/pixelheros/assets/script/game/hero/SCastSystem.ts#L75) 施法入口零改动
|
||||
- ✅ [SBox.ts](file:///d:/game/pixelheros/assets/script/game/map/SBox.ts) 节点工厂零改动
|
||||
|
||||
### 4.3 keep_waves 跨类型语义
|
||||
|
||||
| trigger_type | keep_waves 默认值 | 行为 |
|
||||
|--------------|-----------------|------|
|
||||
| `Instant` | 0 = 用完即销 | `-1` = 每波重置再触发一次 |
|
||||
| `Interval` | -1 = 跨波次维持 | 每波重置 timer 和 trigger_count |
|
||||
| `Field` | -1 = 全程存活 | 不主动触发 |
|
||||
| 事件型 | 由 `trigger_limit` 控制 | 达上限销毁 |
|
||||
|
||||
---
|
||||
|
||||
## 五、文件改动清单
|
||||
|
||||
| 文件 | 改动类型 | 阶段 |
|
||||
|------|---------|------|
|
||||
| [MissionComp.ts](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts) | 补 FightEnd 派发 | 1 |
|
||||
| [HeroAtkSystem.ts](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts) | 补 HeroDead 派发(带过滤) | 1 |
|
||||
| 复活逻辑文件(待定位) | 补 ReviveSuccess 派发 | 1 |
|
||||
| [CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts) | 枚举+接口+透传+30张卡迁移 | 2 |
|
||||
| [SkillBoxComp.ts](file:///d:/game/pixelheros/assets/script/game/map/SkillBoxComp.ts) | 核心重构 | 3 |
|
||||
| [SBox.ts](file:///d:/game/pixelheros/assets/script/game/map/SBox.ts) | **零改动** | — |
|
||||
|
||||
---
|
||||
|
||||
## 六、新增卡牌配置示例
|
||||
|
||||
```typescript
|
||||
// 战斗开始护盾(整局每波开始都给全队加盾)
|
||||
{ uuid: 8310, skill: 6301, wave: 5, name: "起手护盾",
|
||||
trigger_type: CardTriggerType.FightStart, keep_waves: -1,
|
||||
overrides: { TGroup: TGroup.Team, ap: 3 },
|
||||
info: "每波战斗开始时为全体友方添加护盾", is_inst: false }
|
||||
|
||||
// 英雄死亡治疗(整局最多触发 3 次)
|
||||
{ uuid: 8311, skill: 6302, wave: 10, name: "亡语治疗",
|
||||
trigger_type: CardTriggerType.HeroDead, trigger_limit: 3, keep_waves: -1,
|
||||
overrides: { TGroup: TGroup.Team, ap: 200 },
|
||||
info: "己方英雄死亡时治疗全体友方,整局最多触发3次", is_inst: false }
|
||||
|
||||
// 英雄上场攻击强化(每次有新英雄上场都触发,最多 5 次)
|
||||
{ uuid: 8312, skill: 6401, wave: 15, name: "召唤强化",
|
||||
trigger_type: CardTriggerType.HeroCall, trigger_limit: 5, keep_waves: -1,
|
||||
info: "有英雄上场时触发攻击强化,整局最多触发5次", is_inst: false }
|
||||
```
|
||||
@@ -0,0 +1,351 @@
|
||||
# 通用英雄/技能配置编辑器(Cocos Creator 扩展)— 设计规格
|
||||
|
||||
- 日期:2026-06-20
|
||||
- 作者:brainstorm 协作产出
|
||||
- 目标引擎:Cocos Creator **3.8.6**
|
||||
- 部署位置:`extensions/pixelhero-config-editor/`
|
||||
- 关联配置源:`assets/script/game/common/config/{heroSet,SkillSet,HeroAttrs,HeroSkillDesc}.ts`
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述(Overview)
|
||||
|
||||
构建一个 Cocos Creator 编辑器扩展,提供**可视化**的英雄与技能配置编辑能力。扩展以 **schema 驱动 + TypeScript AST 往返**为核心:直接读写现有 `Record<number,X>` 形态的 `.ts` 配置文件,**不改动任何游戏运行时代码**。通过声明式 schema 描述每张表的字段、枚举、引用关系,UI 由 schema 自动生成,从而实现"通用"——未来新增驻场技能表、卡牌表等只需追加 schema,无需编写新 UI。
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
### 目标
|
||||
- 可视化编辑 `HeroInfo`(英雄 50xx + 怪物 60xx/61xx)、`SkillSet`(技能 60xx/63xx/64xx/65xx)、`FieldSkillSet`(驻场 70xx/72xx/74xx)三张表。
|
||||
- 原地回写对应 `.ts` 文件,**保留符号表达式**(如 `AtkSpeedSet[AtkSpeedLv.Slow3].cd`)与手工注释/分节标题。
|
||||
- 异构触发槽、技能引用覆盖(`SkillOverrides`)、进化配置的可视化编辑。
|
||||
- 实时校验 + 实时描述预览(与游戏内 `buildSkillDesc` 一致)。
|
||||
- 新建/复制/删除/保存/还原,并保持 `HeroList` 与 `HeroInfo` 一致。
|
||||
- 纯逻辑层(schema/IO/校验)有自动化单元测试,作为 BLOCKING 证据。
|
||||
|
||||
### 非目标(v1)
|
||||
- 不重构游戏代码、不把数据迁移到 JSON。
|
||||
- 不做运行时热重载游戏逻辑(仅通过 asset-db 刷新让编辑器与编辑器内预览生效)。
|
||||
- 撤销/重做(Undo/Redo)列为 v1.1。
|
||||
- 不编辑 `CardSet / HighlightSet / GameSet / ScoreSet`(架构允许后续以新增 schema 方式扩展,但不在本次范围)。
|
||||
- 不做多人协作/版本对比。
|
||||
|
||||
## 3. 背景事实(已核实)
|
||||
|
||||
| 事实 | 影响 |
|
||||
|---|---|
|
||||
| 引擎 Cocos Creator 3.8.6 | 使用 `package_version:2` 扩展格式;面板经 `Editor.Panel.define({...})`;消息经 `contributions.messages` |
|
||||
| 配置运行时只读、仅按 key 访问、无动态加载 | 回写安全;输出只需是合法 TS 且 `HeroList` 一致 |
|
||||
| `skills[n].cd` 为符号表达式 | IO 必须基于 TS Compiler API,识别并保号往返 |
|
||||
| 现有 `oops-plugin-framework` 仅运行时框架 | 无面板示例可抄;需自建打包 |
|
||||
| `HeroList` 被运行时迭代(`CardSet.ts`) | 写英雄表后必须同步 `HeroList = 排序后的英雄(HERO) uuid` |
|
||||
| `HeroSkillDesc.buildSkillDesc` 生成游戏内描述 | 移植为 JS 用于面板实时预览 |
|
||||
|
||||
## 4. 架构(五层)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ UI 层 Vue3 面板 (dist/panels/default.js) │
|
||||
│ master-detail + 嵌套编辑器 + 校验面板 + 描述预览 │
|
||||
└───────────────────────────────┬────────────────────────────┘
|
||||
Editor.Message.request │ broadcast 'record-changed'
|
||||
┌───────────────────────────────┴────────────────────────────┐
|
||||
│ 主进程 dist/main.js 内存真理源 + 消息处理 + 广播 │
|
||||
└──────┬──────────────────────┬──────────────────────┬───────┘
|
||||
│ │ │
|
||||
┌──────▼───────┐ ┌───────────▼──────────┐ ┌─────────▼────────┐
|
||||
│ 校验层 │ │ Schema 注册表 │ │ IO 层 (TS 往返) │
|
||||
│ validate() │ │ tables/fields/enums │ │ TsConfigFile │
|
||||
│ → Issue[] │ │ → 驱动 UI 生成 │ │ load/patch/save │
|
||||
└──────────────┘ └──────────────────────┘ └──────────┬────────┘
|
||||
│ 写回 .ts
|
||||
Editor.Message.request('asset-db','refresh-asset')
|
||||
```
|
||||
|
||||
层次依赖单向:UI → 主进程 → {校验, schema, IO}。校验与 schema 为纯逻辑(可独立测试)。IO 依赖 `typescript` 包。
|
||||
|
||||
## 5. 组件详设
|
||||
|
||||
### 5.1 Schema 注册表(`src/shared/schema/`)
|
||||
|
||||
"通用"的核心。每个数据表用一份 `TableSchema` 描述:
|
||||
|
||||
```ts
|
||||
interface TableSchema {
|
||||
id: 'hero' | 'skill' | 'field'; // 表标识
|
||||
label: string; // "英雄/怪物"
|
||||
sourceFile: string; // 相对 assets 配置目录,如 'heroSet.ts'
|
||||
exportName: string; // 'HeroInfo' | 'SkillSet' | 'FieldSkillSet'
|
||||
keyType: 'number';
|
||||
idSegments: { label: string; min: number; max: number; note?: string }[];
|
||||
listExportName?: string; // 仅 hero:'HeroList'(需同步)
|
||||
fields: FieldSchema[]; // 记录字段(顺序即 UI 顺序)
|
||||
}
|
||||
|
||||
interface FieldSchema {
|
||||
key: string; // 字段名(对应 .ts 对象键)
|
||||
label: string; // 中文标签
|
||||
type: FieldType; // 见下
|
||||
required?: boolean;
|
||||
default?: unknown;
|
||||
group?: string; // UI 分组("基础"/"触发技能"/...)
|
||||
help?: string;
|
||||
// type 相关的可选元数据:
|
||||
enumRef?: string; // type=enum → enums.ts 中的枚举键
|
||||
refTable?: TableId; // type=ref → 引用哪张表
|
||||
overlayKeys?: string[]; // type=overrides → 可覆盖键集合
|
||||
showIf?: { field: string; in: unknown[] };// 条件显示
|
||||
}
|
||||
|
||||
type FieldType =
|
||||
| 'number' | 'string' | 'boolean'
|
||||
| 'enum' // 下拉,选项来自 enumRef
|
||||
| 'ref' // 引用另一表的 uuid(下拉,显示 目标.name)
|
||||
| 'speedExpr' // 攻速符号表达式,下拉=AtkSpeedLv 档位
|
||||
| 'skillMap' // Record<number,HSkillInfo>(英雄专用)
|
||||
| 'triggerSlots' // 6 种数组触发槽(英雄专用)
|
||||
| 'fieldList' // number[] 驻场技能列表(英雄专用)
|
||||
| 'reviveSlot' // 单对象 {s_uuid,r_num,upr}(英雄专用)
|
||||
| 'overrides'; // SkillOverrides 覆盖层(出现在 triggerSlots 内部)
|
||||
```
|
||||
|
||||
**枚举源 `src/shared/schema/enums.ts`**:镜像游戏枚举为 `{label,value}[]` 字典——`HType, FacSet, TGroup, DTType, SkillKind, DType, IType, RType, EType, FieldSkillType, Attrs(buff_type), AtkSpeedLv`。此文件为编辑器侧单一事实源;并提供 `assertEnumsMatchGame()` 调试期检查(读取游戏 `.ts` 枚举定义比对,不一致则告警),避免漂移。
|
||||
|
||||
**三张表的字段清单(v1)**:
|
||||
|
||||
- **hero (`HeroInfo`)** — 分组:
|
||||
- 基础:`uuid`(number,必填), `name`(string,必填), `path`(string,必填), `icon`?(string), `fac`(enum FacSet,必填), `pool_lv`?(number), `lv`(number,必填,默认1), `type`(enum HType,必填), `hp`(number,必填), `ap`(number,必填), `dis`?(number), `speed`?(number), `info`(string,必填)
|
||||
- 技能:`skills`(skillMap,必填), 触发槽组 `call/dead/fstart/fend/atking/atked`(triggerSlots), `field`(fieldList), `revive`(reviveSlot), `evolve`(evolveMap — v1 只读展示,标注"v1.1 编辑")
|
||||
- ID 段:英雄 [5000,5999];怪物 [6000,6999](6101-6106 为 Boss)
|
||||
- **skill (`SkillSet` → `SkillConfig`)**:
|
||||
- 基础:`uuid`(number,必填), `name`(string,必填), `sp_name`(string,必填), `icon`(string,必填), `act`(string,必填), `info`(string,必填)
|
||||
- 目标/类型:`TGroup`(enum,必填), `DTType`(enum,必填), `IType`(enum,必填), `RType`(enum,必填), `EType`(enum,必填), `kind`?(enum SkillKind), `DType`?(enum,默认 ATK)
|
||||
- 数值:`ap`(number,必填), `gold`?(number), `hit_count`(number,必填), `hitcd`(number,必填), `speed`(number,必填), `ready`(number,必填), `with`(number,必填,默认0)
|
||||
- 动画/特效:`readyAnm`,`endAnm`,`DAnm`(string), `EAnm`(number)
|
||||
- 高级:`crt?`,`stun?`,`frz?`,`bck?`(number), `buff_type`?(enum Attrs), `call_hero`?(ref hero), `time`?, `bezier_start_y?`,`bezier_mid_y?`,`bezier_arc?`(number)
|
||||
- ID 段:6001-6999
|
||||
- **field (`FieldSkillSet` → `FieldSkillConfig`)**:
|
||||
- `uuid`(number,必填), `name`(string,必填), `icon`(string,必填), `type`(enum FieldSkillType,必填), `value`(number,必填), `info`(string,必填)
|
||||
- ID 段:7001-7999
|
||||
|
||||
> 字段清单来源 = 直接对照 `heroSet.ts`/`SkillSet.ts` 的 interface 定义,已逐字段核对类型与必填性。
|
||||
|
||||
### 5.2 IO 层(`src/main/io/TsConfigFile.ts`)
|
||||
|
||||
基于 `typescript` 包(npm,在扩展主进程 Node 上下文中 `require`)。
|
||||
|
||||
```ts
|
||||
class TsConfigFile {
|
||||
load(file, exportName): void; // 解析并缓存 SourceFile + 目标 VariableDeclaration
|
||||
getKeys(): number[]; // 该 const 的所有键
|
||||
read(key): RecordValue; // AST → 结构化值
|
||||
patch(key, value: RecordValue): void; // AST 区间替换该条目
|
||||
add(key, value): void; // 在 const 末尾插入新条目
|
||||
delete(key): void; // 删除条目区间
|
||||
serialize(value): string; // 单条目确定性序列化
|
||||
save(): { ok: boolean; error?: string }; // 写 .bak → 校验可解析 → 落盘 → 触发 asset-db refresh
|
||||
reload(): void;
|
||||
}
|
||||
```
|
||||
|
||||
**值模型 `RecordValue`**:
|
||||
- 标量:`{kind:'num',value}` / `{kind:'str',value}` / `{kind:'bool',value}`
|
||||
- 符号表达式:`{kind:'speed', level:'Slow3'}` ⇄ 源 `AtkSpeedSet[AtkSpeedLv.Slow3].cd`(AST 形态:`PropertyAccessExpression(ElementAccessExpression(Ident,ElementAccessExpression(Ident,Ident)),Ident)`,固定匹配该模式;不匹配则降级为 `{kind:'num',value:<节点文本>}` 并标记需人工确认)
|
||||
- 对象:`{kind:'obj', props: {key: RecordValue}}`
|
||||
- 数组:`{kind:'arr', items: RecordValue[]}`
|
||||
|
||||
**回写策略 = 条目级 AST 区间替换**:
|
||||
1. 解析文件 → 定位目标 `exportName` 的 `ObjectLiteralExpression`。
|
||||
2. 在其中按 key 定位单个 `PropertyAssignment` 的完整文本区间(含其尾随逗号)。
|
||||
3. 对该条目调用 `serialize()` 生成新文本。**确定性格式(固定,无歧义)**:受控多行对象字面量,每字段一行、4 空格缩进、字段顺序按 schema `fields` 顺序、尾随逗号;`cd` 用符号形式 `AtkSpeedSet[AtkSpeedLv.X].cd`;`info` 非空时在条目上方输出一行 `// {info}` 注释。示例:
|
||||
```ts
|
||||
// 每受击3次为自身添加4层护盾
|
||||
5011:{uuid:5011,name:"小铁卫",path:"hk1",fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:400,ap:20,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
|
||||
info:"每受击3次为自身添加4层护盾"},
|
||||
```
|
||||
4. 用新文本替换该区间;其余条目、分节注释、其他 const 原样不动 → **diff 最小**。
|
||||
5. 新增:在 `}` 前插入;删除:移除区间。
|
||||
6. 落盘前:`createProgram` 对改动文件做语法检查;失败则回滚 `.bak` 并报错。
|
||||
7. 落盘后:`await Editor.Message.request('asset-db','refresh-asset', url)`,使编辑器重新导入该脚本。
|
||||
|
||||
**降级与安全**:
|
||||
- 任一步骤异常 → 不写文件,返回结构化错误,面板展示。
|
||||
- 始终先写 `*.bak`;校验通过后再覆盖原文件。
|
||||
- 若条目内出现未识别的初始化表达式(非上述 RecordValue 形态),该条目标记为"只读(含未支持表达式)",可编辑其他条目但不改动它,避免破坏。
|
||||
|
||||
### 5.3 校验层(`src/shared/validation/`)
|
||||
|
||||
纯函数 `validate(tableId, allRecords): Issue[]`,每次改动后对受影响表运行。规则:
|
||||
|
||||
| 规则 | 级别 |
|
||||
|---|---|
|
||||
| uuid 在表内唯一 | error |
|
||||
| uuid 落在该实体声明的 idSegments 内 | error |
|
||||
| 必填字段非空 | error |
|
||||
| enum 字段值 ∈ 枚举集合 | error |
|
||||
| ref 字段目标在引用表中存在(英雄触发槽/技能图引用 → SkillSet;field 列表 → FieldSkillSet;call_hero → HeroInfo) | error |
|
||||
| overrides 仅含 `SkillOverrides` 允许键(TGroup,ap,gold,hit_count,hitcd,crt,frz,stun,bck,buff_type,call_hero) | error |
|
||||
| 英雄表:`HeroList` 与 `HeroInfo` 一致(每 HeroList 项存在且 fac=HERO;每 fac=HERO 条目都在 HeroList) | error |
|
||||
|
||||
**`HeroList` 同步策略(最小 diff)**:保留现有数组顺序与分节注释,仅在新增英雄时把新 uuid 追加到数组末尾、删除英雄时移除其 uuid。**不重排、不重生成**,以避免破坏手工分节注释。同步后再次运行一致性校验。
|
||||
| 怪物无 `pool_lv`/`evolve`(语义警告) | warn |
|
||||
| `info` 文案长度 >0 | warn |
|
||||
|
||||
`Issue = {tableId, key, fieldPath, severity:'error'|'warn', code, message}`。面板据此在列表行显红点、字段内联报错;存在 error 时禁用"保存"。
|
||||
|
||||
### 5.4 主进程(`src/main/index.ts`)
|
||||
|
||||
扩展入口。在内存持有三张表的 `TsConfigFile` 实例与 schema 注册表,作为唯一真理源。处理消息(见 §7)。任何写入先校验:error 则拒绝并返回 Issue 列表;成功后广播 `record-changed {tableId, key}`,所有打开面板据此刷新。
|
||||
|
||||
### 5.5 UI 层(`src/panels/default/`,Vue 3)
|
||||
|
||||
- **打包**:`esbuild` 将 `src/panels/default/index.ts`(含 Vue 3 runtime+compiler)打成单文件 `dist/panels/default.js`。Vue 用**字符串模板**(`compiler` 在线编译),避免 SFC 工具链。
|
||||
- **面板入口**(`Editor.Panel.define`):`template`=`<div id="app"></div>`,`ready()` 中 `createApp({…}).mount(this.$.app)`,`close()` 卸载。
|
||||
- **布局**:左 master(表切换 + 搜索 + 列表),右 detail(schema 驱动表单 + 嵌套编辑器 + 预览),底部校验条。
|
||||
- **控件映射**:number→`<ui-num-input>`;string→`<ui-input>`;boolean→`<ui-checkbox>`;enum→`<ui-select>`;ref→`<ui-select>`(选项=目标表 `{uuid:name}`,空选项=清除);speedExpr→`<ui-select>`(选项=AtkSpeedLv 档位,含"自定义数值"兜底)。
|
||||
- **嵌套编辑器**:
|
||||
- `TriggerSlotsEditor`:对 6 种触发类型,每组可增删行 `{s_uuid(技能 ref 选择器), t_num(number), overrides(可折叠)}`;行内显示该技能基础信息(name/kind)。
|
||||
- `SkillMapEditor`:英雄 `skills`;每项 `uuid + lv + cd(speedExpr)`;至少 1 项(index 0=普攻)。
|
||||
- `FieldListEditor`:`field:number[]` 多选驻场技能。
|
||||
- `ReviveEditor`:`revive` 单对象 `{s_uuid,r_num,upr}` 或空。
|
||||
- `OverrideEditor`:依据所选基础技能 `kind`,仅渲染相关覆盖键(如 Damage→ap/hit_count/crt/stun…;Support→TGroup/ap/buff_type…)。
|
||||
- **实时预览**:`PreviewPane` 调主进程 `query-preview-desc`(移植 `buildSkillDesc`),随编辑即时刷新。
|
||||
- **图标/特效预览**:`icon`、`sp_name` 变更时 `query-asset` 取贴图,旁置缩略图。
|
||||
- **操作栏**:新建(自动取 idSegment 内下一个可用 uuid)、复制(uuid+2 或手动指定)、删除、还原(reload)、保存并刷新(触发 §5.2 save)。未保存改动用 `*` 标记;切换记录前若有未保存改动,弹确认。
|
||||
- **原生观感**:尽量用 Cocos `<ui-*>` 元素,Vue 仅做状态与组合。
|
||||
|
||||
## 6. 内存与持久化
|
||||
|
||||
- 真理源 = 磁盘 `.ts` 文件。主进程首次 `query-*` 时懒加载并缓存 `TsConfigFile`。
|
||||
- 编辑改动先作用于内存 AST;"保存"才落盘。还原=丢弃内存改动重载。
|
||||
- 多面板实例:主进程单例,广播保证一致。
|
||||
|
||||
## 7. 消息协议(`contributions.messages`)
|
||||
|
||||
| 消息 | 方向 | 载荷 | 返回 |
|
||||
|---|---|---|---|
|
||||
| `query-schema` | panel→main | `tableId?` | schema(全部或单表) |
|
||||
| `query-enums` | panel→main | — | 枚举字典 |
|
||||
| `query-keys` | panel→main | `tableId` | `number[]` |
|
||||
| `query-record` | panel→main | `tableId,key` | `RecordValue` |
|
||||
| `query-preview-desc` | panel→main | `hero RecordValue` | `string`(描述) |
|
||||
| `query-asset` | panel→main | `name, type` | 贴图 url 或 null |
|
||||
| `validate` | panel→main | `tableId` | `Issue[]` |
|
||||
| `save-record` | panel→main | `tableId,key,RecordValue` | `{ok, issues?}` |
|
||||
| `add-record` | panel→main | `tableId,key,RecordValue` | `{ok, issues?}` |
|
||||
| `delete-record` | panel→main | `tableId,key` | `{ok, issues?}` |
|
||||
| `revert-record` | panel→main | `tableId,key` | `RecordValue`(重载后值) |
|
||||
| `record-changed` | main→broadcast | `tableId,key` | — |
|
||||
|
||||
`save/add/delete` 成功后主进程自动广播 `record-changed`。
|
||||
|
||||
## 8. 数据流(保存一条英雄)
|
||||
|
||||
```
|
||||
面板改字段 → save-record(hero,5011,value)
|
||||
→ 主进程 validate(hero):error? 返回 {ok:false,issues}
|
||||
→ TsConfigFile.patch(5011, value) // AST 区间替换
|
||||
→ TsConfigFile.save():写 .bak → createProgram 语法校验 → 覆盖原 .ts → asset-db refresh
|
||||
→ 广播 record-changed(hero,5011)
|
||||
→ 面板重查 → UI 刷新(含 HeroList 若变动)
|
||||
```
|
||||
|
||||
## 9. 模块/文件布局
|
||||
|
||||
```
|
||||
extensions/pixelhero-config-editor/
|
||||
├── package.json # package_version:2, panels, contributions.messages/menu, deps: typescript, vue, esbuild
|
||||
├── tsconfig.json
|
||||
├── esbuild.config.mjs # 打包 main + 面板
|
||||
├── i18n/{en,zh}.js
|
||||
├── static/
|
||||
│ ├── template/default/index.html
|
||||
│ └── style/default/index.css
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── index.ts # 扩展入口 + onMessage 注册
|
||||
│ │ └── store.ts # 三张表 TsConfigFile 单例 + 广播
|
||||
│ ├── io/
|
||||
│ │ ├── TsConfigFile.ts # 解析/序列化/patch/save
|
||||
│ │ ├── recordValue.ts # RecordValue 类型与归一化(含 speedExpr)
|
||||
│ │ └── serializer.ts # serialize(entry) 确定性文本生成
|
||||
│ ├── shared/ # 纯逻辑(无 Cocos/Editor 依赖,可独立测试)
|
||||
│ │ ├── schema/
|
||||
│ │ │ ├── types.ts # TableSchema/FieldSchema 定义
|
||||
│ │ │ ├── registry.ts # 三张表 schema 注册
|
||||
│ │ │ ├── hero.ts
|
||||
│ │ │ ├── skill.ts
|
||||
│ │ │ ├── field.ts
|
||||
│ │ │ └── enums.ts # 枚举镜像 + assertEnumsMatchGame
|
||||
│ │ ├── validation/
|
||||
│ │ │ └── index.ts # validate() 规则
|
||||
│ │ └── desc/
|
||||
│ │ └── buildSkillDesc.ts # HeroSkillDesc 的 JS 移植(预览用)
|
||||
│ └── panels/default/
|
||||
│ ├── index.ts # Editor.Panel.define + Vue mount
|
||||
│ └── app/ # Vue 组件(App, MasterList, DetailForm, TriggerSlots, SkillMap, FieldList, Revive, Override, PreviewPane, ValidationBar)
|
||||
├── dist/ # 打包产物(main.js, panels/default.js)
|
||||
└── __tests__/ # node:test 纯逻辑测试
|
||||
├── tsConfigFile.roundtrip.test.ts
|
||||
├── serializer.test.ts
|
||||
├── validation.test.ts
|
||||
├── speedExpr.test.ts
|
||||
├── schema.test.ts
|
||||
└── fixtures/ # 真实配置副本
|
||||
```
|
||||
|
||||
## 10. 构建与开发
|
||||
|
||||
- `npm run build`:`esbuild` 同时打包 `main`(platform=node, format=cjs)与 `panels/default`(platform=browser, format=iife, bundle vue),输出到 `dist/`。
|
||||
- 开发:改完 `build` → 在 Cocos"扩展管理器"重载扩展。
|
||||
- 依赖:`typescript`(IO 用)、`vue`(面板用)、`esbuild`(打包)、`fs-extra`(可选,读模板)。`typescript` 体积大但必要;打 main 时 bundle 进 dist。
|
||||
|
||||
## 11. 测试策略
|
||||
|
||||
遵循项目 `coding-standards.md`:
|
||||
|
||||
- **逻辑层(BLOCKING,自动化,`node:test`)**:
|
||||
- `tsConfigFile.roundtrip`:用真实 `heroSet.ts`/`SkillSet.ts` 副本作 fixture → load → 读全部 → 不改动 → save → 与原文件逐字节相等(验证保号保注释)。
|
||||
- 改动往返:patch 一条英雄 → save → 重新 load → 读回值 == 改入值;且产物经 `createProgram` 语法合法。
|
||||
- `serializer`:给定结构化值 → 序列化文本 → 再解析 → 等价。
|
||||
- `speedExpr`:`AtkSpeedSet[AtkSpeedLv.Slow3].cd` ⇄ `{kind:'speed',level:'Slow3'}` 双向。
|
||||
- `validation`:构造各类非法数据 → 断言 Issue 正确。
|
||||
- `schema`:所有表 schema 字段 key 与对应 interface 一致;枚举镜像与游戏 `.ts` 枚举一致(`assertEnumsMatchGame`)。
|
||||
- 命名/隔离/无外部依赖,遵循测试规范;fixture 为常量文件副本,不内联魔法数。
|
||||
- **UI(ADVISORY)**:`production/qa/evidence/` 下手动走查文档(覆盖三表增删改查、校验阻断、预览一致性)+ 编辑器内截图。
|
||||
|
||||
## 12. 分阶段实施(供 writing-plans 细化)
|
||||
|
||||
| 阶段 | 产出 | 验证 |
|
||||
|---|---|---|
|
||||
| P0 脚手架 | package.json/panels/menu/build;面板可从菜单打开显示 "hello" | 截图 |
|
||||
| P1 IO 层 | TsConfigFile + RecordValue + serializer + roundtrip 测试 | 测试通过 |
|
||||
| P2 schema+枚举+校验 | 三表 schema、enums、validate + 测试 | 测试通过 |
|
||||
| P3 主进程 | store + 全部消息处理(先只读类) | 手动 query 验证 |
|
||||
| P4 面板基础 | master 列表 + 标量/枚举/ref 字段编辑 + 保存往返 | 端到端改一个英雄保存 |
|
||||
| P5 嵌套与预览 | 触发槽/技能图/驻场/复活/覆盖 + 描述预览 + 缩略图 | 编辑复杂英雄保存往返一致 |
|
||||
| P6 收尾 | 新建/复制/删除/HeroList 同步/还原/打磨/QA 文档 | QA 走查通过 |
|
||||
|
||||
## 13. 风险与缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|---|---|
|
||||
| TS AST 往返破坏文件 | 条目级区间替换 + `.bak` + `createProgram` 校验 + 未识别表达式标记只读 |
|
||||
| 符号表达式形态多样 | 固定匹配 `AtkSpeedSet[AtkSpeedLv.X].cd` 模式;其余降级为只读数值并提示 |
|
||||
| 枚举镜像与游戏漂移 | `assertEnumsMatchGame` 调试期比对告警 |
|
||||
| `typescript` 打包体积 | 仅 bundle 进 main(node),面板不包含;可接受 |
|
||||
| esbuild/Vue 在扩展环境运行 | P0 先打通最小面板(Vue mount 成功)再扩展 |
|
||||
| 多面板状态不一致 | 主进程单例 + 广播 record-changed |
|
||||
| `HeroList` 失同步 | 英雄表写入后由 store 强制同步并校验 |
|
||||
|
||||
## 14. 验收标准
|
||||
|
||||
1. 扩展可从"面板"菜单打开,三张表可切换浏览、搜索。
|
||||
2. 对三张表任一记录:可视化查看/编辑所有字段(含触发槽、技能图、覆盖、驻场、复活),保存后磁盘 `.ts` 被更新且为合法 TS,asset-db 已刷新。
|
||||
3. **保号往返**:仅查看后保存,文件零字节变化(测试断言)。
|
||||
4. 实时校验:违反任一 error 规则时"保存"被阻断并列出问题;行/字段级可见。
|
||||
5. 描述预览与游戏内 `buildSkillDesc` 输出一致。
|
||||
6. 新建/复制/删除可用;`HeroList` 与 `HeroInfo` 始终一致。
|
||||
7. 逻辑层单元测试全部通过(BLOCKING)。
|
||||
8. 未改动 `assets/script/game/**` 任何游戏运行时代码(git diff 可证)。
|
||||
3
extensions/pixelhero-config-editor/.gitignore
vendored
Normal file
3
extensions/pixelhero-config-editor/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.bak
|
||||
1
extensions/pixelhero-config-editor/__tests__/.gitignore
vendored
Normal file
1
extensions/pixelhero-config-editor/__tests__/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.tmp/
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { buildSkillDesc } from '../src/shared/desc/buildSkillDesc';
|
||||
import { RecordValue } from '../src/io/recordValue';
|
||||
|
||||
const skillSet: Record<number, RecordValue> = {
|
||||
6301: { kind: 'obj', props: { name: { kind: 'str', value: '护盾' }, kind: { kind: 'enumRef', qualifier: 'SkillKind', member: 'Shield' }, ap: { kind: 'num', value: 4 } } },
|
||||
};
|
||||
const fieldSet: Record<number, RecordValue> = {
|
||||
7015: { kind: 'obj', props: { name: { kind: 'str', value: '死亡强化' }, info: { kind: 'str', value: '死亡触发技能次数+1' } } },
|
||||
};
|
||||
const hero: RecordValue = { kind: 'obj', props: {
|
||||
atked: { kind: 'arr', items: [{ kind: 'obj', props: { s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 }, overrides: { kind: 'obj', props: { ap: { kind: 'num', value: 4 } } } } }] },
|
||||
field: { kind: 'arr', items: [{ kind: 'num', value: 7015 }] },
|
||||
} };
|
||||
|
||||
test('renders atked trigger with shield effect', () => {
|
||||
const out = buildSkillDesc(hero, skillSet, fieldSet);
|
||||
assert.match(out, /受击3次:护盾/);
|
||||
assert.match(out, /护盾4次/);
|
||||
});
|
||||
test('renders field aura', () => {
|
||||
const out = buildSkillDesc(hero, skillSet, fieldSet);
|
||||
assert.match(out, /场上存活:死亡强化 死亡触发技能次数\+1/);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
// 测试夹具:镜像真实 heroSet.ts 片段(speed 表达式 + 枚举引用 + 触发槽 + 驻场 + 复活)
|
||||
export const HeroInfo: Record<number, any> = {
|
||||
5011:{uuid:5011,name:"小铁卫",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:400,ap:20,
|
||||
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
|
||||
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
|
||||
field:[7015],
|
||||
revive:{s_uuid:6501,r_num:1,upr:0.3},
|
||||
info:"每受击3次为自身添加4层护盾"},
|
||||
6001:{uuid:6001,name:"兽人战士",path:"m1", fac:FacSet.MON,lv:1,type:HType.Melee,hp:220,ap:10,speed:70,
|
||||
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础近战位怪"},
|
||||
};
|
||||
|
||||
export const HeroList: number[] = [5011];
|
||||
@@ -0,0 +1,16 @@
|
||||
// 测试夹具:镜像真实 SkillSet.ts 片段(含 SkillSet + FieldSkillSet)
|
||||
export const SkillSet: Record<number, any> = {
|
||||
6001:{uuid:6001,name:"火球",sp_name:"atk_1",icon:"Stat_Attack_01",TGroup:TGroup.Enemy,act:"atk",
|
||||
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.2,
|
||||
IType:IType.Melee,RType:RType.bezier,EType:EType.collision,info:"造成攻击力100%的伤害"},
|
||||
6301:{uuid:6301,name:"护盾",sp_name:"buff_wind",icon:"Stat_Defense",TGroup:TGroup.Self,act:"atk",
|
||||
DTType:DTType.single,kind:SkillKind.Shield,ap:3,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.2,
|
||||
IType:IType.support,RType:RType.fixed,EType:EType.animationEnd,info:"添加护盾"},
|
||||
6501:{uuid:6501,name:"复活",sp_name:"buff_wind",icon:"Stat_HolyDamage",TGroup:TGroup.Self,act:"atk",
|
||||
DTType:DTType.single,kind:SkillKind.Support,ap:50,hit_count:3,hitcd:0.2,speed:720,with:0,ready:0.2,
|
||||
IType:IType.support,RType:RType.fixed,EType:EType.animationEnd,info:"复活百分比"},
|
||||
};
|
||||
|
||||
export const FieldSkillSet: Record<number, any> = {
|
||||
7015:{uuid:7015,name:"死亡强化",icon:"Stat_PoisonChanceIncrease",type:FieldSkillType.DeadCount,value:1,info:"死亡触发技能次数+1"},
|
||||
};
|
||||
47
extensions/pixelhero-config-editor/__tests__/parser.test.ts
Normal file
47
extensions/pixelhero-config-editor/__tests__/parser.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as ts from 'typescript';
|
||||
import { parseExpression, findExportObjectLiteral } from '../src/io/parser';
|
||||
|
||||
function parse(text: string): ts.Expression {
|
||||
const src = ts.createSourceFile('x.ts', `const _ = ${text};`, ts.ScriptTarget.Latest, true);
|
||||
const decl = src.statements[0] as ts.VariableStatement;
|
||||
return decl.declarationList.declarations[0].initializer!;
|
||||
}
|
||||
|
||||
test('num / str / bool', () => {
|
||||
assert.deepEqual(parseExpression(parse('400')), { kind: 'num', value: 400 });
|
||||
assert.deepEqual(parseExpression(parse('"小铁卫"')), { kind: 'str', value: '小铁卫' });
|
||||
assert.deepEqual(parseExpression(parse('true')), { kind: 'bool', value: true });
|
||||
});
|
||||
test('enumRef: FacSet.HERO', () => {
|
||||
assert.deepEqual(parseExpression(parse('FacSet.HERO')), { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' });
|
||||
});
|
||||
test('speed: AtkSpeedSet[AtkSpeedLv.Slow3].cd', () => {
|
||||
assert.deepEqual(parseExpression(parse('AtkSpeedSet[AtkSpeedLv.Slow3].cd')), { kind: 'speed', level: 'Slow3' });
|
||||
});
|
||||
test('non-Speed two-segment access falls back to enumRef', () => {
|
||||
assert.deepEqual(parseExpression(parse('Foo.bar')), { kind: 'enumRef', qualifier: 'Foo', member: 'bar' });
|
||||
});
|
||||
test('arr', () => {
|
||||
assert.deepEqual(parseExpression(parse('[1,2,3]')), { kind: 'arr', items: [
|
||||
{ kind: 'num', value: 1 }, { kind: 'num', value: 2 }, { kind: 'num', value: 3 }] });
|
||||
});
|
||||
test('obj with numeric + identifier keys', () => {
|
||||
const v = parseExpression(parse('{6001:{uuid:6001},name:"x"}'));
|
||||
assert.equal(v.kind, 'obj');
|
||||
assert.deepEqual(v.props['6001'], { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 } } });
|
||||
assert.deepEqual(v.props['name'], { kind: 'str', value: 'x' });
|
||||
});
|
||||
test('raw: unsupported expression preserved verbatim', () => {
|
||||
const v = parseExpression(parse('a + b'));
|
||||
assert.equal(v.kind, 'raw');
|
||||
assert.equal((v as any).text, 'a + b');
|
||||
});
|
||||
test('findExportObjectLiteral locates HeroInfo', () => {
|
||||
const src = ts.createSourceFile('x.ts',
|
||||
`export const HeroInfo: Record<number, any> = { 5011:{uuid:5011} };`, ts.ScriptTarget.Latest, true);
|
||||
const node = findExportObjectLiteral(src, 'HeroInfo');
|
||||
assert.ok(node);
|
||||
assert.equal(node!.properties.length, 1);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { serializeValue, serializeEntry } from '../src/io/serializer';
|
||||
import { RecordValue } from '../src/io/recordValue';
|
||||
|
||||
test('serializeValue: num/str/bool', () => {
|
||||
assert.equal(serializeValue({ kind: 'num', value: 400 }), '400');
|
||||
assert.equal(serializeValue({ kind: 'str', value: '小铁卫' }), '"小铁卫"');
|
||||
assert.equal(serializeValue({ kind: 'bool', value: true }), 'true');
|
||||
});
|
||||
test('serializeValue: enumRef', () => {
|
||||
const v: RecordValue = { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' };
|
||||
assert.equal(serializeValue(v), 'FacSet.HERO');
|
||||
});
|
||||
test('serializeValue: speed', () => {
|
||||
const v: RecordValue = { kind: 'speed', level: 'Slow3' };
|
||||
assert.equal(serializeValue(v), 'AtkSpeedSet[AtkSpeedLv.Slow3].cd');
|
||||
});
|
||||
test('serializeValue: arr', () => {
|
||||
const v: RecordValue = { kind: 'arr', items: [{ kind: 'num', value: 1 }, { kind: 'num', value: 2 }] };
|
||||
assert.equal(serializeValue(v), '[1,2]');
|
||||
});
|
||||
test('serializeValue: obj', () => {
|
||||
const v: RecordValue = { kind: 'obj', props: { a: { kind: 'num', value: 1 }, b: { kind: 'str', value: 'x' } } };
|
||||
assert.equal(serializeValue(v), '{a:1,b:"x"}');
|
||||
});
|
||||
test('serializeValue: raw passthrough', () => {
|
||||
const v: RecordValue = { kind: 'raw', text: 'a + b' };
|
||||
assert.equal(serializeValue(v), 'a + b');
|
||||
});
|
||||
test('serializeEntry: all-scalar one line with trailing comma', () => {
|
||||
const v: RecordValue = { kind: 'obj', props: { uuid: { kind: 'num', value: 1 }, name: { kind: 'str', value: 'x' } } };
|
||||
assert.equal(serializeEntry('1', v), '1:{uuid:1,name:"x"},');
|
||||
});
|
||||
test('serializeEntry: nested on continuation lines', () => {
|
||||
const v: RecordValue = { kind: 'obj', props: {
|
||||
uuid: { kind: 'num', value: 5011 },
|
||||
skills: { kind: 'obj', props: { 6001: { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 } } } } },
|
||||
} };
|
||||
const out = serializeEntry('5011', v);
|
||||
assert.match(out, /^5011:\{uuid:5011,$/m); // 首行:键 + 标量
|
||||
assert.match(out, / skills:\{6001:\{uuid:6001\}\},$/m); // 续行 8 空格
|
||||
assert.match(out, /^ \},$/m); // 闭合 4 空格
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync, copyFileSync, mkdtempSync, readdirSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { TsConfigFile } from '../src/io/TsConfigFile';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const fixture = join(here, 'fixtures', 'heroSet.sample.ts');
|
||||
|
||||
test('load + getKeys', () => {
|
||||
const f = new TsConfigFile(fixture, 'HeroInfo');
|
||||
f.load();
|
||||
assert.deepEqual(f.getKeys(), ['5011', '6001']);
|
||||
});
|
||||
test('read returns structured value with speed/enumRef preserved', () => {
|
||||
const f = new TsConfigFile(fixture, 'HeroInfo');
|
||||
f.load();
|
||||
const v = f.read('5011')!;
|
||||
assert.equal(v.kind, 'obj');
|
||||
assert.deepEqual(v.props['fac'], { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' });
|
||||
const skill = v.props['skills'].props['6001'];
|
||||
assert.deepEqual(skill.props['cd'], { kind: 'speed', level: 'Slow3' });
|
||||
});
|
||||
test('getText after load equals original file (no mutation on load)', () => {
|
||||
const f = new TsConfigFile(fixture, 'HeroInfo');
|
||||
f.load();
|
||||
assert.equal(f.getText(), readFileSync(fixture, 'utf8'));
|
||||
});
|
||||
test('read missing key returns null', () => {
|
||||
const f = new TsConfigFile(fixture, 'HeroInfo');
|
||||
f.load();
|
||||
assert.equal(f.read('9999'), null);
|
||||
});
|
||||
|
||||
function withTempFixture(): { dir: string; file: string } {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'phcfg-'));
|
||||
const file = join(dir, 'heroSet.sample.ts');
|
||||
copyFileSync(fixture, file);
|
||||
return { dir, file };
|
||||
}
|
||||
|
||||
test('patch updates one entry; reload reads new value; other entries intact', () => {
|
||||
const { file } = withTempFixture();
|
||||
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
|
||||
const v = f.read('5011')!;
|
||||
v.props['ap'] = { kind: 'num', value: 99 };
|
||||
f.patch('5011', v);
|
||||
assert.equal(f.isDirty(), true);
|
||||
assert.equal(f.save().ok, true);
|
||||
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
|
||||
assert.deepEqual(f2.read('5011')!.props['ap'], { kind: 'num', value: 99 });
|
||||
assert.equal(f2.read('6001')!.props['name'].value, '兽人战士'); // 另一条未破坏
|
||||
});
|
||||
|
||||
test('patch preserves speed + enumRef on reload', () => {
|
||||
const { file } = withTempFixture();
|
||||
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
|
||||
const v = f.read('5011')!;
|
||||
v.props['hp'] = { kind: 'num', value: 500 };
|
||||
f.patch('5011', v); f.save();
|
||||
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
|
||||
const again = f2.read('5011')!;
|
||||
assert.deepEqual(again.props['fac'], { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' });
|
||||
assert.deepEqual(again.props['skills'].props['6001'].props['cd'], { kind: 'speed', level: 'Slow3' });
|
||||
});
|
||||
|
||||
test('add appends entry readable after save+reload', () => {
|
||||
const { file } = withTempFixture();
|
||||
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
|
||||
f.add('5099', { kind: 'obj', props: { uuid: { kind: 'num', value: 5099 }, name: { kind: 'str', value: '新英雄' } } });
|
||||
f.save();
|
||||
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
|
||||
assert.ok(f2.getKeys().includes('5099'));
|
||||
assert.equal(f2.read('5099')!.props['name'].value, '新英雄');
|
||||
});
|
||||
|
||||
test('delete removes entry', () => {
|
||||
const { file } = withTempFixture();
|
||||
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
|
||||
f.delete('6001'); f.save();
|
||||
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
|
||||
assert.ok(!f2.getKeys().includes('6001'));
|
||||
assert.ok(f2.getKeys().includes('5011'));
|
||||
});
|
||||
|
||||
test('save writes a .bak backup of the pre-save file', () => {
|
||||
const { file } = withTempFixture();
|
||||
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
|
||||
const v = f.read('5011')!; v.props['hp'] = { kind: 'num', value: 1 }; f.patch('5011', v);
|
||||
f.save();
|
||||
assert.equal(readFileSync(file + '.bak', 'utf8'), readFileSync(fixture, 'utf8'));
|
||||
});
|
||||
|
||||
test('save with no edits is a no-op (ok, no .bak written)', () => {
|
||||
const { file, dir } = withTempFixture();
|
||||
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
|
||||
assert.equal(f.save().ok, true);
|
||||
assert.equal(readdirSync(dir).some(p => p.endsWith('.bak')), false);
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { validate } from '../src/shared/validation';
|
||||
import { RecordValue } from '../src/io/recordValue';
|
||||
|
||||
const ctx = {
|
||||
hasSkill: (u: number) => [6001, 6301, 6501].includes(u),
|
||||
hasField: (u: number) => [7015].includes(u),
|
||||
hasHero: (u: number) => [5011, 6001].includes(u),
|
||||
heroListKeys: new Set<string>(['5011']),
|
||||
};
|
||||
|
||||
function heroObj(extra: Record<string, RecordValue> = {}): RecordValue {
|
||||
return { kind: 'obj', props: {
|
||||
uuid: { kind: 'num', value: 5011 }, name: { kind: 'str', value: 'x' }, path: { kind: 'str', value: 'p' },
|
||||
fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' }, lv: { kind: 'num', value: 1 },
|
||||
type: { kind: 'enumRef', qualifier: 'HType', member: 'Melee' }, hp: { kind: 'num', value: 400 },
|
||||
ap: { kind: 'num', value: 20 },
|
||||
skills: { kind: 'obj', props: { 6001: { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 }, lv: { kind: 'num', value: 1 }, cd: { kind: 'speed', level: 'Slow3' }, ccd: { kind: 'num', value: 0 } } } } },
|
||||
info: { kind: 'str', value: 'ok' },
|
||||
...extra,
|
||||
} };
|
||||
}
|
||||
|
||||
test('valid hero → no errors', () => {
|
||||
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx);
|
||||
assert.equal(issues.filter(i => i.severity === 'error').length, 0);
|
||||
});
|
||||
test('duplicate uuid → error', () => {
|
||||
// 两条记录携带相同的 uuid 字段值(Map 键不同,uuid 字段相同)
|
||||
const dup = heroObj(); // uuid=5011
|
||||
const issues = validate('hero', new Map([['5011', heroObj()], ['5012', dup]]), ctx);
|
||||
assert.ok(issues.some(i => i.severity === 'error' && i.code === 'dup-uuid'));
|
||||
});
|
||||
test('missing required field → error', () => {
|
||||
const h = heroObj(); delete h.props['name'];
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'missing-required' && i.fieldPath === 'name'));
|
||||
});
|
||||
test('trigger slot references missing skill → error', () => {
|
||||
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
|
||||
s_uuid: { kind: 'num', value: 9999 }, t_num: { kind: 'num', value: 3 } } }] } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'dangling-ref'));
|
||||
});
|
||||
test('field list references missing field skill → error', () => {
|
||||
const h = heroObj({ field: { kind: 'arr', items: [{ kind: 'num', value: 8888 }] } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'dangling-ref'));
|
||||
});
|
||||
test('HeroList/hero consistency: fac=HERO entry missing from HeroList → error', () => {
|
||||
const ctx2 = { ...ctx, heroListKeys: new Set<string>([]) };
|
||||
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx2);
|
||||
assert.ok(issues.some(i => i.code === 'herolist-inconsistent'));
|
||||
});
|
||||
test('invalid enum value → error', () => {
|
||||
const h = heroObj({ fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'NOPE' } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'bad-enum'));
|
||||
});
|
||||
test('unknown overrides key → error', () => {
|
||||
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
|
||||
s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 },
|
||||
overrides: { kind: 'obj', props: { bogus: { kind: 'num', value: 1 } } } } }] } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'bad-override-key'));
|
||||
});
|
||||
29
extensions/pixelhero-config-editor/esbuild.config.mjs
Normal file
29
extensions/pixelhero-config-editor/esbuild.config.mjs
Normal file
@@ -0,0 +1,29 @@
|
||||
import { build, context } from 'esbuild';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
const watch = process.argv.includes('--watch');
|
||||
|
||||
const common = {
|
||||
bundle: true,
|
||||
sourcemap: false,
|
||||
logLevel: 'info',
|
||||
alias: { 'vue': 'vue/dist/vue.esm-bundler.js' },
|
||||
};
|
||||
|
||||
// 面板进程在 Cocos 的 Electron 渲染层运行,可访问 Node 内建(fs/path),但 vue 必须
|
||||
// 打进浏览器侧 IIFE。因此面板项用 platform:'browser' + 把 node: 内建标为 external,
|
||||
// 交由运行时解析;这样 esbuild 既不抱怨,又保持 plan 的"运行时读 static 文件"语义。
|
||||
const entries = [
|
||||
{ entryPoints: ['src/main/index.ts'], outfile: 'dist/main.js', platform: 'node', format: 'cjs', external: [] },
|
||||
{ entryPoints: ['src/panels/default/index.ts'], outfile: 'dist/panels/default.js', platform: 'browser', format: 'iife', external: ['node:fs', 'node:path'] },
|
||||
];
|
||||
|
||||
if (watch) {
|
||||
for (const e of entries) {
|
||||
const ctx = await context({ ...common, ...e });
|
||||
await ctx.watch();
|
||||
}
|
||||
} else {
|
||||
for (const e of entries) await build({ ...common, ...e });
|
||||
}
|
||||
4
extensions/pixelhero-config-editor/i18n/en.js
Normal file
4
extensions/pixelhero-config-editor/i18n/en.js
Normal file
@@ -0,0 +1,4 @@
|
||||
exports.en = {
|
||||
'pixelhero-config-editor': { description: 'Hero/Skill Config Editor', title: 'Hero/Skill Config' },
|
||||
'menu': { 'panel/英雄技能配置': 'Hero/Skill Config' },
|
||||
};
|
||||
4
extensions/pixelhero-config-editor/i18n/zh.js
Normal file
4
extensions/pixelhero-config-editor/i18n/zh.js
Normal file
@@ -0,0 +1,4 @@
|
||||
exports.zh = {
|
||||
'pixelhero-config-editor': { description: '英雄/技能配置编辑器', title: '英雄/技能配置' },
|
||||
'menu': { 'panel/英雄技能配置': '英雄技能配置' },
|
||||
};
|
||||
1312
extensions/pixelhero-config-editor/package-lock.json
generated
Normal file
1312
extensions/pixelhero-config-editor/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
extensions/pixelhero-config-editor/package.json
Normal file
48
extensions/pixelhero-config-editor/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"package_version": 2,
|
||||
"name": "pixelhero-config-editor",
|
||||
"version": "0.1.0",
|
||||
"description": "英雄/技能配置编辑器",
|
||||
"main": "./dist/main.js",
|
||||
"author": "pixelhero",
|
||||
"editor": ">=3.8.0",
|
||||
"scripts": {
|
||||
"build": "node esbuild.config.mjs",
|
||||
"watch": "node esbuild.config.mjs --watch",
|
||||
"test": "tsx --test __tests__/*.test.ts"
|
||||
},
|
||||
"panels": {
|
||||
"default": {
|
||||
"title": "英雄/技能配置",
|
||||
"type": "dockable",
|
||||
"main": "./dist/panels/default.js",
|
||||
"size": { "width": 960, "height": 640, "min-width": 640, "min-height": 480 }
|
||||
}
|
||||
},
|
||||
"contributions": {
|
||||
"menu": [
|
||||
{ "path": "PixelHero/英雄技能配置", "message": "open-panel" }
|
||||
],
|
||||
"messages": {
|
||||
"open-panel": { "methods": ["open-panel"] },
|
||||
"query-schema": { "methods": ["query-schema"] },
|
||||
"query-enums": { "methods": ["query-enums"] },
|
||||
"query-keys": { "methods": ["query-keys"] },
|
||||
"query-record": { "methods": ["query-record"] },
|
||||
"query-preview-desc": { "methods": ["query-preview-desc"] },
|
||||
"validate": { "methods": ["validate"] },
|
||||
"save-record": { "methods": ["save-record"] },
|
||||
"revert-record": { "methods": ["revert-record"] }
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": "^5.4.0",
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"esbuild": "^0.20.0",
|
||||
"fs-extra": "^11.0.0",
|
||||
"tsx": "^4.7.0"
|
||||
}
|
||||
}
|
||||
110
extensions/pixelhero-config-editor/src/io/TsConfigFile.ts
Normal file
110
extensions/pixelhero-config-editor/src/io/TsConfigFile.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as ts from 'typescript';
|
||||
import { RecordValue } from './recordValue';
|
||||
import { findExportObjectLiteral, parseExpression } from './parser';
|
||||
import { serializeEntry } from './serializer';
|
||||
|
||||
/**
|
||||
* 以"条目级 AST 区间替换"方式读写一个 `export const <name>: Record<number,X> = {...}` 配置文件。
|
||||
* 保留符号表达式(speed)与枚举引用(enumRef),未识别表达式原样保留(raw)。
|
||||
* save() 纯逻辑:仅做 fs 读写 + 语法校验(写 .bak);asset-db 刷新由主进程在 save 成功后调用。
|
||||
*/
|
||||
export class TsConfigFile {
|
||||
private sourceText = '';
|
||||
private sourceFile!: ts.SourceFile;
|
||||
private targetNode!: ts.ObjectLiteralExpression;
|
||||
private dirty = false;
|
||||
|
||||
constructor(public readonly filePath: string, public readonly exportName: string) {}
|
||||
|
||||
load(): void {
|
||||
this.sourceText = fs.readFileSync(this.filePath, 'utf8');
|
||||
this.reparse();
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
private reparse(): void {
|
||||
// 第 4 参数 setParentNodes 必须为 true,否则 node.getStart()/getEnd() 不可用。
|
||||
this.sourceFile = ts.createSourceFile(this.filePath, this.sourceText, ts.ScriptTarget.Latest, true);
|
||||
const node = findExportObjectLiteral(this.sourceFile, this.exportName);
|
||||
if (!node) throw new Error(`export const ${this.exportName} not found or not an object literal in ${this.filePath}`);
|
||||
this.targetNode = node;
|
||||
}
|
||||
|
||||
getKeys(): string[] {
|
||||
return this.targetNode.properties.map(p => (p.name as ts.PropertyName).getText());
|
||||
}
|
||||
|
||||
read(key: string): RecordValue | null {
|
||||
const entry = this.findEntry(key);
|
||||
if (!entry) return null;
|
||||
return parseExpression(entry.initializer);
|
||||
}
|
||||
|
||||
private findEntry(key: string): ts.PropertyAssignment | undefined {
|
||||
return this.targetNode.properties.find(p => (p.name as ts.PropertyName).getText() === key) as ts.PropertyAssignment | undefined;
|
||||
}
|
||||
|
||||
getText(): string { return this.sourceText; }
|
||||
isDirty(): boolean { return this.dirty; }
|
||||
|
||||
patch(key: string, value: RecordValue): void {
|
||||
const entry = this.findEntry(key);
|
||||
const newText = serializeEntry(key, value);
|
||||
if (entry) {
|
||||
const { start, end } = this.entrySpan(entry);
|
||||
this.sourceText = this.sourceText.slice(0, start) + newText + this.sourceText.slice(end);
|
||||
} else {
|
||||
this.insertEntry(newText);
|
||||
}
|
||||
this.reparse();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
add(key: string, value: RecordValue): void {
|
||||
if (this.findEntry(key)) throw new Error(`key ${key} already exists`);
|
||||
this.insertEntry(serializeEntry(key, value));
|
||||
this.reparse();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
delete(key: string): void {
|
||||
const entry = this.findEntry(key);
|
||||
if (!entry) return;
|
||||
const { start, end } = this.entrySpan(entry);
|
||||
this.sourceText = this.sourceText.slice(0, start) + this.sourceText.slice(end);
|
||||
this.reparse();
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
save(): { ok: true } | { ok: false; error: string } {
|
||||
if (!this.dirty) return { ok: true };
|
||||
const check = ts.createSourceFile(this.filePath, this.sourceText, ts.ScriptTarget.Latest, true);
|
||||
if (check.parseDiagnostics.length > 0) {
|
||||
const msg = check.parseDiagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')).join('; ');
|
||||
return { ok: false, error: `syntax error after edit: ${msg}` };
|
||||
}
|
||||
// .bak 取自磁盘原始内容(落盘前的当前文件),保证可回滚。
|
||||
fs.writeFileSync(this.filePath + '.bak', fs.readFileSync(this.filePath));
|
||||
fs.writeFileSync(this.filePath, this.sourceText);
|
||||
this.dirty = false;
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
/** 条目文本区间:从 key 词法起始(不含前导缩进 trivia,避免吞掉上一行换行)到尾随逗号止。 */
|
||||
private entrySpan(entry: ts.PropertyAssignment): { start: number; end: number } {
|
||||
const start = entry.getStart();
|
||||
let end = entry.getEnd();
|
||||
const comma = /^\s*,/.exec(this.sourceText.slice(end));
|
||||
if (comma) end += comma[0].length;
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
/** 在闭合 `}` 前追加一条新记录(4 空格缩进)。 */
|
||||
private insertEntry(entryText: string): void {
|
||||
const closeBrace = this.targetNode.getLastToken();
|
||||
if (!closeBrace) throw new Error('object literal has no closing brace');
|
||||
const pos = closeBrace.getStart();
|
||||
this.sourceText = this.sourceText.slice(0, pos) + ` ${entryText}\n ` + this.sourceText.slice(pos);
|
||||
}
|
||||
}
|
||||
65
extensions/pixelhero-config-editor/src/io/parser.ts
Normal file
65
extensions/pixelhero-config-editor/src/io/parser.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import * as ts from 'typescript';
|
||||
import { RecordValue } from './recordValue';
|
||||
|
||||
/** 在 SourceFile 中查找 `export const <name> = {...}`,返回其对象字面量节点。 */
|
||||
export function findExportObjectLiteral(src: ts.SourceFile, name: string): ts.ObjectLiteralExpression | null {
|
||||
let result: ts.ObjectLiteralExpression | null = null;
|
||||
const visit = (node: ts.Node) => {
|
||||
if (result) return;
|
||||
if (ts.isVariableStatement(node)) {
|
||||
for (const d of node.declarationList.declarations) {
|
||||
if (d.name.getText() === name && d.initializer && ts.isObjectLiteralExpression(d.initializer)) {
|
||||
result = d.initializer;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(src);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 识别 `AtkSpeedSet[AtkSpeedLv.<level>].cd`,否则 null。 */
|
||||
function tryParseSpeedExpr(node: ts.PropertyAccessExpression): RecordValue | null {
|
||||
if (node.name.text !== 'cd') return null;
|
||||
const inner = node.expression;
|
||||
if (!ts.isElementAccessExpression(inner)) return null;
|
||||
if (!ts.isIdentifier(inner.expression) || inner.expression.text !== 'AtkSpeedSet') return null;
|
||||
const arg = inner.argumentExpression;
|
||||
if (!ts.isPropertyAccessExpression(arg)) return null;
|
||||
if (!ts.isIdentifier(arg.expression) || arg.expression.text !== 'AtkSpeedLv') return null;
|
||||
return { kind: 'speed', level: arg.name.text };
|
||||
}
|
||||
|
||||
export function parseExpression(node: ts.Expression): RecordValue {
|
||||
if (ts.isNumericLiteral(node)) return { kind: 'num', value: Number(node.text) };
|
||||
if (ts.isStringLiteral(node)) return { kind: 'str', value: node.text };
|
||||
if (node.kind === ts.SyntaxKind.TrueKeyword) return { kind: 'bool', value: true };
|
||||
if (node.kind === ts.SyntaxKind.FalseKeyword) return { kind: 'bool', value: false };
|
||||
|
||||
if (ts.isPropertyAccessExpression(node)) {
|
||||
const speed = tryParseSpeedExpr(node);
|
||||
if (speed) return speed;
|
||||
if (ts.isIdentifier(node.expression)) {
|
||||
return { kind: 'enumRef', qualifier: node.expression.text, member: node.name.text };
|
||||
}
|
||||
return { kind: 'raw', text: node.getText() }; // 多级 a.b.c 等
|
||||
}
|
||||
|
||||
if (ts.isArrayLiteralExpression(node)) {
|
||||
return { kind: 'arr', items: node.elements.map(e => parseExpression(e)) };
|
||||
}
|
||||
|
||||
if (ts.isObjectLiteralExpression(node)) {
|
||||
const props: Record<string, RecordValue> = {};
|
||||
for (const m of node.properties) {
|
||||
if (ts.isPropertyAssignment(m)) {
|
||||
props[m.name.getText()] = parseExpression(m.initializer);
|
||||
}
|
||||
}
|
||||
return { kind: 'obj', props };
|
||||
}
|
||||
|
||||
return { kind: 'raw', text: node.getText() }; // 二元/模板/调用/其他元素访问
|
||||
}
|
||||
15
extensions/pixelhero-config-editor/src/io/recordValue.ts
Normal file
15
extensions/pixelhero-config-editor/src/io/recordValue.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/** IO 层对配置对象字面量值的结构化表示。零依赖。 */
|
||||
export type RecordValue =
|
||||
| { kind: 'num'; value: number }
|
||||
| { kind: 'str'; value: string }
|
||||
| { kind: 'bool'; value: boolean }
|
||||
| { kind: 'enumRef'; qualifier: string; member: string } // 如 FacSet.HERO
|
||||
| { kind: 'speed'; level: string } // AtkSpeedSet[AtkSpeedLv.X].cd
|
||||
| { kind: 'obj'; props: Record<string, RecordValue> }
|
||||
| { kind: 'arr'; items: RecordValue[] }
|
||||
| { kind: 'raw'; text: string }; // 未识别表达式,原样保留
|
||||
|
||||
export function isScalar(v: RecordValue): boolean {
|
||||
return v.kind === 'num' || v.kind === 'str' || v.kind === 'bool'
|
||||
|| v.kind === 'enumRef' || v.kind === 'speed' || v.kind === 'raw';
|
||||
}
|
||||
32
extensions/pixelhero-config-editor/src/io/serializer.ts
Normal file
32
extensions/pixelhero-config-editor/src/io/serializer.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { RecordValue, isScalar } from './recordValue';
|
||||
|
||||
export function serializeValue(v: RecordValue): string {
|
||||
switch (v.kind) {
|
||||
case 'num': return String(v.value);
|
||||
case 'str': return JSON.stringify(v.value);
|
||||
case 'bool': return String(v.value);
|
||||
case 'enumRef': return `${v.qualifier}.${v.member}`;
|
||||
case 'speed': return `AtkSpeedSet[AtkSpeedLv.${v.level}].cd`;
|
||||
case 'arr': return `[${v.items.map(serializeValue).join(',')}]`;
|
||||
case 'obj': {
|
||||
const body = Object.entries(v.props).map(([k, val]) => `${k}:${serializeValue(val)}`).join(',');
|
||||
return `{${body}}`;
|
||||
}
|
||||
case 'raw': return v.text;
|
||||
}
|
||||
}
|
||||
|
||||
/** 序列化一条顶层记录。首行无缩进(缩进由调用处保留的 trivia 提供);嵌套字段 8 空格续行;闭合 4 空格。 */
|
||||
export function serializeEntry(key: string, obj: RecordValue): string {
|
||||
if (obj.kind !== 'obj') return `${key}:${serializeValue(obj)},`;
|
||||
const props = obj.props;
|
||||
const keys = Object.keys(props);
|
||||
const scalars = keys.filter(k => isScalar(props[k]));
|
||||
const nested = keys.filter(k => !isScalar(props[k]));
|
||||
const scalarTxt = scalars.map(k => `${k}:${serializeValue(props[k])}`).join(',');
|
||||
if (nested.length === 0) return `${key}:{${scalarTxt}},`;
|
||||
const lines = [`${key}:{${scalarTxt},`];
|
||||
for (const k of nested) lines.push(` ${k}:${serializeValue(props[k])},`);
|
||||
lines.push(' },');
|
||||
return lines.join('\n');
|
||||
}
|
||||
48
extensions/pixelhero-config-editor/src/main/index.ts
Normal file
48
extensions/pixelhero-config-editor/src/main/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { store } from './store';
|
||||
|
||||
/**
|
||||
* Cocos Creator 3.8 扩展主进程入口。
|
||||
* 关键约定(与官方 first/message 文档一致):
|
||||
* - 处理函数必须挂在 `methods` 对象里(Cocos 读取 module.exports.methods),
|
||||
* methods 的 key 必须与 package.json 中 contributions.messages[*].methods 引用的名字一致。
|
||||
* - 生命周期钩子为 `load`/`unload`(不是 onLoad)。
|
||||
* - 处理函数直接接收消息参数(无 event),返回值即 Editor.Message.request 的 resolve 结果。
|
||||
*/
|
||||
function load() {
|
||||
store.reloadAll();
|
||||
}
|
||||
function unload() {
|
||||
// 进程级单例随扩展卸载自然销毁,无需显式清理。
|
||||
}
|
||||
|
||||
const methods: Record<string, (...args: any[]) => any> = {
|
||||
'open-panel'() {
|
||||
Editor.Panel.open('pixelhero-config-editor');
|
||||
},
|
||||
'query-schema'(id?: string) {
|
||||
return store.querySchema(id as any);
|
||||
},
|
||||
'query-enums'() {
|
||||
return store.queryEnums();
|
||||
},
|
||||
'query-keys'(id: string) {
|
||||
return store.queryKeys(id as any);
|
||||
},
|
||||
'query-record'(id: string, key: string) {
|
||||
return store.queryRecord(id as any, key);
|
||||
},
|
||||
'query-preview-desc'(hero: any) {
|
||||
return store.queryPreviewDesc(hero);
|
||||
},
|
||||
'validate'(id: string) {
|
||||
return store.validate(id as any);
|
||||
},
|
||||
'save-record'(id: string, key: string, value: any) {
|
||||
return store.saveRecord(id as any, key, value);
|
||||
},
|
||||
'revert-record'(id: string, key: string) {
|
||||
return store.revertRecord(id as any, key);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { methods, load, unload };
|
||||
85
extensions/pixelhero-config-editor/src/main/store.ts
Normal file
85
extensions/pixelhero-config-editor/src/main/store.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { join } from 'node:path';
|
||||
import { TsConfigFile } from '../io/TsConfigFile';
|
||||
import { allSchemas } from '../shared/schema/registry';
|
||||
import { ENUMS } from '../shared/schema/enums';
|
||||
import { validate, ValidationContext } from '../shared/validation';
|
||||
import { buildSkillDesc } from '../shared/desc/buildSkillDesc';
|
||||
import { RecordValue } from '../io/recordValue';
|
||||
import { TableId } from '../shared/schema/types';
|
||||
|
||||
/** 游戏配置目录(相对项目根)。主进程用 Editor.Project.path 解析到项目根的 assets 配置目录。 */
|
||||
const CONFIG_DIR = join(Editor.Project.path, 'assets/script/game/common/config');
|
||||
|
||||
interface Entry { file: TsConfigFile; }
|
||||
const tables: Partial<Record<TableId, Entry>> = {};
|
||||
// skill 与 field 共享 SkillSet.ts,但用不同 exportName 的 TsConfigFile 实例
|
||||
const fileCache: Record<string, TsConfigFile> = {};
|
||||
|
||||
function getTable(id: TableId): Entry {
|
||||
if (tables[id]) return tables[id]!;
|
||||
const schema = allSchemas().find(s => s.id === id)!;
|
||||
const cacheKey = `${schema.sourceFile}:${schema.exportName}`;
|
||||
let file = fileCache[cacheKey];
|
||||
if (!file) { file = new TsConfigFile(join(CONFIG_DIR, schema.sourceFile), schema.exportName); file.load(); fileCache[cacheKey] = file; }
|
||||
const entry = { file };
|
||||
tables[id] = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
function recordsOf(id: TableId): Map<string, RecordValue> {
|
||||
const { file } = getTable(id);
|
||||
const m = new Map<string, RecordValue>();
|
||||
for (const k of file.getKeys()) { const v = file.read(k); if (v) m.set(k, v); }
|
||||
return m;
|
||||
}
|
||||
|
||||
function buildContext(): ValidationContext {
|
||||
const hero = recordsOf('hero');
|
||||
const skill = recordsOf('skill');
|
||||
const field = recordsOf('field');
|
||||
const heroKeys = Array.from(hero.keys()).map(Number);
|
||||
const skillKeys = Array.from(skill.keys()).map(Number);
|
||||
const fieldKeys = Array.from(field.keys()).map(Number);
|
||||
// HeroList 读取(hero 表的 listExportName)
|
||||
const heroList = new TsConfigFile(join(CONFIG_DIR, 'heroSet.ts'), 'HeroList');
|
||||
heroList.load();
|
||||
const listRaw = heroList.getText();
|
||||
const heroListKeys = new Set<string>(
|
||||
(listRaw.match(/HeroList[\s\S]*?=\s*\[([^\]]*)\]/)?.[1].match(/-?\d+/g) ?? []).map(String)
|
||||
);
|
||||
return {
|
||||
hasHero: (u) => heroKeys.includes(u),
|
||||
hasSkill: (u) => skillKeys.includes(u),
|
||||
hasField: (u) => fieldKeys.includes(u),
|
||||
heroListKeys,
|
||||
};
|
||||
}
|
||||
|
||||
export const store = {
|
||||
queryEnums: () => ENUMS,
|
||||
querySchema: (id?: TableId) => id ? allSchemas().find(s => s.id === id) : allSchemas(),
|
||||
queryKeys: (id: TableId) => getTable(id).file.getKeys(),
|
||||
queryRecord: (id: TableId, key: string) => getTable(id).file.read(key),
|
||||
queryPreviewDesc: (hero: RecordValue) => {
|
||||
const skill = recordsOf('skill'); const field = recordsOf('field');
|
||||
const skillSet: Record<number, RecordValue> = {};
|
||||
for (const [k, v] of skill) skillSet[Number(k)] = v;
|
||||
const fieldSet: Record<number, RecordValue> = {};
|
||||
for (const [k, v] of field) fieldSet[Number(k)] = v;
|
||||
return buildSkillDesc(hero, skillSet, fieldSet);
|
||||
},
|
||||
validate: (id: TableId) => validate(id, recordsOf(id), buildContext()),
|
||||
saveRecord: (id: TableId, key: string, value: RecordValue) => {
|
||||
// 先在副本上试写并校验
|
||||
const { file } = getTable(id);
|
||||
file.patch(key, value);
|
||||
const issues = validate(id, recordsOf(id), buildContext()).filter(i => i.key === key && i.severity === 'error');
|
||||
if (issues.length) { file.load(); return { ok: false as const, issues }; } // 回滚内存
|
||||
const r = file.save();
|
||||
if (!r.ok) { file.load(); return { ok: false as const, issues: [] }; }
|
||||
void Editor.Message.request('asset-db', 'refresh-asset', `db://assets/script/game/common/config/${allSchemas().find(s => s.id === id)!.sourceFile}`);
|
||||
return { ok: true as const, issues: [] };
|
||||
},
|
||||
revertRecord: (id: TableId, key: string) => { getTable(id).file.load(); return getTable(id).file.read(key); },
|
||||
reloadAll: () => { for (const k of Object.keys(fileCache)) fileCache[k].load(); },
|
||||
};
|
||||
38
extensions/pixelhero-config-editor/src/panels/default/app.ts
Normal file
38
extensions/pixelhero-config-editor/src/panels/default/app.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createApp, defineComponent, reactive } from 'vue';
|
||||
|
||||
export const App = defineComponent({
|
||||
setup() {
|
||||
const state = reactive({ table: 'hero' as string, keys: [] as string[], picked: '' as string, detail: '' as string });
|
||||
async function load() {
|
||||
const keys = await Editor.Message.request('pixelhero-config-editor', 'query-keys', state.table);
|
||||
state.keys = keys || [];
|
||||
state.picked = ''; state.detail = '';
|
||||
}
|
||||
async function pick(k: string) {
|
||||
state.picked = k;
|
||||
const v = await Editor.Message.request('pixelhero-config-editor', 'query-record', state.table, k);
|
||||
state.detail = JSON.stringify(v, null, 2);
|
||||
}
|
||||
load();
|
||||
return { state, load, pick };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="row">
|
||||
<label>表:</label>
|
||||
<select v-model="state.table" @change="load">
|
||||
<option value="hero">英雄/怪物</option>
|
||||
<option value="skill">技能</option>
|
||||
<option value="field">驻场技能</option>
|
||||
</select>
|
||||
<span style="margin-left:12px;color:#888">共 {{ state.keys.length }} 条(端到端 IPC 已打通)</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="k in state.keys" :key="k" @click="pick(k)" :style="state.picked===k ? 'font-weight:bold' : ''">{{ k }}</li>
|
||||
</ul>
|
||||
<pre v-if="state.detail" style="white-space:pre-wrap;background:var(--color-normal-fill);padding:8px">{{ state.detail }}</pre>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
export function mount(el: HTMLElement) { createApp(App).mount(el); }
|
||||
@@ -0,0 +1,14 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { mount } from './app';
|
||||
|
||||
const template = readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8');
|
||||
const style = readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8');
|
||||
|
||||
module.exports = Editor.Panel.define({
|
||||
template,
|
||||
style,
|
||||
$: { app: '#app' },
|
||||
ready() { mount(this.$.app); },
|
||||
close() { /* Vue 卸载随面板进程退出自动清理 */ },
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { RecordValue } from '../../io/recordValue';
|
||||
|
||||
const TRIGGER_KEYS = ['call', 'dead', 'fstart', 'fend', 'atking', 'atked'] as const;
|
||||
const TRIGGER_DESC: Record<string, string> = {
|
||||
call: '召唤时', dead: '死亡时', fstart: '战斗开始时', fend: '战斗结束时',
|
||||
field: '场上存活', atking: '攻击n次', atked: '受击n次', revive: '复活时',
|
||||
};
|
||||
|
||||
function num(v: RecordValue | undefined): number | undefined { return v && v.kind === 'num' ? v.value : undefined; }
|
||||
function str(v: RecordValue | undefined): string | undefined { return v && v.kind === 'str' ? v.value : undefined; }
|
||||
function member(v: RecordValue | undefined): string | undefined { return v && v.kind === 'enumRef' ? v.member : undefined; }
|
||||
|
||||
function buildEffect(merged: RecordValue): string {
|
||||
if (merged.kind !== 'obj') return '';
|
||||
const kind = member(merged.props['kind']);
|
||||
const ap = num(merged.props['ap']) ?? 0;
|
||||
const parts: string[] = [];
|
||||
if (kind === 'Heal') parts.push(`治疗伙伴${ap}`);
|
||||
else if (kind === 'Shield') parts.push(`护盾${ap}次`);
|
||||
else if (kind === 'Gold') parts.push(`金币+${num(merged.props['gold']) ?? 0}`);
|
||||
else if (kind === 'Support') parts.push(String(str(merged.props['info']) ?? ''));
|
||||
else { // Damage / undefined
|
||||
parts.push(`伤害${ap}%`);
|
||||
if ((num(merged.props['hit_count']) ?? 1) > 1) parts.push(`${num(merged.props['hit_count'])}段`);
|
||||
if (num(merged.props['crt'])) parts.push(`暴击+${num(merged.props['crt'])}%`);
|
||||
if (num(merged.props['stun'])) parts.push(`击晕+${num(merged.props['stun'])}%`);
|
||||
}
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
/** 合并 overrides 到 base(浅合并对象 props) */
|
||||
function merge(base: RecordValue, overrides: RecordValue | undefined): RecordValue {
|
||||
if (!overrides || overrides.kind !== 'obj' || base.kind !== 'obj') return base;
|
||||
return { kind: 'obj', props: { ...base.props, ...overrides.props } };
|
||||
}
|
||||
|
||||
export function buildSkillDesc(hero: RecordValue, skillSet: Record<number, RecordValue>, fieldSet: Record<number, RecordValue>): string {
|
||||
if (hero.kind !== 'obj') return '';
|
||||
const lines: string[] = [];
|
||||
for (const key of TRIGGER_KEYS) {
|
||||
const arr = hero.props[key];
|
||||
if (!arr || arr.kind !== 'arr') continue;
|
||||
const tpl = TRIGGER_DESC[key] ?? key;
|
||||
for (const it of arr.items) {
|
||||
if (it.kind !== 'obj') continue;
|
||||
const su = num(it.props['s_uuid']); if (su === undefined) continue;
|
||||
const base = skillSet[su]; if (!base) continue;
|
||||
const merged = merge(base, it.props['overrides']);
|
||||
const trigger = tpl.includes('n') ? tpl.replace('n', String(num(it.props['t_num']) ?? '')) : tpl;
|
||||
lines.push(`${trigger}:${str(base.props['name'])} ${buildEffect(merged)}`);
|
||||
}
|
||||
}
|
||||
const fl = hero.props['field'];
|
||||
if (fl && fl.kind === 'arr') {
|
||||
for (const it of fl.items) { const u = num(it); if (u === undefined) continue; const fs = fieldSet[u]; if (fs) lines.push(`${TRIGGER_DESC.field}:${str(fs.props['name'])} ${str(fs.props['info'])}`); }
|
||||
}
|
||||
const rv = hero.props['revive'];
|
||||
if (rv && rv.kind === 'obj') {
|
||||
const su = num(rv.props['s_uuid']); const base = su !== undefined ? skillSet[su] : undefined;
|
||||
if (base) lines.push(`${TRIGGER_DESC.revive} : ${str(base.props['name'])} ${buildEffect(merge(base, undefined))}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export interface EnumMember { label: string; value: number | string; }
|
||||
export interface EnumDef { qualifier: string; members: EnumMember[]; }
|
||||
|
||||
export const ENUMS: Record<string, EnumDef> = {
|
||||
HType: { qualifier: 'HType', members: [
|
||||
{ label: '近战 Melee', value: 0 }, { label: '中程 Mid', value: 1 }, { label: '远程 Long', value: 2 } ] },
|
||||
FacSet: { qualifier: 'FacSet', members: [
|
||||
{ label: '英雄 HERO', value: 0 }, { label: '怪物 MON', value: 1 } ] },
|
||||
TGroup: { qualifier: 'TGroup', members: [
|
||||
{ label: '自身 Self', value: 0 }, { label: '友方含己 Ally', value: 1 },
|
||||
{ label: '友方 Team', value: 2 }, { label: '敌方 Enemy', value: 3 }, { label: '全体 All', value: 4 } ] },
|
||||
DTType: { qualifier: 'DTType', members: [
|
||||
{ label: '单体 single', value: 0 }, { label: '范围 range', value: 1 }, { label: '3x3 aoe_grid', value: 2 } ] },
|
||||
SkillKind: { qualifier: 'SkillKind', members: [
|
||||
{ label: '伤害 Damage', value: 0 }, { label: '治疗 Heal', value: 1 }, { label: '护盾 Shield', value: 2 },
|
||||
{ label: '辅助 Support', value: 3 }, { label: '金币 Gold', value: 4 } ] },
|
||||
DType: { qualifier: 'DType', members: [
|
||||
{ label: '物理 ATK', value: 0 }, { label: '冰 ICE', value: 1 }, { label: '火 FIRE', value: 2 }, { label: '风 WIND', value: 3 } ] },
|
||||
IType: { qualifier: 'IType', members: [
|
||||
{ label: '近战 Melee', value: 0 }, { label: '远程 remote', value: 1 }, { label: '辅助 support', value: 2 } ] },
|
||||
RType: { qualifier: 'RType', members: [
|
||||
{ label: '直线 linear', value: 0 }, { label: '贝塞尔 bezier', value: 1 },
|
||||
{ label: '固定起点 fixed', value: 2 }, { label: '固定终点 fixedEnd', value: 3 } ] },
|
||||
EType: { qualifier: 'EType', members: [
|
||||
{ label: '动画结束 animationEnd', value: 0 }, { label: '时间结束 timeEnd', value: 1 }, { label: '碰撞 collision', value: 2 } ] },
|
||||
FieldSkillType: { qualifier: 'FieldSkillType', members: [
|
||||
{ label: '召唤次数 SummonCount', value: 1 }, { label: '死亡次数 DeadCount', value: 2 },
|
||||
{ label: '开场次数 StartCount', value: 3 }, { label: '结束次数 EndCount', value: 4 },
|
||||
{ label: '每波金币 WaveGold', value: 5 }, { label: '卖出金币 SellGold', value: 6 },
|
||||
{ label: '战后回复 WaveHeal', value: 7 }, { label: '英雄攻击 HeroAtk', value: 8 },
|
||||
{ label: '英雄击晕 HeroStun', value: 9 }, { label: '英雄暴击 HeroCrit', value: 10 },
|
||||
{ label: '英雄暴伤 HeroCritDamage', value: 11 }, { label: '英雄攻速 HeroSpeed', value: 12 },
|
||||
{ label: '购买优惠 BuyDiscount', value: 13 }, { label: '刷新优惠 RefreshDiscount', value: 14 },
|
||||
{ label: '英雄生命 HeroHp', value: 16 }, { label: '英雄风怒 HeroWindFury', value: 17 },
|
||||
{ label: '英雄穿刺 HeroPuncture', value: 18 }, { label: '攻击次数 AtkCount', value: 19 },
|
||||
{ label: '受击次数 BeAtkCount', value: 20 } ] },
|
||||
AtkSpeedLv: { qualifier: 'AtkSpeedLv', members: [
|
||||
{ label: '极速++ VeryFast1', value: 1 }, { label: '极速+ VeryFast2', value: 2 }, { label: '极速 VeryFast3', value: 3 },
|
||||
{ label: '快速++ Fast1', value: 4 }, { label: '快速+ Fast2', value: 5 }, { label: '快速 Fast3', value: 6 },
|
||||
{ label: '中速++ Normal1', value: 7 }, { label: '中速+ Normal2', value: 8 }, { label: '中速 Normal3', value: 9 },
|
||||
{ label: '一般+ Mid1', value: 10 }, { label: '一般 Mid2', value: 11 }, { label: '一般- Mid3', value: 12 },
|
||||
{ label: '慢 Slow1', value: 13 }, { label: '慢+ Slow2', value: 14 }, { label: '慢++ Slow3', value: 15 },
|
||||
{ label: '很慢 VerySlow1', value: 16 }, { label: '很慢+ VerySlow2', value: 17 }, { label: '很慢++ VerySlow3', value: 18 } ] },
|
||||
Attrs: { qualifier: 'Attrs', members: [
|
||||
{ label: 'ap', value: 'ap' }, { label: 'hp', value: 'hp' }, { label: 'hp_max', value: 'hp_max' },
|
||||
{ label: 'critical', value: 'critical' }, { label: 'critical_damage', value: 'critical_damage' },
|
||||
{ label: 'stun_chance', value: 'stun_chance' }, { label: 'puncture_chance', value: 'puncture_chance' },
|
||||
{ label: 'wfuny', value: 'wfuny' }, { label: 'freeze_chance', value: 'freeze_chance' },
|
||||
{ label: 'knockback_chance', value: 'knockback_chance' } ] },
|
||||
};
|
||||
|
||||
/** qualifier(如 'FacSet')→ 对应 enumId(如 'FacSet')。当前两者同名。 */
|
||||
export const QUALIFIER_TO_ID: Record<string, string> = Object.fromEntries(
|
||||
Object.entries(ENUMS).map(([id, def]) => [def.qualifier, id])
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { TableSchema } from './types';
|
||||
export const fieldSchema: TableSchema = {
|
||||
id: 'field', label: '驻场技能',
|
||||
sourceFile: 'SkillSet.ts', exportName: 'FieldSkillSet',
|
||||
idSegments: [{ label: '驻场技能', min: 7000, max: 7999 }],
|
||||
fields: [
|
||||
{ key: 'uuid', label: 'UUID', type: 'number', required: true, group: '基础' },
|
||||
{ key: 'name', label: '名称', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'icon', label: '图标', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'type', label: '类型', type: 'enum', enumRef: 'FieldSkillType', required: true, group: '效果' },
|
||||
{ key: 'value', label: '数值', type: 'number', required: true, group: '效果' },
|
||||
{ key: 'info', label: '描述', type: 'string', required: true, group: '基础' },
|
||||
],
|
||||
};
|
||||
33
extensions/pixelhero-config-editor/src/shared/schema/hero.ts
Normal file
33
extensions/pixelhero-config-editor/src/shared/schema/hero.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { TableSchema } from './types';
|
||||
export const heroSchema: TableSchema = {
|
||||
id: 'hero', label: '英雄/怪物',
|
||||
sourceFile: 'heroSet.ts', exportName: 'HeroInfo', listExportName: 'HeroList',
|
||||
idSegments: [
|
||||
{ label: '英雄', min: 5000, max: 5999 },
|
||||
{ label: '怪物', min: 6000, max: 6999 },
|
||||
],
|
||||
fields: [
|
||||
{ key: 'uuid', label: 'UUID', type: 'number', required: true, group: '基础' },
|
||||
{ key: 'name', label: '名称', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'path', label: '资源路径', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'icon', label: '图标', type: 'string', group: '基础' },
|
||||
{ key: 'fac', label: '阵营', type: 'enum', enumRef: 'FacSet', required: true, group: '基础' },
|
||||
{ key: 'pool_lv', label: '卡片等级', type: 'number', group: '基础' },
|
||||
{ key: 'lv', label: '英雄等级', type: 'number', required: true, default: 1, group: '基础' },
|
||||
{ key: 'type', label: '攻击定位', type: 'enum', enumRef: 'HType', required: true, group: '基础' },
|
||||
{ key: 'hp', label: '生命上限', type: 'number', required: true, group: '基础' },
|
||||
{ key: 'ap', label: '攻击力', type: 'number', required: true, group: '基础' },
|
||||
{ key: 'dis', label: '攻击距离', type: 'number', group: '基础' },
|
||||
{ key: 'speed', label: '移动速度', type: 'number', group: '基础' },
|
||||
{ key: 'skills', label: '携带技能', type: 'skillMap', required: true, group: '技能' },
|
||||
{ key: 'call', label: '召唤触发', type: 'triggerSlots', group: '触发技能' },
|
||||
{ key: 'dead', label: '死亡触发', type: 'triggerSlots', group: '触发技能' },
|
||||
{ key: 'fstart', label: '战斗开始触发', type: 'triggerSlots', group: '触发技能' },
|
||||
{ key: 'fend', label: '战斗结束触发', type: 'triggerSlots', group: '触发技能' },
|
||||
{ key: 'atking', label: '攻击触发', type: 'triggerSlots', group: '触发技能' },
|
||||
{ key: 'atked', label: '受击触发', type: 'triggerSlots', group: '触发技能' },
|
||||
{ key: 'field', label: '驻场技能', type: 'fieldList', group: '触发技能' },
|
||||
{ key: 'revive', label: '复活触发', type: 'reviveSlot', group: '触发技能' },
|
||||
{ key: 'info', label: '描述文案', type: 'string', required: true, group: '基础' },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { TableId, TableSchema } from './types';
|
||||
import { heroSchema } from './hero';
|
||||
import { skillSchema } from './skill';
|
||||
import { fieldSchema } from './field';
|
||||
|
||||
export const REGISTRY: Record<TableId, TableSchema> = {
|
||||
hero: heroSchema, skill: skillSchema, field: fieldSchema,
|
||||
};
|
||||
export function getSchema(id: TableId): TableSchema { return REGISTRY[id]; }
|
||||
export function allSchemas(): TableSchema[] { return Object.values(REGISTRY); }
|
||||
@@ -0,0 +1,41 @@
|
||||
import { TableSchema } from './types';
|
||||
export const skillSchema: TableSchema = {
|
||||
id: 'skill', label: '技能',
|
||||
sourceFile: 'SkillSet.ts', exportName: 'SkillSet',
|
||||
idSegments: [{ label: '技能', min: 6000, max: 6999 }],
|
||||
fields: [
|
||||
{ key: 'uuid', label: 'UUID', type: 'number', required: true, group: '基础' },
|
||||
{ key: 'name', label: '名称', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'sp_name', label: '特效名', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'icon', label: '图标ID', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'act', label: '执行动画', type: 'string', required: true, group: '基础' },
|
||||
{ key: 'TGroup', label: '目标群体', type: 'enum', enumRef: 'TGroup', required: true, group: '目标' },
|
||||
{ key: 'DTType', label: '伤害类型', type: 'enum', enumRef: 'DTType', required: true, group: '目标' },
|
||||
{ key: 'IType', label: '技能类型', type: 'enum', enumRef: 'IType', required: true, group: '目标' },
|
||||
{ key: 'RType', label: '运行类型', type: 'enum', enumRef: 'RType', required: true, group: '目标' },
|
||||
{ key: 'EType', label: '结束条件', type: 'enum', enumRef: 'EType', required: true, group: '目标' },
|
||||
{ key: 'kind', label: '主效果', type: 'enum', enumRef: 'SkillKind', group: '目标' },
|
||||
{ key: 'ap', label: 'ap(伤害%/护盾次)', type: 'number', required: true, group: '数值' },
|
||||
{ key: 'gold', label: '金币值', type: 'number', group: '数值' },
|
||||
{ key: 'hit_count', label: '可命中次数', type: 'number', required: true, group: '数值' },
|
||||
{ key: 'hitcd', label: '伤害间隔', type: 'number', required: true, group: '数值' },
|
||||
{ key: 'speed', label: '移动速度', type: 'number', required: true, group: '数值' },
|
||||
{ key: 'ready', label: '前摇时间', type: 'number', required: true, group: '数值' },
|
||||
{ key: 'with', label: '宽度', type: 'number', default: 0, group: '数值' },
|
||||
{ key: 'readyAnm', label: '前摇动画', type: 'string', group: '动画' },
|
||||
{ key: 'endAnm', label: '结束动画', type: 'string', group: '动画' },
|
||||
{ key: 'DAnm', label: '命中动画ID', type: 'string', group: '动画' },
|
||||
{ key: 'EAnm', label: '结束动画ID', type: 'number', group: '动画' },
|
||||
{ key: 'crt', label: '额外暴击率', type: 'number', group: '高级' },
|
||||
{ key: 'stun', label: '额外击晕概率', type: 'number', group: '高级' },
|
||||
{ key: 'frz', label: '额外冰冻概率', type: 'number', group: '高级' },
|
||||
{ key: 'bck', label: '额外击退概率', type: 'number', group: '高级' },
|
||||
{ key: 'buff_type', label: 'Buff类型', type: 'enum', enumRef: 'Attrs', group: '高级' },
|
||||
{ key: 'call_hero', label: '召唤英雄', type: 'ref', refTable: 'hero', group: '高级' },
|
||||
{ key: 'time', label: '持续时间', type: 'number', group: '高级' },
|
||||
{ key: 'bezier_start_y', label: '贝塞尔起始Y', type: 'number', group: '高级' },
|
||||
{ key: 'bezier_mid_y', label: '贝塞尔中点Y', type: 'number', group: '高级' },
|
||||
{ key: 'bezier_arc', label: '贝塞尔弧度', type: 'number', group: '高级' },
|
||||
{ key: 'info', label: '描述', type: 'string', required: true, group: '基础' },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
export type TableId = 'hero' | 'skill' | 'field';
|
||||
|
||||
export type FieldType =
|
||||
| 'number' | 'string' | 'boolean'
|
||||
| 'enum' // 下拉,选项来自 enumRef
|
||||
| 'ref' // 引用另一表 uuid
|
||||
| 'speedExpr' // 攻速符号表达式
|
||||
| 'skillMap' | 'triggerSlots' | 'fieldList' | 'reviveSlot' | 'overrides';
|
||||
|
||||
export interface FieldSchema {
|
||||
key: string;
|
||||
label: string;
|
||||
type: FieldType;
|
||||
required?: boolean;
|
||||
default?: unknown;
|
||||
group?: string;
|
||||
enumRef?: string; // type='enum'
|
||||
refTable?: TableId; // type='ref'
|
||||
}
|
||||
|
||||
export interface IdSegment { label: string; min: number; max: number; }
|
||||
|
||||
export interface TableSchema {
|
||||
id: TableId;
|
||||
label: string;
|
||||
sourceFile: string; // 相对 assets/script/game/common/config/
|
||||
exportName: string; // 'HeroInfo' | 'SkillSet' | 'FieldSkillSet'
|
||||
listExportName?: string; // 'HeroList'(仅 hero)
|
||||
idSegments: IdSegment[];
|
||||
fields: FieldSchema[];
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { RecordValue } from '../../io/recordValue';
|
||||
import { TableId } from '../schema/types';
|
||||
import { ENUMS } from '../schema/enums';
|
||||
|
||||
export type Severity = 'error' | 'warn';
|
||||
export interface Issue {
|
||||
tableId: TableId; key: string; fieldPath: string;
|
||||
severity: Severity; code: string; message: string;
|
||||
}
|
||||
export interface ValidationContext {
|
||||
hasSkill: (u: number) => boolean;
|
||||
hasField: (u: number) => boolean;
|
||||
hasHero: (u: number) => boolean;
|
||||
heroListKeys: Set<string>;
|
||||
}
|
||||
|
||||
const OVERRIDE_KEYS = new Set(['TGroup', 'ap', 'gold', 'hit_count', 'hitcd', 'crt', 'frz', 'stun', 'bck', 'buff_type', 'call_hero']);
|
||||
const TRIGGER_ARRAY_KEYS = ['call', 'dead', 'fstart', 'fend', 'atking', 'atked'];
|
||||
|
||||
function num(v: RecordValue | undefined): number | undefined {
|
||||
return v && v.kind === 'num' ? v.value : undefined;
|
||||
}
|
||||
function isKnownEnum(qualifier: string, member: string): boolean {
|
||||
const def = ENUMS[qualifier] ?? Object.values(ENUMS).find(e => e.qualifier === qualifier);
|
||||
return !!def && def.members.some(m => String(m.value) === member || m.label.split(' ').pop() === member);
|
||||
}
|
||||
|
||||
export function validate(tableId: TableId, records: Map<string, RecordValue>, ctx: ValidationContext): Issue[] {
|
||||
const issues: Issue[] = [];
|
||||
// 跟踪 uuid 字段值(而非 Map 键)—— JS Map 会折叠重复键,但两条记录可能携带相同的 uuid 字段值。
|
||||
const seenUuids = new Set<string>();
|
||||
|
||||
for (const [key, rec] of records) {
|
||||
const push = (fieldPath: string, severity: Severity, code: string, message: string) =>
|
||||
issues.push({ tableId, key, fieldPath, severity, code, message });
|
||||
|
||||
if (rec.kind !== 'obj') { push('', 'error', 'not-object', '记录不是对象'); continue; }
|
||||
const p = rec.props;
|
||||
|
||||
const uVal = num(p['uuid']);
|
||||
if (uVal !== undefined) {
|
||||
const uKey = String(uVal);
|
||||
if (seenUuids.has(uKey)) push('uuid', 'error', 'dup-uuid', `重复 uuid: ${uKey}`);
|
||||
seenUuids.add(uKey);
|
||||
}
|
||||
|
||||
// 必填(按表最小集合)
|
||||
const required: Record<TableId, string[]> = {
|
||||
hero: ['uuid', 'name', 'path', 'fac', 'lv', 'type', 'hp', 'ap', 'skills', 'info'],
|
||||
skill: ['uuid', 'name', 'sp_name', 'icon', 'act', 'TGroup', 'DTType', 'IType', 'RType', 'EType', 'ap', 'hit_count', 'hitcd', 'speed', 'ready', 'info'],
|
||||
field: ['uuid', 'name', 'icon', 'type', 'value', 'info'],
|
||||
};
|
||||
for (const f of required[tableId]) {
|
||||
if (p[f] === undefined) push(f, 'error', 'missing-required', `缺少必填字段 ${f}`);
|
||||
}
|
||||
|
||||
// 枚举值合法
|
||||
for (const [f, v] of Object.entries(p)) {
|
||||
if (v.kind === 'enumRef' && !isKnownEnum(v.qualifier, v.member)) {
|
||||
push(f, 'error', 'bad-enum', `非法枚举值 ${v.qualifier}.${v.member}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (tableId === 'hero') {
|
||||
// 触发槽数组引用 + overrides 键
|
||||
for (const tk of TRIGGER_ARRAY_KEYS) {
|
||||
const arr = p[tk];
|
||||
if (!arr || arr.kind !== 'arr') continue;
|
||||
for (const it of arr.items) {
|
||||
if (it.kind !== 'obj') continue;
|
||||
const su = num(it.props['s_uuid']);
|
||||
if (su !== undefined && !ctx.hasSkill(su)) push(tk, 'error', 'dangling-ref', `引用了不存在的技能 ${su}`);
|
||||
const ov = it.props['overrides'];
|
||||
if (ov && ov.kind === 'obj') {
|
||||
for (const k of Object.keys(ov.props)) {
|
||||
if (!OVERRIDE_KEYS.has(k)) push(`${tk}.overrides`, 'error', 'bad-override-key', `非法覆盖键 ${k}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// field 列表
|
||||
const fl = p['field'];
|
||||
if (fl && fl.kind === 'arr') {
|
||||
for (const it of fl.items) { const u = num(it); if (u !== undefined && !ctx.hasField(u)) push('field', 'error', 'dangling-ref', `引用了不存在的驻场技能 ${u}`); }
|
||||
}
|
||||
// revive
|
||||
const rv = p['revive'];
|
||||
if (rv && rv.kind === 'obj') {
|
||||
const su = num(rv.props['s_uuid']); if (su !== undefined && !ctx.hasSkill(su)) push('revive', 'error', 'dangling-ref', `引用了不存在的技能 ${su}`);
|
||||
}
|
||||
// call_hero(技能表里的 ref 也用 hasHero)
|
||||
}
|
||||
if (tableId === 'skill') {
|
||||
const ch = num(p['call_hero']); if (ch !== undefined && !ctx.hasHero(ch)) push('call_hero', 'error', 'dangling-ref', `引用了不存在的英雄 ${ch}`);
|
||||
}
|
||||
|
||||
// HeroList 一致性(仅 hero 表)
|
||||
if (tableId === 'hero') {
|
||||
const u = num(p['uuid']); const fac = p['fac'];
|
||||
const isHero = fac && fac.kind === 'enumRef' && fac.member === 'HERO';
|
||||
if (isHero && u !== undefined && !ctx.heroListKeys.has(String(u))) {
|
||||
push('uuid', 'error', 'herolist-inconsistent', `英雄 ${u} 不在 HeroList 中`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#app { color: var(--color-font-normal); font-size: 12px; }
|
||||
.row { margin: 6px 0; }
|
||||
button { margin-right: 8px; }
|
||||
ul { list-style: none; padding: 0; max-height: 360px; overflow:auto; }
|
||||
li { padding: 3px 6px; cursor: pointer; }
|
||||
li:hover { background: var(--color-hover-bg); }
|
||||
.err { color: var(--color-warn); }
|
||||
@@ -0,0 +1 @@
|
||||
<div id="app" style="padding:12px;"></div>
|
||||
15
extensions/pixelhero-config-editor/tsconfig.json
Normal file
15
extensions/pixelhero-config-editor/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": ["node"],
|
||||
"lib": ["ES2020", "DOM"]
|
||||
},
|
||||
"include": ["src/**/*", "__tests__/**/*"]
|
||||
}
|
||||
93
production/qa/evidence/2026-06-20-config-editor-plan-a.md
Normal file
93
production/qa/evidence/2026-06-20-config-editor-plan-a.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 配置编辑器 Plan A — 验证证据
|
||||
|
||||
- **Plan**: `docs/superpowers/plans/2026-06-20-config-editor-foundation.md`
|
||||
- **Branch**: `card0614`
|
||||
- **Date**: 2026-06-21
|
||||
- **Tasks implemented**: 1–13 (full plan)
|
||||
|
||||
---
|
||||
|
||||
## 自动化测试(BLOCKING 门槛)
|
||||
|
||||
- **命令**: `npx tsx --test __tests__/*.test.ts`(在 `extensions/pixelhero-config-editor/` 下运行)
|
||||
- **结果**: **36 / 36 PASS**,0 fail,0 skip
|
||||
- **耗时**: ~560–600ms
|
||||
|
||||
覆盖范围:
|
||||
|
||||
| 测试文件 | 用例数 | 覆盖点 |
|
||||
|---|---|---|
|
||||
| `recordValue.serializer.test.ts` | 8 | num/str/bool/enumRef/speed/arr/obj/raw 序列化;`serializeEntry` 单行与嵌套续行 |
|
||||
| `parser.test.ts` | 8 | AST → RecordValue;speed 表达式识别;enumRef fallback;raw 保留;`findExportObjectLiteral` |
|
||||
| `tsConfigFile.test.ts` | 10 | load/getKeys/read;patch/add/delete;`.bak` 备份;语法校验阻断;dirty/no-op save;speed+enumRef 往返不丢 |
|
||||
| `validation.test.ts` | 8 | dup-uuid、missing-required、bad-enum、dangling-ref(触发槽/field/revive)、bad-override-key、herolist-inconsistent |
|
||||
| `buildSkillDesc.test.ts` | 2 | 受击触发盾效、驻场光环描述 |
|
||||
|
||||
> **说明**:Task 11(`store.ts`)与 Task 12(`index.ts`/面板)依赖 Cocos `Editor` 全局,无法在 Node 中 import/运行,故无单元测试——按计划设计。自动化门槛以纯逻辑层 36 用例为准。
|
||||
|
||||
---
|
||||
|
||||
## 构建(BLOCKING 门槛)
|
||||
|
||||
- **命令**: `npm run build`(在 `extensions/pixelhero-config-editor/` 下运行)
|
||||
- **结果**: 成功,两个产物均生成,esbuild 无错误。
|
||||
|
||||
| 产物 | 大小(字节) | 说明 |
|
||||
|---|---|---|
|
||||
| `dist/main.js` | 9,982,310 (~9.5 MB) | node/cjs;内联 `typescript` 编译器 API;`Editor` 为运行时全局(无 esbuild 报错) |
|
||||
| `dist/panels/default.js` | 642,825 (~628 KB) | browser/iife;内联 `vue/dist/vue.esm-bundler.js`(含运行时模板编译器) |
|
||||
|
||||
`dist/` 已被 `extensions/pixelhero-config-editor/.gitignore` 忽略,未提交。
|
||||
|
||||
### 构建过程中的必要修正(已记入 Task 12 commit message)
|
||||
|
||||
Plan 给出的 `esbuild.config.mjs` 将面板入口设为 `platform: 'browser'` 且 `external: []`。但 Plan 的面板 `src/panels/default/index.ts` 在运行时用 `node:fs`/`node:path` 读取 `static/template/default/index.html` 与 `static/style/default/index.css`。esbuild 在 browser 平台下拒绝打包 `node:` 内建,报错:
|
||||
|
||||
```
|
||||
X [ERROR] Could not resolve "node:fs"
|
||||
X [ERROR] Could not resolve "node:path"
|
||||
```
|
||||
|
||||
**修正**:将面板入口的 `external` 改为 `['node:fs', 'node:path']`。理由:Cocos 面板进程在 Electron 渲染层运行,能访问 Node 内建;vue 仍按 plan 打进 IIFE。`dist/main.js` 不受影响(platform:'node' 本就允许 `node:` 内建)。
|
||||
|
||||
---
|
||||
|
||||
## 编辑器内集成(ADVISORY — 待人工完成)
|
||||
|
||||
> 以下为人工在 Cocos Creator 3.8.6 中执行的验证清单。完成后请勾选并补截图路径。
|
||||
|
||||
- [ ] 1. 打开 Cocos Creator 3.8.6 项目 `d:\game\pixelheros`。
|
||||
- [ ] 2. 扩展管理器(Extension Manager)→ 项目扩展 → 启用/重载 `pixelhero-config-editor`;控制台无报错。
|
||||
- [ ] 3. 主菜单 → 面板(Panel)→ 英雄技能配置(Hero/Skill Config),面板打开。
|
||||
- [ ] 4. 表下拉切换 `英雄/怪物` → 列表显示真实 uuid(应含 5011/5012/.../6106 等英雄条目)。
|
||||
- [ ] 5. 表下拉切换 `技能` → 列表显示 6xxx 系列技能 uuid;切 `驻场技能` → 7xxx 系列。
|
||||
- [ ] 6. 点选 hero 表任一条 → 右侧 `<pre>` 显示结构化 JSON:
|
||||
- [ ] 6a. `fac` 字段为 `{ "kind": "enumRef", "qualifier": "FacSet", "member": "HERO" }`(或 `MON`)。
|
||||
- [ ] 6b. 技能 `skills.<uuid>.cd` 为 `{ "kind": "speed", "level": "Slow3" }`(或其它 AtkSpeedLv 成员)。
|
||||
- [ ] 6c. 数值/字符串字段为 `{ "kind": "num"|"str", "value": ... }`。
|
||||
- [ ] 7. 点选 skill 表任一条 → JSON 中 `TGroup`/`DTType`/`IType`/`RType`/`EType` 等字段均为 `enumRef`,`call_hero`(若有)为 `num` 或 `enumRef`。
|
||||
- [ ] 8. 结论:端到端 IPC 打通,IO 层正确解析真实 `assets/script/game/common/config/heroSet.ts` 与 `SkillSet.ts`。
|
||||
- [ ] 9. 截图存档路径(填写):`production/qa/evidence/screenshots/config-editor-plan-a-YYYYMMDD.png`
|
||||
|
||||
---
|
||||
|
||||
## 提交记录
|
||||
|
||||
| Task | Commit SHA | 标题 |
|
||||
|---|---|---|
|
||||
| 11 | `e3102c63` | feat(config-editor): main-process store (in-memory truth + message impls + asset-db refresh) |
|
||||
| 12 | `24b5c498` | feat(config-editor): extension entry + minimal Vue panel proving end-to-end IPC |
|
||||
| 13 | (本提交) | test(config-editor): record Plan A verification evidence |
|
||||
|
||||
Tasks 1–10 由前序批次完成并提交,SHAs 详见 `git log --oneline extensions/pixelhero-config-editor/`。
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done 对照
|
||||
|
||||
1. **扩展可被 Cocos 3.8.6 加载,菜单可打开面板** — 待人工确认(见上节步骤 2–3)。
|
||||
2. **IO 层对真实配置正确解析(含 speed 表达式与枚举引用)** — 单元测试覆盖(fixtures 镜像真实形态),人工确认真实文件见步骤 6。
|
||||
3. **patch 一条英雄保存后:磁盘文件更新且为合法 TS;其他条目与符号表达式原样保留;`.bak` 已生成;asset-db 已刷新** — TsConfigFile 单元测试覆盖文件行为(patch/add/delete/save/.bak/语法校验);asset-db refresh 在 `store.saveRecord` 中调用,待人工在编辑器内验证一次保存。
|
||||
4. **校验层对各类非法数据正确报错(error 级阻断保存)** — 8 个 validation 单元测试覆盖;`saveRecord` 在 error 时回滚不落盘。
|
||||
5. **全部单元测试 PASS(BLOCKING)** — **36/36 PASS**。
|
||||
6. **未改动 `assets/script/game/**` 任何游戏运行时代码** — 本批次仅改动 `extensions/pixelhero-config-editor/**` 与本证据文件。
|
||||
Reference in New Issue
Block a user