Closed wexxlee closed 2 months ago
Delays are untested, but the IDs should be correct.
{
id: 'R4S Tail Thrust Fire West East',
type: 'StartsUsing',
netRegex: { id: '9602', capture: false },
delaySeconds: (data) => {
++data.tailCount;
if (data.tailCount === 1)
return 0;
if (data.tailCount === 2)
return 58;
return 54;
},
infoText: (_data, _matches, output) => {
return output.eastWest!();
},
outputStrings: {
eastWest: {
en: 'Fire AoE, East => West',
},
},
},
{
id: 'R4S Tail Thrust Water West East',
type: 'StartsUsing',
netRegex: { id: '9603', capture: false },
delaySeconds: (data) => {
++data.tailCount;
if (data.tailCount === 1)
return 0;
if (data.tailCount === 2)
return 58;
return 54;
},
infoText: (_data, _matches, output) => {
return output.westEast!();
},
outputStrings: {
westEast: {
en: 'Water KB, West => East',
},
},
},
{
id: 'R4S Tail Thrust Fire East West',
type: 'StartsUsing',
netRegex: { id: '9604', capture: false },
delaySeconds: (data) => {
++data.tailCount;
if (data.tailCount === 1)
return 0;
if (data.tailCount === 2)
return 58;
return 54;
},
infoText: (_data, _matches, output) => {
return output.westEast!();
},
outputStrings: {
westEast: {
en: 'Fire AoE, West => East',
},
},
},
{
id: 'R4S Tail Thrust Water East West',
type: 'StartsUsing',
netRegex: { id: '9605', capture: false },
delaySeconds: (data) => {
++data.tailCount;
if (data.tailCount === 1)
return 0;
if (data.tailCount === 2)
return 58;
return 54;
},
infoText: (_data, _matches, output) => {
return output.eastWest!();
},
outputStrings: {
eastWest: {
en: 'Water KB, East => West',
},
},
},
Delays are untested, but the IDs should be correct.
I was able to grab the effect/order from the preceding Aetherial Conversion
cast, and then just use the Tail Thrust cast to call the previously cached mechanic, e.g.:
{
id: 'R4S Aetherial Conversion',
type: 'StartsUsing',
netRegex: { id: Object.keys(aetherialAbility), source: 'Wicked Thunder' },
durationSeconds: 7,
infoText: (data, matches, output) => {
if (!isAetherialId(matches.id))
throw new UnreachableCode();
data.aetherialEffect = aetherialAbility[matches.id];
return output.stored!({ effect: output[data.aetherialEffect]!() });
},
outputStrings: {
...tailThrustOutputStrings,
stored: {
en: 'Stored: ${effect}',
},
},
},
{
id: 'R4S Tail Thrust',
type: 'StartsUsing',
// 9606-9609 correspond to the id casts for the triggering Aetherial Conversion,
// but we don't care which is which at this point because we've already stored the effect
netRegex: { id: ['9606', '9607', '9608', '9609'], source: 'Wicked Thunder', capture: false },
alertText: (data, _matches, output) => output[data.aetherialEffect ?? 'unknown']!(),
outputStrings: tailThrustOutputStrings,
},
Won't your version double the callout for the first instance? Also the cast time on the Tail Thrust
is only 4.7 seconds which might not be enough time to react and move to the appropriate spot.
Won't your version double the callout for the first instance?
It does a 'Stored' call, and the actual mech call is ~10s later. If that's too noisy, I can just add a suppress to the first one.
Also the cast time on the
Tail Thrust
is only 4.7 seconds which might not be enough time to react and move to the appropriate spot.
I went back to look at vod, and it seems like it should be fine? I found multiple instances of running all the way from the far corner (without sprint) to the front of the room on the other side or the far KB spot as the cast bar started and having no problem. The cast is ~4.7 on the log line, but the ability line (and position snapshot) is actually more like ~5.1s.
If you (or someone else) is in there today and can test the timing, would be great to validate that though. Otherwise, I think it's probably good enough for now and can be adjusted later if needed.
Raining Swords triggers. Cleaned up from the original version I DM'd you, and actually working properly instead of being inverted. Verified against VODs with three different patterns.
diff --git a/ui/raidboss/data/07-dt/raid/r4s.ts b/ui/raidboss/data/07-dt/raid/r4s.ts
index e640caa28..c5eb666e0 100644
--- a/ui/raidboss/data/07-dt/raid/r4s.ts
+++ b/ui/raidboss/data/07-dt/raid/r4s.ts
@@ -1,6 +1,7 @@
import Conditions from '../../../../../resources/conditions';
import { UnreachableCode } from '../../../../../resources/not_reached';
import Outputs from '../../../../../resources/outputs';
+import { callOverlayHandler } from '../../../../../resources/overlay_plugin_api';
import { Responses } from '../../../../../resources/responses';
import {
DirectionOutput8,
@@ -178,6 +179,13 @@ export interface Data extends RaidbossData {
sunriseClones: string[];
sunriseTowerSpots?: SunriseCardinalPair;
seenFirstSunrise: boolean;
+ rainingSwords: {
+ mySide?: 'left' | 'right';
+ tetherCount: number;
+ firstActorId: number;
+ left: number[][];
+ right: number[][];
+ };
}
const triggerSet: TriggerSet<Data> = {
@@ -204,6 +212,12 @@ const triggerSet: TriggerSet<Data> = {
sunriseCannons: [],
sunriseClones: [],
seenFirstSunrise: false,
+ rainingSwords: {
+ tetherCount: 0,
+ firstActorId: 0,
+ left: [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]],
+ right: [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]],
+ },
};
},
timelineTriggers: [
@@ -1370,6 +1384,109 @@ const triggerSet: TriggerSet<Data> = {
},
},
// Sword Quiver - 4th line based on original cast id: 95F9 - front, FA - mid, FB - back
+
+ // Raining Swords
+ {
+ id: 'R4S Raining Swords Collector',
+ type: 'StartsUsing',
+ netRegex: { id: '9616', source: 'Wicked Thunder', capture: false },
+ promise: async (data) => {
+ const actors = (await callOverlayHandler({
+ call: 'getCombatants',
+ })).combatants;
+
+ const swordActorIds = actors
+ .filter((actor) => actor.BNpcID === 17327)
+ .sort((left, right) => left.ID! - right.ID!)
+ .map((actor) => actor.ID!);
+
+ if (swordActorIds.length !== 8) {
+ console.error(
+ `R4S Raining Swords Collector: Missing swords, count ${swordActorIds.length}`,
+ );
+ }
+
+ data.rainingSwords.firstActorId = swordActorIds[0] ?? 0;
+ },
+ },
+ {
+ id: 'R4S Raining Swords My Side Detector',
+ type: 'Ability',
+ // No source for this as the names aren't always correct for some reason
+ netRegex: { id: '9617', capture: true },
+ condition: Conditions.targetIsYou(),
+ run: (data, matches) =>
+ data.rainingSwords.mySide = parseFloat(matches.x) < 100 ? 'left' : 'right',
+ },
+ {
+ id: 'R4S Raining Swords Tethers',
+ type: 'Tether',
+ netRegex: { id: ['0117', '0118'], capture: true },
+ infoText: (data, matches, output) => {
+ // 24 tethers total, in sets of 3, 8 sets total. Sets 1 and 2 correspond to first safe spots, etc.
+ const swordId = matches.sourceId;
+ let swordIndex = parseInt(swordId, 16) - data.rainingSwords.firstActorId;
+ const swordSet = swordIndex > 3 ? data.rainingSwords.right : data.rainingSwords.left;
+ // Swords are actually ordered south to north, invert them so it makes more sense
+ swordIndex = 3 - (swordIndex % 4);
+ const tetherSet = Math.floor(data.rainingSwords.tetherCount / 6);
+ data.rainingSwords.tetherCount++;
+ swordSet[tetherSet] = swordSet[tetherSet]?.filter((spot) => spot !== swordIndex) ?? [];
+
+ // Early warning of first safe spot
+ if (data.rainingSwords.tetherCount === 6) {
+ const leftSafe = data.rainingSwords.left[0]?.[0] ?? 0;
+ const rightSafe = data.rainingSwords.right[0]?.[0] ?? 0;
+
+ const mySide = data.rainingSwords.mySide;
+
+ // Here (and below) if side couldn't be detected because player was dead
+ // we could print out both sides instead of an unknown output?
+ // And yes, it's possible to miss a tower in week one gear and survive.
+ if (mySide === undefined)
+ return output.unknown!();
+
+ return output.safe!({
+ side: output[mySide]!(),
+ order: mySide === 'left' ? leftSafe + 1 : rightSafe + 1,
+ });
+ }
+
+ // We don't care about the last 6, once we have our first three "safe" spots
+ if (data.rainingSwords.tetherCount !== 18)
+ return;
+
+ const mySide = data.rainingSwords.mySide;
+
+ if (mySide === undefined)
+ return output.unknown!();
+
+ const calloutSideSet = data.rainingSwords[mySide];
+
+ const safeSpots = [
+ calloutSideSet[0]?.[0] ?? 0,
+ calloutSideSet[1]?.[0] ?? 0,
+ calloutSideSet[2]?.[0] ?? 0,
+ ];
+
+ // Trim our last possible spot based on existing three safe spots
+ safeSpots.push([0, 1, 2, 3].filter((spot) => !safeSpots.includes(spot))[0] ?? 0);
+
+ return output.safe!({
+ side: output[mySide]!(),
+ order: safeSpots.map((i) => i + 1).join(','),
+ });
+ },
+ outputStrings: {
+ left: Outputs.left,
+ right: Outputs.right,
+ safe: {
+ en: '${side}, ${order}',
+ },
+ unknown: Outputs.unknown,
+ },
+ },
+ // Sword Burst - # 95F9 - front, FA - mid, FB - back
],
};
Raining Swords triggers. Cleaned up from the original version I DM'd you, and actually working properly instead of being inverted. Verified against VODs with three different patterns.
@valarnin Thanks! I checked against a handful of pulls and vod, and it LGTM as well. In the commit (6ae4a91
), I did split the output trigger into two separate triggers - one that does the collect + initial call, and one that does the followup with the full list. I think two triggers works a little better to do different output levels and durations, so the full list can be kept up through the entire mechanic. (It probably could all still be done in one trigger, but two was simpler).
I think it might make sense eventually to add follow-up alertText
outputs for each movement, as we do with other multi-step mechanics. But this seems more than good for an initial pass, and I want to go touch grass for a while.
OK, this should be ready for merge now (barring mistakes). I haven't been back in to do in-game testing, but I ran this against 4-5 pulls in the emulator (with sync'd vod), and everything checks out including the timing.
From what I could test in game from p1, it's been good.
From what I could test in game from p1, it's been good.
Sorry, there was some lost communication for this PR in discord DMs because I was so wiped after getting my clear this morning that I didn't think to reflect my thoughts here. p1 and p2 triggers are all good here from my (unfortunately extensive) testing. There were some issues I brought up in DMs but wexxlee fixed them.
I do not understand the sync stuff but just looking at it vs the timeline, the boss starts casting crosstail @ 6:46 and the cast ends at 6:52 while the damage ends at 7:01. None of those numbers translate to the 408.1 (I assumed it would be at 412.1)
Syncs in timelines have an implicit 2 second window to either side. Sometimes, fight timelines in FFXIV can skew slightly (usually caused by the amount of distance a boss has to turn when doing a teleporting ability). This skew never impacts the enrage time of an encounter (that's why all enrages have some time of nothing happening before they start, because that allows the enrage to sync to the exact correct time).
That being said, pulling timestamps from raw network packets (not the Network_*.log
files generated by FFXIV_ACT_Plugin) shows the following data:
FirstAttack:
2024-08-05T08:24:11.0430000-04:00 (000.000s)
Cross Tail Switch starts casting:
2024-08-05T08:30:57.5730000-04:00 (406.530s)
Cross Tail Switch finishes casting:
2024-08-05T08:31:02.5390000-04:00 (411.496s)
Cross Tail Switch hits:
2024-08-05T08:31:03.7030000-04:00 (412.660s)
2024-08-05T08:31:04.7310000-04:00 (413.688s)
2024-08-05T08:31:05.7600000-04:00 (414.717s)
2024-08-05T08:31:06.7890000-04:00 (415.746s)
2024-08-05T08:31:07.8170000-04:00 (416.774s)
2024-08-05T08:31:08.8490000-04:00 (417.806s)
2024-08-05T08:31:09.8780000-04:00 (418.835s)
2024-08-05T08:31:10.9080000-04:00 (419.865s)
2024-08-05T08:31:11.9350000-04:00 (420.892s)
The start timestamp here determines the absolute truth on when the encounter starts (as opposed to, for example, the first player ability hitting the enemy), as it is from the packet which indicates that the enemy has entered combat.
I guess to summarize:
Syncs in timelines have an implicit 2 second window to either side. Sometimes, fight timelines in FFXIV can skew slightly (usually caused by the amount of distance a boss has to turn when doing a teleporting ability). This skew never impacts the enrage time of an encounter (that's why all enrages have some time of nothing happening before they start, because that allows the enrage to sync to the exact correct time).
That being said, pulling timestamps from raw network packets (not the
Network_*.log
files generated by FFXIV_ACT_Plugin) shows the following data:FirstAttack: 2024-08-05T08:24:11.0430000-04:00 (000.000s) Cross Tail Switch starts casting: 2024-08-05T08:30:57.5730000-04:00 (406.530s) Cross Tail Switch finishes casting: 2024-08-05T08:31:02.5390000-04:00 (411.496s) Cross Tail Switch hits: 2024-08-05T08:31:03.7030000-04:00 (412.660s) 2024-08-05T08:31:04.7310000-04:00 (413.688s) 2024-08-05T08:31:05.7600000-04:00 (414.717s) 2024-08-05T08:31:06.7890000-04:00 (415.746s) 2024-08-05T08:31:07.8170000-04:00 (416.774s) 2024-08-05T08:31:08.8490000-04:00 (417.806s) 2024-08-05T08:31:09.8780000-04:00 (418.835s) 2024-08-05T08:31:10.9080000-04:00 (419.865s) 2024-08-05T08:31:11.9350000-04:00 (420.892s)
The start timestamp here determines the absolute truth on when the encounter starts (as opposed to, for example, the first player ability hitting the enemy), as it is from the packet which indicates that the enemy has entered combat.
I guess to summarize:
- cactbot timelines are generated from actual pull data which may contain skew
- cactbot timelines contain an implicit sync window which allows them to sync regardless of skew
- The tool used to generate cactbot timelines does not have access to the level of data that can be seen from raw network packet decoding, but it does not need to in order to sync properly
Thanks for the information, but is it not possible to instead make the timeline with the raw network packets data and also add syncs incase it desyncs?
Thanks for the information, but is it not possible to instead make the timeline with the raw network packets data and also add syncs incase it desyncs?
Almost no one actually logs raw network packets, as they bloat the log file massively. That's why the option to do so is locked behind a (DEBUG)
setting in FFXIV_ACT_Plugin
. It also causes a lot more lag unless the user is running a much higher end PC. Additionally, syncs can't be written against raw network packets because that would require users to have raw network packet logging enabled and the raw network packets would have to be decoded from hex to binary then to actual data, which would be incredibly slow to do in an interpreted language such as JS/TS (which is what cactbot runs in).
Linear timelines such as this one don't really need bigger syncs to prevent a desync. You can test this yourself against any local log file you have, there's a built in test_timeline
tool.
In the example below, you can see that the synced entries skew a bit, but the implicit 2 second window on syncs ensures that the timeline remains accurate.
The three entries that show Missed
are because there are variations on ID for those abilities which the timeline isn't accounting for, but they're not relevant for maintaining the sync of the timeline because it's a linear timeline.
C:\Users\valarnin\Source\cactbot>node --max-old-space-size=8192 --loader=ts-node/esm ./util/logtools/test_timeline.ts -f C:\Stuff\Games\ACT\FFXIVLogs\Network_27102_20240804.log -lf 26 -t "r4s"
(node:28700) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
Timeline:
0.000 | 16 | 0.0 "--sync--" InCombat { inGameCombat: "1" } window 0,1
+0.155 | 17 | 10.6 "--sync--" StartsUsing { id: "95EF", source: "Wicked Thunder" }
+0.024 | 18 | 15.6 "Wrath of Zeus" Ability { id: "95EF", source: "Wicked Thunder" }
-0.030 | 19 | 23.8 "--sync--" Ability { id: "92A9", source: "Wicked Thunder" }
-0.010 | 20 | 30.9 "--north--" Ability { id: "92C9", source: "Wicked Thunder" }
Missed | 21 | 38.2 "Bewitching Flight" Ability { id: "9671", source: "Wicked Thunder" }
-0.089 | 22 | 41.3 "--center--" Ability { id: "92C9", source: "Wicked Thunder" }
-0.011 | 23 | 49.6 "Electrifying Witch Hunt" Ability { id: "95E5", source: "Wicked Thunder" }
-0.131 | 24 | 57.6 "Witch Hunt + Forked Lightning" Ability { id: "95DE", source: "Wicked Thunder" }
-0.022 | 25 | 74.7 "Widening Witch Hunt/Narrowing Witch Hunt" Ability { id: "95E0|95E1", source: "Wicked Thunder" }
-0.199 | 26 | 78.1 "Thundering/Lightning Vortex 2" Ability { id: "95E2|95E3", source: "Wicked Thunder" }
-0.152 | 27 | 81.5 "Thundering/Lightning Vortex 3" Ability { id: "95E2|95E3", source: "Wicked Thunder" }
-0.155 | 28 | 84.9 "Thundering/Lightning Vortex 4" Ability { id: "95E2|95E3", source: "Wicked Thunder" }
-0.197 | 29 | 88.3 "--sync--" Ability { id: "92AA", source: "Wicked Thunder" }
+0.016 | 30 | 98.4 "Wrath of Zeus" Ability { id: "95EF", source: "Wicked Thunder" }
-0.039 | 31 | 105.6 "--center--" Ability { id: "92C9", source: "Wicked Thunder" }
-0.078 | 32 | 109.7 "Electrope Edge (mines)" Ability { id: "95C5", source: "Wicked Thunder" }
-0.056 | 33 | 116.8 "Witchgleam" Ability { id: "95C6", source: "Wicked Thunder" }
-0.270 | 40 | 134.4 "Spark + Sidewise Spark" Ability { id: "95EC|95ED", source: "Wicked Thunder" }
-0.046 | 41 | 134.4 "Four Star/Eight Star" Ability { id: "95D0|95D1", source: "Wicked Thunder" }
-0.025 | 42 | 142.6 "Wicked Jolt" Ability { id: "95F0", source: "Wicked Thunder" }
-0.008 | 43 | 145.7 "Wicked Jolt (second hit)" Ability { id: "95F1", source: "Wicked Thunder" }
+0.004 | 44 | 152.9 "--center--" Ability { id: "92C9", source: "Wicked Thunder" }
-0.078 | 45 | 157.0 "Electrope Edge (players)" Ability { id: "95C5", source: "Wicked Thunder" }
-0.321 | 52 | 172.1 "Lightning Cage" Ability { id: "95CE", source: "Wicked Thunder" }
-0.052 | 58 | 189.2 "Sidewise Spark" Ability { id: "95EC|95ED", source: "Wicked Thunder" }
-0.045 | 59 | 189.2 "Four Star/Eight Star" Ability { id: "95D0|95D1", source: "Wicked Thunder" }
+0.070 | 61 | 206.4 "Wicked Bolt x6" Ability { id: "92C2", source: "Wicked Thunder" } duration 5
-0.138 | 62 | 221.6 "--north--" Ability { id: "92C9", source: "Wicked Thunder" }
-0.132 | 65 | 230.0 "Right Roll/Left Roll" Ability { id: "95D2|95D3", source: "Wicked Thunder" }
-0.045 | 66 | 232.0 "Stampeding Thunder x6" Ability { id: "8A06", source: "Wicked Thunder" } duration 7
-0.290 | 67 | 239.0 "(floor no more)" Ability { id: "8E2F", source: "Wicked Thunder" }
+0.098 | 68 | 241.9 "--teleport--" Ability { id: "92C9", source: "Wicked Thunder" }
-0.038 | 69 | 249.2 "Electron Stream" Ability { id: "95D7", source: "Wicked Thunder" }
-0.015 | 74 | 262.3 "Electron Stream" Ability { id: "95D6", source: "Wicked Thunder" }
-0.155 | 76 | 275.3 "Electron Stream" Ability { id: "95D7", source: "Wicked Thunder" }
+0.027 | 78 | 288.7 "Wicked Jolt" Ability { id: "95F0", source: "Wicked Thunder" }
-0.013 | 79 | 291.8 "Wicked Jolt (second hit)" Ability { id: "95F1", source: "Wicked Thunder" }
-0.072 | 80 | 302.9 "--center--" Ability { id: "92C9", source: "Wicked Thunder" }
+0.051 | 81 | 313.7 "Electrope Transplant" Ability { id: "98D3", source: "Wicked Thunder" }
-0.147 | 82 | 323.9 "Fulminous Field 1" Ability { id: "98CD", source: "Wicked Thunder" }
-0.024 | 83 | 326.9 "Fulminous Field 2" Ability { id: "98CD", source: "Wicked Thunder" }
-0.021 | 84 | 329.9 "Fulminous Field 3" Ability { id: "98CD", source: "Wicked Thunder" }
-0.021 | 87 | 332.9 "Conduction Point" Ability { id: "98CE", source: "Wicked Thunder" }
-0.024 | 88 | 335.9 "Fulminous Field 6" Ability { id: "98CD", source: "Wicked Thunder" }
-0.027 | 92 | 338.9 "Fulminous Field 7" Ability { id: "98CD", source: "Wicked Thunder" }
-0.062 | 93 | 349.9 "Fulminous Field 1" Ability { id: "98CD", source: "Wicked Thunder" }
-0.021 | 94 | 352.9 "Fulminous Field 2" Ability { id: "98CD", source: "Wicked Thunder" }
-0.022 | 95 | 355.9 "Fulminous Field 3" Ability { id: "98CD", source: "Wicked Thunder" }
-0.023 | 98 | 358.9 "Conduction Point" Ability { id: "98CE", source: "Wicked Thunder" }
-0.023 | 99 | 361.9 "Fulminous Field 6" Ability { id: "98CD", source: "Wicked Thunder" }
-0.024 | 101 | 364.9 "Fulminous Field 7" Ability { id: "98CD", source: "Wicked Thunder" }
-0.063 | 102 | 375.2 "Soulshock" Ability { id: "4E41", source: "Wicked Thunder" }
+0.121 | 107 | 383.9 "Cannonbolt" Ability { id: "98D0", source: "Wicked Thunder" }
-0.034 | 113 | 403.1 "--sync--" StartsUsing { id: "95F2", source: "Wicked Thunder" }
+0.022 | 114 | 408.1 "Cross Tail Switch x10" Ability { id: "95F2", source: "Wicked Thunder" } duration 9
-0.268 | 119 | 426.0 "Wicked Blaze" Ability { id: "95F7", source: "Wicked Thunder" }
+0.039 | 124 | 437.2 "Wicked Special" Ability { id: "9611|9613", source: "Wicked Thunder" }
+0.030 | 127 | 452.4 "--sync--" Ability { id: "961E", source: "Wicked Thunder" }
Missed | 130 | 470.7 "--sync--" Ability { id: "9603", source: "Wicked Thunder" }
+0.998 | 131 | 479.8 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.978 | 131 | 479.8 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
+0.929 | 132 | 483.8 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.978 | 132 | 483.8 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.073 | 133 | 500.9 "Azure Thunder" Ability { id: "962F", source: "Wicked Thunder" }
-0.039 | 134 | 507.0 "Twilight Sabbath" Ability { id: "9623", source: "Wicked Thunder" }
-0.073 | 135 | 515.1 "Wicked Fire (puddles drop)" Ability { id: "9630", source: "Wicked Thunder" }
Missed | 139 | 526.2 "Wicked Special + Sidewise Spark" Ability { id: "9613", source: "Wicked Thunder" }
+0.003 | 140 | 536.4 "Midnight Sabbath" Ability { id: "9AB9", source: "Wicked Thunder" }
-0.151 | 141 | 547.5 "Wicked Flare/Wicked Spark 1" Ability { id: "962D|962E", source: "Wicked Thunder" }
-0.003 | 142 | 551.5 "Wicked Flare/Wicked Spark 2" Ability { id: "962D|962E", source: "Wicked Thunder" }
-0.020 | 143 | 556.5 "Wicked Special" Ability { id: "9611|9613", source: "Wicked Thunder" }
-0.053 | 144 | 563.6 "Wicked Thunder" Ability { id: "949B", source: "Wicked Thunder" }
-0.045 | 145 | 583.9 "Flame Slash" Ability { id: "9614", source: "Wicked Thunder" }
-0.012 | 146 | 589.0 "Raining Swords" Ability { id: "9616", source: "Wicked Thunder" }
-0.019 | 147 | 610.1 "Chain Lightning" Ability { id: "9619", source: "Wicked Thunder" } duration 19.8
+0.869 | 148 | 637.2 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.979 | 148 | 637.2 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
+0.890 | 149 | 641.2 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.976 | 149 | 641.2 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.012 | 152 | 678.6 "Azure Thunder" Ability { id: "962F", source: "Wicked Thunder" }
-0.036 | 154 | 684.7 "Ion Cluster" Ability { id: "9622", source: "Wicked Thunder" }
-0.033 | 155 | 690.8 "Sunrise Sabbath" Ability { id: "9ABA", source: "Wicked Thunder" }
-0.184 | 160 | 711.8 "Wicked Special" Ability { id: "9611|9613", source: "Wicked Thunder" }
+0.898 | 162 | 727.9 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.976 | 162 | 727.9 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
+0.892 | 163 | 731.9 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
-0.977 | 163 | 731.9 "Tail Thrust/Switch of Tides" Ability { id: "960E|960F", source: "Wicked Thunder" }
+0.044 | 166 | 743.1 "Sword Quiver" Ability { id: "95F9|95FA|95FB", source: "Wicked Thunder" } duration 4.5
-0.184 | 167 | 751.8 "Burst + Laceration" Ability { id: "9600", source: "Wicked Thunder" }
-0.049 | 168 | 760.0 "Sword Quiver" Ability { id: "95F9|95FA|95FB", source: "Wicked Thunder" } duration 4.5
-0.106 | 169 | 768.8 "Burst + Laceration" Ability { id: "9600", source: "Wicked Thunder" }
-0.124 | 170 | 776.9 "Sword Quiver" Ability { id: "95F9|95FA|95FB", source: "Wicked Thunder" } duration 4.5
-0.186 | 171 | 785.6 "Burst + Laceration" Ability { id: "9600", source: "Wicked Thunder" }
-790.665 | 13 | 0.0 "--Reset--" ActorControl { command: "4000000F" } window 0,100000 jump 0
Timeline is done, barring a mistake somewhere. Triggers are still wip - p1 is done, but doesn't have much p2 yet. Planning to get at least a little more p2 in there before this gets merged.
This looks fine in raid emulator, but it could probably use some in-game testing if anyone is able to. Worst case, we can merge and I can in-game test and adjust if needed on Tuesday.