From afd6fc75cd22076685d93a49a189ff11109b1e4c Mon Sep 17 00:00:00 2001 From: Rakibei Date: Sun, 2 Aug 2026 18:01:46 +0200 Subject: [PATCH 1/4] Added config to hide weather, Added function for alternating cooldowns --- ui/eureka/eureka.css | 9 ++ ui/eureka/eureka.ts | 161 ++++++++++++++++++++++------------- ui/eureka/eureka_config.ts | 8 ++ ui/eureka/zone_north_horn.ts | 8 +- 4 files changed, 123 insertions(+), 63 deletions(-) diff --git a/ui/eureka/eureka.css b/ui/eureka/eureka.css index 1e68c9e606b..4e2917abdc0 100644 --- a/ui/eureka/eureka.css +++ b/ui/eureka/eureka.css @@ -284,11 +284,20 @@ padding-top: 5px; } +#label-weather-container.hidden { + display: none; +} + #label-time { padding-bottom: 5px; border-bottom: 2px dashed rgb(255 255 255 / 30%); } +#label-time.weather-hidden { + padding-bottom: 0; + border-bottom: none; +} + .aspect-ratio-anemos #label-container { top: 0%; } diff --git a/ui/eureka/eureka.ts b/ui/eureka/eureka.ts index f4862017ec8..2ec95ed24ac 100644 --- a/ui/eureka/eureka.ts +++ b/ui/eureka/eureka.ts @@ -66,6 +66,8 @@ type NMInfo = { x: number; y: number; respawnMinutes?: number; + partnerFateId?: number; + partnerFateRespawnMinutes?: number; // Modified element?: HTMLElement; @@ -117,6 +119,7 @@ const defaultEurekaConfigOptions = { FlagTimeoutMs: 90, CompleteNamesSTQ: false, EnrichedSTQ: false, + HideWeather: false, PopNoiseForNM: true, PopNoiseForBunny: true, PopNoiseForSkirmish: false, @@ -546,8 +549,40 @@ class EurekaTracker { } } + StartPartnerFateRespawnTimer(fate: NMInfo) { + if (!fate.partnerFateId || !fate.partnerFateRespawnMinutes) + return; + + const partnerFate = Object.values(this.nms).find( + (nm) => nm.fateId === fate.partnerFateId, + ); + + if (!partnerFate) { + this.DebugPrint( + `Partner FATE ${fate.partnerFateId} not found for ${this.TransByDispLang(fate.label)}`, + ); + return; + } + + partnerFate.respawnTimeMsLocal = Date.now() + fate.partnerFateRespawnMinutes * 60 * 1000; + + partnerFate.element?.classList.remove('nm-hidden'); + partnerFate.element?.classList.remove('nm-pop'); + partnerFate.element?.classList.add('nm-down'); + + if (partnerFate.progressElement) + partnerFate.progressElement.innerText = ''; + + this.DebugPrint( + `Started ${fate.partnerFateRespawnMinutes} minute timer for partner FATE: ${ + this.TransByDispLang(partnerFate.label) + }`, + ); + } + OnFateKill(fate: NMInfo) { this.DebugPrint(`OnFateKill: ${this.TransByDispLang(fate.label)}`); + this.StartPartnerFateRespawnTimer(fate); this.UpdateTimes(); if (fate.element?.classList.contains('nm-pop')) { if (this.zoneInfo?.onlyShowInactiveWithExplicitRespawns && !fate.respawnMinutes) @@ -586,71 +621,79 @@ class EurekaTracker { return; const nowMs = +new Date(); - const primaryWeatherList = this.zoneInfo?.primaryWeather; - const currentWeather = getWeather(nowMs, zoneId); - if (primaryWeatherList) { - for (let i = 0; i < numWeatherElem; ++i) { - const iconElem = document.getElementById(`label-weather-icon${i}`); - const textElem = document.getElementById(`label-weather-text${i}`); - if (!iconElem || !textElem) - throw new UnreachableCode(); - iconElem.innerHTML = ''; - textElem.innerHTML = ''; - } + const weatherContainer = document.getElementById('label-weather-container'); + const labelTime = document.getElementById('label-time'); - primaryWeatherList.forEach((primaryWeather, i) => { - const weatherIcon = gWeatherIcons[primaryWeather]; - let weatherStr = ''; - if (currentWeather === primaryWeather) { - const stopTime = findNextWeatherNot(nowMs, zoneId, primaryWeather); - weatherStr = this.TransByDispLang(this.options.timeStrings.weatherFor)(nowMs, stopTime); - } else { - const startTime = findNextWeather(nowMs, zoneId, primaryWeather); - if (startTime !== undefined) - weatherStr = this.TransByDispLang(this.options.timeStrings.weatherIn)(nowMs, startTime); - } - const iconElem = document.getElementById(`label-weather-icon${i}`); - const textElem = document.getElementById(`label-weather-text${i}`); - if (!iconElem || !textElem) - throw new UnreachableCode(); + if (!weatherContainer || !labelTime) + throw new UnreachableCode(); + + weatherContainer.classList.toggle('hidden', this.options.HideWeather); + labelTime.classList.toggle('weather-hidden', this.options.HideWeather); + + if (!this.options.HideWeather) { + const primaryWeatherList = this.zoneInfo?.primaryWeather; + const currentWeather = getWeather(nowMs, zoneId); + + if (primaryWeatherList) { + primaryWeatherList.forEach((primaryWeather, i) => { + const weatherIcon = gWeatherIcons[primaryWeather]; + let weatherStr = ''; + if (currentWeather === primaryWeather) { + const stopTime = findNextWeatherNot(nowMs, zoneId, primaryWeather); + weatherStr = this.TransByDispLang(this.options.timeStrings.weatherFor)(nowMs, stopTime); + } else { + const startTime = findNextWeather(nowMs, zoneId, primaryWeather); + if (startTime !== undefined) { + weatherStr = this.TransByDispLang(this.options.timeStrings.weatherIn)( + nowMs, + startTime, + ); + } + } + const iconElem = document.getElementById(`label-weather-icon${i}`); + const textElem = document.getElementById(`label-weather-text${i}`); + if (!iconElem || !textElem) + throw new UnreachableCode(); + + iconElem.innerHTML = weatherIcon ?? ''; + textElem.innerHTML = weatherStr; + }); + } else if (currentWeather !== undefined) { + const stopTime = findNextWeatherNot(nowMs, zoneId, currentWeather); + const weatherIcon = gWeatherIcons[currentWeather]; + let weatherStr = this.TransByDispLang(this.options.timeStrings.weatherFor)(nowMs, stopTime); + + const iconElem = document.getElementById('label-weather-icon0'); + const textElem = document.getElementById('label-weather-text0'); - iconElem.innerHTML = weatherIcon ?? ''; - textElem.innerHTML = weatherStr; - }); - } else if (currentWeather !== undefined) { - const stopTime = findNextWeatherNot(nowMs, zoneId, currentWeather); - const weatherIcon = gWeatherIcons[currentWeather]; - let weatherStr = this.TransByDispLang(this.options.timeStrings.weatherFor)(nowMs, stopTime); - - const iconElem = document.getElementById(`label-weather-icon0`); - const textElem = document.getElementById(`label-weather-text0`); - if (!iconElem || !textElem) - throw new UnreachableCode(); - iconElem.innerHTML = weatherIcon ?? ''; - textElem.innerHTML = weatherStr; - - // round up current time - let lastTime = nowMs; - let lastWeather = currentWeather; - for (let i = 1; i < 5; ++i) { - const startTime = findNextWeatherNot(lastTime, zoneId, lastWeather); - if (startTime === undefined) - continue; - const weather = getWeather(startTime + 1, zoneId); - if (weather === undefined) - continue; - const weatherIcon = gWeatherIcons[weather]; - weatherStr = this.TransByDispLang(this.options.timeStrings.weatherIn)(nowMs, startTime); - - const iconElem = document.getElementById(`label-weather-icon${i}`); - const textElem = document.getElementById(`label-weather-text${i}`); if (!iconElem || !textElem) throw new UnreachableCode(); - iconElem.innerHTML = weatherIcon ?? ''; textElem.innerHTML = weatherStr; - lastTime = startTime; - lastWeather = weather; + + let lastTime = nowMs; + let lastWeather = currentWeather; + + for (let i = 1; i < numWeatherElem; ++i) { + const startTime = findNextWeatherNot(lastTime, zoneId, lastWeather); + if (startTime === undefined) + continue; + const weather = getWeather(startTime + 1, zoneId); + if (weather === undefined) + continue; + const weatherIcon = gWeatherIcons[weather]; + weatherStr = this.TransByDispLang(this.options.timeStrings.weatherIn)(nowMs, startTime); + + const iconElem = document.getElementById(`label-weather-icon${i}`); + const textElem = document.getElementById(`label-weather-text${i}`); + if (!iconElem || !textElem) + throw new UnreachableCode(); + + iconElem.innerHTML = weatherIcon ?? ''; + textElem.innerHTML = weatherStr; + lastTime = startTime; + lastWeather = weather; + } } } diff --git a/ui/eureka/eureka_config.ts b/ui/eureka/eureka_config.ts index 022e372f33c..bb4907c2b5b 100644 --- a/ui/eureka/eureka_config.ts +++ b/ui/eureka/eureka_config.ts @@ -70,6 +70,14 @@ UserConfig.registerOptions('eureka', { type: 'checkbox', default: false, }, + { + id: 'HideWeather', + name: { + en: 'Hide weather information', + }, + type: 'checkbox', + default: false, + }, { id: 'PopNoiseForNM', name: { diff --git a/ui/eureka/zone_north_horn.ts b/ui/eureka/zone_north_horn.ts index f8013006ae8..abf72d59fb3 100644 --- a/ui/eureka/zone_north_horn.ts +++ b/ui/eureka/zone_north_horn.ts @@ -5,8 +5,6 @@ import northHornMap from './northhorn.png'; // https://v2.xivapi.com/api/search?sheets=Fate&limit=9999&fields=Name@en,Name@de,Name@fr,Name@ja&query=EurekaFate=3 - Fates // https://v2.xivapi.com/api/search?sheets=MKDLore&limit=9999&fields=Name@en,Name@de,Name@fr,Name@ja&query=Unknown5=4 - Occult Records -/* TODO: Write code for alternating cooldowns (e.g., for the "Pots" fates) */ - export const zoneInfoNorthHorn: EurekaZoneInfo = { mapImage: northHornMap, mapWidth: 2048, @@ -495,7 +493,8 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { y: 25.8, bunny: true, fateId: 2073, - respawnMinutes: 60, + partnerFateId: 2072, + partnerFateRespawnMinutes: 30, }, daylightpottery: { label: { @@ -519,7 +518,8 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { y: 11.6, bunny: true, fateId: 2072, - respawnMinutes: 60, + partnerFateId: 2073, + partnerFateRespawnMinutes: 30, }, /* theforkedtowermagic: { label: { From a3583d0a7c99cc82fb02cc7ce692b4ed04f9a305 Mon Sep 17 00:00:00 2001 From: Rakibei Date: Sun, 2 Aug 2026 18:05:54 +0200 Subject: [PATCH 2/4] eureka: added alternating cooldown to south horn pots --- ui/eureka/zone_south_horn.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/eureka/zone_south_horn.ts b/ui/eureka/zone_south_horn.ts index 129335407a0..41b96005fc9 100644 --- a/ui/eureka/zone_south_horn.ts +++ b/ui/eureka/zone_south_horn.ts @@ -450,7 +450,8 @@ export const zoneInfoSouthHorn: EurekaZoneInfo = { y: 17.1, bunny: true, fateId: 1976, - respawnMinutes: 60, + partnerFateId: 1977, + respawnMinutes: 30, }, pleadingpots: { label: { @@ -476,7 +477,8 @@ export const zoneInfoSouthHorn: EurekaZoneInfo = { y: 32, bunny: true, fateId: 1977, - respawnMinutes: 60, + partnerFateId: 1976, + respawnMinutes: 30, }, /* theforkedtowerblood: { label: { From 11100fe179343a297a015e7aad5830fba74d50a4 Mon Sep 17 00:00:00 2001 From: Rakibei Date: Sun, 9 Aug 2026 13:11:47 +0200 Subject: [PATCH 3/4] eureka: corrected occult note ids to match journal --- ui/eureka/zone_north_horn.ts | 136 +++++++++++++++++------------------ 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/ui/eureka/zone_north_horn.ts b/ui/eureka/zone_north_horn.ts index abf72d59fb3..e7e18ec77df 100644 --- a/ui/eureka/zone_north_horn.ts +++ b/ui/eureka/zone_north_horn.ts @@ -19,7 +19,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { mapToPixelYConstant: -50, fieldNotes: [ { - id: 1, + id: 11, name: { en: 'Persistent Pots', de: 'Der Wunderpott', @@ -39,7 +39,67 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 2, + id: 33, + name: { + en: 'Arbatel', + de: 'Arbatel', + fr: 'Arbatel', + ja: 'アルバテル', + cn: '古术魔典', + ko: '아르바텔', + }, + shortName: { + en: 'Arbatel', + de: 'Arbatel', + fr: 'Arbatel', + ja: 'アルバテル', + cn: '古术魔典', + ko: '아르바텔', + }, + rarity: 0, + }, + { + id: 34, + name: { + en: 'Tiny Mage', + de: 'Winzige Magier', + fr: 'Petit mage', + ja: 'タイニーメイジ', + cn: '小小法师', + ko: '타이니 메이지', + }, + shortName: { + en: 'Mage', + de: 'Magier', + fr: 'Mage', + ja: 'メイジ', + cn: '小小法师', + ko: '메이지', + }, + rarity: 0, + }, + { + id: 35, + name: { + en: 'Algol', + de: 'Algol', + fr: 'Algol', + ja: 'アルゴル', + cn: '阿尔戈尔', + ko: '알골', + }, + shortName: { + en: 'Algol', + de: 'Algol', + fr: 'Algol', + ja: 'アルゴル', + cn: '阿尔戈尔', + ko: '알골', + }, + rarity: 0, + }, + { + id: 38, name: { en: 'Metamorph', de: 'Metamorpha', @@ -59,7 +119,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 3, + id: 41, name: { en: 'Pallmagia', de: 'Bleicher Magia', @@ -79,7 +139,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 4, + id: 42, name: { en: 'Phantom Necromancer', de: 'Kreszenter Nekromant', @@ -99,7 +159,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 5, + id: 44, name: { en: 'The Abductor', de: 'Abduktor', @@ -119,7 +179,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 6, + id: 48, name: { en: 'Claret Dragon', de: 'Rubrum-Drache', @@ -139,7 +199,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 7, + id: 50, name: { en: 'The Alabaster Blade', de: 'Alabaster-Klinge', @@ -159,7 +219,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { rarity: 0, }, { - id: 8, + id: 53, name: { en: 'Conjured Calofisteri', de: 'Calofisteri-Doppelgängerin', @@ -178,66 +238,6 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { }, rarity: 0, }, - { - id: 9, - name: { - en: 'Arbatel', - de: 'Arbatel', - fr: 'Arbatel', - ja: 'アルバテル', - cn: '古术魔典', - ko: '아르바텔', - }, - shortName: { - en: 'Arbatel', - de: 'Arbatel', - fr: 'Arbatel', - ja: 'アルバテル', - cn: '古术魔典', - ko: '아르바텔', - }, - rarity: 0, - }, - { - id: 10, - name: { - en: 'Algol', - de: 'Algol', - fr: 'Algol', - ja: 'アルゴル', - cn: '阿尔戈尔', - ko: '알골', - }, - shortName: { - en: 'Algol', - de: 'Algol', - fr: 'Algol', - ja: 'アルゴル', - cn: '阿尔戈尔', - ko: '알골', - }, - rarity: 0, - }, - { - id: 11, - name: { - en: 'Tiny Mage', - de: 'Winzige Magier', - fr: 'Petit mage', - ja: 'タイニーメイジ', - cn: '小小法师', - ko: '타이니 메이지', - }, - shortName: { - en: 'Mage', - de: 'Magier', - fr: 'Mage', - ja: 'メイジ', - cn: '小小法师', - ko: '메이지', - }, - rarity: 0, - }, ], nms: { ragingthrall: { From 0d4bccc0d2983d1523dca8c36f028c0118e5021f Mon Sep 17 00:00:00 2001 From: Rakibei Date: Sun, 9 Aug 2026 13:13:02 +0200 Subject: [PATCH 4/4] eureka: corrected occult note ids to match journal v2 --- ui/eureka/zone_north_horn.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/eureka/zone_north_horn.ts b/ui/eureka/zone_north_horn.ts index e7e18ec77df..e4f2de1862d 100644 --- a/ui/eureka/zone_north_horn.ts +++ b/ui/eureka/zone_north_horn.ts @@ -480,7 +480,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '被吹飞的魔法罐', ko: '날아가는 마법 항아리', }, - fieldNotes: 1, + fieldNotes: 11, shortLabel: { en: 'Pots', de: 'Pott', @@ -505,7 +505,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '被欺负的魔法罐', ko: '찾아다니는 마법 항아리', }, - fieldNotes: 1, + fieldNotes: 11, shortLabel: { en: 'Pots', de: 'Pott', @@ -574,7 +574,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '魔女复制体——卡洛菲斯提莉二重身', ko: '마녀의 복제 \'칼로피스테리 분신\'', }, - fieldNotes: 8, + fieldNotes: 53, shortLabel: { en: 'Trouble', de: 'Doppelt', @@ -597,7 +597,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '纯白守护者——雪石膏之剑', ko: '하얀 수호자 \'설화석고 검사\'', }, - fieldNotes: 7, + fieldNotes: 50, shortLabel: { en: 'Quarried', de: 'Schwert', @@ -620,7 +620,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '禁书化形——古术魔典', ko: '금단의 마도서 \'아르바텔\'', }, - fieldNotes: 9, + fieldNotes: 33, shortLabel: { en: 'Folios', de: 'Wissen', @@ -643,7 +643,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '暗红尸骸——赤龙', ko: '검붉은 시룡 \'루브룸 드래곤\'', }, - fieldNotes: 6, + fieldNotes: 48, shortLabel: { en: 'Resurgence', de: 'Kralle', @@ -666,7 +666,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '暴食咒鬼——阿尔戈尔', ko: '폭식의 저주괴물 \'알골\'', }, - fieldNotes: 10, + fieldNotes: 35, shortLabel: { en: 'Diet', de: 'Unheil', @@ -733,7 +733,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '天道好轮回——魔亡灵法师', ko: '사령을 다루는 망령 \'마기 강령술사\'', }, - fieldNotes: 4, + fieldNotes: 42, shortLabel: { en: 'Artistry', de: 'Tod', @@ -778,7 +778,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '诅咒的继承者——惨白魔人', ko: '저주를 잇는 자 \'창백한 마기아\'', }, - fieldNotes: 3, + fieldNotes: 41, shortLabel: { en: 'Behavior', de: 'Verflucht', @@ -824,7 +824,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '孤岛的绑架犯——诱拐魔', ko: '외딴섬의 납치범 \'유괴자\'', }, - fieldNotes: 5, + fieldNotes: 44, shortLabel: { en: 'Wind', de: 'Winde', @@ -869,7 +869,7 @@ export const zoneInfoNorthHorn: EurekaZoneInfo = { cn: '拟态使魔——变形法师', ko: '변화하는 사역마 \'메타몰퍼\'', }, - fieldNotes: 2, + fieldNotes: 38, shortLabel: { en: 'Imitators', de: 'Mimikry',