Skip to content

Commit

Permalink
raidboss: TOP Unlimited Wave Cannon Dodge + Clock (#5370)
Browse files Browse the repository at this point in the history
I noticed I could use some code I wrote for DSR gigaflares here that
should also work for determining the exaflare clock here.

This calls the card/intercard that is safe and next to the first
exaflare. In the same output, it calls the direction
(clock/counterclock) the exaflares spawned in which is where the player
will need to run later.
  • Loading branch information
Legends0 authored May 21, 2023
1 parent d96997b commit 7c0235b
Showing 1 changed file with 156 additions and 0 deletions.
156 changes: 156 additions & 0 deletions ui/raidboss/data/06-ew/ultimate/the_omega_protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface Data extends RaidbossData {
cosmoArrowCount: number;
cosmoArrowIn?: boolean;
cosmoArrowExaCount: number;
waveCannonFlares: number[];
}

const phaseReset = (data: Data) => {
Expand Down Expand Up @@ -209,6 +210,7 @@ const triggerSet: TriggerSet<Data> = {
trioDebuff: {},
cosmoArrowCount: 0,
cosmoArrowExaCount: 0,
waveCannonFlares: [],
};
},
timelineTriggers: [
Expand Down Expand Up @@ -2148,6 +2150,160 @@ const triggerSet: TriggerSet<Data> = {
},
},
},
{
id: 'TOP Unlimited Wave Cannon Collect',
// Invisible NPCs cast Wave Cannon from starting position of the Exaflares
// Data from ACT can be innacurate, use OverlayPlugin
// These casts start 1 second after each other
type: 'StartsUsing',
netRegex: { id: '7BAD', source: 'Alpha Omega' },
run: (data, matches) => {
// Cleanup collector if second set
if (data.waveCannonFlares.length === 4)
data.waveCannonFlares = [];
data.waveCannonFlares.push(parseInt(matches.sourceId, 16));
},
},
{
id: 'TOP Unlimited Wave Cannon Dodges',
// As low as 1.2s delay works consistently on low latency, but 1.5s works for more players
type: 'StartsUsing',
netRegex: { id: '7BAC', source: 'Alpha Omega', capture: false },
delaySeconds: 1.5,
durationSeconds: 10.6, // Time until 3rd puddle
promise: async (data) => {
if (data.waveCannonFlares.length < 2) {
console.error(
`TOP Unlimited Wave Cannon Dodge: Expected at least 2 casts, Got: ${
JSON.stringify(data.waveCannonFlares.length)
}`,
);
}
data.combatantData = [];
data.combatantData = (await callOverlayHandler({
call: 'getCombatants',
ids: [...data.waveCannonFlares],
})).combatants;
},
infoText: (data, _matches, output) => {
if (data.combatantData.length < 2) {
console.error(
`TOP Unlimited Wave Cannon Dodge: Expected at least 2 Wave Cannons, Got: ${
JSON.stringify(data.combatantData)
}`,
);
return;
}
const firstWaveCannon =
data.combatantData.filter((combatant) => combatant.ID === data.waveCannonFlares[0])[0];
const secondWaveCannon =
data.combatantData.filter((combatant) => combatant.ID === data.waveCannonFlares[1])[0];

if (firstWaveCannon === undefined || secondWaveCannon === undefined) {
console.error(
`TOP Unlimited Wave Cannon Dodge: Failed to retreive combatant Data: ${
JSON.stringify(data.combatantData)
}`,
);
return;
}

// Collect Exaflare position
const first = [firstWaveCannon.PosX - 100, firstWaveCannon.PosY - 100];
const second = [secondWaveCannon.PosX - 100, secondWaveCannon.PosY - 100];
if (
first[0] === undefined || first[1] === undefined ||
second[0] === undefined || second[1] === undefined
) {
console.error(`TOP Unlimited Wave Cannon Dodge: missing coordinates`);
return;
}

// Compute atan2 of determinant and dot product to get rotational direction
// Note: X and Y are flipped due to Y axis being reversed
const getRotation = (x1: number, y1: number, x2: number, y2: number) => {
return Math.atan2(y1 * x2 - x1 * y2, y1 * y2 + x1 * x2);
};

// Get rotation of first and second exaflares
const rotation = getRotation(first[0], first[1], second[0], second[1]);

// Get location to dodge to by looking at first exaflare position
// Calculate combatant position in an all 8 cards/intercards
const matchedPositionTo8Dir = (combatant: PluginCombatantState) => {
// Positions are moved up 100 and right 100
const y = combatant.PosY - 100;
const x = combatant.PosX - 100;

// During Unlimited Wave Cannon, 4 Wave Cannons spawn in order around the map
// N = (100, 76), E = (124, 100), S = (100, 124), W = (76, 100)
// NE = (116.97, 83.03), SE = (116.97, 116.97), SW = (83.03, 116.97), NW = (83.03, 83.03)
//
// Map NW = 0, N = 1, ..., W = 7

return Math.round(5 - 4 * Math.atan2(x, y) / Math.PI) % 8;
};

const dir = matchedPositionTo8Dir(firstWaveCannon);
const dirs: { [dir: number]: string } = {
0: output.northwest!(),
1: output.north!(),
2: output.northeast!(),
3: output.east!(),
4: output.southeast!(),
5: output.south!(),
6: output.southwest!(),
7: output.west!(),
};

const startDir = rotation < 0 ? (dir - 1) % 8 : (dir + 1) % 8;
const start = dirs[startDir] ?? output.unknown!();

if (rotation < 0) {
return output.directions!({
start: start,
rotation: output.clockwise!(),
});
}
if (rotation > 0) {
return output.directions!({
start: start,
rotation: output.counterclock!(),
});
}
},
outputStrings: {
directions: {
en: '${start} => ${rotation}',
de: '${start} => ${rotation}',
cn: '${start} => ${rotation}',
ko: '${start} => ${rotation}',
},
north: Outputs.north,
northeast: Outputs.northeast,
east: Outputs.east,
southeast: Outputs.southeast,
south: Outputs.south,
southwest: Outputs.southwest,
west: Outputs.west,
northwest: Outputs.northwest,
unknown: Outputs.unknown,
clockwise: {
en: 'Clockwise',
de: 'Im Uhrzeigersinn',
ja: '時計回り',
cn: '顺时针',
ko: '시계방향',
},
counterclock: {
en: 'Counterclockwise',
de: 'Gegen den Uhrzeigersinn',
ja: '反時計回り',
cn: '逆时针',
ko: '반시계방향',
},
},
},
{
id: 'TOP Wave Cannon Wild Charge',
type: 'StartsUsing',
Expand Down

0 comments on commit 7c0235b

Please sign in to comment.