Skip to content

Commit ca8fdb3

Browse files
Legends0valarnin
andauthored
raidboss: oc add phantom job and level tracker (OverlayPlugin#796)
This works, but is not always reliable without a refresh of the job buff. Certain situations such as reloading the overlay cause the data to be undefined. Reloading into the instance I think refreshes the buff as does resurrecting or manually changing the phantom job. This should be kept in mind when designing triggers based on the role. Triggers in Forked Tower are setup to always call out if unable to determine phantom job or phantom job level due to severity of consequences of missing a callout. Includes some Phantom Time Mage Triggers for CEs and commented, example functions similar to what we have for Conditions for regular roles/jobs. --------- Co-authored-by: valarnin <valarnin@gmail.com>
1 parent 92154a9 commit ca8fdb3

1 file changed

Lines changed: 267 additions & 1 deletion

File tree

ui/raidboss/data/07-dt/eureka/occult_crescent_south_horn.ts

Lines changed: 267 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { TriggerSet } from '../../../../../types/trigger';
1111

1212
export interface Data extends RaidbossData {
1313
ce?: string;
14+
phantomJob?: string;
15+
phantomJobLevel?: number;
1416
sisyphusResoundingMemoryWedge?: 'intercards' | 'cardinals' | 'unknown';
1517
demonTabletChiselTargets: string[];
1618
demonTabletRotationCounter: number;
@@ -413,6 +415,171 @@ const magitaurOutputStrings = {
413415
},
414416
};
415417

418+
// Used to filter the GainsEffect
419+
const phantomJobEffectIds = [
420+
'1092', // Freelancer
421+
'1106', // Knight
422+
'1107', // Berserker
423+
'1108', // Monk
424+
'1109', // Ranger
425+
'1110', // Oracle
426+
'1111', // Thief
427+
'110A', // Samurai
428+
'110B', // Bard
429+
'110C', // Geomancer
430+
'110D', // Time Mage
431+
'110E', // Cannonneer
432+
'110F', // Chemist
433+
'12C3', // Mystic Knight
434+
'12C4', // Gladiator
435+
'12C5', // Dancer
436+
];
437+
438+
// Useful for matching on job name in condition trigger
439+
const phantomJobData = {
440+
'freelancer': '1092',
441+
'knight': '1106',
442+
'berserker': '1107',
443+
'monk': '1108',
444+
'ranger': '1109',
445+
'oracle': '1110',
446+
'thief': '1111',
447+
'samurai': '110A',
448+
'bard': '110B',
449+
'geomancer': '110C',
450+
'timeMage': '110D',
451+
'cannoneer': '110E',
452+
'chemist': '110F',
453+
'mysticKnight': '12C3',
454+
'gladiator': '12C4',
455+
'dancer': '12C5',
456+
} as const;
457+
458+
// Return if the player has a phantom job that can dispel
459+
// Phantom Time Mage Lv 4: Dispel
460+
const phantomCanDispel = (
461+
phantomJob: string,
462+
phantomJobLevel: number,
463+
): boolean => {
464+
if (phantomJob === phantomJobData.timeMage && phantomJobLevel >= 4)
465+
return true;
466+
return false;
467+
};
468+
469+
// Return if the player has a phantom job that can slow
470+
// Phantom Time Mage Lv 1: Slowga
471+
/*
472+
const phantomCanSlow = (
473+
phantomJob: string,
474+
phantomJobLevel: number,
475+
): boolean => {
476+
if (phantomJob === phantomJobData.timeMage && phantomJobLevel >= 1)
477+
return true;
478+
return false;
479+
};
480+
*/
481+
482+
// Return if the player has a phantom job that can cleanse
483+
// Phantom Oracle Lv 2: Recuperation
484+
const phantomCanCleanse = (
485+
phantomJob: string,
486+
phantomJobLevel: number,
487+
): boolean => {
488+
if (phantomJob === phantomJobData.oracle && phantomJobLevel >= 2)
489+
return true;
490+
return false;
491+
};
492+
493+
// Return if the player has a phantom job that can freeze time
494+
// Phantom Bard Lv 2: Romeo's Ballad (aoe)
495+
// Phantom Dancer Lv 1 may be able to use Dance with Tempting Tango proc (single-target)
496+
const phantomCanFreeze = (
497+
phantomJob: string,
498+
phantomJobLevel: number,
499+
): boolean => {
500+
if (phantomJob === phantomJobData.bard && phantomJobLevel >= 2)
501+
return true;
502+
if (phantomJob === phantomJobData.dancer && phantomJobLevel >= 1)
503+
return true;
504+
return false;
505+
};
506+
507+
// Return if the player has a phantom job that can suspend
508+
// Phantom Geomancer Lv 4: Suspend
509+
/*
510+
const phantomCanSuspend = (
511+
phantomJob: string,
512+
phantomJobLevel: number,
513+
): boolean => {
514+
if (phantomJob === phantomJobData.geomancer && phantomJobLevel >= 4)
515+
return true;
516+
return false;
517+
};
518+
*/
519+
520+
// Return if the player has a phantom job that can reduce tankbuster
521+
// Phantom Knight Lv 4: Phantom Guard + Enhanced Phantom Guard (90%)
522+
// Phantom Knight Lv 6: Pledge
523+
// Phantom Oracle Lv 6: Invulnerability
524+
// Phantom Dancer Lv 3: Steadfast Dance (10% MaxHP Barrier)
525+
// Phantom Dancer Lv 4: Mesmerize (40%)
526+
// Phantom Mystic Knight Lv 2: Magic Shell (20% MaxHP Barrier of caster)
527+
// Phantom Gladiator Lv 2: Defend (50%)
528+
/*
529+
const phantomCaresAboutTankbuster = (
530+
phantomJob: string,
531+
phantomJobLevel: number,
532+
): boolean => {
533+
if (phantomJob === phantomJobData.knight && phantomJobLevel >= 4)
534+
return true;
535+
if (phantomJob === phantomJobData.oracle && phantomJobLevel >= 6)
536+
return true;
537+
if (phantomJob === phantomJobData.dancer && phantomJobLevel >= 3)
538+
return true;
539+
if (phantomJob === phantomJobData.mysticKnight && phantomJobLevel >= 2)
540+
return true;
541+
if (phantomJob === phantomJobData.gladiator && phantomJobLevel >= 2)
542+
return true;
543+
return false;
544+
};
545+
*/
546+
547+
// Return if the player has a phantom job that can block physical damage
548+
// Phantom Samurai Lv 2: Shirahadori
549+
// Phantom Oracle Lv 6: Invulnerability
550+
/*
551+
const phantomCanBlockPhysical = (
552+
phantomJob: string,
553+
phantomJobLevel: number,
554+
): boolean => {
555+
if (phantomJob === phantomJobData.samurai && phantomJobLevel >= 2)
556+
return true;
557+
if (phantomJob === phantomJobData.oracle && phantomJobLevel >= 6)
558+
return true;
559+
return false;
560+
};
561+
*/
562+
563+
// Return if the player has a phantom job that helps with enemy aoes
564+
// Phantom Bard Lv 3: Mighty March (+20% MaxHP)
565+
// Phantom Ranger Lv 6: Occult Unicorn (40k AoE Shield)
566+
// Phantom Dancer Lv 4: Mesmerize (Require's target, 4s 40% damage reduction then 100s 10% damage reduction)
567+
// Phantom Geomance Lv 2 may be able to use Weather with Blessed Rain, Misty Mirage, Sunbath, or Cloudy Caress effects
568+
/*
569+
const phantomCaresAboutAOE = (
570+
phantomJob: string,
571+
phantomJobLevel: number,
572+
): boolean => {
573+
if (phantomJob === phantomJobData.bard && phantomJobLevel >= 3)
574+
return true;
575+
if (phantomJob === phantomJobData.ranger && phantomJobLevel >= 6)
576+
return true;
577+
if (phantomJob === phantomJobData.dancer && phantomJobLevel >= 4)
578+
return true;
579+
return false;
580+
};
581+
*/
582+
416583
const triggerSet: TriggerSet<Data> = {
417584
id: 'TheOccultCrescentSouthHorn',
418585
zoneId: ZoneId.TheOccultCrescentSouthHorn,
@@ -693,6 +860,42 @@ const triggerSet: TriggerSet<Data> = {
693860
console.log(`Start CE: ??? (${ceId})`);
694861
},
695862
},
863+
{
864+
id: 'Occult Crescent Phantom Job Tracker',
865+
// count also contains a Phantom Job id and level, it's supposed to be two bytes but has weird padding in logs
866+
// Expecting first two characters to be part of Phantom Job id, and the later two to be the level
867+
// First digit is the job:
868+
// Dancer = F
869+
// Gladiator = E
870+
// Mystic Knight = D
871+
// Thief = C
872+
// Oracle = B
873+
// Chemist = A
874+
// Cannoneer = 9
875+
// Time Mage = 8
876+
// Geomancer = 7
877+
// Bard = 6
878+
// Samurai = 5
879+
// Ranger = 4
880+
// Monk = 3
881+
// Berserker = 2
882+
// Knight = 1
883+
// Freelancer = null
884+
// Freelancer level is accumulation of maxed jobs +1, can also be inferred from stacks of Phantom Mastery (1082)
885+
type: 'GainsEffect',
886+
netRegex: { effectId: [...phantomJobEffectIds], capture: true },
887+
condition: Conditions.targetIsYou(),
888+
run: (data, matches) => {
889+
data.phantomJob = matches.effectId;
890+
const jobData = matches.count?.padStart(4, '0');
891+
892+
// Assuming this isn't possible given the filter on statuses
893+
if (jobData === undefined)
894+
return;
895+
896+
data.phantomJobLevel = parseInt(jobData.slice(2), 16);
897+
},
898+
},
696899
{
697900
id: 'Occult Crescent Forked Tower: Blood Clear Data',
698901
type: 'SystemLogMessage',
@@ -762,6 +965,29 @@ const triggerSet: TriggerSet<Data> = {
762965
netRegex: { source: 'Crescent Berserker', id: 'A6C3', capture: false },
763966
response: Responses.getBehind(),
764967
},
968+
{
969+
id: 'Occult Crescent Crescent Berserker Damage Up',
970+
// Crescent Berserker gains Damage Up (20s) from Channeled Rage (7846)
971+
// Crescent Berserker gains Damage Up (40s) from Heightened Rage (93B1)
972+
type: 'GainsEffect',
973+
netRegex: { effectId: '3D', target: 'Crescent Berserker', capture: true },
974+
condition: (data) => {
975+
if (data.phantomJob === undefined || data.phantomJobLevel === undefined)
976+
return false;
977+
return phantomCanDispel(data.phantomJob, data.phantomJobLevel);
978+
},
979+
infoText: (_data, matches, output) => output.dispel!({ name: matches.target }),
980+
outputStrings: {
981+
dispel: {
982+
en: 'Dispel ${name}',
983+
de: 'Entferne ${name}',
984+
fr: 'Dissipez ${name}',
985+
ja: '${name}にバフ解除',
986+
cn: '驱散 ${name} 的BUFF',
987+
ko: '${name} 버프 해제',
988+
},
989+
},
990+
},
765991
{
766992
id: 'Occult Crescent Hinkypunk Dread Dive',
767993
type: 'StartsUsing',
@@ -811,6 +1037,28 @@ const triggerSet: TriggerSet<Data> = {
8111037
netRegex: { source: 'Neo Garula', id: 'A0E5', capture: true },
8121038
response: Responses.tankBuster(),
8131039
},
1040+
{
1041+
id: 'Occult Crescent Neo Garula Damage Up',
1042+
// Neo Garula gains Damage Up (60s) after casting Agitated Groan
1043+
type: 'GainsEffect',
1044+
netRegex: { effectId: '489', target: 'Neo Garula', capture: true },
1045+
condition: (data) => {
1046+
if (data.phantomJob === undefined || data.phantomJobLevel === undefined)
1047+
return false;
1048+
return phantomCanDispel(data.phantomJob, data.phantomJobLevel);
1049+
},
1050+
infoText: (_data, matches, output) => output.dispel!({ name: matches.target }),
1051+
outputStrings: {
1052+
dispel: {
1053+
en: 'Dispel ${name}',
1054+
de: 'Entferne ${name}',
1055+
fr: 'Dissipez ${name}',
1056+
ja: '${name}にバフ解除',
1057+
cn: '驱散 ${name} 的BUFF',
1058+
ko: '${name} 버프 해제',
1059+
},
1060+
},
1061+
},
8141062
{
8151063
id: 'Occult Crescent Lion Rampant Fearsome Glint',
8161064
type: 'StartsUsing',
@@ -3307,6 +3555,15 @@ const triggerSet: TriggerSet<Data> = {
33073555
// This will count until all 12 have started casting
33083556
type: 'StartsUsing',
33093557
netRegex: { source: 'Tower Idol', id: 'A61F', capture: true },
3558+
condition: (data) => {
3559+
if (
3560+
data.phantomJob === undefined ||
3561+
data.phantomJobLevel === undefined ||
3562+
phantomCanFreeze(data.phantomJob, data.phantomJobLevel)
3563+
)
3564+
return true;
3565+
return false;
3566+
},
33103567
promise: async (data, matches) => {
33113568
const combatants = (await callOverlayHandler({
33123569
call: 'getCombatants',
@@ -4885,7 +5142,16 @@ const triggerSet: TriggerSet<Data> = {
48855142
// TODO: Cleanse call for Doom, but it is not yet logged, it's probably 11CE?
48865143
type: 'GainsEffect',
48875144
netRegex: { effectId: '115C', capture: true },
4888-
condition: Conditions.targetIsYou(),
5145+
condition: (data, matches) => {
5146+
if (
5147+
(data.me === matches.target) &&
5148+
(data.phantomJob === undefined ||
5149+
data.phantomJobLevel === undefined ||
5150+
phantomCanCleanse(data.phantomJob, data.phantomJobLevel))
5151+
)
5152+
return true;
5153+
return false;
5154+
},
48895155
// 25s - 20s, plus some delay for buff/debuff propagation
48905156
delaySeconds: (_data, matches) => parseFloat(matches.duration) - 20 + 0.5,
48915157
suppressSeconds: 1,

0 commit comments

Comments
 (0)