|
| 1 | +# Headmarker Guide |
| 2 | + |
| 3 | +This is a guide to finding the real ids for "headmarkers", |
| 4 | +i.e. the visual that appears above your player's head when you need to spread, stack, etc. |
| 5 | + |
| 6 | +cactbot calls them "headmarkers", |
| 7 | +e.g. `NetRegexes.headMarker()`. |
| 8 | +The FFXIV parsing plugin calls them "TargetIcon", |
| 9 | +e.g. `[21:40:13.596] TargetIcon 1B:4002ADD7:Arcane Cylinder:0000:0000:00B5:0000:0000:0000`. |
| 10 | +FFXIV itself calls them "Lockon", |
| 11 | +e.g. <https://github.com/xivapi/ffxiv-datamining/blob/master/csv/Lockon.csv>. |
| 12 | + |
| 13 | +This guide will call them "headmarkers" from here on out. |
| 14 | + |
| 15 | +## History and Motivation |
| 16 | + |
| 17 | +During TEA, |
| 18 | +Square Enix changed headmarkers to have a per-instance offset in certain content. |
| 19 | +This now happens in all ultimates, extremes, and savage raids. |
| 20 | +Once the content is no longer current, |
| 21 | +sometimes the offsets are then removed. |
| 22 | + |
| 23 | +This same thing happened with ability ids as well during o4s. |
| 24 | +However, the FFXIV parsing plugin and fflogs cannot function without real ability ids, |
| 25 | +and so the parsing plugin takes care of this itself. |
| 26 | +Headmarkers are not parsing related, |
| 27 | +and so headmarker offsets are left to downstream developers to fix themselves. |
| 28 | + |
| 29 | +The offset is sent to the client when zoning into the instance, |
| 30 | +and so is the same for every pull while in the same instance. |
| 31 | + |
| 32 | +## Handling this in cactbot |
| 33 | + |
| 34 | +cactbot chooses to handle this by trying to figure out the true headmarker ids. |
| 35 | +This is so that it's easy to find different content using the same id, |
| 36 | +e.g. limit cut starting at `004F` (usually, but not always). |
| 37 | +It's also easier to double check that the values are correct. |
| 38 | +Ideally, when finding headmarkers, please leave a comment with the full avfx path. |
| 39 | + |
| 40 | +cactbot handles offsets by looking for headmarkers, |
| 41 | +recording the first id it finds, |
| 42 | +and then comparing to the expected first headmarker to calculate the offset. |
| 43 | + |
| 44 | +### No Offsets |
| 45 | + |
| 46 | +If the encounter does not have headmarker offsets, |
| 47 | +please use headmarker ids directly in triggers, |
| 48 | +e.g. [p9n](https://github.com/quisquous/cactbot/blob/7b904e35c7d678013d229080c858f19d35510ac1/ui/raidboss/data/06-ew/raid/p9n.ts#L33-L38). |
| 49 | + |
| 50 | +### Same first headmarker |
| 51 | + |
| 52 | +Most of the time, |
| 53 | +if the encounter does have headmarker offsets, |
| 54 | +the first headmarker id will always be the same. |
| 55 | + |
| 56 | +Most trigger sets do something like [p11s](https://github.com/quisquous/cactbot/blob/3ca3589/ui/raidboss/data/06-ew/raid/p11s.ts). |
| 57 | + |
| 58 | +There's a helper function to set the headmarker offset if it's not found, |
| 59 | +and return the true headmarker id. |
| 60 | + |
| 61 | +```typescript |
| 62 | +// Helper functions. |
| 63 | +const firstHeadmarker = parseInt(headmarkers.dike, 16); |
| 64 | + |
| 65 | +const getHeadmarkerId = (data: Data, matches: NetMatches['HeadMarker']) => { |
| 66 | + if (data.decOffset === undefined) |
| 67 | + data.decOffset = parseInt(matches.id, 16) - firstHeadmarker; |
| 68 | + return (parseInt(matches.id, 16) - data.decOffset).toString(16).toUpperCase().padStart(4, '0'); |
| 69 | +}; |
| 70 | +``` |
| 71 | + |
| 72 | +There's usually also a trigger at the top of the file to always try to set the offset. |
| 73 | +In the past there's been bugs where `getHeadmarkerId` has been used in `condition` functions. |
| 74 | +(See the P9S defamation example below for how the `getHeadmarkerId` call might be skipped.) |
| 75 | + |
| 76 | +```typescript |
| 77 | + { |
| 78 | + id: 'P11S Headmarker Tracker', |
| 79 | + type: 'HeadMarker', |
| 80 | + netRegex: {}, |
| 81 | + condition: (data) => data.decOffset === undefined, |
| 82 | + // Unconditionally set the first headmarker here so that future triggers are conditional. |
| 83 | + run: (data, matches) => getHeadmarkerId(data, matches), |
| 84 | + }, |
| 85 | +``` |
| 86 | + |
| 87 | +Then, any later headmarker trigger has to match all headmarker lines, |
| 88 | +and use `getHeadmarkerId` to figure out the correct id. |
| 89 | +It's definitely a little bit cumbersome and inefficient, but that's what we got. |
| 90 | + |
| 91 | +```typescript |
| 92 | + { |
| 93 | + id: 'P9S Defamation', |
| 94 | + type: 'HeadMarker', |
| 95 | + netRegex: {}, |
| 96 | + condition: (data, matches) => { |
| 97 | + return data.me === matches.target && |
| 98 | + getHeadmarkerId(data, matches) === headmarkers.defamation; |
| 99 | + }, |
| 100 | + alarmText: (_data, _matches, output) => output.defamation!(), |
| 101 | + // etc |
| 102 | +``` |
| 103 | +
|
| 104 | +### Different first headmarker |
| 105 | +
|
| 106 | +In rare cases, the first headmarker is not consistent. |
| 107 | +
|
| 108 | +As cactbot resets all trigger info (including recorded headmarker offset) on wipe, |
| 109 | +any trigger file must handle the first headmarker from any door boss and final boss simultaneously. |
| 110 | +
|
| 111 | +For example, [P12S](https://github.com/quisquous/cactbot/blob/4700770/ui/raidboss/data/06-ew/raid/p12s.ts#L159-L179) |
| 112 | +has a door boss with two different first headmarkers (bottom left / bottom right wing) |
| 113 | +and a final boss with one first headmarker. |
| 114 | +
|
| 115 | +See that file for how that can be solved. |
| 116 | +
|
| 117 | +## Lockon table |
| 118 | +
|
| 119 | +All visual effects are avfx game data files and are referenced by the `Lockon` table. |
| 120 | +
|
| 121 | +You can `exd Lockon` from a local copy of [SaintCoinach](https://github.com/xivapi/SaintCoinach), |
| 122 | +or alternatively you can browse the Lockon table online here: <https://github.com/xivapi/ffxiv-datamining/blob/master/csv/Lockon.csv> |
| 123 | +
|
| 124 | +Headmarkers are 2 byte hex values, |
| 125 | +and the `key` field in the `Lockon` is the decimal representation of that hex value. |
| 126 | +
|
| 127 | +Here is some code from: <https://github.com/quisquous/cactbot/blob/main/ui/raidboss/data/06-ew/raid/p9s.ts> |
| 128 | +
|
| 129 | +```typescript |
| 130 | +const headmarkers = { |
| 131 | + // vfx/lockon/eff/tank_lockonae_0m_5s_01t.avfx |
| 132 | + dualityOfDeath: '01D4', |
| 133 | + // vfx/lockon/eff/m0361trg_a1t.avfx (through m0361trg_a8t) |
| 134 | + limitCut1: '004F', |
| 135 | + limitCut2: '0050', |
| 136 | + limitCut3: '0051', |
| 137 | + limitCut4: '0052', |
| 138 | + limitCut5: '0053', |
| 139 | + limitCut6: '0054', |
| 140 | + limitCut7: '0055', |
| 141 | + limitCut8: '0056', |
| 142 | + // vfx/lockon/eff/r1fz_skywl_s9x.avfx |
| 143 | + defamation: '014A', |
| 144 | + // vfx/lockon/eff/n5r9_lockon_bht_c0g.avfx |
| 145 | + cometMarker: '01B3', |
| 146 | +} as const; |
| 147 | +``` |
| 148 | +
|
| 149 | +The comet marker (that appears on one of the two unbroken meteors during Charybdis in P9S) is `01B3`. |
| 150 | +`0x01B3` = 435 in decimal. |
| 151 | +In the [LockOn table](https://github.com/xivapi/ffxiv-datamining/blob/master/csv/Lockon.csv#L439), |
| 152 | +you can find that key 435 has the string `n5r9_lockon_bht_c0g`. |
| 153 | +
|
| 154 | +This corresponds to the game asset `vfx/lockon/eff/n5r9_lockon_bht_c0g.avfx`. |
| 155 | +My understanding is that all headmarker effects have this `avfx` extension |
| 156 | +and are in the `vfx/lockon/eff/` game directory. |
| 157 | +
|
| 158 | +## How to test headmarkers in game |
| 159 | +
|
| 160 | +In order to verify that you have the correct headmarker id, |
| 161 | +you can use VFXEditor. |
| 162 | +
|
| 163 | +Install [FFXIVQuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher). |
| 164 | +
|
| 165 | +Install the [VFXEditor plugin](https://github.com/0ceal0t/Dalamud-VFXEditor). |
| 166 | +
|
| 167 | +Type `/vfxedit` to start. |
| 168 | +
|
| 169 | + |
| 170 | +
|
| 171 | +Pick a skill to replace that's easy to do in game, like Cure 1. |
| 172 | +
|
| 173 | + |
| 174 | +
|
| 175 | +Now, type a vfx to replace it with. |
| 176 | +
|
| 177 | + |
| 178 | +
|
| 179 | +Finally, cast cure and observe the different animation. |
| 180 | +
|
| 181 | + |
| 182 | +
|
| 183 | +Headmarkers on mobs (see: p12s wings, this meteor marker) may have odd positions and rotations. |
| 184 | +
|
| 185 | +## How to Find True Ids |
| 186 | +
|
| 187 | +Mostly this is about sleuthing it out. |
| 188 | +
|
| 189 | +Here's some suggestions on things to consider: |
| 190 | +
|
| 191 | +- look for new Lockon entries that weren't there in previous patches |
| 192 | +- check if normal mode ids still apply (e.g. p12s wings) |
| 193 | +- check if the same ids from previous encounters apply (e.g. limit cut ids) |
| 194 | +- read names in the Lockon table and try to make connections, e.g. `m0515_turning_right01c` is the orange clockwise laser rotation |
| 195 | +
|
| 196 | +## Future Work |
| 197 | +
|
| 198 | +If somebody tracked down the network data and game code to find the actual offset and math, |
| 199 | +then OverlayPlugin could emit a custom log line for the offset |
| 200 | +or custom log lines with adjusted headmarker ids. |
| 201 | +
|
| 202 | +Barring that, it's also possible that OverlayPlugin could handle all of the |
| 203 | +"first headmarker" tracking itself per zone in C# code and emit custom log lines. |
| 204 | +
|
| 205 | +Finally, it'd be nice if somebody could figure out how to automatically extract avfx into gifs |
| 206 | +and then we could have an online library of headmarker ids mapped to visuals. |
0 commit comments