feat(ass): Milestone M26 Assassin Asset Pipeline and Trap Mechanics (closes #473)
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "bladefury",
|
||||
"celFile": "Expansion/bladefury",
|
||||
"width": 512,
|
||||
"height": 6,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 7,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
],
|
||||
[
|
||||
14,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
],
|
||||
[
|
||||
28,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
],
|
||||
[
|
||||
42,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
],
|
||||
[
|
||||
56,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
],
|
||||
[
|
||||
70,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
],
|
||||
[
|
||||
84,
|
||||
0,
|
||||
13,
|
||||
6,
|
||||
-6,
|
||||
-34
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 877 B |
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "bladefuryb",
|
||||
"celFile": "Expansion/bladefury_B",
|
||||
"width": 512,
|
||||
"height": 9,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 7,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
],
|
||||
[
|
||||
21,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
],
|
||||
[
|
||||
42,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
],
|
||||
[
|
||||
63,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
],
|
||||
[
|
||||
84,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
],
|
||||
[
|
||||
105,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
],
|
||||
[
|
||||
126,
|
||||
0,
|
||||
20,
|
||||
9,
|
||||
-10,
|
||||
-35
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "bladefuryc",
|
||||
"celFile": "Expansion/bladefury_C",
|
||||
"width": 512,
|
||||
"height": 11,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 7,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
],
|
||||
[
|
||||
25,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
],
|
||||
[
|
||||
50,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
],
|
||||
[
|
||||
75,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
],
|
||||
[
|
||||
100,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
],
|
||||
[
|
||||
125,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
],
|
||||
[
|
||||
150,
|
||||
0,
|
||||
24,
|
||||
11,
|
||||
-12,
|
||||
-36
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "bladeshieldmissile",
|
||||
"celFile": "Expansion/blade_shield_missile",
|
||||
"width": 512,
|
||||
"height": 11,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 5,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
11,
|
||||
-10,
|
||||
-7
|
||||
],
|
||||
[
|
||||
21,
|
||||
0,
|
||||
20,
|
||||
11,
|
||||
-10,
|
||||
-7
|
||||
],
|
||||
[
|
||||
42,
|
||||
0,
|
||||
20,
|
||||
11,
|
||||
-10,
|
||||
-7
|
||||
],
|
||||
[
|
||||
63,
|
||||
0,
|
||||
20,
|
||||
11,
|
||||
-10,
|
||||
-7
|
||||
],
|
||||
[
|
||||
84,
|
||||
0,
|
||||
20,
|
||||
11,
|
||||
-10,
|
||||
-7
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 489 B |
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"name": "deathsentryexplode",
|
||||
"celFile": "Expansion/dethsentry02",
|
||||
"width": 512,
|
||||
"height": 275,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 12,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
116,
|
||||
0,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
232,
|
||||
0,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
348,
|
||||
0,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
0,
|
||||
92,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
116,
|
||||
92,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
232,
|
||||
92,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
348,
|
||||
92,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
0,
|
||||
184,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
116,
|
||||
184,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
232,
|
||||
184,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
],
|
||||
[
|
||||
348,
|
||||
184,
|
||||
115,
|
||||
91,
|
||||
-66,
|
||||
-66
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"name": "firetrauma",
|
||||
"celFile": "expansion/FireTraumaMissile",
|
||||
"width": 512,
|
||||
"height": 10,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 20,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
13,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
26,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
39,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
52,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
65,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
78,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
91,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
104,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
117,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
130,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
143,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
156,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
169,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
182,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
195,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
208,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
221,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
234,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
],
|
||||
[
|
||||
247,
|
||||
0,
|
||||
12,
|
||||
10,
|
||||
-6,
|
||||
-13
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"name": "firetraumaexplode",
|
||||
"celFile": "expansion/FireTraumaXplosion",
|
||||
"width": 512,
|
||||
"height": 451,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 20,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
88,
|
||||
0,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
176,
|
||||
0,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
264,
|
||||
0,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
352,
|
||||
0,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
0,
|
||||
113,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
88,
|
||||
113,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
176,
|
||||
113,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
264,
|
||||
113,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
352,
|
||||
113,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
0,
|
||||
226,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
88,
|
||||
226,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
176,
|
||||
226,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
264,
|
||||
226,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
352,
|
||||
226,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
0,
|
||||
339,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
88,
|
||||
339,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
176,
|
||||
339,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
264,
|
||||
339,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
],
|
||||
[
|
||||
352,
|
||||
339,
|
||||
87,
|
||||
112,
|
||||
-45,
|
||||
-111
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 66 KiB |
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "firetraumaground",
|
||||
"celFile": "expansion/FireTraumaGround",
|
||||
"width": 512,
|
||||
"height": 10,
|
||||
"directions": 1,
|
||||
"framesPerDirection": 1,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
11,
|
||||
10,
|
||||
-5,
|
||||
-13
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 350 B |
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -0,0 +1,667 @@
|
|||
{
|
||||
"name": "shockfieldground",
|
||||
"celFile": "Expansion/ShockField",
|
||||
"width": 1024,
|
||||
"height": 647,
|
||||
"directions": 8,
|
||||
"framesPerDirection": 10,
|
||||
"animSpeed": 16,
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
103,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
206,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
309,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
412,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
515,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
618,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
721,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
824,
|
||||
0,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
],
|
||||
[
|
||||
0,
|
||||
61,
|
||||
102,
|
||||
60,
|
||||
-56,
|
||||
-32
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
103,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
199,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
295,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
391,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
487,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
583,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
679,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
775,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
871,
|
||||
61,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
],
|
||||
[
|
||||
0,
|
||||
122,
|
||||
95,
|
||||
60,
|
||||
-40,
|
||||
-31
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
96,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
197,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
298,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
399,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
500,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
601,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
702,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
803,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
904,
|
||||
122,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
],
|
||||
[
|
||||
0,
|
||||
200,
|
||||
100,
|
||||
77,
|
||||
-43,
|
||||
-31
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
101,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
198,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
295,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
392,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
489,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
586,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
683,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
780,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
877,
|
||||
200,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
],
|
||||
[
|
||||
0,
|
||||
279,
|
||||
96,
|
||||
78,
|
||||
-49,
|
||||
-33
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
97,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
193,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
289,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
385,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
481,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
577,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
673,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
769,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
865,
|
||||
279,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
],
|
||||
[
|
||||
0,
|
||||
358,
|
||||
95,
|
||||
77,
|
||||
-41,
|
||||
-34
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
96,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
202,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
308,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
414,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
520,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
626,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
732,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
838,
|
||||
358,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
0,
|
||||
436,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
],
|
||||
[
|
||||
106,
|
||||
436,
|
||||
105,
|
||||
56,
|
||||
-50,
|
||||
-27
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
212,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
306,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
400,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
494,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
588,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
682,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
776,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
870,
|
||||
436,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
0,
|
||||
507,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
],
|
||||
[
|
||||
94,
|
||||
507,
|
||||
93,
|
||||
70,
|
||||
-53,
|
||||
-33
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
188,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
290,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
392,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
494,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
596,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
698,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
800,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
902,
|
||||
507,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
0,
|
||||
578,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
],
|
||||
[
|
||||
102,
|
||||
578,
|
||||
101,
|
||||
69,
|
||||
-55,
|
||||
-32
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 138 KiB |
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"name": "blade_shield",
|
||||
"celFile": "Expansion/blade_shield",
|
||||
"width": 512,
|
||||
"height": 200,
|
||||
"directions": 1,
|
||||
"frames": 20,
|
||||
"framesPerDirection": 20,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -33,
|
||||
"top": -68,
|
||||
"width": 64,
|
||||
"height": 66
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"name": "blade_shield",
|
||||
"celFile": "Expansion/blade_shield",
|
||||
"width": 512,
|
||||
"height": 200,
|
||||
"directions": 1,
|
||||
"frames": 20,
|
||||
"framesPerDirection": 20,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -33,
|
||||
"top": -68,
|
||||
"width": 64,
|
||||
"height": 66
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"name": "blade_shield",
|
||||
"celFile": "Expansion/blade_shield",
|
||||
"width": 512,
|
||||
"height": 200,
|
||||
"directions": 1,
|
||||
"frames": 20,
|
||||
"framesPerDirection": 20,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -33,
|
||||
"top": -68,
|
||||
"width": 64,
|
||||
"height": 66
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "deathsentry_explode",
|
||||
"celFile": "expansion/dethsentry01",
|
||||
"width": 512,
|
||||
"height": 547,
|
||||
"directions": 1,
|
||||
"frames": 12,
|
||||
"framesPerDirection": 12,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -73,
|
||||
"top": -93,
|
||||
"width": 130,
|
||||
"height": 136
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "deathsentry_explode",
|
||||
"celFile": "expansion/dethsentry01",
|
||||
"width": 512,
|
||||
"height": 547,
|
||||
"directions": 1,
|
||||
"frames": 12,
|
||||
"framesPerDirection": 12,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -73,
|
||||
"top": -93,
|
||||
"width": 130,
|
||||
"height": 136
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "deathsentry_explode",
|
||||
"celFile": "expansion/dethsentry01",
|
||||
"width": 512,
|
||||
"height": 547,
|
||||
"directions": 1,
|
||||
"frames": 12,
|
||||
"framesPerDirection": 12,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -73,
|
||||
"top": -93,
|
||||
"width": 130,
|
||||
"height": 136
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"name": "deathsentry_explode",
|
||||
"celFile": "expansion/dethsentry01",
|
||||
"width": 512,
|
||||
"height": 547,
|
||||
"directions": 1,
|
||||
"frames": 12,
|
||||
"framesPerDirection": 12,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -73,
|
||||
"top": -93,
|
||||
"width": 130,
|
||||
"height": 136
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -181,6 +181,18 @@ const MISSILE_TARGETS = [
|
|||
{ name: 'hurricaneswoosh', celFile: 'expansion/hurricane_swoosh', animSpeed: 8, trans: 1 },
|
||||
{ name: 'hurricanerock', celFile: 'expansion/hurricane_rocks', animSpeed: 16, trans: 0 },
|
||||
{ name: 'hurricanetree', celFile: 'expansion/hurricane_tree', animSpeed: 16, trans: 0 },
|
||||
|
||||
// Assassin Traps
|
||||
{ name: 'firetrauma', celFile: 'expansion/FireTraumaMissile', animSpeed: 16, trans: 1 },
|
||||
{ name: 'firetraumaground', celFile: 'expansion/FireTraumaGround', animSpeed: 16, trans: 1 },
|
||||
{ name: 'firetraumaexplode', celFile: 'expansion/FireTraumaXplosion', animSpeed: 16, trans: 1 },
|
||||
{ name: 'shockfieldair', celFile: 'expansion/caltrops', animSpeed: 16, trans: 0 },
|
||||
{ name: 'shockfieldground', celFile: 'Expansion/ShockField', animSpeed: 16, trans: 1 },
|
||||
{ name: 'bladefury', celFile: 'Expansion/bladefury', animSpeed: 16, trans: 0 },
|
||||
{ name: 'bladefuryb', celFile: 'Expansion/bladefury_B', animSpeed: 16, trans: 0 },
|
||||
{ name: 'bladefuryc', celFile: 'Expansion/bladefury_C', animSpeed: 16, trans: 0 },
|
||||
{ name: 'deathsentryexplode', celFile: 'Expansion/dethsentry02', animSpeed: 16, trans: 1 },
|
||||
{ name: 'bladeshieldmissile', celFile: 'Expansion/blade_shield_missile', animSpeed: 16, trans: 1 },
|
||||
] as const
|
||||
|
||||
const PAGE_SIZE = 4096
|
||||
|
|
|
|||
|
|
@ -1479,6 +1479,30 @@ export const OVERLAY_TARGETS: readonly OverlayTarget[] = [
|
|||
lightRadius: 0,
|
||||
lightColor: [255, 255, 255] as const,
|
||||
},
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Authentic Diablo II v1.13c Assassin Trap Overlays
|
||||
// ---------------------------------------------------------------------------
|
||||
{
|
||||
name: 'blade_shield',
|
||||
celFile: 'Expansion/blade_shield',
|
||||
aliases: ['bladeshield', 'Expansion/blade_shield'],
|
||||
animRate: 16,
|
||||
trans: 3,
|
||||
preDraw: false,
|
||||
lightRadius: 0,
|
||||
lightColor: [255, 255, 255] as const,
|
||||
},
|
||||
{
|
||||
name: 'deathsentry_explode',
|
||||
celFile: 'expansion/dethsentry01',
|
||||
aliases: ['deathsentry', 'dethsentry01', 'expansion/dethsentry01'],
|
||||
animRate: 16,
|
||||
trans: 3,
|
||||
preDraw: false,
|
||||
lightRadius: 0,
|
||||
lightColor: [255, 255, 255] as const,
|
||||
},
|
||||
] as const
|
||||
|
||||
export async function bakeOverlays(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,951 @@
|
|||
/**
|
||||
* Diablo II: Lord of Destruction v1.13c — Assassin Traps Module
|
||||
*
|
||||
* Dedicated pure calculations, stationary sentry limits, NextDelay tracking,
|
||||
* Death Sentry dual-mode corpse explosion AI, blade skill weapon scaling (3/8, 3/4, 1/4),
|
||||
* and adversarial kinematics for the 10 Assassin Trap Tree Skills:
|
||||
* - Skill 251: Fire Trauma / Fire Blast (ReqLevel 1)
|
||||
* - Skill 256: Shock Field / Shock Web (ReqLevel 6)
|
||||
* - Skill 257: Blade Sentinel (ReqLevel 6)
|
||||
* - Skill 261: Charged Bolt Sentry (ReqLevel 12)
|
||||
* - Skill 262: Wake of Fire Sentry (ReqLevel 12)
|
||||
* - Skill 266: Blade Fury (ReqLevel 18)
|
||||
* - Skill 271: Lightning Sentry (ReqLevel 24)
|
||||
* - Skill 272: Inferno Sentry / Wake of Inferno (ReqLevel 24)
|
||||
* - Skill 276: Death Sentry (ReqLevel 30)
|
||||
* - Skill 277: Blade Shield (ReqLevel 30)
|
||||
*
|
||||
* 1.13c Ground Truth:
|
||||
* - Skills.txt, Missiles.txt, MonStats.txt, PetType.txt (Patch_D2.mpq).
|
||||
* - Global Trap Cap:
|
||||
* PetType `assassintrap` has global max = 5 across all stationary sentries
|
||||
* (Charged Bolt, Wake of Fire, Lightning, Inferno, Death Sentry) and Blade Sentinel.
|
||||
* Summoning a 6th trap causes strict FIFO eviction of the oldest active trap.
|
||||
* - NextDelay Mechanics:
|
||||
* Wake of Fire (`wake of destruction maker`): NextDelay = 4 frames (0.16s).
|
||||
* Shock Web (`shock field on ground`): NextDelay = 25 frames (1.0s).
|
||||
* Blade Sentinel (`blade creeper`): NextDelay = 25 frames (1.0s).
|
||||
* Entities taking damage enter a temporary immunity window for missiles sharing that NextDelay.
|
||||
* - Weapon Damage Scaling (`srcDam` in 128ths):
|
||||
* Blade Sentinel: srcDam = 48 -> 48 / 128 = 3/8 (37.5%).
|
||||
* Blade Fury: srcDam = 96 -> 96 / 128 = 3/4 (75.0%).
|
||||
* Blade Shield: srcDam = 32 -> 32 / 128 = 1/4 (25.0%).
|
||||
* - Death Sentry Dual-Mode AI:
|
||||
* Prioritizes valid corpses within radius: 40% to 80% corpse max HP explosion (50% phys / 50% fire).
|
||||
* Radius: 10 + (slvl - 1) subtiles (3.3 yards base, +0.33 yards/lvl).
|
||||
* If no corpse in radius, fires piercing lightning bolt at live targets.
|
||||
* Shots: 5 base + floor(Fire Blast blvl / 3).
|
||||
* - Zero runtime MPQ reading.
|
||||
*/
|
||||
|
||||
import { compute5BandScaling } from '../engine/calc-ast.ts'
|
||||
|
||||
// --- Skill IDs ---
|
||||
|
||||
export const ASSASSIN_TRAP_SKILL_IDS = {
|
||||
FIRE_BLAST: 251,
|
||||
SHOCK_WEB: 256,
|
||||
BLADE_SENTINEL: 257,
|
||||
CHARGED_BOLT_SENTRY: 261,
|
||||
WAKE_OF_FIRE: 262,
|
||||
BLADE_FURY: 266,
|
||||
LIGHTNING_SENTRY: 271,
|
||||
WAKE_OF_INFERNO: 272,
|
||||
DEATH_SENTRY: 276,
|
||||
BLADE_SHIELD: 277,
|
||||
} as const
|
||||
|
||||
// Pet sub-skill IDs for sentries
|
||||
export const ASSASSIN_PET_SUB_SKILL_IDS = {
|
||||
WAKE_OF_DESTRUCTION_MAKER: 281,
|
||||
BOLT_SENTRY: 306,
|
||||
MON_INFERNO_SENTRY: 311,
|
||||
MON_DEATH_SENTRY: 312,
|
||||
SENTRY_LIGHTNING: 313,
|
||||
DEATH_SENTRY_LTNG: 324,
|
||||
} as const
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
export const MAX_ACTIVE_TRAPS = 5
|
||||
export const TRAP_PET_TYPE = 'assassintrap'
|
||||
|
||||
export const NEXT_DELAY_FRAMES = {
|
||||
WAKE_OF_FIRE: 4,
|
||||
SHOCK_WEB: 25,
|
||||
BLADE_SENTINEL: 25,
|
||||
DRAGON_FLIGHT: 4,
|
||||
} as const
|
||||
|
||||
export const BLADE_SRC_DAM = {
|
||||
BLADE_SENTINEL: 48, // 48 / 128 = 3/8
|
||||
BLADE_FURY: 96, // 96 / 128 = 3/4
|
||||
BLADE_SHIELD: 32, // 32 / 128 = 1/4
|
||||
} as const
|
||||
|
||||
// --- Interfaces ---
|
||||
|
||||
export interface ActiveTrap {
|
||||
readonly id: string
|
||||
readonly ownerId: string | number
|
||||
readonly skillId: number
|
||||
readonly summonMon: string
|
||||
readonly x: number
|
||||
readonly y: number
|
||||
readonly slvl: number
|
||||
remainingShots: number
|
||||
readonly createdAtFrame: number
|
||||
readonly maxLifetimeFrames: number
|
||||
lastAttackFrame: number
|
||||
}
|
||||
|
||||
export interface NextDelayRecord {
|
||||
readonly entityId: string | number
|
||||
readonly channelId: string | number
|
||||
expiresAtFrame: number
|
||||
}
|
||||
|
||||
// --- NextDelay Engine ---
|
||||
|
||||
export class NextDelayTracker {
|
||||
private readonly records = new Map<string, number>()
|
||||
|
||||
private makeKey(entityId: string | number, channel: string | number): string {
|
||||
return `${entityId}:${channel}`
|
||||
}
|
||||
|
||||
canTakeDamage(entityId: string | number, channel: string | number, currentFrame: number): boolean {
|
||||
const key = this.makeKey(entityId, channel)
|
||||
const expiresAt = this.records.get(key)
|
||||
if (expiresAt === undefined) return true
|
||||
return currentFrame >= expiresAt
|
||||
}
|
||||
|
||||
recordHit(entityId: string | number, channel: string | number, currentFrame: number, delayFrames: number): void {
|
||||
const key = this.makeKey(entityId, channel)
|
||||
this.records.set(key, currentFrame + delayFrames)
|
||||
}
|
||||
|
||||
cleanExpired(currentFrame: number): void {
|
||||
for (const [key, expiresAt] of this.records.entries()) {
|
||||
if (currentFrame >= expiresAt) {
|
||||
this.records.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.records.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// --- Trap Manager Engine ---
|
||||
|
||||
export class TrapManager {
|
||||
private readonly traps: ActiveTrap[] = []
|
||||
private nextTrapId = 1
|
||||
|
||||
getActiveTraps(ownerId?: string | number): readonly ActiveTrap[] {
|
||||
if (ownerId === undefined) return this.traps
|
||||
return this.traps.filter(t => t.ownerId === ownerId)
|
||||
}
|
||||
|
||||
getActiveTrapCount(ownerId: string | number): number {
|
||||
return this.traps.filter(t => t.ownerId === ownerId).length
|
||||
}
|
||||
|
||||
registerTrap(trapData: {
|
||||
ownerId: string | number
|
||||
skillId: number
|
||||
summonMon: string
|
||||
x: number
|
||||
y: number
|
||||
slvl: number
|
||||
shots: number
|
||||
currentFrame: number
|
||||
lifetimeFrames?: number
|
||||
}): { trap: ActiveTrap; evictedTrap?: ActiveTrap | undefined } {
|
||||
let evictedTrap: ActiveTrap | undefined
|
||||
|
||||
const ownerTraps = this.traps.filter(t => t.ownerId === trapData.ownerId)
|
||||
if (ownerTraps.length >= MAX_ACTIVE_TRAPS) {
|
||||
// FIFO: evict the oldest trap
|
||||
evictedTrap = ownerTraps[0]
|
||||
const idx = this.traps.indexOf(evictedTrap!)
|
||||
if (idx !== -1) {
|
||||
this.traps.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const trap: ActiveTrap = {
|
||||
id: `trap_${this.nextTrapId++}`,
|
||||
ownerId: trapData.ownerId,
|
||||
skillId: trapData.skillId,
|
||||
summonMon: trapData.summonMon,
|
||||
x: trapData.x,
|
||||
y: trapData.y,
|
||||
slvl: trapData.slvl,
|
||||
remainingShots: trapData.shots,
|
||||
createdAtFrame: trapData.currentFrame,
|
||||
maxLifetimeFrames: trapData.lifetimeFrames ?? 3000,
|
||||
lastAttackFrame: -1000,
|
||||
}
|
||||
|
||||
this.traps.push(trap)
|
||||
return { trap, evictedTrap }
|
||||
}
|
||||
|
||||
consumeShot(trapId: string): { remaining: number; destroyed: boolean } {
|
||||
const idx = this.traps.findIndex(t => t.id === trapId)
|
||||
if (idx === -1) return { remaining: 0, destroyed: true }
|
||||
|
||||
const trap = this.traps[idx]!
|
||||
trap.remainingShots -= 1
|
||||
if (trap.remainingShots <= 0) {
|
||||
this.traps.splice(idx, 1)
|
||||
return { remaining: 0, destroyed: true }
|
||||
}
|
||||
return { remaining: trap.remainingShots, destroyed: false }
|
||||
}
|
||||
|
||||
removeTrap(trapId: string): boolean {
|
||||
const idx = this.traps.findIndex(t => t.id === trapId)
|
||||
if (idx === -1) return false
|
||||
this.traps.splice(idx, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
clear(ownerId?: string | number): void {
|
||||
if (ownerId === undefined) {
|
||||
this.traps.length = 0
|
||||
} else {
|
||||
for (let i = this.traps.length - 1; i >= 0; i--) {
|
||||
if (this.traps[i]!.ownerId === ownerId) {
|
||||
this.traps.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Pure Damage & Stats Calculators ---
|
||||
|
||||
/**
|
||||
* 1.13c Weapon damage scaling for Assassin Blade skills.
|
||||
* srcDam is in 128ths.
|
||||
*/
|
||||
export function computeBladeWeaponDamage(baseWeaponDamage: number, skillId: number): number {
|
||||
switch (skillId) {
|
||||
case ASSASSIN_TRAP_SKILL_IDS.BLADE_SENTINEL:
|
||||
// 48 / 128 = 3/8 = 37.5%
|
||||
return Math.floor((baseWeaponDamage * BLADE_SRC_DAM.BLADE_SENTINEL) / 128)
|
||||
case ASSASSIN_TRAP_SKILL_IDS.BLADE_FURY:
|
||||
// 96 / 128 = 3/4 = 75.0%
|
||||
return Math.floor((baseWeaponDamage * BLADE_SRC_DAM.BLADE_FURY) / 128)
|
||||
case ASSASSIN_TRAP_SKILL_IDS.BLADE_SHIELD:
|
||||
// 32 / 128 = 1/4 = 25.0%
|
||||
return Math.floor((baseWeaponDamage * BLADE_SRC_DAM.BLADE_SHIELD) / 128)
|
||||
default:
|
||||
return baseWeaponDamage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #251: Fire Blast / Fire Trauma
|
||||
* 1.13c Parity:
|
||||
* - 5-band Fire damage: base 3-4, +2..+7 per band (HitShift 8)
|
||||
* - Synergies: +9% per hard point of CBS, WoF, LS, WoI, DS, Shock Web
|
||||
* - Radius: 5 subtiles base + floor(allTrapSynergies / 3)
|
||||
*/
|
||||
export function evaluateFireBlastStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
shockWeb?: number
|
||||
chargedBoltSentry?: number
|
||||
wakeOfFire?: number
|
||||
lightningSentry?: number
|
||||
wakeOfInferno?: number
|
||||
deathSentry?: number
|
||||
} = {}
|
||||
): {
|
||||
minDamage: number
|
||||
maxDamage: number
|
||||
minDamage256: number
|
||||
maxDamage256: number
|
||||
radiusSubtiles: number
|
||||
manaCost: number
|
||||
manaCost256: number
|
||||
synergyPct: number
|
||||
} {
|
||||
const minBase = compute5BandScaling(slvl, 3, 2, 3, 4, 6, 7)
|
||||
const maxBase = compute5BandScaling(slvl, 4, 3, 4, 6, 8, 9)
|
||||
|
||||
const synCount =
|
||||
(hardPoints.shockWeb ?? 0) +
|
||||
(hardPoints.chargedBoltSentry ?? 0) +
|
||||
(hardPoints.wakeOfFire ?? 0) +
|
||||
(hardPoints.lightningSentry ?? 0) +
|
||||
(hardPoints.wakeOfInferno ?? 0) +
|
||||
(hardPoints.deathSentry ?? 0)
|
||||
|
||||
const synergyPct = synCount * 9
|
||||
const mult = 1 + synergyPct / 100
|
||||
|
||||
const minDamage = Math.floor(minBase * mult)
|
||||
const maxDamage = Math.floor(maxBase * mult)
|
||||
|
||||
const manaCost = Math.max(1, 3 + Math.floor((slvl - 1) / 8))
|
||||
const radiusSubtiles = 5 + Math.floor(synCount / 3)
|
||||
|
||||
return {
|
||||
minDamage,
|
||||
maxDamage,
|
||||
minDamage256: minDamage * 256,
|
||||
maxDamage256: maxDamage * 256,
|
||||
radiusSubtiles,
|
||||
manaCost,
|
||||
manaCost256: manaCost * 256,
|
||||
synergyPct,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #256: Shock Web / Shock Field
|
||||
* 1.13c Parity:
|
||||
* - Missiles: 6 + floor(slvl / 4) + floor(fireBlastBlvl / 3)
|
||||
* - 5-band Lightning damage: 2-20 base, max scales +6, +12, +20, +30, +42 (HitShift 7 -> /2)
|
||||
* - Synergies: +11% per hard point of CBS, LS, DS
|
||||
* - NextDelay: 25 frames
|
||||
*/
|
||||
export function evaluateShockWebStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
fireBlast?: number
|
||||
chargedBoltSentry?: number
|
||||
lightningSentry?: number
|
||||
deathSentry?: number
|
||||
} = {}
|
||||
): {
|
||||
missileCount: number
|
||||
minDamage: number
|
||||
maxDamage: number
|
||||
minDamage256: number
|
||||
maxDamage256: number
|
||||
manaCost: number
|
||||
synergyPct: number
|
||||
nextDelayFrames: number
|
||||
} {
|
||||
const missileCount = 6 + Math.floor(slvl / 4) + Math.floor((hardPoints.fireBlast ?? 0) / 3)
|
||||
|
||||
const minBase = 1 // 2 with hitshift 7 = 1
|
||||
const maxBase = Math.floor(compute5BandScaling(slvl, 20, 6, 12, 20, 30, 42) / 2)
|
||||
|
||||
const synCount =
|
||||
(hardPoints.chargedBoltSentry ?? 0) +
|
||||
(hardPoints.lightningSentry ?? 0) +
|
||||
(hardPoints.deathSentry ?? 0)
|
||||
const synergyPct = synCount * 11
|
||||
const mult = 1 + synergyPct / 100
|
||||
|
||||
const minDamage = Math.max(1, Math.floor(minBase * mult))
|
||||
const maxDamage = Math.max(minDamage, Math.floor(maxBase * mult))
|
||||
|
||||
return {
|
||||
missileCount,
|
||||
minDamage,
|
||||
maxDamage,
|
||||
minDamage256: minDamage * 256,
|
||||
maxDamage256: maxDamage * 256,
|
||||
manaCost: 6,
|
||||
synergyPct,
|
||||
nextDelayFrames: NEXT_DELAY_FRAMES.SHOCK_WEB,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #257: Blade Sentinel
|
||||
* 1.13c Parity:
|
||||
* - Weapon Damage: 3/8 (srcDam = 48)
|
||||
* - Flat Physical Damage: 6-10 base, +3/+4/+5 per band
|
||||
* - Duration: 100 + 12 * (slvl - 1) frames (4s to 13.12s)
|
||||
* - NextDelay: 25 frames
|
||||
* - PetType: assassintrap (max 5)
|
||||
*/
|
||||
export function evaluateBladeSentinelStats(
|
||||
slvl: number,
|
||||
weaponDamage = 0
|
||||
): {
|
||||
flatMinPhys: number
|
||||
flatMaxPhys: number
|
||||
scaledWeaponPhys: number
|
||||
totalMinPhys: number
|
||||
totalMaxPhys: number
|
||||
durationFrames: number
|
||||
manaCost: number
|
||||
attackRatingBonusPct: number
|
||||
nextDelayFrames: number
|
||||
petMax: number
|
||||
} {
|
||||
const flatMinPhys = compute5BandScaling(slvl, 6, 3, 4, 5, 5, 5)
|
||||
const flatMaxPhys = compute5BandScaling(slvl, 10, 3, 4, 5, 5, 5)
|
||||
const scaledWeaponPhys = computeBladeWeaponDamage(weaponDamage, ASSASSIN_TRAP_SKILL_IDS.BLADE_SENTINEL)
|
||||
|
||||
const durationFrames = 100 + 12 * (slvl - 1)
|
||||
const attackRatingBonusPct = slvl * 5
|
||||
|
||||
return {
|
||||
flatMinPhys,
|
||||
flatMaxPhys,
|
||||
scaledWeaponPhys,
|
||||
totalMinPhys: flatMinPhys + scaledWeaponPhys,
|
||||
totalMaxPhys: flatMaxPhys + scaledWeaponPhys,
|
||||
durationFrames,
|
||||
manaCost: 7,
|
||||
attackRatingBonusPct,
|
||||
nextDelayFrames: NEXT_DELAY_FRAMES.BLADE_SENTINEL,
|
||||
petMax: MAX_ACTIVE_TRAPS,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #261: Charged Bolt Sentry
|
||||
* 1.13c Parity:
|
||||
* - Shots: 5 + floor(LightningSentry.blvl / 4)
|
||||
* - Bolts per shot: 5 + floor(ShockWeb.blvl / 3)
|
||||
* - Lightning Damage: 1 base min, max scales +1, +2, +3, +4, +5 (HitShift 8)
|
||||
* - Synergies: +6% per hard point of Fire Blast, Shock Web, LS, DS
|
||||
* - PetType: assassintrap (max 5)
|
||||
*/
|
||||
export function evaluateChargedBoltSentryStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
fireBlast?: number
|
||||
shockWeb?: number
|
||||
lightningSentry?: number
|
||||
deathSentry?: number
|
||||
} = {}
|
||||
): {
|
||||
shots: number
|
||||
boltsPerShot: number
|
||||
minDamage: number
|
||||
maxDamage: number
|
||||
minDamage256: number
|
||||
maxDamage256: number
|
||||
manaCost: number
|
||||
synergyPct: number
|
||||
petMax: number
|
||||
} {
|
||||
const shots = 5 + Math.floor((hardPoints.lightningSentry ?? 0) / 4)
|
||||
const boltsPerShot = 5 + Math.floor((hardPoints.shockWeb ?? 0) / 3)
|
||||
|
||||
const minBase = 1
|
||||
const maxBase = compute5BandScaling(slvl, 7, 1, 2, 3, 4, 5)
|
||||
|
||||
const synCount =
|
||||
(hardPoints.fireBlast ?? 0) +
|
||||
(hardPoints.shockWeb ?? 0) +
|
||||
(hardPoints.lightningSentry ?? 0) +
|
||||
(hardPoints.deathSentry ?? 0)
|
||||
const synergyPct = synCount * 6
|
||||
const mult = 1 + synergyPct / 100
|
||||
|
||||
const minDamage = minBase
|
||||
const maxDamage = Math.max(minDamage, Math.floor(maxBase * mult))
|
||||
|
||||
return {
|
||||
shots,
|
||||
boltsPerShot,
|
||||
minDamage,
|
||||
maxDamage,
|
||||
minDamage256: minDamage * 256,
|
||||
maxDamage256: maxDamage * 256,
|
||||
manaCost: 13,
|
||||
synergyPct,
|
||||
petMax: MAX_ACTIVE_TRAPS,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #262: Wake of Fire Sentry
|
||||
* 1.13c Parity:
|
||||
* - Shots: 5 waves
|
||||
* - NextDelay: 4 frames
|
||||
* - Fire Damage: 5-10 base, scales +2, +3, +4, +5, +6 (HitShift 8)
|
||||
* - Synergies: +8% per hard point of Fire Blast, Wake of Inferno
|
||||
* - PetType: assassintrap (max 5)
|
||||
*/
|
||||
export function evaluateWakeOfFireStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
fireBlast?: number
|
||||
wakeOfInferno?: number
|
||||
} = {}
|
||||
): {
|
||||
shots: number
|
||||
minDamage: number
|
||||
maxDamage: number
|
||||
minDamage256: number
|
||||
maxDamage256: number
|
||||
manaCost: number
|
||||
synergyPct: number
|
||||
nextDelayFrames: number
|
||||
petMax: number
|
||||
} {
|
||||
const minBase = compute5BandScaling(slvl, 5, 2, 3, 4, 5, 6)
|
||||
const maxBase = compute5BandScaling(slvl, 10, 2, 3, 4, 5, 6)
|
||||
|
||||
const synCount = (hardPoints.fireBlast ?? 0) + (hardPoints.wakeOfInferno ?? 0)
|
||||
const synergyPct = synCount * 8
|
||||
const mult = 1 + synergyPct / 100
|
||||
|
||||
const minDamage = Math.floor(minBase * mult)
|
||||
const maxDamage = Math.floor(maxBase * mult)
|
||||
|
||||
return {
|
||||
shots: 5,
|
||||
minDamage,
|
||||
maxDamage,
|
||||
minDamage256: minDamage * 256,
|
||||
maxDamage256: maxDamage * 256,
|
||||
manaCost: 13,
|
||||
synergyPct,
|
||||
nextDelayFrames: NEXT_DELAY_FRAMES.WAKE_OF_FIRE,
|
||||
petMax: MAX_ACTIVE_TRAPS,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #266: Blade Fury
|
||||
* 1.13c Parity:
|
||||
* - Weapon Damage: 3/4 (srcDam = 96)
|
||||
* - Flat Physical Damage: 8-10 base, +3/+5/+8
|
||||
* - Channeled tick rate: fires every 6 frames (5 frame delay + 1 action)
|
||||
* - Mana: 3 to start, 1 per shot
|
||||
*/
|
||||
export function evaluateBladeFuryStats(
|
||||
slvl: number,
|
||||
weaponDamage = 0
|
||||
): {
|
||||
flatMinPhys: number
|
||||
flatMaxPhys: number
|
||||
scaledWeaponPhys: number
|
||||
totalMinPhys: number
|
||||
totalMaxPhys: number
|
||||
startManaCost: number
|
||||
perShotManaCost: number
|
||||
framesPerShot: number
|
||||
} {
|
||||
const flatMinPhys = compute5BandScaling(slvl, 8, 3, 5, 8, 8, 8)
|
||||
const flatMaxPhys = compute5BandScaling(slvl, 10, 3, 5, 8, 8, 8)
|
||||
const scaledWeaponPhys = computeBladeWeaponDamage(weaponDamage, ASSASSIN_TRAP_SKILL_IDS.BLADE_FURY)
|
||||
|
||||
return {
|
||||
flatMinPhys,
|
||||
flatMaxPhys,
|
||||
scaledWeaponPhys,
|
||||
totalMinPhys: flatMinPhys + scaledWeaponPhys,
|
||||
totalMaxPhys: flatMaxPhys + scaledWeaponPhys,
|
||||
startManaCost: 3,
|
||||
perShotManaCost: 1,
|
||||
framesPerShot: 6,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #271: Lightning Sentry
|
||||
* 1.13c Parity:
|
||||
* - Shots: 10 piercing lightning beams
|
||||
* - Lightning Damage: 1 base min, max scales +7, +10, +13, +16, +19 (HitShift 8)
|
||||
* - Synergies: +12% per hard point of Shock Web, CBS, DS
|
||||
* - PetType: assassintrap (max 5)
|
||||
*/
|
||||
export function evaluateLightningSentryStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
shockWeb?: number
|
||||
chargedBoltSentry?: number
|
||||
deathSentry?: number
|
||||
} = {}
|
||||
): {
|
||||
shots: number
|
||||
minDamage: number
|
||||
maxDamage: number
|
||||
minDamage256: number
|
||||
maxDamage256: number
|
||||
manaCost: number
|
||||
synergyPct: number
|
||||
petMax: number
|
||||
} {
|
||||
const minBase = 1
|
||||
const maxBase = compute5BandScaling(slvl, 20, 10, 16, 24, 34, 44)
|
||||
|
||||
const synCount =
|
||||
(hardPoints.shockWeb ?? 0) +
|
||||
(hardPoints.chargedBoltSentry ?? 0) +
|
||||
(hardPoints.deathSentry ?? 0)
|
||||
const synergyPct = synCount * 12
|
||||
const mult = 1 + synergyPct / 100
|
||||
|
||||
const minDamage = minBase
|
||||
const maxDamage = Math.max(minDamage, Math.floor(maxBase * mult))
|
||||
|
||||
return {
|
||||
shots: 10,
|
||||
minDamage,
|
||||
maxDamage,
|
||||
minDamage256: minDamage * 256,
|
||||
maxDamage256: maxDamage * 256,
|
||||
manaCost: 20,
|
||||
synergyPct,
|
||||
petMax: MAX_ACTIVE_TRAPS,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #272: Inferno Sentry / Wake of Inferno
|
||||
* 1.13c Parity:
|
||||
* - Attacks: 10 flamethrower bursts
|
||||
* - Fire Damage: 1.25 - 3.125 base per tick, scales with level
|
||||
* - Synergies: +7% per hard point of Fire Blast, Wake of Fire, DS
|
||||
* - PetType: assassintrap (max 5)
|
||||
*/
|
||||
export function evaluateWakeOfInfernoStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
fireBlast?: number
|
||||
wakeOfFire?: number
|
||||
deathSentry?: number
|
||||
} = {}
|
||||
): {
|
||||
attacks: number
|
||||
minDamage256: number
|
||||
maxDamage256: number
|
||||
minDamage: number
|
||||
maxDamage: number
|
||||
manaCost: number
|
||||
synergyPct: number
|
||||
petMax: number
|
||||
} {
|
||||
const minBase256 = compute5BandScaling(slvl, 320, 96, 128, 160, 192, 224)
|
||||
const maxBase256 = compute5BandScaling(slvl, 800, 144, 192, 240, 288, 336)
|
||||
|
||||
const synCount =
|
||||
(hardPoints.fireBlast ?? 0) +
|
||||
(hardPoints.wakeOfFire ?? 0) +
|
||||
(hardPoints.deathSentry ?? 0)
|
||||
const synergyPct = synCount * 7
|
||||
const mult = 1 + synergyPct / 100
|
||||
|
||||
const minDamage256 = Math.floor(minBase256 * mult)
|
||||
const maxDamage256 = Math.floor(maxBase256 * mult)
|
||||
|
||||
return {
|
||||
attacks: 10,
|
||||
minDamage256,
|
||||
maxDamage256,
|
||||
minDamage: Math.floor(minDamage256 / 256),
|
||||
maxDamage: Math.floor(maxDamage256 / 256),
|
||||
manaCost: 20,
|
||||
synergyPct,
|
||||
petMax: MAX_ACTIVE_TRAPS,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #276: Death Sentry
|
||||
* 1.13c Parity:
|
||||
* - Shots: 5 + floor(FireBlast.blvl / 3)
|
||||
* - Lightning Damage: 1-50 base, max scales +11, +15, +19, +23, +27 (HitShift 8)
|
||||
* - Lightning Synergy: +12% per hard point of Lightning Sentry
|
||||
* - Corpse Explosion:
|
||||
* 40% to 80% of corpse base HP
|
||||
* 50% Physical, 50% Fire
|
||||
* Radius: 10 + (slvl - 1) subtiles (3.3 yards base, +0.33 yards/lvl)
|
||||
* - PetType: assassintrap (max 5)
|
||||
*/
|
||||
export function evaluateDeathSentryStats(
|
||||
slvl: number,
|
||||
hardPoints: {
|
||||
fireBlast?: number
|
||||
lightningSentry?: number
|
||||
} = {}
|
||||
): {
|
||||
shots: number
|
||||
minLtngDamage: number
|
||||
maxLtngDamage: number
|
||||
minLtngDamage256: number
|
||||
maxLtngDamage256: number
|
||||
corpseExplosionMinPct: number
|
||||
corpseExplosionMaxPct: number
|
||||
corpseExplosionFireRatio: number
|
||||
corpseExplosionPhysRatio: number
|
||||
corpseExplosionRadiusSubtiles: number
|
||||
manaCost: number
|
||||
ltngSynergyPct: number
|
||||
petMax: number
|
||||
} {
|
||||
const shots = 5 + Math.floor((hardPoints.fireBlast ?? 0) / 3)
|
||||
|
||||
const minBase = 1
|
||||
const maxBase = compute5BandScaling(slvl, 50, 11, 15, 19, 23, 27)
|
||||
const ltngSynergyPct = (hardPoints.lightningSentry ?? 0) * 12
|
||||
const mult = 1 + ltngSynergyPct / 100
|
||||
|
||||
const minLtngDamage = minBase
|
||||
const maxLtngDamage = Math.max(minLtngDamage, Math.floor(maxBase * mult))
|
||||
|
||||
const corpseExplosionRadiusSubtiles = 10 + (slvl - 1)
|
||||
|
||||
return {
|
||||
shots,
|
||||
minLtngDamage,
|
||||
maxLtngDamage,
|
||||
minLtngDamage256: minLtngDamage * 256,
|
||||
maxLtngDamage256: maxLtngDamage * 256,
|
||||
corpseExplosionMinPct: 40,
|
||||
corpseExplosionMaxPct: 80,
|
||||
corpseExplosionFireRatio: 0.5,
|
||||
corpseExplosionPhysRatio: 0.5,
|
||||
corpseExplosionRadiusSubtiles,
|
||||
manaCost: 20,
|
||||
ltngSynergyPct,
|
||||
petMax: MAX_ACTIVE_TRAPS,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill #277: Blade Shield
|
||||
* 1.13c Parity:
|
||||
* - Weapon Damage: 1/4 (srcDam = 32)
|
||||
* - Flat Physical Damage: 1-30 base, +5/+6/+7
|
||||
* - Duration: 500 + 125 * (slvl - 1) frames (20s to 115s at slvl 20)
|
||||
* - Periodic Tick: every 25 frames (1.0s)
|
||||
* - Radius: 6 subtiles (2.0 yards = 64 px)
|
||||
* - Mana: 27 + 2 * (slvl - 1)
|
||||
*/
|
||||
export function evaluateBladeShieldStats(
|
||||
slvl: number,
|
||||
weaponDamage = 0
|
||||
): {
|
||||
flatMinPhys: number
|
||||
flatMaxPhys: number
|
||||
scaledWeaponPhys: number
|
||||
totalMinPhys: number
|
||||
totalMaxPhys: number
|
||||
durationFrames: number
|
||||
tickRateFrames: number
|
||||
radiusSubtiles: number
|
||||
manaCost: number
|
||||
} {
|
||||
const flatMinPhys = compute5BandScaling(slvl, 1, 5, 6, 7, 7, 7)
|
||||
const flatMaxPhys = compute5BandScaling(slvl, 30, 5, 6, 7, 7, 7)
|
||||
const scaledWeaponPhys = computeBladeWeaponDamage(weaponDamage, ASSASSIN_TRAP_SKILL_IDS.BLADE_SHIELD)
|
||||
|
||||
const durationFrames = 500 + 125 * (slvl - 1)
|
||||
const manaCost = 27 + 2 * (slvl - 1)
|
||||
|
||||
return {
|
||||
flatMinPhys,
|
||||
flatMaxPhys,
|
||||
scaledWeaponPhys,
|
||||
totalMinPhys: flatMinPhys + scaledWeaponPhys,
|
||||
totalMaxPhys: flatMaxPhys + scaledWeaponPhys,
|
||||
durationFrames,
|
||||
tickRateFrames: 25,
|
||||
radiusSubtiles: 6,
|
||||
manaCost,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Kinematics & Simulation Helpers ---
|
||||
|
||||
export interface SimulationCorpse {
|
||||
readonly id: string
|
||||
readonly x: number
|
||||
readonly y: number
|
||||
readonly baseMaxHp: number
|
||||
consumed: boolean
|
||||
}
|
||||
|
||||
export interface SimulationEnemy {
|
||||
readonly id: string
|
||||
readonly x: number
|
||||
readonly y: number
|
||||
currentHp: number
|
||||
readonly maxHp: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate Death Sentry AI target selection:
|
||||
* 1. If valid unconsumed corpse within explosion radius -> triggers Corpse Explosion (50% fire, 50% phys).
|
||||
* 2. Else if enemy within lightning attack range (e.g. 15 yards / 480px) -> triggers Lightning Bolt.
|
||||
* 3. Else -> idle.
|
||||
*/
|
||||
export function simulateDeathSentryAI(
|
||||
trap: ActiveTrap,
|
||||
corpses: SimulationCorpse[],
|
||||
enemies: SimulationEnemy[],
|
||||
options: {
|
||||
fireBlastBlvl?: number
|
||||
lightningSentryBlvl?: number
|
||||
subtilePixelSize?: number
|
||||
} = {}
|
||||
): {
|
||||
action: 'corpse_explosion' | 'lightning' | 'idle'
|
||||
targetId?: string
|
||||
damageDealt?: { phys: number; fire: number; ltng: number }
|
||||
affectedEnemyIds?: string[]
|
||||
} {
|
||||
const stats = evaluateDeathSentryStats(trap.slvl, {
|
||||
fireBlast: options.fireBlastBlvl ?? 0,
|
||||
lightningSentry: options.lightningSentryBlvl ?? 0,
|
||||
})
|
||||
|
||||
const pixelSize = options.subtilePixelSize ?? 16
|
||||
const ceRadiusPx = stats.corpseExplosionRadiusSubtiles * pixelSize
|
||||
|
||||
// 1. Search for nearest corpse in CE radius
|
||||
let bestCorpse: SimulationCorpse | null = null
|
||||
let bestCorpseDistSq = Infinity
|
||||
|
||||
for (const c of corpses) {
|
||||
if (c.consumed) continue
|
||||
const dx = c.x - trap.x
|
||||
const dy = c.y - trap.y
|
||||
const distSq = dx * dx + dy * dy
|
||||
if (distSq <= ceRadiusPx * ceRadiusPx && distSq < bestCorpseDistSq) {
|
||||
bestCorpseDistSq = distSq
|
||||
bestCorpse = c
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCorpse) {
|
||||
// Consume corpse
|
||||
bestCorpse.consumed = true
|
||||
// Calculate 40-80% corpse life damage (average 60%)
|
||||
const pct = (stats.corpseExplosionMinPct + stats.corpseExplosionMaxPct) / 200
|
||||
const rawDamage = Math.floor(bestCorpse.baseMaxHp * pct)
|
||||
const physDmg = Math.floor(rawDamage * stats.corpseExplosionPhysRatio)
|
||||
const fireDmg = rawDamage - physDmg
|
||||
|
||||
const affectedEnemyIds: string[] = []
|
||||
for (const e of enemies) {
|
||||
const dx = e.x - bestCorpse.x
|
||||
const dy = e.y - bestCorpse.y
|
||||
if (dx * dx + dy * dy <= ceRadiusPx * ceRadiusPx) {
|
||||
affectedEnemyIds.push(e.id)
|
||||
e.currentHp = Math.max(0, e.currentHp - rawDamage)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
action: 'corpse_explosion',
|
||||
targetId: bestCorpse.id,
|
||||
damageDealt: { phys: physDmg, fire: fireDmg, ltng: 0 },
|
||||
affectedEnemyIds,
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search for nearest live enemy within lightning range (~480 px)
|
||||
const maxLtngDistPx = 480
|
||||
let bestEnemy: SimulationEnemy | null = null
|
||||
let bestEnemyDistSq = Infinity
|
||||
|
||||
for (const e of enemies) {
|
||||
if (e.currentHp <= 0) continue
|
||||
const dx = e.x - trap.x
|
||||
const dy = e.y - trap.y
|
||||
const distSq = dx * dx + dy * dy
|
||||
if (distSq <= maxLtngDistPx * maxLtngDistPx && distSq < bestEnemyDistSq) {
|
||||
bestEnemyDistSq = distSq
|
||||
bestEnemy = e
|
||||
}
|
||||
}
|
||||
|
||||
if (bestEnemy) {
|
||||
const avgLtng = Math.floor((stats.minLtngDamage + stats.maxLtngDamage) / 2)
|
||||
bestEnemy.currentHp = Math.max(0, bestEnemy.currentHp - avgLtng)
|
||||
return {
|
||||
action: 'lightning',
|
||||
targetId: bestEnemy.id,
|
||||
damageDealt: { phys: 0, fire: 0, ltng: avgLtng },
|
||||
affectedEnemyIds: [bestEnemy.id],
|
||||
}
|
||||
}
|
||||
|
||||
return { action: 'idle' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate Wake of Fire wave emission with NextDelay protection:
|
||||
* Each target can only be damaged once per 4 frames by Wake of Fire.
|
||||
*/
|
||||
export function simulateWakeOfFireWaveHit(
|
||||
trap: ActiveTrap,
|
||||
enemies: SimulationEnemy[],
|
||||
currentFrame: number,
|
||||
nextDelayTracker: NextDelayTracker,
|
||||
options: {
|
||||
fireBlastBlvl?: number
|
||||
wakeOfInfernoBlvl?: number
|
||||
waveRangePx?: number
|
||||
} = {}
|
||||
): {
|
||||
enemiesHit: string[]
|
||||
totalDamageDealt: number
|
||||
} {
|
||||
const stats = evaluateWakeOfFireStats(trap.slvl, {
|
||||
fireBlast: options.fireBlastBlvl ?? 0,
|
||||
wakeOfInferno: options.wakeOfInfernoBlvl ?? 0,
|
||||
})
|
||||
|
||||
const rangePx = options.waveRangePx ?? 300
|
||||
const enemiesHit: string[] = []
|
||||
let totalDamageDealt = 0
|
||||
|
||||
const avgDamage = Math.floor((stats.minDamage + stats.maxDamage) / 2)
|
||||
|
||||
for (const e of enemies) {
|
||||
if (e.currentHp <= 0) continue
|
||||
const dx = e.x - trap.x
|
||||
const dy = e.y - trap.y
|
||||
if (dx * dx + dy * dy <= rangePx * rangePx) {
|
||||
// Check NextDelay
|
||||
if (nextDelayTracker.canTakeDamage(e.id, 'wake_of_fire', currentFrame)) {
|
||||
enemiesHit.push(e.id)
|
||||
e.currentHp = Math.max(0, e.currentHp - avgDamage)
|
||||
totalDamageDealt += avgDamage
|
||||
nextDelayTracker.recordHit(e.id, 'wake_of_fire', currentFrame, stats.nextDelayFrames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { enemiesHit, totalDamageDealt }
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Shock Web cluster points dispersed around the target impact point.
|
||||
*/
|
||||
export function computeShockWebClusterPoints(
|
||||
targetX: number,
|
||||
targetY: number,
|
||||
slvl: number,
|
||||
fireBlastBlvl = 0,
|
||||
radiusPx = 64
|
||||
): { x: number; y: number }[] {
|
||||
const stats = evaluateShockWebStats(slvl, { fireBlast: fireBlastBlvl })
|
||||
const points: { x: number; y: number }[] = []
|
||||
|
||||
// Deterministic circular / hexagonal distribution
|
||||
for (let i = 0; i < stats.missileCount; i++) {
|
||||
const angle = (2 * Math.PI * i) / stats.missileCount
|
||||
// Varied distances for natural cluster density
|
||||
const r = radiusPx * (0.3 + 0.7 * ((i % 3) / 2))
|
||||
points.push({
|
||||
x: Math.round(targetX + r * Math.cos(angle)),
|
||||
y: Math.round(targetY + r * Math.sin(angle)),
|
||||
})
|
||||
}
|
||||
|
||||
return points
|
||||
}
|
||||
|
|
@ -16697,6 +16697,637 @@ export const HURRICANETREE_META: MissileMeta = {
|
|||
],
|
||||
}
|
||||
|
||||
export const FIRETRAUMA_META: MissileMeta = {
|
||||
name: "firetrauma",
|
||||
celFile: "expansion/FireTraumaMissile",
|
||||
width: 512,
|
||||
height: 10,
|
||||
directions: 1,
|
||||
framesPerDirection: 20,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 12, 10, -6, -13],
|
||||
[13, 0, 12, 10, -6, -13],
|
||||
[26, 0, 12, 10, -6, -13],
|
||||
[39, 0, 12, 10, -6, -13],
|
||||
[52, 0, 12, 10, -6, -13],
|
||||
[65, 0, 12, 10, -6, -13],
|
||||
[78, 0, 12, 10, -6, -13],
|
||||
[91, 0, 12, 10, -6, -13],
|
||||
[104, 0, 12, 10, -6, -13],
|
||||
[117, 0, 12, 10, -6, -13],
|
||||
[130, 0, 12, 10, -6, -13],
|
||||
[143, 0, 12, 10, -6, -13],
|
||||
[156, 0, 12, 10, -6, -13],
|
||||
[169, 0, 12, 10, -6, -13],
|
||||
[182, 0, 12, 10, -6, -13],
|
||||
[195, 0, 12, 10, -6, -13],
|
||||
[208, 0, 12, 10, -6, -13],
|
||||
[221, 0, 12, 10, -6, -13],
|
||||
[234, 0, 12, 10, -6, -13],
|
||||
[247, 0, 12, 10, -6, -13]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const FIRETRAUMAGROUND_META: MissileMeta = {
|
||||
name: "firetraumaground",
|
||||
celFile: "expansion/FireTraumaGround",
|
||||
width: 512,
|
||||
height: 10,
|
||||
directions: 1,
|
||||
framesPerDirection: 1,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 11, 10, -5, -13]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const FIRETRAUMAEXPLODE_META: MissileMeta = {
|
||||
name: "firetraumaexplode",
|
||||
celFile: "expansion/FireTraumaXplosion",
|
||||
width: 512,
|
||||
height: 451,
|
||||
directions: 1,
|
||||
framesPerDirection: 20,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 87, 112, -45, -111],
|
||||
[88, 0, 87, 112, -45, -111],
|
||||
[176, 0, 87, 112, -45, -111],
|
||||
[264, 0, 87, 112, -45, -111],
|
||||
[352, 0, 87, 112, -45, -111],
|
||||
[0, 113, 87, 112, -45, -111],
|
||||
[88, 113, 87, 112, -45, -111],
|
||||
[176, 113, 87, 112, -45, -111],
|
||||
[264, 113, 87, 112, -45, -111],
|
||||
[352, 113, 87, 112, -45, -111],
|
||||
[0, 226, 87, 112, -45, -111],
|
||||
[88, 226, 87, 112, -45, -111],
|
||||
[176, 226, 87, 112, -45, -111],
|
||||
[264, 226, 87, 112, -45, -111],
|
||||
[352, 226, 87, 112, -45, -111],
|
||||
[0, 339, 87, 112, -45, -111],
|
||||
[88, 339, 87, 112, -45, -111],
|
||||
[176, 339, 87, 112, -45, -111],
|
||||
[264, 339, 87, 112, -45, -111],
|
||||
[352, 339, 87, 112, -45, -111]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const SHOCKFIELDAIR_META: MissileMeta = {
|
||||
name: "shockfieldair",
|
||||
celFile: "expansion/caltrops",
|
||||
width: 512,
|
||||
height: 65,
|
||||
directions: 32,
|
||||
framesPerDirection: 8,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 11, 10, -5, -1],
|
||||
[12, 0, 11, 10, -5, -1],
|
||||
[24, 0, 11, 10, -5, -1],
|
||||
[36, 0, 11, 10, -5, -1],
|
||||
[48, 0, 11, 10, -5, -1],
|
||||
[60, 0, 11, 10, -5, -1],
|
||||
[72, 0, 11, 10, -5, -1],
|
||||
[84, 0, 11, 10, -5, -1]
|
||||
],
|
||||
[
|
||||
[96, 0, 10, 10, -2, 0],
|
||||
[107, 0, 10, 10, -2, 0],
|
||||
[118, 0, 10, 10, -2, 0],
|
||||
[129, 0, 10, 10, -2, 0],
|
||||
[140, 0, 10, 10, -2, 0],
|
||||
[151, 0, 10, 10, -2, 0],
|
||||
[162, 0, 10, 10, -2, 0],
|
||||
[173, 0, 10, 10, -2, 0]
|
||||
],
|
||||
[
|
||||
[184, 0, 9, 9, -5, 2],
|
||||
[194, 0, 9, 9, -5, 2],
|
||||
[204, 0, 9, 9, -5, 2],
|
||||
[214, 0, 9, 9, -5, 2],
|
||||
[224, 0, 9, 9, -5, 2],
|
||||
[234, 0, 9, 9, -5, 2],
|
||||
[244, 0, 9, 9, -5, 2],
|
||||
[254, 0, 9, 9, -5, 2]
|
||||
],
|
||||
[
|
||||
[264, 0, 11, 10, -8, 0],
|
||||
[276, 0, 11, 10, -8, 0],
|
||||
[288, 0, 11, 10, -8, 0],
|
||||
[300, 0, 11, 10, -8, 0],
|
||||
[312, 0, 11, 10, -8, 0],
|
||||
[324, 0, 11, 10, -8, 0],
|
||||
[336, 0, 11, 10, -8, 0],
|
||||
[348, 0, 11, 10, -8, 0]
|
||||
],
|
||||
[
|
||||
[360, 0, 7, 10, -5, -1],
|
||||
[368, 0, 7, 10, -5, -1],
|
||||
[376, 0, 7, 10, -5, -1],
|
||||
[384, 0, 7, 10, -5, -1],
|
||||
[392, 0, 7, 10, -5, -1],
|
||||
[400, 0, 7, 10, -5, -1],
|
||||
[408, 0, 7, 10, -5, -1],
|
||||
[416, 0, 7, 10, -5, -1]
|
||||
],
|
||||
[
|
||||
[424, 0, 10, 9, -3, 0],
|
||||
[435, 0, 10, 9, -3, 0],
|
||||
[446, 0, 10, 9, -3, 0],
|
||||
[457, 0, 10, 9, -3, 0],
|
||||
[468, 0, 10, 9, -3, 0],
|
||||
[479, 0, 10, 9, -3, 0],
|
||||
[490, 0, 10, 9, -3, 0],
|
||||
[501, 0, 10, 9, -3, 0]
|
||||
],
|
||||
[
|
||||
[0, 11, 8, 10, -2, 1],
|
||||
[9, 11, 8, 10, -2, 1],
|
||||
[18, 11, 8, 10, -2, 1],
|
||||
[27, 11, 8, 10, -2, 1],
|
||||
[36, 11, 8, 10, -2, 1],
|
||||
[45, 11, 8, 10, -2, 1],
|
||||
[54, 11, 8, 10, -2, 1],
|
||||
[63, 11, 8, 10, -2, 1]
|
||||
],
|
||||
[
|
||||
[72, 11, 10, 10, -7, 1],
|
||||
[83, 11, 10, 10, -7, 1],
|
||||
[94, 11, 10, 10, -7, 1],
|
||||
[105, 11, 10, 10, -7, 1],
|
||||
[116, 11, 10, 10, -7, 1],
|
||||
[127, 11, 10, 10, -7, 1],
|
||||
[138, 11, 10, 10, -7, 1],
|
||||
[149, 11, 10, 10, -7, 1]
|
||||
],
|
||||
[
|
||||
[160, 11, 9, 10, -5, -1],
|
||||
[170, 11, 9, 10, -5, -1],
|
||||
[180, 11, 9, 10, -5, -1],
|
||||
[190, 11, 9, 10, -5, -1],
|
||||
[200, 11, 9, 10, -5, -1],
|
||||
[210, 11, 9, 10, -5, -1],
|
||||
[220, 11, 9, 10, -5, -1],
|
||||
[230, 11, 9, 10, -5, -1]
|
||||
],
|
||||
[
|
||||
[240, 11, 10, 10, -3, -1],
|
||||
[251, 11, 10, 10, -3, -1],
|
||||
[262, 11, 10, 10, -3, -1],
|
||||
[273, 11, 10, 10, -3, -1],
|
||||
[284, 11, 10, 10, -3, -1],
|
||||
[295, 11, 10, 10, -3, -1],
|
||||
[306, 11, 10, 10, -3, -1],
|
||||
[317, 11, 10, 10, -3, -1]
|
||||
],
|
||||
[
|
||||
[328, 11, 11, 10, -3, 0],
|
||||
[340, 11, 11, 10, -3, 0],
|
||||
[352, 11, 11, 10, -3, 0],
|
||||
[364, 11, 11, 10, -3, 0],
|
||||
[376, 11, 11, 10, -3, 0],
|
||||
[388, 11, 11, 10, -3, 0],
|
||||
[400, 11, 11, 10, -3, 0],
|
||||
[412, 11, 11, 10, -3, 0]
|
||||
],
|
||||
[
|
||||
[424, 11, 10, 10, -3, 1],
|
||||
[435, 11, 10, 10, -3, 1],
|
||||
[446, 11, 10, 10, -3, 1],
|
||||
[457, 11, 10, 10, -3, 1],
|
||||
[468, 11, 10, 10, -3, 1],
|
||||
[479, 11, 10, 10, -3, 1],
|
||||
[490, 11, 10, 10, -3, 1],
|
||||
[501, 11, 10, 10, -3, 1]
|
||||
],
|
||||
[
|
||||
[0, 22, 9, 10, -3, 1],
|
||||
[10, 22, 9, 10, -3, 1],
|
||||
[20, 22, 9, 10, -3, 1],
|
||||
[30, 22, 9, 10, -3, 1],
|
||||
[40, 22, 9, 10, -3, 1],
|
||||
[50, 22, 9, 10, -3, 1],
|
||||
[60, 22, 9, 10, -3, 1],
|
||||
[70, 22, 9, 10, -3, 1]
|
||||
],
|
||||
[
|
||||
[80, 22, 10, 10, -6, 1],
|
||||
[91, 22, 10, 10, -6, 1],
|
||||
[102, 22, 10, 10, -6, 1],
|
||||
[113, 22, 10, 10, -6, 1],
|
||||
[124, 22, 10, 10, -6, 1],
|
||||
[135, 22, 10, 10, -6, 1],
|
||||
[146, 22, 10, 10, -6, 1],
|
||||
[157, 22, 10, 10, -6, 1]
|
||||
],
|
||||
[
|
||||
[168, 22, 10, 9, -8, 1],
|
||||
[179, 22, 10, 9, -8, 1],
|
||||
[190, 22, 10, 9, -8, 1],
|
||||
[201, 22, 10, 9, -8, 1],
|
||||
[212, 22, 10, 9, -8, 1],
|
||||
[223, 22, 10, 9, -8, 1],
|
||||
[234, 22, 10, 9, -8, 1],
|
||||
[245, 22, 10, 9, -8, 1]
|
||||
],
|
||||
[
|
||||
[256, 22, 9, 9, -6, 0],
|
||||
[266, 22, 9, 9, -6, 0],
|
||||
[276, 22, 9, 9, -6, 0],
|
||||
[286, 22, 9, 9, -6, 0],
|
||||
[296, 22, 9, 9, -6, 0],
|
||||
[306, 22, 9, 9, -6, 0],
|
||||
[316, 22, 9, 9, -6, 0],
|
||||
[326, 22, 9, 9, -6, 0]
|
||||
],
|
||||
[
|
||||
[336, 22, 8, 10, -5, -1],
|
||||
[345, 22, 8, 10, -5, -1],
|
||||
[354, 22, 8, 10, -5, -1],
|
||||
[363, 22, 8, 10, -5, -1],
|
||||
[372, 22, 8, 10, -5, -1],
|
||||
[381, 22, 8, 10, -5, -1],
|
||||
[390, 22, 8, 10, -5, -1],
|
||||
[399, 22, 8, 10, -5, -1]
|
||||
],
|
||||
[
|
||||
[408, 22, 9, 10, -5, -1],
|
||||
[418, 22, 9, 10, -5, -1],
|
||||
[428, 22, 9, 10, -5, -1],
|
||||
[438, 22, 9, 10, -5, -1],
|
||||
[448, 22, 9, 10, -5, -1],
|
||||
[458, 22, 9, 10, -5, -1],
|
||||
[468, 22, 9, 10, -5, -1],
|
||||
[478, 22, 9, 10, -5, -1]
|
||||
],
|
||||
[
|
||||
[488, 22, 10, 10, -4, -1],
|
||||
[499, 22, 10, 10, -4, -1],
|
||||
[0, 33, 10, 10, -4, -1],
|
||||
[11, 33, 10, 10, -4, -1],
|
||||
[22, 33, 10, 10, -4, -1],
|
||||
[33, 33, 10, 10, -4, -1],
|
||||
[44, 33, 10, 10, -4, -1],
|
||||
[55, 33, 10, 10, -4, -1]
|
||||
],
|
||||
[
|
||||
[66, 33, 10, 9, -3, 0],
|
||||
[77, 33, 10, 9, -3, 0],
|
||||
[88, 33, 10, 9, -3, 0],
|
||||
[99, 33, 10, 9, -3, 0],
|
||||
[110, 33, 10, 9, -3, 0],
|
||||
[121, 33, 10, 9, -3, 0],
|
||||
[132, 33, 10, 9, -3, 0],
|
||||
[143, 33, 10, 9, -3, 0]
|
||||
],
|
||||
[
|
||||
[154, 33, 9, 10, -2, 0],
|
||||
[164, 33, 9, 10, -2, 0],
|
||||
[174, 33, 9, 10, -2, 0],
|
||||
[184, 33, 9, 10, -2, 0],
|
||||
[194, 33, 9, 10, -2, 0],
|
||||
[204, 33, 9, 10, -2, 0],
|
||||
[214, 33, 9, 10, -2, 0],
|
||||
[224, 33, 9, 10, -2, 0]
|
||||
],
|
||||
[
|
||||
[234, 33, 9, 10, -2, 0],
|
||||
[244, 33, 9, 10, -2, 0],
|
||||
[254, 33, 9, 10, -2, 0],
|
||||
[264, 33, 9, 10, -2, 0],
|
||||
[274, 33, 9, 10, -2, 0],
|
||||
[284, 33, 9, 10, -2, 0],
|
||||
[294, 33, 9, 10, -2, 0],
|
||||
[304, 33, 9, 10, -2, 0]
|
||||
],
|
||||
[
|
||||
[314, 33, 10, 10, -3, 0],
|
||||
[325, 33, 10, 10, -3, 0],
|
||||
[336, 33, 10, 10, -3, 0],
|
||||
[347, 33, 10, 10, -3, 0],
|
||||
[358, 33, 10, 10, -3, 0],
|
||||
[369, 33, 10, 10, -3, 0],
|
||||
[380, 33, 10, 10, -3, 0],
|
||||
[391, 33, 10, 10, -3, 0]
|
||||
],
|
||||
[
|
||||
[402, 33, 9, 10, -2, 1],
|
||||
[412, 33, 9, 10, -2, 1],
|
||||
[422, 33, 9, 10, -2, 1],
|
||||
[432, 33, 9, 10, -2, 1],
|
||||
[442, 33, 9, 10, -2, 1],
|
||||
[452, 33, 9, 10, -2, 1],
|
||||
[462, 33, 9, 10, -2, 1],
|
||||
[472, 33, 9, 10, -2, 1]
|
||||
],
|
||||
[
|
||||
[482, 33, 8, 10, -3, 1],
|
||||
[491, 33, 8, 10, -3, 1],
|
||||
[500, 33, 8, 10, -3, 1],
|
||||
[0, 44, 8, 10, -3, 1],
|
||||
[9, 44, 8, 10, -3, 1],
|
||||
[18, 44, 8, 10, -3, 1],
|
||||
[27, 44, 8, 10, -3, 1],
|
||||
[36, 44, 8, 10, -3, 1]
|
||||
],
|
||||
[
|
||||
[45, 44, 10, 10, -5, 1],
|
||||
[56, 44, 10, 10, -5, 1],
|
||||
[67, 44, 10, 10, -5, 1],
|
||||
[78, 44, 10, 10, -5, 1],
|
||||
[89, 44, 10, 10, -5, 1],
|
||||
[100, 44, 10, 10, -5, 1],
|
||||
[111, 44, 10, 10, -5, 1],
|
||||
[122, 44, 10, 10, -5, 1]
|
||||
],
|
||||
[
|
||||
[133, 44, 10, 10, -6, 1],
|
||||
[144, 44, 10, 10, -6, 1],
|
||||
[155, 44, 10, 10, -6, 1],
|
||||
[166, 44, 10, 10, -6, 1],
|
||||
[177, 44, 10, 10, -6, 1],
|
||||
[188, 44, 10, 10, -6, 1],
|
||||
[199, 44, 10, 10, -6, 1],
|
||||
[210, 44, 10, 10, -6, 1]
|
||||
],
|
||||
[
|
||||
[221, 44, 10, 9, -7, 1],
|
||||
[232, 44, 10, 9, -7, 1],
|
||||
[243, 44, 10, 9, -7, 1],
|
||||
[254, 44, 10, 9, -7, 1],
|
||||
[265, 44, 10, 9, -7, 1],
|
||||
[276, 44, 10, 9, -7, 1],
|
||||
[287, 44, 10, 9, -7, 1],
|
||||
[298, 44, 10, 9, -7, 1]
|
||||
],
|
||||
[
|
||||
[309, 44, 9, 10, -7, 1],
|
||||
[319, 44, 9, 10, -7, 1],
|
||||
[329, 44, 9, 10, -7, 1],
|
||||
[339, 44, 9, 10, -7, 1],
|
||||
[349, 44, 9, 10, -7, 1],
|
||||
[359, 44, 9, 10, -7, 1],
|
||||
[369, 44, 9, 10, -7, 1],
|
||||
[379, 44, 9, 10, -7, 1]
|
||||
],
|
||||
[
|
||||
[389, 44, 10, 10, -8, 0],
|
||||
[400, 44, 10, 10, -8, 0],
|
||||
[411, 44, 10, 10, -8, 0],
|
||||
[422, 44, 10, 10, -8, 0],
|
||||
[433, 44, 10, 10, -8, 0],
|
||||
[444, 44, 10, 10, -8, 0],
|
||||
[455, 44, 10, 10, -8, 0],
|
||||
[466, 44, 10, 10, -8, 0]
|
||||
],
|
||||
[
|
||||
[477, 44, 9, 10, -7, -1],
|
||||
[487, 44, 9, 10, -7, -1],
|
||||
[497, 44, 9, 10, -7, -1],
|
||||
[0, 55, 9, 10, -7, -1],
|
||||
[10, 55, 9, 10, -7, -1],
|
||||
[20, 55, 9, 10, -7, -1],
|
||||
[30, 55, 9, 10, -7, -1],
|
||||
[40, 55, 9, 10, -7, -1]
|
||||
],
|
||||
[
|
||||
[50, 55, 9, 10, -6, -1],
|
||||
[60, 55, 9, 10, -6, -1],
|
||||
[70, 55, 9, 10, -6, -1],
|
||||
[80, 55, 9, 10, -6, -1],
|
||||
[90, 55, 9, 10, -6, -1],
|
||||
[100, 55, 9, 10, -6, -1],
|
||||
[110, 55, 9, 10, -6, -1],
|
||||
[120, 55, 9, 10, -6, -1]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const SHOCKFIELDGROUND_META: MissileMeta = {
|
||||
name: "shockfieldground",
|
||||
celFile: "Expansion/ShockField",
|
||||
width: 1024,
|
||||
height: 647,
|
||||
directions: 8,
|
||||
framesPerDirection: 10,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 102, 60, -56, -32],
|
||||
[103, 0, 102, 60, -56, -32],
|
||||
[206, 0, 102, 60, -56, -32],
|
||||
[309, 0, 102, 60, -56, -32],
|
||||
[412, 0, 102, 60, -56, -32],
|
||||
[515, 0, 102, 60, -56, -32],
|
||||
[618, 0, 102, 60, -56, -32],
|
||||
[721, 0, 102, 60, -56, -32],
|
||||
[824, 0, 102, 60, -56, -32],
|
||||
[0, 61, 102, 60, -56, -32]
|
||||
],
|
||||
[
|
||||
[103, 61, 95, 60, -40, -31],
|
||||
[199, 61, 95, 60, -40, -31],
|
||||
[295, 61, 95, 60, -40, -31],
|
||||
[391, 61, 95, 60, -40, -31],
|
||||
[487, 61, 95, 60, -40, -31],
|
||||
[583, 61, 95, 60, -40, -31],
|
||||
[679, 61, 95, 60, -40, -31],
|
||||
[775, 61, 95, 60, -40, -31],
|
||||
[871, 61, 95, 60, -40, -31],
|
||||
[0, 122, 95, 60, -40, -31]
|
||||
],
|
||||
[
|
||||
[96, 122, 100, 77, -43, -31],
|
||||
[197, 122, 100, 77, -43, -31],
|
||||
[298, 122, 100, 77, -43, -31],
|
||||
[399, 122, 100, 77, -43, -31],
|
||||
[500, 122, 100, 77, -43, -31],
|
||||
[601, 122, 100, 77, -43, -31],
|
||||
[702, 122, 100, 77, -43, -31],
|
||||
[803, 122, 100, 77, -43, -31],
|
||||
[904, 122, 100, 77, -43, -31],
|
||||
[0, 200, 100, 77, -43, -31]
|
||||
],
|
||||
[
|
||||
[101, 200, 96, 78, -49, -33],
|
||||
[198, 200, 96, 78, -49, -33],
|
||||
[295, 200, 96, 78, -49, -33],
|
||||
[392, 200, 96, 78, -49, -33],
|
||||
[489, 200, 96, 78, -49, -33],
|
||||
[586, 200, 96, 78, -49, -33],
|
||||
[683, 200, 96, 78, -49, -33],
|
||||
[780, 200, 96, 78, -49, -33],
|
||||
[877, 200, 96, 78, -49, -33],
|
||||
[0, 279, 96, 78, -49, -33]
|
||||
],
|
||||
[
|
||||
[97, 279, 95, 77, -41, -34],
|
||||
[193, 279, 95, 77, -41, -34],
|
||||
[289, 279, 95, 77, -41, -34],
|
||||
[385, 279, 95, 77, -41, -34],
|
||||
[481, 279, 95, 77, -41, -34],
|
||||
[577, 279, 95, 77, -41, -34],
|
||||
[673, 279, 95, 77, -41, -34],
|
||||
[769, 279, 95, 77, -41, -34],
|
||||
[865, 279, 95, 77, -41, -34],
|
||||
[0, 358, 95, 77, -41, -34]
|
||||
],
|
||||
[
|
||||
[96, 358, 105, 56, -50, -27],
|
||||
[202, 358, 105, 56, -50, -27],
|
||||
[308, 358, 105, 56, -50, -27],
|
||||
[414, 358, 105, 56, -50, -27],
|
||||
[520, 358, 105, 56, -50, -27],
|
||||
[626, 358, 105, 56, -50, -27],
|
||||
[732, 358, 105, 56, -50, -27],
|
||||
[838, 358, 105, 56, -50, -27],
|
||||
[0, 436, 105, 56, -50, -27],
|
||||
[106, 436, 105, 56, -50, -27]
|
||||
],
|
||||
[
|
||||
[212, 436, 93, 70, -53, -33],
|
||||
[306, 436, 93, 70, -53, -33],
|
||||
[400, 436, 93, 70, -53, -33],
|
||||
[494, 436, 93, 70, -53, -33],
|
||||
[588, 436, 93, 70, -53, -33],
|
||||
[682, 436, 93, 70, -53, -33],
|
||||
[776, 436, 93, 70, -53, -33],
|
||||
[870, 436, 93, 70, -53, -33],
|
||||
[0, 507, 93, 70, -53, -33],
|
||||
[94, 507, 93, 70, -53, -33]
|
||||
],
|
||||
[
|
||||
[188, 507, 101, 69, -55, -32],
|
||||
[290, 507, 101, 69, -55, -32],
|
||||
[392, 507, 101, 69, -55, -32],
|
||||
[494, 507, 101, 69, -55, -32],
|
||||
[596, 507, 101, 69, -55, -32],
|
||||
[698, 507, 101, 69, -55, -32],
|
||||
[800, 507, 101, 69, -55, -32],
|
||||
[902, 507, 101, 69, -55, -32],
|
||||
[0, 578, 101, 69, -55, -32],
|
||||
[102, 578, 101, 69, -55, -32]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const BLADEFURY_META: MissileMeta = {
|
||||
name: "bladefury",
|
||||
celFile: "Expansion/bladefury",
|
||||
width: 512,
|
||||
height: 6,
|
||||
directions: 1,
|
||||
framesPerDirection: 7,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 13, 6, -6, -34],
|
||||
[14, 0, 13, 6, -6, -34],
|
||||
[28, 0, 13, 6, -6, -34],
|
||||
[42, 0, 13, 6, -6, -34],
|
||||
[56, 0, 13, 6, -6, -34],
|
||||
[70, 0, 13, 6, -6, -34],
|
||||
[84, 0, 13, 6, -6, -34]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const BLADEFURYB_META: MissileMeta = {
|
||||
name: "bladefuryb",
|
||||
celFile: "Expansion/bladefury_B",
|
||||
width: 512,
|
||||
height: 9,
|
||||
directions: 1,
|
||||
framesPerDirection: 7,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 20, 9, -10, -35],
|
||||
[21, 0, 20, 9, -10, -35],
|
||||
[42, 0, 20, 9, -10, -35],
|
||||
[63, 0, 20, 9, -10, -35],
|
||||
[84, 0, 20, 9, -10, -35],
|
||||
[105, 0, 20, 9, -10, -35],
|
||||
[126, 0, 20, 9, -10, -35]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const BLADEFURYC_META: MissileMeta = {
|
||||
name: "bladefuryc",
|
||||
celFile: "Expansion/bladefury_C",
|
||||
width: 512,
|
||||
height: 11,
|
||||
directions: 1,
|
||||
framesPerDirection: 7,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 24, 11, -12, -36],
|
||||
[25, 0, 24, 11, -12, -36],
|
||||
[50, 0, 24, 11, -12, -36],
|
||||
[75, 0, 24, 11, -12, -36],
|
||||
[100, 0, 24, 11, -12, -36],
|
||||
[125, 0, 24, 11, -12, -36],
|
||||
[150, 0, 24, 11, -12, -36]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const DEATHSENTRYEXPLODE_META: MissileMeta = {
|
||||
name: "deathsentryexplode",
|
||||
celFile: "Expansion/dethsentry02",
|
||||
width: 512,
|
||||
height: 275,
|
||||
directions: 1,
|
||||
framesPerDirection: 12,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 115, 91, -66, -66],
|
||||
[116, 0, 115, 91, -66, -66],
|
||||
[232, 0, 115, 91, -66, -66],
|
||||
[348, 0, 115, 91, -66, -66],
|
||||
[0, 92, 115, 91, -66, -66],
|
||||
[116, 92, 115, 91, -66, -66],
|
||||
[232, 92, 115, 91, -66, -66],
|
||||
[348, 92, 115, 91, -66, -66],
|
||||
[0, 184, 115, 91, -66, -66],
|
||||
[116, 184, 115, 91, -66, -66],
|
||||
[232, 184, 115, 91, -66, -66],
|
||||
[348, 184, 115, 91, -66, -66]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const BLADESHIELDMISSILE_META: MissileMeta = {
|
||||
name: "bladeshieldmissile",
|
||||
celFile: "Expansion/blade_shield_missile",
|
||||
width: 512,
|
||||
height: 11,
|
||||
directions: 1,
|
||||
framesPerDirection: 5,
|
||||
animSpeed: 16,
|
||||
groups: [
|
||||
[
|
||||
[0, 0, 20, 11, -10, -7],
|
||||
[21, 0, 20, 11, -10, -7],
|
||||
[42, 0, 20, 11, -10, -7],
|
||||
[63, 0, 20, 11, -10, -7],
|
||||
[84, 0, 20, 11, -10, -7]
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
export const BLIZZARD4_META: MissileMeta = {
|
||||
...BLIZZARD3_META,
|
||||
name: 'blizzard4',
|
||||
|
|
@ -16939,6 +17570,16 @@ export const MISSILE_METAS: Readonly<Record<string, MissileMeta>> = Object.freez
|
|||
'hurricaneswoosh': HURRICANESWOOSH_META,
|
||||
'hurricanerock': HURRICANEROCK_META,
|
||||
'hurricanetree': HURRICANETREE_META,
|
||||
'firetrauma': FIRETRAUMA_META,
|
||||
'firetraumaground': FIRETRAUMAGROUND_META,
|
||||
'firetraumaexplode': FIRETRAUMAEXPLODE_META,
|
||||
'shockfieldair': SHOCKFIELDAIR_META,
|
||||
'shockfieldground': SHOCKFIELDGROUND_META,
|
||||
'bladefury': BLADEFURY_META,
|
||||
'bladefuryb': BLADEFURYB_META,
|
||||
'bladefuryc': BLADEFURYC_META,
|
||||
'deathsentryexplode': DEATHSENTRYEXPLODE_META,
|
||||
'bladeshieldmissile': BLADESHIELDMISSILE_META,
|
||||
'revivesmall': REVIVESMALL_META,
|
||||
'revivemedium': REVIVEMEDIUM_META,
|
||||
'revivelarge': REVIVELARGE_META,
|
||||
|
|
|
|||
|
|
@ -22058,6 +22058,320 @@ export const DIREWOLFCHARGED_OVERLAY_META: OverlayMeta = {
|
|||
]
|
||||
}
|
||||
|
||||
export const BLADE_SHIELD_META: OverlayMeta = {
|
||||
"name": "blade_shield",
|
||||
"celFile": "Expansion/blade_shield",
|
||||
"width": 512,
|
||||
"height": 200,
|
||||
"directions": 1,
|
||||
"frames": 20,
|
||||
"framesPerDirection": 20,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -33,
|
||||
"top": -68,
|
||||
"width": 64,
|
||||
"height": 66
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
0,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
390,
|
||||
67,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
0,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
65,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
130,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
195,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
260,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
],
|
||||
[
|
||||
325,
|
||||
134,
|
||||
64,
|
||||
66,
|
||||
-33,
|
||||
-68
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
export const DEATHSENTRY_EXPLODE_META: OverlayMeta = {
|
||||
"name": "deathsentry_explode",
|
||||
"celFile": "expansion/dethsentry01",
|
||||
"width": 512,
|
||||
"height": 547,
|
||||
"directions": 1,
|
||||
"frames": 12,
|
||||
"framesPerDirection": 12,
|
||||
"animRate": 16,
|
||||
"trans": 3,
|
||||
"preDraw": false,
|
||||
"lightRadius": 0,
|
||||
"lightColor": [
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"box": {
|
||||
"left": -73,
|
||||
"top": -93,
|
||||
"width": 130,
|
||||
"height": 136
|
||||
},
|
||||
"groups": [
|
||||
[
|
||||
[
|
||||
0,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
0,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
137,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
274,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
0,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
131,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
],
|
||||
[
|
||||
262,
|
||||
411,
|
||||
130,
|
||||
136,
|
||||
-73,
|
||||
-93
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
// Backward compatibility aliases for primary aura constants
|
||||
export const MIGHT_AURA_META: OverlayMeta = MIGHT_FRONT_AURA_META
|
||||
|
|
@ -22297,6 +22611,8 @@ export const ALL_OVERLAY_NAMES: readonly string[] = [
|
|||
"feralrage4back",
|
||||
"feralrage5back",
|
||||
"direwolfcharged",
|
||||
"blade_shield",
|
||||
"deathsentry_explode",
|
||||
]
|
||||
|
||||
export const OVERLAY_METAS: Readonly<Record<string, OverlayMeta>> = {
|
||||
|
|
@ -22899,4 +23215,12 @@ export const OVERLAY_METAS: Readonly<Record<string, OverlayMeta>> = {
|
|||
"expansion/direwolfcharged": DIREWOLFCHARGED_OVERLAY_META,
|
||||
"dire_wolf_charged": DIREWOLFCHARGED_OVERLAY_META,
|
||||
"direwolf_charged": DIREWOLFCHARGED_OVERLAY_META,
|
||||
"blade_shield": BLADE_SHIELD_META,
|
||||
"Expansion/blade_shield": BLADE_SHIELD_META,
|
||||
"expansion/blade_shield": BLADE_SHIELD_META,
|
||||
"bladeshield": BLADE_SHIELD_META,
|
||||
"deathsentry_explode": DEATHSENTRY_EXPLODE_META,
|
||||
"expansion/dethsentry01": DEATHSENTRY_EXPLODE_META,
|
||||
"deathsentry": DEATHSENTRY_EXPLODE_META,
|
||||
"dethsentry01": DEATHSENTRY_EXPLODE_META,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,560 @@
|
|||
/**
|
||||
* Adversarial Stress & Ground Truth Parity Test Suite — Assassin Traps (10 skills)
|
||||
*
|
||||
* Validates 1.13c Diablo II mechanics:
|
||||
* 1. Global 5-trap cap on PetType `assassintrap` with strict FIFO eviction.
|
||||
* 2. NextDelay mechanics (Wake of Fire 4-frame immunity, Shock Web 25-frame immunity).
|
||||
* 3. Death Sentry dual-mode AI (corpse prioritization, 50/50 phys/fire split, radius progression, shot scaling).
|
||||
* 4. Blade skills weapon damage scaling (3/8 for Sentinel, 3/4 for Fury, 1/4 for Shield).
|
||||
* 5. Shock Web missile cluster dispersion and synergy scaling.
|
||||
* 6. Sentry shot depletion and life cycles.
|
||||
*
|
||||
* Gitea Milestone: M26 (Issue #473)
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
ASSASSIN_TRAP_SKILL_IDS,
|
||||
MAX_ACTIVE_TRAPS,
|
||||
BLADE_SRC_DAM,
|
||||
NEXT_DELAY_FRAMES,
|
||||
TrapManager,
|
||||
NextDelayTracker,
|
||||
computeBladeWeaponDamage,
|
||||
evaluateFireBlastStats,
|
||||
evaluateShockWebStats,
|
||||
evaluateBladeSentinelStats,
|
||||
evaluateChargedBoltSentryStats,
|
||||
evaluateWakeOfFireStats,
|
||||
evaluateBladeFuryStats,
|
||||
evaluateLightningSentryStats,
|
||||
evaluateWakeOfInfernoStats,
|
||||
evaluateDeathSentryStats,
|
||||
evaluateBladeShieldStats,
|
||||
simulateDeathSentryAI,
|
||||
simulateWakeOfFireWaveHit,
|
||||
computeShockWebClusterPoints,
|
||||
type SimulationCorpse,
|
||||
type SimulationEnemy,
|
||||
} from '../../../src/game/skills/assassin-traps.ts'
|
||||
|
||||
describe('Assassin Traps Ground Truth & Adversarial Suite (Milestone M26)', () => {
|
||||
describe('1. Global Trap Cap & FIFO Eviction (PetType: assassintrap, Limit: 5)', () => {
|
||||
it('allows up to 5 traps without eviction', () => {
|
||||
const manager = new TrapManager()
|
||||
const ownerId = 'assassin_1'
|
||||
|
||||
// Summon 5 different traps
|
||||
const trap1 = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.CHARGED_BOLT_SENTRY,
|
||||
summonMon: 'chargeboltsentry',
|
||||
x: 100,
|
||||
y: 100,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 10,
|
||||
})
|
||||
expect(trap1.evictedTrap).toBeUndefined()
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(1)
|
||||
|
||||
manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.WAKE_OF_FIRE,
|
||||
summonMon: 'wakeofdestruction',
|
||||
x: 120,
|
||||
y: 100,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 20,
|
||||
})
|
||||
manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.LIGHTNING_SENTRY,
|
||||
summonMon: 'lightningsentry',
|
||||
x: 140,
|
||||
y: 100,
|
||||
slvl: 20,
|
||||
shots: 10,
|
||||
currentFrame: 30,
|
||||
})
|
||||
manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.WAKE_OF_INFERNO,
|
||||
summonMon: 'infernosentry',
|
||||
x: 160,
|
||||
y: 100,
|
||||
slvl: 20,
|
||||
shots: 10,
|
||||
currentFrame: 40,
|
||||
})
|
||||
const trap5 = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.DEATH_SENTRY,
|
||||
summonMon: 'deathsentry',
|
||||
x: 180,
|
||||
y: 100,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 50,
|
||||
})
|
||||
|
||||
expect(trap5.evictedTrap).toBeUndefined()
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(5)
|
||||
})
|
||||
|
||||
it('strictly evicts the oldest trap (FIFO) when a 6th trap is summoned', () => {
|
||||
const manager = new TrapManager()
|
||||
const ownerId = 'assassin_1'
|
||||
|
||||
// Register 5 traps
|
||||
const t1 = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.LIGHTNING_SENTRY,
|
||||
summonMon: 'lightningsentry',
|
||||
x: 10,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 10,
|
||||
currentFrame: 10,
|
||||
}).trap
|
||||
|
||||
const t2 = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.LIGHTNING_SENTRY,
|
||||
summonMon: 'lightningsentry',
|
||||
x: 20,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 10,
|
||||
currentFrame: 20,
|
||||
}).trap
|
||||
|
||||
manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.LIGHTNING_SENTRY,
|
||||
summonMon: 'lightningsentry',
|
||||
x: 30,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 10,
|
||||
currentFrame: 30,
|
||||
})
|
||||
|
||||
manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.DEATH_SENTRY,
|
||||
summonMon: 'deathsentry',
|
||||
x: 40,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 40,
|
||||
})
|
||||
|
||||
manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.DEATH_SENTRY,
|
||||
summonMon: 'deathsentry',
|
||||
x: 50,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 50,
|
||||
})
|
||||
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(5)
|
||||
|
||||
// Summon 6th trap -> t1 (the oldest) MUST be evicted!
|
||||
const res6 = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.WAKE_OF_FIRE,
|
||||
summonMon: 'wakeofdestruction',
|
||||
x: 60,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 60,
|
||||
})
|
||||
|
||||
expect(res6.evictedTrap).toBeDefined()
|
||||
expect(res6.evictedTrap?.id).toBe(t1.id)
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(5)
|
||||
|
||||
// Active traps should NOT contain t1
|
||||
const activeIds = manager.getActiveTraps(ownerId).map(t => t.id)
|
||||
expect(activeIds).not.toContain(t1.id)
|
||||
expect(activeIds).toContain(res6.trap.id)
|
||||
|
||||
// Summon 7th trap -> t2 MUST be evicted!
|
||||
const res7 = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.CHARGED_BOLT_SENTRY,
|
||||
summonMon: 'chargeboltsentry',
|
||||
x: 70,
|
||||
y: 10,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 70,
|
||||
})
|
||||
expect(res7.evictedTrap?.id).toBe(t2.id)
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(5)
|
||||
})
|
||||
|
||||
it('isolates trap quotas per player/owner in multiplayer arena', () => {
|
||||
const manager = new TrapManager()
|
||||
const p1 = 'assassin_alice'
|
||||
const p2 = 'assassin_bob'
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
manager.registerTrap({
|
||||
ownerId: p1,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.LIGHTNING_SENTRY,
|
||||
summonMon: 'lightningsentry',
|
||||
x: i * 10,
|
||||
y: 0,
|
||||
slvl: 20,
|
||||
shots: 10,
|
||||
currentFrame: 0,
|
||||
})
|
||||
manager.registerTrap({
|
||||
ownerId: p2,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.DEATH_SENTRY,
|
||||
summonMon: 'deathsentry',
|
||||
x: i * 10,
|
||||
y: 50,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 0,
|
||||
})
|
||||
}
|
||||
|
||||
expect(manager.getActiveTrapCount(p1)).toBe(5)
|
||||
expect(manager.getActiveTrapCount(p2)).toBe(5)
|
||||
|
||||
// Summon 6th trap for p1; only p1's trap is evicted, p2's remain intact
|
||||
const res = manager.registerTrap({
|
||||
ownerId: p1,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.WAKE_OF_FIRE,
|
||||
summonMon: 'wakeofdestruction',
|
||||
x: 999,
|
||||
y: 0,
|
||||
slvl: 20,
|
||||
shots: 5,
|
||||
currentFrame: 100,
|
||||
})
|
||||
|
||||
expect(res.evictedTrap?.ownerId).toBe(p1)
|
||||
expect(manager.getActiveTrapCount(p1)).toBe(5)
|
||||
expect(manager.getActiveTrapCount(p2)).toBe(5)
|
||||
})
|
||||
|
||||
it('automatically destroys trap when shot count reaches zero', () => {
|
||||
const manager = new TrapManager()
|
||||
const ownerId = 'assassin_1'
|
||||
|
||||
const { trap } = manager.registerTrap({
|
||||
ownerId,
|
||||
skillId: ASSASSIN_TRAP_SKILL_IDS.DEATH_SENTRY,
|
||||
summonMon: 'deathsentry',
|
||||
x: 0,
|
||||
y: 0,
|
||||
slvl: 1,
|
||||
shots: 3,
|
||||
currentFrame: 0,
|
||||
})
|
||||
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(1)
|
||||
|
||||
const s1 = manager.consumeShot(trap.id)
|
||||
expect(s1.remaining).toBe(2)
|
||||
expect(s1.destroyed).toBe(false)
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(1)
|
||||
|
||||
const s2 = manager.consumeShot(trap.id)
|
||||
expect(s2.remaining).toBe(1)
|
||||
expect(s2.destroyed).toBe(false)
|
||||
|
||||
const s3 = manager.consumeShot(trap.id)
|
||||
expect(s3.remaining).toBe(0)
|
||||
expect(s3.destroyed).toBe(true)
|
||||
expect(manager.getActiveTrapCount(ownerId)).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('2. NextDelay Mechanics (Wake of Fire & Shock Web)', () => {
|
||||
it('enforces 4-frame damage immunity for Wake of Fire', () => {
|
||||
const tracker = new NextDelayTracker()
|
||||
const enemyId = 'monster_1'
|
||||
|
||||
// At frame 100, monster is eligible
|
||||
expect(tracker.canTakeDamage(enemyId, 'wake_of_fire', 100)).toBe(true)
|
||||
|
||||
// Hit monster at frame 100 with NextDelay 4
|
||||
tracker.recordHit(enemyId, 'wake_of_fire', 100, NEXT_DELAY_FRAMES.WAKE_OF_FIRE)
|
||||
|
||||
// Monster must be immune at frame 100, 101, 102, 103
|
||||
expect(tracker.canTakeDamage(enemyId, 'wake_of_fire', 100)).toBe(false)
|
||||
expect(tracker.canTakeDamage(enemyId, 'wake_of_fire', 101)).toBe(false)
|
||||
expect(tracker.canTakeDamage(enemyId, 'wake_of_fire', 102)).toBe(false)
|
||||
expect(tracker.canTakeDamage(enemyId, 'wake_of_fire', 103)).toBe(false)
|
||||
|
||||
// At frame 104, NextDelay window has expired (100 + 4 = 104)
|
||||
expect(tracker.canTakeDamage(enemyId, 'wake_of_fire', 104)).toBe(true)
|
||||
})
|
||||
|
||||
it('enforces 25-frame damage immunity for Shock Web', () => {
|
||||
const tracker = new NextDelayTracker()
|
||||
const enemyId = 'monster_boss'
|
||||
|
||||
expect(tracker.canTakeDamage(enemyId, 'shock_web', 500)).toBe(true)
|
||||
tracker.recordHit(enemyId, 'shock_web', 500, NEXT_DELAY_FRAMES.SHOCK_WEB)
|
||||
|
||||
// Immune throughout frames 500..524
|
||||
expect(tracker.canTakeDamage(enemyId, 'shock_web', 510)).toBe(false)
|
||||
expect(tracker.canTakeDamage(enemyId, 'shock_web', 524)).toBe(false)
|
||||
|
||||
// Vulnerable again at frame 525 (500 + 25 = 525)
|
||||
expect(tracker.canTakeDamage(enemyId, 'shock_web', 525)).toBe(true)
|
||||
})
|
||||
|
||||
it('simulates Wake of Fire wave hit against grouped enemies with NextDelay', () => {
|
||||
const tracker = new NextDelayTracker()
|
||||
const enemies: SimulationEnemy[] = [
|
||||
{ id: 'm1', x: 100, y: 100, currentHp: 500, maxHp: 500 },
|
||||
{ id: 'm2', x: 150, y: 100, currentHp: 500, maxHp: 500 },
|
||||
]
|
||||
|
||||
const trap: any = {
|
||||
id: 'wof_1',
|
||||
x: 0,
|
||||
y: 100,
|
||||
slvl: 20,
|
||||
}
|
||||
|
||||
// First wave at frame 0
|
||||
const hit0 = simulateWakeOfFireWaveHit(trap, enemies, 0, tracker)
|
||||
expect(hit0.enemiesHit).toContain('m1')
|
||||
expect(hit0.enemiesHit).toContain('m2')
|
||||
expect(enemies[0]!.currentHp).toBeLessThan(500)
|
||||
|
||||
// Rapid overlap wave at frame 2 (within 4-frame NextDelay window) -> deals 0 damage!
|
||||
const hpBefore = enemies[0]!.currentHp
|
||||
const hit2 = simulateWakeOfFireWaveHit(trap, enemies, 2, tracker)
|
||||
expect(hit2.enemiesHit).toHaveLength(0)
|
||||
expect(hit2.totalDamageDealt).toBe(0)
|
||||
expect(enemies[0]!.currentHp).toBe(hpBefore)
|
||||
|
||||
// Next wave at frame 5 (> 4 frames) -> hits again!
|
||||
const hit5 = simulateWakeOfFireWaveHit(trap, enemies, 5, tracker)
|
||||
expect(hit5.enemiesHit).toContain('m1')
|
||||
expect(enemies[0]!.currentHp).toBeLessThan(hpBefore)
|
||||
})
|
||||
})
|
||||
|
||||
describe('3. Death Sentry Dual-Mode Corpse Explosion AI', () => {
|
||||
it('prioritizes Corpse Explosion over Lightning when a corpse is in radius', () => {
|
||||
const trap: any = { id: 'ds_1', x: 100, y: 100, slvl: 20 }
|
||||
const corpses: SimulationCorpse[] = [
|
||||
{ id: 'corpse_1', x: 120, y: 100, baseMaxHp: 1000, consumed: false },
|
||||
]
|
||||
const enemies: SimulationEnemy[] = [
|
||||
{ id: 'live_mon', x: 130, y: 100, currentHp: 800, maxHp: 800 },
|
||||
]
|
||||
|
||||
const decision = simulateDeathSentryAI(trap, corpses, enemies)
|
||||
expect(decision.action).toBe('corpse_explosion')
|
||||
expect(decision.targetId).toBe('corpse_1')
|
||||
expect(corpses[0]!.consumed).toBe(true)
|
||||
// 40-80% of 1000 HP -> 600 damage avg (300 phys, 300 fire)
|
||||
expect(decision.damageDealt?.phys).toBe(300)
|
||||
expect(decision.damageDealt?.fire).toBe(300)
|
||||
expect(decision.damageDealt?.ltng).toBe(0)
|
||||
expect(enemies[0]!.currentHp).toBe(200)
|
||||
})
|
||||
|
||||
it('falls back to piercing lightning bolt when no valid corpse exists in radius', () => {
|
||||
const trap: any = { id: 'ds_1', x: 0, y: 0, slvl: 20 }
|
||||
const corpses: SimulationCorpse[] = [
|
||||
// Corpse is far away outside radius (> 29 subtiles * 16px = 464px)
|
||||
{ id: 'corpse_far', x: 900, y: 900, baseMaxHp: 1000, consumed: false },
|
||||
]
|
||||
const enemies: SimulationEnemy[] = [
|
||||
{ id: 'near_mon', x: 100, y: 0, currentHp: 500, maxHp: 500 },
|
||||
]
|
||||
|
||||
const decision = simulateDeathSentryAI(trap, corpses, enemies)
|
||||
expect(decision.action).toBe('lightning')
|
||||
expect(decision.targetId).toBe('near_mon')
|
||||
expect(decision.damageDealt?.ltng).toBeGreaterThan(0)
|
||||
expect(decision.damageDealt?.phys).toBe(0)
|
||||
expect(decision.damageDealt?.fire).toBe(0)
|
||||
expect(corpses[0]!.consumed).toBe(false)
|
||||
})
|
||||
|
||||
it('evaluates Death Sentry shot count scaling with Fire Blast synergy', () => {
|
||||
// Base shots: 5. +1 shot per 3 hard points of Fire Blast
|
||||
const s0 = evaluateDeathSentryStats(20, { fireBlast: 0 })
|
||||
expect(s0.shots).toBe(5)
|
||||
|
||||
const s2 = evaluateDeathSentryStats(20, { fireBlast: 2 })
|
||||
expect(s2.shots).toBe(5)
|
||||
|
||||
const s3 = evaluateDeathSentryStats(20, { fireBlast: 3 })
|
||||
expect(s3.shots).toBe(6)
|
||||
|
||||
const s9 = evaluateDeathSentryStats(20, { fireBlast: 9 })
|
||||
expect(s9.shots).toBe(8)
|
||||
|
||||
const s20 = evaluateDeathSentryStats(20, { fireBlast: 20 })
|
||||
expect(s20.shots).toBe(11) // 5 + floor(20 / 3) = 5 + 6 = 11 shots
|
||||
})
|
||||
|
||||
it('verifies Corpse Explosion radius progression: 10 + (slvl - 1) subtiles', () => {
|
||||
const s1 = evaluateDeathSentryStats(1)
|
||||
expect(s1.corpseExplosionRadiusSubtiles).toBe(10) // 10 subtiles = 3.3 yards
|
||||
|
||||
const s10 = evaluateDeathSentryStats(10)
|
||||
expect(s10.corpseExplosionRadiusSubtiles).toBe(19) // 19 subtiles = 6.3 yards
|
||||
|
||||
const s20 = evaluateDeathSentryStats(20)
|
||||
expect(s20.corpseExplosionRadiusSubtiles).toBe(29) // 29 subtiles = 9.6 yards
|
||||
})
|
||||
})
|
||||
|
||||
describe('4. Blade Skills Weapon Damage Scaling (srcDam 128ths)', () => {
|
||||
it('scales Blade Sentinel weapon damage to exact 3/8 (37.5%)', () => {
|
||||
expect(BLADE_SRC_DAM.BLADE_SENTINEL).toBe(48)
|
||||
// 48 / 128 = 3/8
|
||||
|
||||
// Weapon damage 80 -> 80 * 3/8 = 30
|
||||
expect(computeBladeWeaponDamage(80, ASSASSIN_TRAP_SKILL_IDS.BLADE_SENTINEL)).toBe(30)
|
||||
// Weapon damage 240 -> 240 * 3/8 = 90
|
||||
expect(computeBladeWeaponDamage(240, ASSASSIN_TRAP_SKILL_IDS.BLADE_SENTINEL)).toBe(90)
|
||||
// Weapon damage 100 -> floor(100 * 48 / 128) = floor(37.5) = 37
|
||||
expect(computeBladeWeaponDamage(100, ASSASSIN_TRAP_SKILL_IDS.BLADE_SENTINEL)).toBe(37)
|
||||
})
|
||||
|
||||
it('scales Blade Fury weapon damage to exact 3/4 (75.0%)', () => {
|
||||
expect(BLADE_SRC_DAM.BLADE_FURY).toBe(96)
|
||||
// 96 / 128 = 3/4
|
||||
|
||||
// Weapon damage 80 -> 80 * 3/4 = 60
|
||||
expect(computeBladeWeaponDamage(80, ASSASSIN_TRAP_SKILL_IDS.BLADE_FURY)).toBe(60)
|
||||
// Weapon damage 240 -> 240 * 3/4 = 180
|
||||
expect(computeBladeWeaponDamage(240, ASSASSIN_TRAP_SKILL_IDS.BLADE_FURY)).toBe(180)
|
||||
// Weapon damage 100 -> floor(100 * 96 / 128) = 75
|
||||
expect(computeBladeWeaponDamage(100, ASSASSIN_TRAP_SKILL_IDS.BLADE_FURY)).toBe(75)
|
||||
})
|
||||
|
||||
it('scales Blade Shield weapon damage to exact 1/4 (25.0%)', () => {
|
||||
expect(BLADE_SRC_DAM.BLADE_SHIELD).toBe(32)
|
||||
// 32 / 128 = 1/4
|
||||
|
||||
// Weapon damage 80 -> 80 * 1/4 = 20
|
||||
expect(computeBladeWeaponDamage(80, ASSASSIN_TRAP_SKILL_IDS.BLADE_SHIELD)).toBe(20)
|
||||
// Weapon damage 240 -> 240 * 1/4 = 60
|
||||
expect(computeBladeWeaponDamage(240, ASSASSIN_TRAP_SKILL_IDS.BLADE_SHIELD)).toBe(60)
|
||||
// Weapon damage 100 -> floor(100 * 32 / 128) = 25
|
||||
expect(computeBladeWeaponDamage(100, ASSASSIN_TRAP_SKILL_IDS.BLADE_SHIELD)).toBe(25)
|
||||
})
|
||||
|
||||
it('combines flat physical damage with scaled weapon damage on Blade skills', () => {
|
||||
const sentStats = evaluateBladeSentinelStats(20, 200)
|
||||
// flat min = 79, max = 83. scaled weapon (200 * 3/8) = 75
|
||||
expect(sentStats.scaledWeaponPhys).toBe(75)
|
||||
expect(sentStats.totalMinPhys).toBe(79 + 75)
|
||||
expect(sentStats.totalMaxPhys).toBe(83 + 75)
|
||||
|
||||
const furyStats = evaluateBladeFuryStats(20, 200)
|
||||
// flat min = 101, max = 103. scaled weapon (200 * 3/4) = 150
|
||||
expect(furyStats.scaledWeaponPhys).toBe(150)
|
||||
expect(furyStats.totalMinPhys).toBe(101 + 150)
|
||||
expect(furyStats.totalMaxPhys).toBe(103 + 150)
|
||||
|
||||
const shieldStats = evaluateBladeShieldStats(20, 200)
|
||||
// flat min = 112, max = 141. scaled weapon (200 * 1/4) = 50
|
||||
expect(shieldStats.scaledWeaponPhys).toBe(50)
|
||||
expect(shieldStats.totalMinPhys).toBe(112 + 50)
|
||||
expect(shieldStats.totalMaxPhys).toBe(141 + 50)
|
||||
})
|
||||
})
|
||||
|
||||
describe('5. Shock Web Cluster & Synergy Mechanics', () => {
|
||||
it('calculates exact missile count: 6 + floor(slvl / 4) + floor(FireBlast.blvl / 3)', () => {
|
||||
const sw1 = evaluateShockWebStats(1, { fireBlast: 0 })
|
||||
expect(sw1.missileCount).toBe(6)
|
||||
|
||||
const sw4 = evaluateShockWebStats(4, { fireBlast: 0 })
|
||||
expect(sw4.missileCount).toBe(7)
|
||||
|
||||
const sw20 = evaluateShockWebStats(20, { fireBlast: 0 })
|
||||
expect(sw20.missileCount).toBe(11) // 6 + 5 = 11
|
||||
|
||||
const sw20_syn = evaluateShockWebStats(20, { fireBlast: 20 })
|
||||
expect(sw20_syn.missileCount).toBe(17) // 6 + 5 + 6 = 17
|
||||
})
|
||||
|
||||
it('generates distributed cluster points around impact origin', () => {
|
||||
const points = computeShockWebClusterPoints(200, 200, 20, 20, 64)
|
||||
expect(points).toHaveLength(17)
|
||||
// Ensure all points lie within max cluster radius (64 + epsilon)
|
||||
for (const pt of points) {
|
||||
const dist = Math.hypot(pt.x - 200, pt.y - 200)
|
||||
expect(dist).toBeLessThanOrEqual(65)
|
||||
}
|
||||
})
|
||||
|
||||
it('applies +11% synergy per hard point of CBS, LS, DS', () => {
|
||||
const base = evaluateShockWebStats(20, { chargedBoltSentry: 0, lightningSentry: 0, deathSentry: 0 })
|
||||
const syn = evaluateShockWebStats(20, { chargedBoltSentry: 20, lightningSentry: 20, deathSentry: 20 })
|
||||
// 60 hard points * 11% = 660% bonus damage
|
||||
expect(syn.synergyPct).toBe(660)
|
||||
expect(syn.maxDamage).toBe(Math.floor(base.maxDamage * (1 + 6.6)))
|
||||
})
|
||||
})
|
||||
|
||||
describe('6. Sentry Lifecycles & Synergies', () => {
|
||||
it('evaluates Charged Bolt Sentry shots and bolt count scaling', () => {
|
||||
const cbs = evaluateChargedBoltSentryStats(20, {
|
||||
lightningSentry: 20, // +5 shots
|
||||
shockWeb: 20, // +6 bolts
|
||||
})
|
||||
// Base shots: 5 + floor(20/4) = 10 shots
|
||||
expect(cbs.shots).toBe(10)
|
||||
// Base bolts: 5 + floor(20/3) = 11 bolts per shot
|
||||
expect(cbs.boltsPerShot).toBe(11)
|
||||
})
|
||||
|
||||
it('evaluates Lightning Sentry piercing damage and 12% synergy scaling', () => {
|
||||
const lsBase = evaluateLightningSentryStats(20, { shockWeb: 0, chargedBoltSentry: 0, deathSentry: 0 })
|
||||
expect(lsBase.shots).toBe(10)
|
||||
expect(lsBase.minDamage).toBe(1)
|
||||
expect(lsBase.maxDamage).toBe(314) // 5-band level 20 max
|
||||
|
||||
const lsMaxSyn = evaluateLightningSentryStats(20, { shockWeb: 20, chargedBoltSentry: 20, deathSentry: 20 })
|
||||
// 60 * 12% = 720% bonus
|
||||
expect(lsMaxSyn.synergyPct).toBe(720)
|
||||
expect(lsMaxSyn.maxDamage).toBe(Math.floor(314 * (1 + 7.2)))
|
||||
})
|
||||
|
||||
it('evaluates Wake of Inferno 10 attacks and 7% synergies', () => {
|
||||
const woi = evaluateWakeOfInfernoStats(20, { fireBlast: 20, wakeOfFire: 20, deathSentry: 20 })
|
||||
expect(woi.attacks).toBe(10)
|
||||
// 60 * 7% = 420% synergy
|
||||
expect(woi.synergyPct).toBe(420)
|
||||
})
|
||||
|
||||
it('evaluates Blade Shield 20s-115s duration and 25-frame tick rate', () => {
|
||||
const bs1 = evaluateBladeShieldStats(1)
|
||||
expect(bs1.durationFrames).toBe(500) // 20s
|
||||
expect(bs1.tickRateFrames).toBe(25) // 1.0s
|
||||
expect(bs1.radiusSubtiles).toBe(6)
|
||||
|
||||
const bs20 = evaluateBladeShieldStats(20)
|
||||
expect(bs20.durationFrames).toBe(2875) // 500 + 125 * 19 = 2875 frames = 115s
|
||||
expect(bs20.tickRateFrames).toBe(25)
|
||||
expect(bs20.manaCost).toBe(65) // 27 + 2 * 19 = 65
|
||||
})
|
||||
})
|
||||
})
|
||||