diff --git a/.history/CHANGELOG_20260222010122.md b/.history/CHANGELOG_20260222010122.md new file mode 100644 index 0000000..2ca30dd --- /dev/null +++ b/.history/CHANGELOG_20260222010122.md @@ -0,0 +1,143 @@ +# Changelog + +## v9.8.0 + +### Fixes +- **[Quest]** Modified `BAG_UPDATE_DELAYED` to trigger a quest log update and instantly refresh the tracker when items are deposited directly into bags by autoloot bots that bypass standard item events. +- **[Map]** Fixed an issue where "Special Objectives" (e.g., source item drops) failed to check player bag contents upon completion, causing their map icons to linger indefinitely until the main quest was turned in. + +## v9.7.12 + +### Fixes +- **[Quest]** Fixed an issue where tracking icons for dynamically updated custom quests like Peak Predators would prematurely disappear due to sync delays on WotLK servers. +- **[Map]** Resolved a frame pool leak that prevented the yellow Finisher icon from appearing immediately on the map after turning in or completing a quest. + + +## v9.7.11 + +### Fixes +- **[Quest]** Fixed an issue where quest objective icons and waypoints failed to clear from the map after a quest was completed or abandoned. +- **[Tracker]** Prevented quests from falsely flagging as complete and wiping tracking data when the World of Warcraft server drops its objective arrays. Fixes "Peak Predators" icons disappearing randomly. +- **[Database]** Corrected an error that improperly identified Item Finishers as GameObject Finishers on custom servers, causing map pinpointing errors (e.g. `[QuestieDB:GetObject] rawdata is nil for objectID:`). +- **[Database]** Appended correct fallback spawn data for the "Thorim" listen bunny (`NPC 30514`) so Sibling Rivalry's turn in/listen point functions correctly on WotLK clients. + +## v9.7.10 + +### Fixes +- **[Arrow]** Refactored Arrow logic to drastically improve target distance calculations and prioritize targets correctly based on the player's current zone. +- **[Arrow]** Fixed a bug where the Arrow would mistakenly point to previously completed objective locations instead of the Quest Finisher's exact location. +- **[Tracker]** Fixed `QuestieDB.IsComplete` edge case returning incomplete incorrectly; now verifies `numFulfilled == numRequired` to immediately acknowledge completed quests while awaiting the server flag. +- **[Tracker]** Resolved false-positive "broken quest log" errors spamming chat on WotLK servers by correctly handling API responses for trackable objectives. +- **[Tracker]** Demoted harmless WotLK objective cache count mismatches from Error to Debug visibility level to eliminate chat spam on login. +- **[Quest]** Fixed quest arrow pointing to the key-drop NPC after using all consumable quest keys (e.g. Cold Iron Key for "They Took Our Men!" quest 12843). When a quest uses a key item that is consumed on interaction, it leaves the bag and `CheckQuestSourceItem` returns false, incorrectly triggering a quest reset that re-drew the key source NPC on the map. Now checks if all tracked objectives are already `Completed=true` before applying the reset, preventing the spurious icon. + +## v9.7.9 + +### Fixes +- **[Quest]** Fixed tracker objective count staying stale (e.g. stuck at 14/16 when quest is complete) when an autoloot bot bypasses the standard loot frame. Registered `BAG_UPDATE_DELAYED` event to force a full quest log scan on bag changes, catching progress updates that `QUEST_WATCH_UPDATE` would normally fire for manual looting. + +## v9.7.8 + +### Fixes +- **[Quest]** Fixed "There was an error populating objectives" error in chat for `triggerEnd` quests with no map coordinates (e.g., "complete N quests in zone"). `_RegisterObjectiveTooltips` now silently returns for `event`-type objectives with no `spawnList` — these have nothing to register a tooltip for. + +## v9.7.7 + +### Fixes +- **[Quest]** Fixed "Missing event data for Objective" error appearing in chat for all `triggerEnd` quests with no map coordinates (e.g., "complete N quests in zone" types). Nil coordinates are valid for server-tracked objectives with no pin — the handler now returns silently instead of logging a visible error. + +## v9.7.6 + +### Fixes +- **[Database]** Fixed "Missing objective data" error for all "complete N quests in zone" quest types (IDs 50151, 50145, 50098, 50100, 50149, 50099, 50108, 50111, 50150). Added `triggerEnd` (`[9]`) field so Questie correctly registers the server-tracked objective without drawing map pins. + +## v9.7.5 + +### Fixes +- **[Quest]** Fixed objective pins/icons persisting on the map after a quest is completed or abandoned. Added `CleanupRemovedQuestsFallback` which diffs Questie's quest log against the game's actual quest log on every `QUEST_LOG_UPDATE` and correctly calls `CompleteQuest` or `AbandonedQuest` for any quest that silently disappeared, ensuring map icons are removed. +- **[Quest]** Fixed re-accepted repeatable custom quests (e.g., Stormforged Scales) not showing objective icons on the map after being accepted a second time. + +### New Quests +- **[Database]** Added **Storm Peak Orders** (ID 50150) - *The Storm Peaks* + - Objective: Complete any 6 quests in The Storm Peaks. +- **[Database]** Added **Wild Basin** (ID 50094) - *Sholazar Basin* + - Objective: Kill 75 Beasts. Includes 29 Beast NPC types with full spawn coordinates. + - NPCs: King Krush, Shardhorn Rhino, Aotona, Pitch, Serfex the Reaver, Dreadsaber, Hardknuckle Matriarch, Shango, Venomtip, Bushwhacker, Hardknuckle Charger, Ravenous Mangal Crocolisk, Farunn, Zeptek the Destroyer, Goretalon Matriarch, Sapphire Hive Wasp, Emperor Cobra, Sapphire Hive Drone, Shattertusk Bull, Siltslither Eel, Spirit of Atha, Stranded Thresher, Mangal Crocolisk, Spirit of Koosu, Longneck Grazer, Goretalon Roc, Sapphire Hive Queen, Spirit of Ha-Khalan, Bittertide Hydra + +## v9.7.4 + +### Fixes +- **[Tooltips]** Fixed "attempt to concatenate local 'name' (a nil value)" error when quest starters/finishers have missing names in the database. +- **[Database]** Added missing spawn coordinates for Quest 50031 "Stormbound" elementals in Storm Peaks (zone 67). +- **[Database]** Fixed "Unknown Zone" issue for custom quests by correcting Zone ID index usage (swapped `[6]` RequiredRaces for `[17]` ZoneID). +- **[Database]** Corrected Dragonblight Zone ID in custom quest definitions. + +### New Quests +- **[Database]** Added **Morogh Missions** (ID 50098) - *Dun Morogh* + - Objective: Complete any 6 quests in Dun Morogh. Auto-completes upon reaching the objective. +- **[Database]** Added **Azuremyst Aid** (ID 50100) - *Azuremyst Isle* + - Objective: Complete any 6 quests in Azuremyst Isle. Auto-completes upon reaching the objective. +- **[Database]** Added **Stormforged Scales** (ID 50066) - *The Storm Peaks* + - Objective: Kill 30 Dragonkin. Includes 8 Dragonkin NPC types with full spawn coordinates. +- **[Database]** Added **Peak Predators** (ID 50095) - *The Storm Peaks* + - Objective: Kill 75 Beasts. Includes 24 Beast NPC types with full spawn coordinates. +- **[Database]** Added **Peak Predators** (ID 50096) - *Icecrown* + - Objective: Kill 75 Beasts. Includes 12 Beast NPC types with full spawn coordinates. +- **[Database]** Added **Icecrown Advance** (ID 50151) - *Icecrown* + - Objective: Complete any 6 quests in Icecrown. Auto-completes upon reaching the objective. +- **[Database]** Added **Storm Peaks Trophy** (ID 50205) - *The Storm Peaks* + - Objective: Kill 1 Rare in The Storm Peaks. Includes 4 Rare NPC types (Skoll, Time-Lost Proto-Drake, Vyragosa, Dirkee) with full spawn coordinates. + +## v9.7.3 + +### New Features +- **[Database]** Implemented **Ebonhold Database Module**. + - Created dedicated `Database/Ebonhold/` structure for custom server data. + - Added `EbonholdLoader` to inject custom Quests, NPCs, Objects, and Items as overrides. + - **Note:** This structure preserves custom data during upstream Questie updates. +- **[Objectives]** Implemented **Automated Text Retrieval**. + - Questie now attempts to fetch quest text from the server at runtime for custom quests that are missing from the database. + - Added "Objectives Board" (ID 600600) as a global quest starter. + +### Quests (Custom Content) +- **[New]** Added **Heart of the Dragonflights** (ID 50064) - *Dragonblight* + - Objective: Kill 30 Dragonkin. Includes 49 Dragonkin NPC types. +- **[New]** Added **Skies of Blade's Edge** (ID 50060) - *Blade's Edge Mountains* + - Objective: Kill 75 Dragonkin. Includes 17 Dragonkin NPC types. +- **[New]** Added **Shadowed Beasts** (ID 50087) - *Shadowmoon Valley* + - Objective: Kill 75 Beasts. Includes 25 Beast NPC types. +- **[New]** Added **Forest Stalkers** (ID 50083) - *Terokkar Forest* + - Objective: Kill 75 Beasts. Includes 53 Beast NPC types. +- **[New]** Added **Savage Heights** (ID 50085) - *Blade's Edge Mountains* + - Objective: Kill 75 Beasts. Includes 47 Beast NPC types. +- **[New]** Added **Unstable Fauna** (ID 50086) - *Netherstorm* + - Objective: Kill 75 Beasts. Includes 18 Beast NPC types. +- **[New]** Added **Elemental Balance** (ID 50026) - *Nagrand* + - Objective: Kill 30 Elementals. Includes 12 Elemental NPC types. +- **[New]** Added **Redridge Trophy** (ID 50160) and **Zangarmarsh Trophy** (ID 50192). + +### New Ascension Quests +- **Westfall**: Agria's Medicine, Seven Years of Bad Luck, Worm-Eaten Apple, Goldshire's Generosity, Bookworm, Knowledge Corrupts, The Ruins of Northshire, Accursed Sisterhood, Words That Shepherd Madness, Oracular Idol, A Betrayal Within, The Maid I Left Behind, The Saddest Among Us, The Threat Swept Downstream, Stay a While, Defias Disruption. +- **Dun Morogh**: A Small Mistake, We Found Her!, The Scout's Favor, Old Mirsinth, Smoke on the Wind, A Promising Path, A Fitting Disguise, His Radiant Majesty, Deciphering Radiation, Soaking the Masses, Sever the Right Hand, A Growing Business, Thunderbrew's Hop, Stay a While, Live-Fire Demo, Bots on Strike, A Brother's Betrayal, The True Story, Timber for the Coldhewn, Icehide the Unbroken. +- **Teldrassil**: The Carrion Road, The Sister Who Never Returned, Finding the Good Meat, Transsubstantiating the Flesh, Communion Banquet, A Trail of Petals, Restless entrails, A Dark Warning, The Aid of Theren-Dion, No Place for Scavengers, Termites in Teldrassil, Stay a While, Elydna's Heirloom. +- **Durotar**: To Find a Cure, A Dangerous Sample, Knowledge of the Centaurs, Those Who Fell, The Way is Shut, The Sinister Triad, So That He May Hear Again, A Sinister Ritual, Innocents for Sinners, Unease Makes Tongues Wag, A Door Left Ajar, Esgramor's Master, Shinies!, Echoes of Hirsutta, Auction the Past, Stay a While, Durotar's Dire Drought, The Queen's Decree, Avianna's Rose, The Last Piece, Reversion. +- **Tirisfal Glades**: Rude Awakening, Marla's Last Wish, Monsters With Noble Intentions, Restless Family Members, An Unspeakable Secret, A Noble Heritage, The True Heir of the Cains, The Friends We Make Along the Way, I'm Home, Apothecary Flemer, The Nature of Freedom, Spotless Standing, Stay a While, More Than the Sum of its Parts, Scarlet Correspondence, A Humble Duty, The Balnirs' Rest, Brewing Disarray, This Is Justice. +- **Mulgore**: Death and Tribute, Death and Exile, Death and Dishonor, Death and Justice, Death by Laughter, To Whom I Devote, Fighting Over Carrion, Smoke on the Horizon, Amphora of Sacred Water, Stay a While, Exile of Embers, The Smoke that Remembers, The Circle’s Rite. + +### New Ebonhold Quests +- **Outland**: Elemental Balance, Savage Heights, Unstable Fauna, Forest Stalkers, Marsh Predators, Skies of Blade's Edge, Shadowed Beasts, Zangarmarsh Trophy. +- **Northrend**: Tundra Turbulence, Stormbound, Dragonblight Trophy, Heart of the Dragonflights, Peak Predators, Icecrown Advance, Storm Peaks Trophy, Stormforged Scales, Fjord Front, Wild Basin, Grizzly Trophy, Zul'Drak Trophy, Basin Expeditions. +- **Azeroth**: Redridge Trophy, Morogh Missions, Azuremyst Aid, Shadow of Teldrassil, Trials of Durotar, Song of the Woods, Morogh Trophy, Tirisfal Trophy. + +### Fixes +- **[Tracker]** **Combat Update Fix**: Tracker now updates objectives immediately during combat without causing Lua errors or taint. +- **[Tracker]** **Bag Update Fix**: Quest progress now updates immediately when looting items (fixes delay with loot bots). +- **[Arrow]** **Refined Visibility Logic**: + - **Auto Nearby**: Arrow correctly defaults to showing the nearest quest when no quests are tracked. + - **Zone Filter**: In "Auto Mode", the arrow hides if the nearest quest is in a different zone. + - **Instance Filter**: Arrow explicitly hides if the target is in a different instance. +- **[Map]** Fixed an issue where completed quest icons would persist on the map (`RequestMapUpdate` logic). +- **[Database]** Updated `wotlkNpcDB.lua` with scraped spawn data for 12 key beast NPCs in Terokkar Forest to ensure accuracy. +- **[Arrow]** Fixed a nil function error for `_CollectObjective` when processing incomplete quests. +- **[Arrow]** Fixed syntax issues that prevented `QuestieArrow` module from initializing correctly. +- **[Database]** Fixed a runtime crash in `ZoneDB` when encountering maps with no AreaId mapping (e.g., Kalimdor). diff --git a/.history/Localization/Translations/Options/Advanced_20260116015930.lua b/.history/Localization/Translations/Options/Advanced_20260116015930.lua new file mode 100644 index 0000000..7a5bc62 --- /dev/null +++ b/.history/Localization/Translations/Options/Advanced_20260116015930.lua @@ -0,0 +1,429 @@ +---@type l10n +local l10n = QuestieLoader:ImportModule("l10n") + +local advancedOptionsLocales = { + ["Advanced"] = { + ["ptBR"] = "Avançado", + ["ruRU"] = "Дополнительно", + ["deDE"] = "Erweitert", + ["koKR"] = "고급", + ["esMX"] = "Avanzado", + ["enUS"] = true, + ["zhCN"] = "进阶", + ["zhTW"] = "進階", + ["esES"] = "Avanzado", + ["frFR"] = "Avancé", + }, + ["Advanced Settings"] = { + ["ptBR"] = "Configurações avançadas", + ["ruRU"] = "Расширенные настройки", + ["deDE"] = "Erweiterte Einstellungen", + ["koKR"] = "고급 설정", + ["esMX"] = "Configuración avanzada", + ["enUS"] = true, + ["zhCN"] = "进阶选项", + ["zhTW"] = "進階設定", + ["esES"] = "Configuración avanzada", + ["frFR"] = "Configuration avancée", + }, + ["Enable Icon Limit"] = { + ["ptBR"] = "Ativar limite de ícone", + ["ruRU"] = "Лимит значков", + ["deDE"] = "Icons limitieren", + ["koKR"] = "아이콘 개수 제한 활성화", + ["esMX"] = "Habilitar límite de icono", + ["enUS"] = true, + ["zhCN"] = "启用图标限制", + ["zhTW"] = "啟用圖示限制", + ["esES"] = "Habilitar límite de icono", + ["frFR"] = "Limiter le nombre d'icônes", + }, + ["Icon Limit"] = { + ["ptBR"] = "Limite de ícone", + ["ruRU"] = "Лимит количества значков", + ["deDE"] = "Icon Limit", + ["koKR"] = "아이콘 개수 제한", + ["esMX"] = "Límite de icono", + ["enUS"] = true, + ["zhCN"] = "图标限制", + ["zhTW"] = "圖示數量限制", + ["esES"] = "Límite de icono", + ["frFR"] = "Limite d'icônes", + }, + ["Enable the limit of icons drawn per type."] = { + ["ptBR"] = "Ativa o limite de ícones desenhados por tipo.", + ["ruRU"] = "Включает ограничение количества значков, отображаемых для каждого типа", + ["deDE"] = "Aktiviert/Deaktiviert, ob die Menge an Icons pro Typ limitiert wird.", + ["koKR"] = "아이콘의 종류 별로 표시할 개수를 제한합니다.", + ["esMX"] = "Habilita el límite de iconos por tipo.", + ["enUS"] = true, + ["zhCN"] = "启用对每种类型图标的限制", + ["zhTW"] = "啟用每種類型繪製圖示的限制。", + ["esES"] = "Habilita el límite de iconos por tipo.", + ["frFR"] = "Active la limite au nombre d'icônes affichées d'un même type.", + }, + ["Limits the amount of icons drawn per type. ( Default: %s )"] = { + ["ptBR"] = "Limita a quantidade de ícones desenhados por tipo. ( Por defeito: %s )", + ["ruRU"] = "Регулировка ограничения количества значков, отображаемых для каждого типа (по умолчанию: %s)", + ["deDE"] = "Wie viele Icons pro Type gezeigt werden sollen. ( Standard: %s )", + ["koKR"] = "아이콘의 종류 별로 표시를 제한할 개수를 설정합니다. ( 기본값: %s )", + ["esMX"] = "Limita la cantidad de iconos por tipo. ( Predeterminado: %s)", + ["enUS"] = true, + ["zhCN"] = "限制每种类型图标数量 ( 默认: %s )", + ["zhTW"] = "限制每種類型繪製的圖示數量。(預設值: %s )", + ["esES"] = "Limita la cantidad de iconos por tipo. ( Predeterminado: %s)", + ["frFR"] = "Limite le nombre d'icônes affichées d'un même type. (Par défaut : %s)", + }, + ["Enable bug workarounds"] = { + ["ptBR"] = "Ativar soluções alternativas para bugs", + ["ruRU"] = "Обходные решения", + ["deDE"] = "Bug-Workarounds aktivieren", + ["koKR"] = false, + ["esMX"] = "Activar soluciones de errores", + ["enUS"] = true, + ["zhCN"] = "启用错误修正功能", + ["zhTW"] = "啟用錯誤修正功能", + ["esES"] = "Activar soluciones de errores", + ["frFR"] = "Activer les contournements de bugs", + }, + ["When enabled, Questie will hotfix vanilla UI bugs."] = { + ["ptBR"] = "Quando ativado, o Questie corrigirá bugs da interface do usuário do Vanilla.", + ["ruRU"] = "Когда включено, Questie будет исправлять ошибки классического интерфейса", + ["deDE"] = "Aktiviert/Deaktiviert, dass Questie Fehler im Vanilla UI behebt.", + ["koKR"] = false, + ["esMX"] = "Si está habilitado, Questie corregirá los errores de la interfaz de usuario.", + ["enUS"] = true, + ["zhCN"] = "启用时,任务提示插件会热修经典的界面错误。", + ["zhTW"] = "啟用時,任務位置提示插件會熱修經典的介面錯誤。", + ["esES"] = "Si está habilitado, Questie corregirá los errores de la interfaz de usuario.", + ["frFR"] = "Si activé, Questie corrigera les bugs de l'interface utilisateur Vanilla.", + }, + ["Developer Options"] = { + ["ptBR"] = "Opções do desenvolvedor", + ["ruRU"] = "Настройки разработчика", + ["deDE"] = "Entwickler-Einstellungen", + ["koKR"] = "개발자 설정", + ["esMX"] = "Opciones de desarrollador", + ["enUS"] = true, + ["zhCN"] = "开发选项", + ["zhTW"] = "開發者選項", + ["esES"] = "Opciones de desarrollador", + ["frFR"] = "Options de développement", + }, + ["Show Quest IDs"] = { + ["ptBR"] = "Mostrar IDs da missão", + ["ruRU"] = "Показать ID заданий", + ["deDE"] = "Zeige Quest-IDs", + ["koKR"] = "퀘스트 ID 표시", + ["esMX"] = "Mostrar ID de misión", + ["enUS"] = true, + ["zhCN"] = "显示任务ID", + ["zhTW"] = "顯示任務 ID", + ["esES"] = "Mostrar ID de misión", + ["frFR"] = "Afficher l'ID des quêtes", + }, + ["When this is checked, the ID of quests will show in tooltips and the tracker."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID das missões será exibido nas dicas de ferramentas e no rastreador.", + ["ruRU"] = "Когда включено, ID заданий будут отображаться во всплывающих подсказках и в трекере", + ["deDE"] = "Aktiviert/Deaktiviert, dass die Quest-ID in den Tooltips und dem Tracker angezeigt werden.", + ["koKR"] = "퀘스트의 ID를 툴팁과 추적기에 표시합니다.", + ["esMX"] = "Si está marcado, la ID de misiones mostrará en los tooltips y el rastreador.", + ["enUS"] = true, + ["zhCN"] = "勾选此选项后,任务ID将显示在鼠标提示和跟踪器中", + ["zhTW"] = "啟用時,會在浮動提示資訊和任務追蹤清單內顯示任務 ID。", + ["esES"] = "Si está marcado, la ID de misiones mostrará en los tooltips y el rastreador.", + ["frFR"] = "Active l'affichage de l'ID des quêtes dans les infobulles et le suivi des quêtes.", + }, + ["Enable Debug"] = { + ["ptBR"] = "Ativar depuração", + ["ruRU"] = "Режим отладки", + ["deDE"] = "Debugging aktivieren", + ["koKR"] = "디버그 활성화", + ["esMX"] = "Habilitar depurar", + ["enUS"] = true, + ["zhCN"] = "启用除错", + ["zhTW"] = "啟用除錯", + ["esES"] = "Habilitar depurar", + ["frFR"] = "Mode debug", + }, + ["Enable or disable debug functionality."] = { + ["ptBR"] = "Ativar ou desativar o recurso de depuração.", + ["ruRU"] = "Включить/отключить режим отладки", + ["deDE"] = "Aktiviert/Deaktiviert die Debugging Funktion.", + ["koKR"] = "디버그 기능을 사용합니다.", + ["esMX"] = "Habilita o deshabilita el modo depura.", + ["enUS"] = true, + ["zhCN"] = "启用或停用除错功能", + ["zhTW"] = "啟用或停用除錯的功能。", + ["esES"] = "Habilita o deshabilita el modo depura.", + ["frFR"] = "Active le mode debug.", + }, + ["Debug level to print"] = { + ["ptBR"] = "Nível de depuração para impressão", + ["ruRU"] = "Уровень отладки для вывода в чат", + ["deDE"] = "Debug-Level", + ["koKR"] = "디버그 레벨 출력", + ["esMX"] = "Nivel de depura a mostrar", + ["enUS"] = true, + ["zhCN"] = "除错等级", + ["zhTW"] = "除錯訊息等級", + ["esES"] = "Nivel de depura a mostrar", + ["frFR"] = "Niveau de debug", + }, + ["Hitting this button will reset all of the Questie configuration settings back to their default values. (Excluding Localization)"] = { + ["ptBR"] = "Se clicar nesse botão redefinirá todas as configurações do Questie para seus valores. (Não localização / tradução)", + ["ruRU"] = "Нажатие этой кнопки приведет к сбросу всех настроек аддона (за исключением языка)", + ["deDE"] = "Setzt alle Einstellungen von Questie auf den Standardwert zurück (Lokalisierung ausgeschlossen).", + ["koKR"] = "이 버튼을 누르면 모든 Questie 설정이 기본값으로 초기화됩니다. (언어 설정 제외)", + ["esMX"] = "Al hacer clic este botón restablecerá toda la configuración de Questie a los valores predeterminados. (Menos el idioma)", + ["enUS"] = true, + ["zhCN"] = "点击这个按钮将会使Questie除了本地化以外的所有设置重置为默认值。", + ["zhTW"] = "按下這個按鈕會將任務位置提示插件的所有設定重置成預設值。(語言設定除外)", + ["esES"] = "Al hacer clic este botón restablecerá toda la configuración de Questie a los valores predeterminados. (Menos el idioma)", + ["frFR"] = "Activer ce bouton réinitialisera tous les paramètres de Questie à leurs valeurs par défaut. (sauf choix de la langue)", + }, + ["Reset Questie"] = { + ["ptBR"] = "Redefinir Questie", + ["ruRU"] = "Сбросить настройки", + ["deDE"] = "Questie zurücksetzen", + ["koKR"] = "Quetie 초기화", + ["esMX"] = "Restablecer Questie", + ["enUS"] = true, + ["zhCN"] = "重置", + ["zhTW"] = "重置任務位置提示", + ["esES"] = "Restablecer Questie", + ["frFR"] = "Réinitialiser Questie", + }, + ["Reset Questie to the default values for all settings."] = { + ["ptBR"] = "Redefina a Questie para valores para todas as configurações.", + ["ruRU"] = "Сбросить настройки аддона к значениям по умолчанию", + ["deDE"] = "Setzt alle Einstellungen von Questie auf den Standardwert zurück.", + ["koKR"] = "모든 Questie 설정을 기본값으로 초기화합니다.", + ["esMX"] = "Restablece Questie a todos los valores predeterminados.", + ["enUS"] = true, + ["zhCN"] = "将所有设置重置为默认值。", + ["zhTW"] = "重置任務位置提示插件,所有設定都恢復成預設值。", + ["esES"] = "Restablece Questie a todos los valores predeterminados.", + ["frFR"] = "Réinitialiser Questie aux valeurs par défaut pour tous les paramètres.", + }, + ["Recompile Database"] = { + ["ptBR"] = "Recompilar banco de dados", + ["ruRU"] = "Перекомпиляция БД", + ["deDE"] = "Datenbank neukompilieren", + ["koKR"] = "데이터베이스 재설정", + ["esMX"] = "Recompilar base de datos", + ["enUS"] = true, + ["zhCN"] = "启用资料库", + ["zhTW"] = "重建資料庫", + ["esES"] = "Recompilar base de datos", + ["frFR"] = "Recompiler la base de données", + }, + ["Forces a recompile of the Questie database. This will also reload the UI."] = { + ["ptBR"] = "Força uma recompilação do banco de dados do Questie. Isso também recarregará a interface do usuário.", + ["ruRU"] = "Принудительная перекомпиляция базы данных Questie. При этом перезагрузится пользовательский интерфейс", + ["deDE"] = "Lässt die Questie Datenbank neu kompilieren und läd das UI neu.", + ["koKR"] = "퀘스트 데이터베이스를 강제로 재설정합니다. UI는 Reload됩니다.", + ["esMX"] = "Fuerza una recompilación de la base de datos Questie. Esto también volverá a cargar la interfaz de usuario.", + ["enUS"] = true, + ["zhCN"] = "强制重建任务资料库。这会重新加载插件", + ["zhTW"] = "強制重建任務位置提示插件的資料庫,同時也會重新載入介面。", + ["esES"] = "Fuerza una recompilación de la base de datos Questie. Esto también volverá a cargar la interfaz de usuario.", + ["frFR"] = "Force une recompilation de la base de données Questie. Cela recharge aussi votre interface graphique.", + }, + ["Open Profiler"] = { + ["ptBR"] = "Abrir perfilador", + ["ruRU"] = "Открыть профайлер", + ["deDE"] = "Profiler öffnen", + ["koKR"] = "프로파일러를 엽니다.", + ["esMX"] = "Abrir perfilador", + ["enUS"] = true, + ["zhCN"] = "打开分析器", + ["zhTW"] = "打開效能分析", + ["esES"] = "Abrir perfilador", + ["frFR"] = "Ouvrir le profileur", + }, + ["Open the Questie profiler, this is useful for tracking down the source of lag / frame spikes."] = { + ["ptBR"] = "Abre o perfilador Questie, isso é útil para rastrear a fonte de picos da latência.", + ["ruRU"] = "Открывает профайлер Questie. Полезен для отслеживания источников лагов или падения FPS", + ["deDE"] = "Der Profiler ermöglich das Ermitteln von Ursachen für Lags und FPS Verlusten.", + ["koKR"] = "Questie 프로파일러를 엽니다. 이 프로파일러는 지연/프레임 스파이크의 소스를 추적하는 데 유용합니다.", + ["esMX"] = "Abre el perfilador de Questie, esto es útil para rastrear la fuente de picos de latencia.", + ["enUS"] = true, + ["zhCN"] = "打开 Questie 分析器, 这对于追踪延迟/帧数峰值的来源很有用", + ["zhTW"] = "打開任務位置提示插件的效能分析,對於追蹤延遲/掉幀的原因很有幫助。", + ["esES"] = "Abre el perfilador de Questie, esto es útil para rastrear la fuente de picos de latencia.", + ["frFR"] = "Ouvrez le profileur Questie, cela est utile pour rechercher la source des pics de latence/image.", + }, + ["Localization Settings"] = { + ["ptBR"] = "Configurações de localização", + ["ruRU"] = "Язык аддона", + ["deDE"] = "Spracheinstellungen", + ["koKR"] = "언어 설정", + ["esMX"] = "Opciones de idioma", + ["enUS"] = true, + ["zhCN"] = "语言设置", + ["zhTW"] = "語言設定", + ["esES"] = "Opciones de idioma", + ["frFR"] = "Paramètre de localisation", + }, + ["Select UI Locale"] = { + ["ptBR"] = "Selecione a interface do utilizador", + ["ruRU"] = "Выберите язык", + ["deDE"] = "Interface-Sprache wählen", + ["koKR"] = "UI 언어 설정", + ["esMX"] = "Seleccionar idioma de interfaz", + ["enUS"] = true, + ["zhCN"] = "选择界面语言", + ["zhTW"] = "選擇介面語言", + ["esES"] = "Seleccionar idioma de interfaz", + ["frFR"] = "Sélection de la langue :", + }, + ["Automatic"] = { + ["ptBR"] = "Automático", + ["ruRU"] = "Автовыбор", + ["deDE"] = "Automatisch", + ["koKR"] = "자동", + ["esMX"] = "Automático", + ["enUS"] = true, + ["zhCN"] = "自动选择", + ["zhTW"] = "自動", + ["esES"] = "Automático", + ["frFR"] = "Automatique", + }, + ["The database needs to be updated to change language. Press reload to apply the new language"] = { + ["ptBR"] = "O banco de dados precisa ser atualizado para alterar o idioma. Pressione recarregar para aplicar o novo idioma", + ["ruRU"] = "БД должна обновиться для изменения языка. Нажмите 'Перезагрузка' для использования нового языка", + ["deDE"] = "Die Datenbank muss durch den Sprachenwechsel neugeladen werden. Drücke reload um die neue Sprache zu aktivieren.", + ["koKR"] = "언어 설정을 위해 데이터베이스 업데이트가 필요합니다. 새로 적용된 언어를 위해 Reload를 누르세요.", + ["esMX"] = "Es necesario actualizar la base de datos para cambiar el idioma. Presiona recargar para aplicar el nuevo idioma", + ["enUS"] = true, + ["zhCN"] = "需要更新数据库及更改语言。 需重新加载以应用新语言", + ["zhTW"] = "更改語言需要更新資料庫,請按下重新載入來套用新的語言。", + ["esES"] = "Es necesario actualizar la base de datos para cambiar el idioma. Presiona recargar para aplicar el nuevo idioma", + ["frFR"] = "La base de données doit être mise à jour pour changer la langue. Appuyez sur Recharger pour appliquer la nouvelle langue.", + }, + ["Questie is under active development for World of Warcraft: Classic. Please check GitHub for the latest alpha builds or to report issues. Or join us on our discord! (( https://github.com/Questie/Questie/ ))"] = { + ["ptBR"] = "Questie está em desenvolvimento para World of Warcraft: Classic. Verifique o GitHub para obter as versões mais recentes ou relatar problemas. Ou junte-se a nós em nossa discórdia!(( https://github.com/Questie/Questie/ )) ", + ["ruRU"] = "Questie находится в стадии активной разработки для World of Warcraft: Classic. Проверяйте наличие обновлений на сайте GitHub (https://github.com/Questie/Questie/) или присоединяйтесь к нашему серверу в Discord (https://discord.gg/phKnHVu)", + ["deDE"] = "Questie befindet sich noch aktiv in der Entwicklungsphase für World of Warcraft: Classic. Für Neuigkeiten und aktuelle Versionen schau auf Github vorbei oder trete unserem Discord Server bei! (( https://github.com/Questie/Questie/ ))", + ["koKR"] = "Questie는 월드 오브 워크래프트:클래식을 위해 활발히 개발 중입니다. 최신 알파 빌드 또는 보고된 문제들을 확인하시려면 GitHub을 확인해주세요. 또는 저희 디스코드에 참여하세요! (( https://github.com/Questie/Questie/ ))", + ["esMX"] = "Questie está bajo desarrollo activo para World of Warcraft: Classic. Por favor ve a Github para las versiones alfa o para reportar problemas. O únete a nuestro Discord! (( https://github.com/Questie/Questie/ ))", + ["enUS"] = true, + ["zhCN"] = "Questie是一款为魔兽世界经典旧世制作的任务插件,目前仍在开发中,请到Github查看最新版本、提交问题或提供建议,或是加入我们的 discord 频道。(( https://github.com/Questie/Questie/ ))", + ["zhTW"] = "任務位置提示插件 Questie 正在為了魔獸世界:經典版積極開發中。請到 GitHub 取得最新的 alpha 版本和回報問題,或是在 Discord 上加入我們! (( https://github.com/Questie/Questie/ ))", + ["esES"] = "Questie está bajo desarrollo activo para World of Warcraft: Classic. Por favor ve a Github para las versiones alfa o para reportar problemas. O únete a nuestro Discord! (( https://github.com/Questie/Questie/ ))", + ["frFR"] = "Questie est en cours de développement pour World of Warcraft: Classic. Veuillez consulter GitHub pour connaître les dernières versions alpha ou signaler des problèmes. Ou rejoignez nous sur notre discord ! ( https://github.com/Questie/Questie/ ) ", + }, + ["Skip Validation"] = { + ["ptBR"] = "Ignorar validação", + ["ruRU"] = "Пропуск валидации", + ["deDE"] = "Validierung überspringen", + ["koKR"] = false, + ["esMX"] = "Omitir validación", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "跳過檢查", + ["esES"] = "Omitir validación", + ["frFR"] = "Passer la validation", + }, + ["Skip database validation upon recompile. Validation is only present with debug enabled in the first place."] = { + ["ptBR"] = "Ignora validação do banco de dados ao recompilar. A validação está presente apenas quando o modo de depuração está habilitado desde o início.", + ["ruRU"] = "Пропустить валидацию БД после перекомпиляции. Валидация имеется только при включенном режиме отладки", + ["deDE"] = "Die Datenbank-Validierung beim Neukompilieren überspringen. Die Validierung findet nur bei aktivem Debugging statt.", + ["koKR"] = false, + ["esMX"] = "Omite la validación de la base de datos al volver a compilar. La validación solo está presente con la depuración activada.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "重新編譯時跳過資料庫驗證。只有先啟用除錯的情況下才會進行驗證。", + ["esES"] = "Omite la validación de la base de datos al volver a compilar. La validación solo está presente con la depuración activada.", + ["frFR"] = "Passe la validation de la base de données lors de la recompilation. La validation n'est présente que lorsque le mode de débogage est activé dès le départ.", + }, + ["Quel'Danas Settings"] = { + ["ptBR"] = "Configuração de Quel'Danas", + ["ruRU"] = "Настройки Кель'Данаса", + ["deDE"] = "Quel'Danas Einstellungen", + ["koKR"] = false, + ["esMX"] = "Configuración de Quel'Danas", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "奎爾達納斯設定", + ["esES"] = "Configuración de Quel'Danas", + ["frFR"] = "Paramètres de Quel'Danas", + }, + ["Show Item IDs"] = { + ["ptBR"] = "Mostrar IDs de itens", + ["ruRU"] = "Показать ID предметов", + ["deDE"] = "Gegenstand-IDs anzeigen", + ["koKR"] = false, + ["esMX"] = "Mostrar ID de artículo", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "顯示物品 ID", + ["esES"] = "Mostrar ID de artículo", + ["frFR"] = "Afficher les ID d'objets", + }, + ["When this is checked, the ID of items will shown in tooltips."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID dos itens será mostrado nas dicas de ferramentas.", + ["ruRU"] = "Когда это включено, в подсказках будут отображаться ID предметов", + ["deDE"] = "Wenn diese Option aktiviert ist, werden die Gegenstand-IDs in den Tooltips angezeigt.", + ["koKR"] = false, + ["esMX"] = "Si está marcado, la identificación de los artículos se mostrará en los tooltips.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "啟用時,會在浮動提示資訊中顯示物品 ID。", + ["esES"] = "Si está marcado, la identificación de los artículos se mostrará en los tooltips.", + ["frFR"] = "Si cette option est cochée, l'ID des objets sera affiché dans les infobulles.", + }, + ["Show Object IDs"] = { + ["ptBR"] = "Mostrar IDs de objetos", + ["ruRU"] = "Показать ID объектов", + ["deDE"] = "Objekt-IDs anzeigen", + ["koKR"] = false, + ["esMX"] = "Mostrar ID de objeto", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "顯示物件 ID", + ["esES"] = "Mostrar ID de objeto", + ["frFR"] = "Afficher les IDs d'objets", + }, + ["When this is checked, the ID of objects will be shown in tooltips. These are guesses and only show the first matching ID in the QuestieDB."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID dos objetos será mostrado nas dicas de ferramentas. Esses são palpites e mostram apenas o primeiro ID correspondente no QuestieDB.", + ["ruRU"] = "Когда это включено, в подсказках будут отображаться ID объектов. Эти данные являются предполагаемыми, отображается только первый совпадающий ID в БД Questie", + ["deDE"] = "Wenn diese Option aktiviert ist, werden die Objekt-IDs in den Tooltips angezeigt. Diese sind Schätzungen und zeigen nur die erste passende ID aus der QuestieDB.", + ["koKR"] = false, + ["esMX"] = "Si está marcado, la identificación de los objetos se mostrará en los tooltips. Estas son conjeturas y solo muestran la primera identificación coincidente en QuestieDB.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "啟用時,會在浮動提示資訊中顯示物品 ID。僅顯示 QuestieDB 中第一個符合的 ID,為猜測的值。", + ["esES"] = "Si está marcado, la identificación de los objetos se mostrará en los tooltips. Estas son conjeturas y solo muestran la primera identificación coincidente en QuestieDB.", + ["frFR"] = "Si cette option est cochée, l'ID des objets sera affiché dans les infobulles. Ce ne sont que des suppositions et ne montrent que le premier ID correspondant dans la base de données de Questie.", + }, + ["Show NPC IDs"] = { + ["ptBR"] = "Mostrar IDs dos NPCs", + ["ruRU"] = "Показать ID NPC", + ["deDE"] = "NPC-IDs anzeigen", + ["koKR"] = false, + ["esMX"] = "Mostrar ID de PNJ", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "顯示 NPC ID", + ["esES"] = "Mostrar ID de PNJ", + ["frFR"] = "Afficher l'ID des PNJ", + }, + ["When this is checked, the ID of NPCs will be shown in tooltips."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID dos NPCs será mostrado nas dicas de ferramentas.", + ["ruRU"] = "Когда это включено, в подсказках будут отображаться ID NPC", + ["deDE"] = "Wenn diese Option aktiviert ist, werden die NPC-IDs in den Tooltips angezeigt.", + ["koKR"] = false, + ["esMX"] = "Si está marcado, la identificación de los PNJs se mostrará en los tooltips.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "啟用時,會在浮動提示資訊中顯示 NPC ID。", + ["esES"] = "Si está marcado, la identificación de los PNJs se mostrará en los tooltips.", + ["frFR"] = "Si cette option est cochée, l'ID des PNJ sera affiché dans les infobulles.", + }, +} + +for k, v in pairs(advancedOptionsLocales) do + l10n.translations[k] = v +end diff --git a/.history/Localization/Translations/Options/Advanced_20260222003421.lua b/.history/Localization/Translations/Options/Advanced_20260222003421.lua new file mode 100644 index 0000000..9daa5ad --- /dev/null +++ b/.history/Localization/Translations/Options/Advanced_20260222003421.lua @@ -0,0 +1,429 @@ +---@type l10n +local l10n = QuestieLoader:ImportModule("l10n") + +local advancedOptionsLocales = { + ["Advanced"] = { + ["ptBR"] = "Avançado", + ["ruRU"] = "Дополнительно", + ["deDE"] = "Erweitert", + ["koKR"] = "고급", + ["esMX"] = "Avanzado", + ["enUS"] = true, + ["zhCN"] = "进阶", + ["zhTW"] = "進階", + ["esES"] = "Avanzado", + ["frFR"] = "Avancé", + }, + ["Advanced Settings"] = { + ["ptBR"] = "Configurações avançadas", + ["ruRU"] = "Расширенные настройки", + ["deDE"] = "Erweiterte Einstellungen", + ["koKR"] = "고급 설정", + ["esMX"] = "Configuración avanzada", + ["enUS"] = true, + ["zhCN"] = "进阶选项", + ["zhTW"] = "進階設定", + ["esES"] = "Configuración avanzada", + ["frFR"] = "Configuration avancée", + }, + ["Enable Icon Limit"] = { + ["ptBR"] = "Ativar limite de ícone", + ["ruRU"] = "Лимит значков", + ["deDE"] = "Icons limitieren", + ["koKR"] = "아이콘 개수 제한 활성화", + ["esMX"] = "Habilitar límite de icono", + ["enUS"] = true, + ["zhCN"] = "启用图标限制", + ["zhTW"] = "啟用圖示限制", + ["esES"] = "Habilitar límite de icono", + ["frFR"] = "Limiter le nombre d'icônes", + }, + ["Icon Limit"] = { + ["ptBR"] = "Limite de ícone", + ["ruRU"] = "Лимит количества значков", + ["deDE"] = "Icon Limit", + ["koKR"] = "아이콘 개수 제한", + ["esMX"] = "Límite de icono", + ["enUS"] = true, + ["zhCN"] = "图标限制", + ["zhTW"] = "圖示數量限制", + ["esES"] = "Límite de icono", + ["frFR"] = "Limite d'icônes", + }, + ["Enable the limit of icons drawn per type."] = { + ["ptBR"] = "Ativa o limite de ícones desenhados por tipo.", + ["ruRU"] = "Включает ограничение количества значков, отображаемых для каждого типа", + ["deDE"] = "Aktiviert/Deaktiviert, ob die Menge an Icons pro Typ limitiert wird.", + ["koKR"] = "아이콘의 종류 별로 표시할 개수를 제한합니다.", + ["esMX"] = "Habilita el límite de iconos por tipo.", + ["enUS"] = true, + ["zhCN"] = "启用对每种类型图标的限制", + ["zhTW"] = "啟用每種類型繪製圖示的限制。", + ["esES"] = "Habilita el límite de iconos por tipo.", + ["frFR"] = "Active la limite au nombre d'icônes affichées d'un même type.", + }, + ["Limits the amount of icons drawn per type. ( Default: %s )"] = { + ["ptBR"] = "Limita a quantidade de ícones desenhados por tipo. ( Por defeito: %s )", + ["ruRU"] = "Регулировка ограничения количества значков, отображаемых для каждого типа (по умолчанию: %s)", + ["deDE"] = "Wie viele Icons pro Type gezeigt werden sollen. ( Standard: %s )", + ["koKR"] = "아이콘의 종류 별로 표시를 제한할 개수를 설정합니다. ( 기본값: %s )", + ["esMX"] = "Limita la cantidad de iconos por tipo. ( Predeterminado: %s)", + ["enUS"] = true, + ["zhCN"] = "限制每种类型图标数量 ( 默认: %s )", + ["zhTW"] = "限制每種類型繪製的圖示數量。(預設值: %s )", + ["esES"] = "Limita la cantidad de iconos por tipo. ( Predeterminado: %s)", + ["frFR"] = "Limite le nombre d'icônes affichées d'un même type. (Par défaut : %s)", + }, + ["Enable bug workarounds"] = { + ["ptBR"] = "Ativar soluções alternativas para bugs", + ["ruRU"] = "Обходные решения", + ["deDE"] = "Bug-Workarounds aktivieren", + ["koKR"] = false, + ["esMX"] = "Activar soluciones de errores", + ["enUS"] = true, + ["zhCN"] = "启用错误修正功能", + ["zhTW"] = "啟用錯誤修正功能", + ["esES"] = "Activar soluciones de errores", + ["frFR"] = "Activer les contournements de bugs", + }, + ["When enabled, Questie will hotfix vanilla UI bugs."] = { + ["ptBR"] = "Quando ativado, o Questie corrigirá bugs da interface do usuário do Vanilla.", + ["ruRU"] = "Когда включено, Questie будет исправлять ошибки классического интерфейса", + ["deDE"] = "Aktiviert/Deaktiviert, dass Questie Fehler im Vanilla UI behebt.", + ["koKR"] = false, + ["esMX"] = "Si está habilitado, Questie corregirá los errores de la interfaz de usuario.", + ["enUS"] = true, + ["zhCN"] = "启用时,任务提示插件会热修经典的界面错误。", + ["zhTW"] = "啟用時,任務位置提示插件會熱修經典的介面錯誤。", + ["esES"] = "Si está habilitado, Questie corregirá los errores de la interfaz de usuario.", + ["frFR"] = "Si activé, Questie corrigera les bugs de l'interface utilisateur Vanilla.", + }, + ["Developer Options"] = { + ["ptBR"] = "Opções do desenvolvedor", + ["ruRU"] = "Настройки разработчика", + ["deDE"] = "Entwickler-Einstellungen", + ["koKR"] = "개발자 설정", + ["esMX"] = "Opciones de desarrollador", + ["enUS"] = true, + ["zhCN"] = "开发选项", + ["zhTW"] = "開發者選項", + ["esES"] = "Opciones de desarrollador", + ["frFR"] = "Options de développement", + }, + ["Show Quest IDs"] = { + ["ptBR"] = "Mostrar IDs da missão", + ["ruRU"] = "Показать ID заданий", + ["deDE"] = "Zeige Quest-IDs", + ["koKR"] = "퀘스트 ID 표시", + ["esMX"] = "Mostrar ID de misión", + ["enUS"] = true, + ["zhCN"] = "显示任务ID", + ["zhTW"] = "顯示任務 ID", + ["esES"] = "Mostrar ID de misión", + ["frFR"] = "Afficher l'ID des quêtes", + }, + ["When this is checked, the ID of quests will show in tooltips and the tracker."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID das missões será exibido nas dicas de ferramentas e no rastreador.", + ["ruRU"] = "Когда включено, ID заданий будут отображаться во всплывающих подсказках и в трекере", + ["deDE"] = "Aktiviert/Deaktiviert, dass die Quest-ID in den Tooltips und dem Tracker angezeigt werden.", + ["koKR"] = "퀘스트의 ID를 툴팁과 추적기에 표시합니다.", + ["esMX"] = "Si está marcado, la ID de misiones mostrará en los tooltips y el rastreador.", + ["enUS"] = true, + ["zhCN"] = "勾选此选项后,任务ID将显示在鼠标提示和跟踪器中", + ["zhTW"] = "啟用時,會在浮動提示資訊和任務追蹤清單內顯示任務 ID。", + ["esES"] = "Si está marcado, la ID de misiones mostrará en los tooltips y el rastreador.", + ["frFR"] = "Active l'affichage de l'ID des quêtes dans les infobulles et le suivi des quêtes.", + }, + ["Enable Debug"] = { + ["ptBR"] = "Ativar depuração", + ["ruRU"] = "Режим отладки", + ["deDE"] = "Debugging aktivieren", + ["koKR"] = "디버그 활성화", + ["esMX"] = "Habilitar depurar", + ["enUS"] = true, + ["zhCN"] = "启用除错", + ["zhTW"] = "啟用除錯", + ["esES"] = "Habilitar depurar", + ["frFR"] = "Mode debug", + }, + ["Enable or disable debug functionality."] = { + ["ptBR"] = "Ativar ou desativar o recurso de depuração.", + ["ruRU"] = "Включить/отключить режим отладки", + ["deDE"] = "Aktiviert/Deaktiviert die Debugging Funktion.", + ["koKR"] = "디버그 기능을 사용합니다.", + ["esMX"] = "Habilita o deshabilita el modo depura.", + ["enUS"] = true, + ["zhCN"] = "启用或停用除错功能", + ["zhTW"] = "啟用或停用除錯的功能。", + ["esES"] = "Habilita o deshabilita el modo depura.", + ["frFR"] = "Active le mode debug.", + }, + ["Debug level to print"] = { + ["ptBR"] = "Nível de depuração para impressão", + ["ruRU"] = "Уровень отладки для вывода в чат", + ["deDE"] = "Debug-Level", + ["koKR"] = "디버그 레벨 출력", + ["esMX"] = "Nivel de depura a mostrar", + ["enUS"] = true, + ["zhCN"] = "除错等级", + ["zhTW"] = "除錯訊息等級", + ["esES"] = "Nivel de depura a mostrar", + ["frFR"] = "Niveau de debug", + }, + ["Hitting this button will reset all of the Questie configuration settings back to their default values. (Excluding Localization)"] = { + ["ptBR"] = "Se clicar nesse botão redefinirá todas as configurações do Questie para seus valores. (Não localização / tradução)", + ["ruRU"] = "Нажатие этой кнопки приведет к сбросу всех настроек аддона (за исключением языка)", + ["deDE"] = "Setzt alle Einstellungen von Questie auf den Standardwert zurück (Lokalisierung ausgeschlossen).", + ["koKR"] = "이 버튼을 누르면 모든 Questie 설정이 기본값으로 초기화됩니다. (언어 설정 제외)", + ["esMX"] = "Al hacer clic este botón restablecerá toda la configuración de Questie a los valores predeterminados. (Menos el idioma)", + ["enUS"] = true, + ["zhCN"] = "点击这个按钮将会使Questie除了本地化以外的所有设置重置为默认值。", + ["zhTW"] = "按下這個按鈕會將任務位置提示插件的所有設定重置成預設值。(語言設定除外)", + ["esES"] = "Al hacer clic este botón restablecerá toda la configuración de Questie a los valores predeterminados. (Menos el idioma)", + ["frFR"] = "Activer ce bouton réinitialisera tous les paramètres de Questie à leurs valeurs par défaut. (sauf choix de la langue)", + }, + ["Reset Questie"] = { + ["ptBR"] = "Redefinir Questie", + ["ruRU"] = "Сбросить настройки", + ["deDE"] = "Questie zurücksetzen", + ["koKR"] = "Quetie 초기화", + ["esMX"] = "Restablecer Questie", + ["enUS"] = true, + ["zhCN"] = "重置", + ["zhTW"] = "重置任務位置提示", + ["esES"] = "Restablecer Questie", + ["frFR"] = "Réinitialiser Questie", + }, + ["Reset Questie to the default values for all settings."] = { + ["ptBR"] = "Redefina a Questie para valores para todas as configurações.", + ["ruRU"] = "Сбросить настройки аддона к значениям по умолчанию", + ["deDE"] = "Setzt alle Einstellungen von Questie auf den Standardwert zurück.", + ["koKR"] = "모든 Questie 설정을 기본값으로 초기화합니다.", + ["esMX"] = "Restablece Questie a todos los valores predeterminados.", + ["enUS"] = true, + ["zhCN"] = "将所有设置重置为默认值。", + ["zhTW"] = "重置任務位置提示插件,所有設定都恢復成預設值。", + ["esES"] = "Restablece Questie a todos los valores predeterminados.", + ["frFR"] = "Réinitialiser Questie aux valeurs par défaut pour tous les paramètres.", + }, + ["Recompile Database"] = { + ["ptBR"] = "Recompilar banco de dados", + ["ruRU"] = "Перекомпиляция БД", + ["deDE"] = "Datenbank neukompilieren", + ["koKR"] = "데이터베이스 재설정", + ["esMX"] = "Recompilar base de datos", + ["enUS"] = true, + ["zhCN"] = "启用资料库", + ["zhTW"] = "重建資料庫", + ["esES"] = "Recompilar base de datos", + ["frFR"] = "Recompiler la base de données", + }, + ["Forces a recompile of the Questie database. This will also reload the UI."] = { + ["ptBR"] = "Força uma recompilação do banco de dados do Questie. Isso também recarregará a interface do usuário.", + ["ruRU"] = "Принудительная перекомпиляция базы данных Questie. При этом перезагрузится пользовательский интерфейс", + ["deDE"] = "Lässt die Questie Datenbank neu kompilieren und läd das UI neu.", + ["koKR"] = "퀘스트 데이터베이스를 강제로 재설정합니다. UI는 Reload됩니다.", + ["esMX"] = "Fuerza una recompilación de la base de datos Questie. Esto también volverá a cargar la interfaz de usuario.", + ["enUS"] = true, + ["zhCN"] = "强制重建任务资料库。这会重新加载插件", + ["zhTW"] = "強制重建任務位置提示插件的資料庫,同時也會重新載入介面。", + ["esES"] = "Fuerza una recompilación de la base de datos Questie. Esto también volverá a cargar la interfaz de usuario.", + ["frFR"] = "Force une recompilation de la base de données Questie. Cela recharge aussi votre interface graphique.", + }, + ["Open Profiler"] = { + ["ptBR"] = "Abrir perfilador", + ["ruRU"] = "Открыть профайлер", + ["deDE"] = "Profiler öffnen", + ["koKR"] = "프로파일러를 엽니다.", + ["esMX"] = "Abrir perfilador", + ["enUS"] = true, + ["zhCN"] = "打开分析器", + ["zhTW"] = "打開效能分析", + ["esES"] = "Abrir perfilador", + ["frFR"] = "Ouvrir le profileur", + }, + ["Open the Questie profiler, this is useful for tracking down the source of lag / frame spikes."] = { + ["ptBR"] = "Abre o perfilador Questie, isso é útil para rastrear a fonte de picos da latência.", + ["ruRU"] = "Открывает профайлер Questie. Полезен для отслеживания источников лагов или падения FPS", + ["deDE"] = "Der Profiler ermöglich das Ermitteln von Ursachen für Lags und FPS Verlusten.", + ["koKR"] = "Questie 프로파일러를 엽니다. 이 프로파일러는 지연/프레임 스파이크의 소스를 추적하는 데 유용합니다.", + ["esMX"] = "Abre el perfilador de Questie, esto es útil para rastrear la fuente de picos de latencia.", + ["enUS"] = true, + ["zhCN"] = "打开 Questie 分析器, 这对于追踪延迟/帧数峰值的来源很有用", + ["zhTW"] = "打開任務位置提示插件的效能分析,對於追蹤延遲/掉幀的原因很有幫助。", + ["esES"] = "Abre el perfilador de Questie, esto es útil para rastrear la fuente de picos de latencia.", + ["frFR"] = "Ouvrez le profileur Questie, cela est utile pour rechercher la source des pics de latence/image.", + }, + ["Localization Settings"] = { + ["ptBR"] = "Configurações de localização", + ["ruRU"] = "Язык аддона", + ["deDE"] = "Spracheinstellungen", + ["koKR"] = "언어 설정", + ["esMX"] = "Opciones de idioma", + ["enUS"] = true, + ["zhCN"] = "语言设置", + ["zhTW"] = "語言設定", + ["esES"] = "Opciones de idioma", + ["frFR"] = "Paramètre de localisation", + }, + ["Select UI Locale"] = { + ["ptBR"] = "Selecione a interface do utilizador", + ["ruRU"] = "Выберите язык", + ["deDE"] = "Interface-Sprache wählen", + ["koKR"] = "UI 언어 설정", + ["esMX"] = "Seleccionar idioma de interfaz", + ["enUS"] = true, + ["zhCN"] = "选择界面语言", + ["zhTW"] = "選擇介面語言", + ["esES"] = "Seleccionar idioma de interfaz", + ["frFR"] = "Sélection de la langue :", + }, + ["Automatic"] = { + ["ptBR"] = "Automático", + ["ruRU"] = "Автовыбор", + ["deDE"] = "Automatisch", + ["koKR"] = "자동", + ["esMX"] = "Automático", + ["enUS"] = true, + ["zhCN"] = "自动选择", + ["zhTW"] = "自動", + ["esES"] = "Automático", + ["frFR"] = "Automatique", + }, + ["The database needs to be updated to change language. Press reload to apply the new language"] = { + ["ptBR"] = "O banco de dados precisa ser atualizado para alterar o idioma. Pressione recarregar para aplicar o novo idioma", + ["ruRU"] = "БД должна обновиться для изменения языка. Нажмите 'Перезагрузка' для использования нового языка", + ["deDE"] = "Die Datenbank muss durch den Sprachenwechsel neugeladen werden. Drücke reload um die neue Sprache zu aktivieren.", + ["koKR"] = "언어 설정을 위해 데이터베이스 업데이트가 필요합니다. 새로 적용된 언어를 위해 Reload를 누르세요.", + ["esMX"] = "Es necesario actualizar la base de datos para cambiar el idioma. Presiona recargar para aplicar el nuevo idioma", + ["enUS"] = true, + ["zhCN"] = "需要更新数据库及更改语言。 需重新加载以应用新语言", + ["zhTW"] = "更改語言需要更新資料庫,請按下重新載入來套用新的語言。", + ["esES"] = "Es necesario actualizar la base de datos para cambiar el idioma. Presiona recargar para aplicar el nuevo idioma", + ["frFR"] = "La base de données doit être mise à jour pour changer la langue. Appuyez sur Recharger pour appliquer la nouvelle langue.", + }, + ["Questie is under active development and being maintained by Xurkon. Please check my GitHub for the latest build or to report issues. (( https://github.com/Xurkon/PE-Questie ))"] = { + ["ptBR"] = "Questie está em desenvolvimento para World of Warcraft: Classic. Verifique o GitHub para obter as versões mais recentes ou relatar problemas. Ou junte-se a nós em nossa discórdia!(( https://github.com/Questie/Questie/ )) ", + ["ruRU"] = "Questie находится в стадии активной разработки для World of Warcraft: Classic. Проверяйте наличие обновлений на сайте GitHub (https://github.com/Questie/Questie/) или присоединяйтесь к нашему серверу в Discord (https://discord.gg/phKnHVu)", + ["deDE"] = "Questie befindet sich noch aktiv in der Entwicklungsphase für World of Warcraft: Classic. Für Neuigkeiten und aktuelle Versionen schau auf Github vorbei oder trete unserem Discord Server bei! (( https://github.com/Questie/Questie/ ))", + ["koKR"] = "Questie는 월드 오브 워크래프트:클래식을 위해 활발히 개발 중입니다. 최신 알파 빌드 또는 보고된 문제들을 확인하시려면 GitHub을 확인해주세요. 또는 저희 디스코드에 참여하세요! (( https://github.com/Questie/Questie/ ))", + ["esMX"] = "Questie está bajo desarrollo activo para World of Warcraft: Classic. Por favor ve a Github para las versiones alfa o para reportar problemas. O únete a nuestro Discord! (( https://github.com/Questie/Questie/ ))", + ["enUS"] = true, + ["zhCN"] = "Questie是一款为魔兽世界经典旧世制作的任务插件,目前仍在开发中,请到Github查看最新版本、提交问题或提供建议,或是加入我们的 discord 频道。(( https://github.com/Questie/Questie/ ))", + ["zhTW"] = "任務位置提示插件 Questie 正在為了魔獸世界:經典版積極開發中。請到 GitHub 取得最新的 alpha 版本和回報問題,或是在 Discord 上加入我們! (( https://github.com/Questie/Questie/ ))", + ["esES"] = "Questie está bajo desarrollo activo para World of Warcraft: Classic. Por favor ve a Github para las versiones alfa o para reportar problemas. O únete a nuestro Discord! (( https://github.com/Questie/Questie/ ))", + ["frFR"] = "Questie est en cours de développement pour World of Warcraft: Classic. Veuillez consulter GitHub pour connaître les dernières versions alpha ou signaler des problèmes. Ou rejoignez nous sur notre discord ! ( https://github.com/Questie/Questie/ ) ", + }, + ["Skip Validation"] = { + ["ptBR"] = "Ignorar validação", + ["ruRU"] = "Пропуск валидации", + ["deDE"] = "Validierung überspringen", + ["koKR"] = false, + ["esMX"] = "Omitir validación", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "跳過檢查", + ["esES"] = "Omitir validación", + ["frFR"] = "Passer la validation", + }, + ["Skip database validation upon recompile. Validation is only present with debug enabled in the first place."] = { + ["ptBR"] = "Ignora validação do banco de dados ao recompilar. A validação está presente apenas quando o modo de depuração está habilitado desde o início.", + ["ruRU"] = "Пропустить валидацию БД после перекомпиляции. Валидация имеется только при включенном режиме отладки", + ["deDE"] = "Die Datenbank-Validierung beim Neukompilieren überspringen. Die Validierung findet nur bei aktivem Debugging statt.", + ["koKR"] = false, + ["esMX"] = "Omite la validación de la base de datos al volver a compilar. La validación solo está presente con la depuración activada.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "重新編譯時跳過資料庫驗證。只有先啟用除錯的情況下才會進行驗證。", + ["esES"] = "Omite la validación de la base de datos al volver a compilar. La validación solo está presente con la depuración activada.", + ["frFR"] = "Passe la validation de la base de données lors de la recompilation. La validation n'est présente que lorsque le mode de débogage est activé dès le départ.", + }, + ["Quel'Danas Settings"] = { + ["ptBR"] = "Configuração de Quel'Danas", + ["ruRU"] = "Настройки Кель'Данаса", + ["deDE"] = "Quel'Danas Einstellungen", + ["koKR"] = false, + ["esMX"] = "Configuración de Quel'Danas", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "奎爾達納斯設定", + ["esES"] = "Configuración de Quel'Danas", + ["frFR"] = "Paramètres de Quel'Danas", + }, + ["Show Item IDs"] = { + ["ptBR"] = "Mostrar IDs de itens", + ["ruRU"] = "Показать ID предметов", + ["deDE"] = "Gegenstand-IDs anzeigen", + ["koKR"] = false, + ["esMX"] = "Mostrar ID de artículo", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "顯示物品 ID", + ["esES"] = "Mostrar ID de artículo", + ["frFR"] = "Afficher les ID d'objets", + }, + ["When this is checked, the ID of items will shown in tooltips."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID dos itens será mostrado nas dicas de ferramentas.", + ["ruRU"] = "Когда это включено, в подсказках будут отображаться ID предметов", + ["deDE"] = "Wenn diese Option aktiviert ist, werden die Gegenstand-IDs in den Tooltips angezeigt.", + ["koKR"] = false, + ["esMX"] = "Si está marcado, la identificación de los artículos se mostrará en los tooltips.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "啟用時,會在浮動提示資訊中顯示物品 ID。", + ["esES"] = "Si está marcado, la identificación de los artículos se mostrará en los tooltips.", + ["frFR"] = "Si cette option est cochée, l'ID des objets sera affiché dans les infobulles.", + }, + ["Show Object IDs"] = { + ["ptBR"] = "Mostrar IDs de objetos", + ["ruRU"] = "Показать ID объектов", + ["deDE"] = "Objekt-IDs anzeigen", + ["koKR"] = false, + ["esMX"] = "Mostrar ID de objeto", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "顯示物件 ID", + ["esES"] = "Mostrar ID de objeto", + ["frFR"] = "Afficher les IDs d'objets", + }, + ["When this is checked, the ID of objects will be shown in tooltips. These are guesses and only show the first matching ID in the QuestieDB."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID dos objetos será mostrado nas dicas de ferramentas. Esses são palpites e mostram apenas o primeiro ID correspondente no QuestieDB.", + ["ruRU"] = "Когда это включено, в подсказках будут отображаться ID объектов. Эти данные являются предполагаемыми, отображается только первый совпадающий ID в БД Questie", + ["deDE"] = "Wenn diese Option aktiviert ist, werden die Objekt-IDs in den Tooltips angezeigt. Diese sind Schätzungen und zeigen nur die erste passende ID aus der QuestieDB.", + ["koKR"] = false, + ["esMX"] = "Si está marcado, la identificación de los objetos se mostrará en los tooltips. Estas son conjeturas y solo muestran la primera identificación coincidente en QuestieDB.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "啟用時,會在浮動提示資訊中顯示物品 ID。僅顯示 QuestieDB 中第一個符合的 ID,為猜測的值。", + ["esES"] = "Si está marcado, la identificación de los objetos se mostrará en los tooltips. Estas son conjeturas y solo muestran la primera identificación coincidente en QuestieDB.", + ["frFR"] = "Si cette option est cochée, l'ID des objets sera affiché dans les infobulles. Ce ne sont que des suppositions et ne montrent que le premier ID correspondant dans la base de données de Questie.", + }, + ["Show NPC IDs"] = { + ["ptBR"] = "Mostrar IDs dos NPCs", + ["ruRU"] = "Показать ID NPC", + ["deDE"] = "NPC-IDs anzeigen", + ["koKR"] = false, + ["esMX"] = "Mostrar ID de PNJ", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "顯示 NPC ID", + ["esES"] = "Mostrar ID de PNJ", + ["frFR"] = "Afficher l'ID des PNJ", + }, + ["When this is checked, the ID of NPCs will be shown in tooltips."] = { + ["ptBR"] = "Quando isso estiver marcado, o ID dos NPCs será mostrado nas dicas de ferramentas.", + ["ruRU"] = "Когда это включено, в подсказках будут отображаться ID NPC", + ["deDE"] = "Wenn diese Option aktiviert ist, werden die NPC-IDs in den Tooltips angezeigt.", + ["koKR"] = false, + ["esMX"] = "Si está marcado, la identificación de los PNJs se mostrará en los tooltips.", + ["enUS"] = true, + ["zhCN"] = false, + ["zhTW"] = "啟用時,會在浮動提示資訊中顯示 NPC ID。", + ["esES"] = "Si está marcado, la identificación de los PNJs se mostrará en los tooltips.", + ["frFR"] = "Si cette option est cochée, l'ID des PNJ sera affiché dans les infobulles.", + }, +} + +for k, v in pairs(advancedOptionsLocales) do + l10n.translations[k] = v +end diff --git a/.history/Modules/Options/AdvancedTab/QuestieOptionsAdvanced_20260116015930.lua b/.history/Modules/Options/AdvancedTab/QuestieOptionsAdvanced_20260116015930.lua new file mode 100644 index 0000000..0b3f2dc --- /dev/null +++ b/.history/Modules/Options/AdvancedTab/QuestieOptionsAdvanced_20260116015930.lua @@ -0,0 +1,439 @@ +------------------------- +--Import modules. +------------------------- +---@type QuestieQuest +local QuestieQuest = QuestieLoader:ImportModule("QuestieQuest"); +---@type QuestieOptions +local QuestieOptions = QuestieLoader:ImportModule("QuestieOptions"); +---@type QuestieOptionsDefaults +local QuestieOptionsDefaults = QuestieLoader:ImportModule("QuestieOptionsDefaults"); +---@type QuestieOptionsUtils +local QuestieOptionsUtils = QuestieLoader:ImportModule("QuestieOptionsUtils"); +---@type QuestieTracker +local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker"); +---@type IsleOfQuelDanas +local IsleOfQuelDanas = QuestieLoader:ImportModule("IsleOfQuelDanas"); +---@type l10n +local l10n = QuestieLoader:ImportModule("l10n") + +QuestieOptions.tabs.advanced = {...} +local optionsDefaults = QuestieOptionsDefaults:Load() +local _GetLanguages + +function QuestieOptions.tabs.advanced:Initialize() + -- This needs to be called inside of the Init process for l10n to be fully loaded + StaticPopupDialogs["QUESTIE_LANG_CHANGED_RELOAD"] = { + button1 = l10n('Reload UI'), + button2 = l10n('Cancel'), + OnAccept = function() + ReloadUI() + end, + text = l10n('The database needs to be updated to change language. Press reload to apply the new language'), + OnShow = function(self) + self:SetFrameStrata("TOOLTIP") + end, + timeout = 0, + whileDead = true, + hideOnEscape = true, + preferredIndex = 3 + } + + return { + name = function() return l10n('Advanced'); end, + type = "group", + order = 7, + args = { + map_options = { + type = "header", + order = 1, + name = function() return l10n('Advanced Settings'); end, + }, + enableIconLimit = { + type = "toggle", + order = 1.1, + name = function() return l10n('Enable Icon Limit'); end, + desc = function() return l10n('Enable the limit of icons drawn per type.'); end, + width = "full", + get = function (info) return QuestieOptions:GetProfileValue(info); end, + set = function (info, value) + QuestieOptions:SetProfileValue(info, value) + QuestieOptionsUtils:Delay(0.5, QuestieQuest.SmoothReset, l10n('Setting icon limit value to %s : Redrawing!', value)) + end, + }, + iconLimit = { + type = "range", + order = 1.2, + name = function() return l10n('Icon Limit'); end, + desc = function() return l10n('Limits the amount of icons drawn per type. ( Default: %s )', optionsDefaults.profile.iconLimit); end, + width = 1.5, + min = 10, + max = 500, + step = 10, + disabled = function() return (not Questie.db.profile.enableIconLimit); end, + get = function(info) return QuestieOptions:GetProfileValue(info); end, + set = function (info, value) + QuestieOptions:SetProfileValue(info, value) + QuestieOptionsUtils:Delay(0.5, QuestieQuest.SmoothReset, l10n('Setting icon limit value to %s : Redrawing!', value)) + end, + }, + iconSpacer = { + type = "description", + order = 1.3, + name = "", + desc = "", + image = "", + imageWidth = 0.3, + width = 0.3, + func = function() end, + }, + clusterLevelHotzone = { + type = "range", + order = 1.4, + name = function() return l10n('Objective icon cluster amount'); end, + desc = function() return l10n('How much objective icons should cluster.'); end, + width = 1.5, + disabled = function() return (not Questie.db.profile.enabled); end, + min = 1, + max = 300, + step = 1, + get = function(info) return QuestieOptions:GetProfileValue(info); end, + set = function(info, value) + QuestieOptionsUtils:Delay(0.5, QuestieOptions.ClusterRedraw, l10n('Setting clustering value, clusterLevelHotzone set to %s : Redrawing!', value)) + QuestieOptions:SetProfileValue(info, value) + QuestieOptionsUtils.DetermineTheme() + end, + }, + quelDanasSpacer1 = QuestieOptionsUtils:Spacer(1.45, (not Questie.IsTBC)), + npcrules_group = { + type = "group", + order = 1.5, + inline = true, + width = 0.5, + hidden = (not Questie.IsTBC), + name = function() return l10n("Quel'Danas Settings"); end, + disabled = function() return not Questie.db.profile.autoaccept end, + args = { + isleOfQuelDanasPhase = { + type = "select", + order = 1.3, + width = 1.5, + values = IsleOfQuelDanas.localizedPhaseNames, + style = 'dropdown', + name = function() return l10n("Isle of Quel'Danas Phase") end, + desc = function() return l10n("Select the phase fitting your realm progress on the Isle of Quel'Danas"); end, + disabled = function() return (not Questie.IsWotlk) end, + get = function() return Questie.db.profile.isleOfQuelDanasPhase; end, + set = function(_, key) + Questie.db.profile.isleOfQuelDanasPhase = key + QuestieQuest:SmoothReset() + end, + }, + quelDanasSpacer2 = { + type = "description", + order = 1.4, + name = "", + desc = "", + image = "", + imageWidth = 0.2, + width = 0.2, + func = function() end, + }, + isleOfQuelDanasPhaseReminder = { + type = "toggle", + order = 1.5, + name = function() return l10n('Disable Phase reminder'); end, + desc = function() return l10n("Enable or disable the reminder on login to set the Isle of Quel'Danas phase"); end, + disabled = function() return (not Questie.IsWotlk) end, + width = 1, + get = function() return Questie.db.profile.isIsleOfQuelDanasPhaseReminderDisabled; end, + set = function(_, value) + Questie.db.profile.isIsleOfQuelDanasPhaseReminderDisabled = value + end, + }, + }, + }, + + Spacer_A = QuestieOptionsUtils:Spacer(2.9), + locale_header = { + type = "header", + order = 3, + name = function() return l10n('Localization Settings'); end, + }, + locale_dropdown = { + type = "select", + order = 3.1, + values = _GetLanguages(), + style = 'dropdown', + name = function() return l10n('Select UI Locale'); end, + get = function() + if not Questie.db.global.questieLocaleDiff then + return 'auto' + else + return l10n:GetUILocale(); + end + end, + set = function(_, lang) + local previousLocale = Questie.db.global.questieLocale + if lang == 'auto' then + local clientLocale = GetLocale() + if QUESTIE_LOCALES_OVERRIDE ~= nil then + clientLocale = QUESTIE_LOCALES_OVERRIDE.locale + end + l10n:SetUILocale(clientLocale) + Questie.db.global.questieLocale = clientLocale + Questie.db.global.questieLocaleDiff = false + else + l10n:SetUILocale(lang); + Questie.db.global.questieLocale = lang; + Questie.db.global.questieLocaleDiff = true; + end + + if previousLocale ~= Questie.db.global.questieLocale then + if Questie.IsSoD then + Questie.db.global.sod.dbIsCompiled = nil -- recompile db with new lang if locale changed + else + Questie.db.global.dbIsCompiled = nil -- recompile db with new lang if locale changed + end + StaticPopup_Show("QUESTIE_LANG_CHANGED_RELOAD") + end + end, + }, + Spacer_C = QuestieOptionsUtils:Spacer(3.9), + reset_header = { + type = "header", + order = 4, + name = function() return l10n('Reset Questie'); end, + }, + Spacer_D = QuestieOptionsUtils:Spacer(22), + reset_text = { + type = "description", + order = 4.1, + name = function() return l10n('Hitting this button will reset all of the Questie configuration settings back to their default values. (Excluding Localization)'); end, + fontSize = "medium", + }, + questieReset = { + type = "execute", + order = 4.2, + name = function() return l10n('Reset Questie'); end, + desc = function() return l10n('Reset Questie to the default values for all settings.'); end, + func = function (_, _) + -- update all values to default + for k,v in pairs(optionsDefaults.profile) do + Questie.db.profile[k] = v + end + + -- only toggle questie if it's off (must be called before resetting the value) + if (not Questie.db.profile.enabled) then + Questie.db.profile.enabled = true + --QuestieQuest:ToggleNotes(true); + end + + Questie.db.profile.enabled = optionsDefaults.profile.enabled; + Questie.db.profile.lowLevelStyle = optionsDefaults.profile.lowLevelStyle; + + Questie.db.profile.migrationVersion = nil + + Questie.db.profile.minimap.hide = optionsDefaults.profile.minimap.hide; + + if Questie.IsSoD then + Questie.db.global.sod.dbIsCompiled = false + else + Questie.db.global.dbIsCompiled = false + end + + Questie.db.char.hidden = nil + Questie.db.char.hiddenDailies = optionsDefaults.char.hiddenDailies; + + ReloadUI() + + end, + }, + Spacer_E = QuestieOptionsUtils:Spacer(4.3), + recompileDatabase = { + type = "execute", + order = 4.4, + name = function() return l10n('Recompile Database'); end, + desc = function() return l10n('Forces a recompile of the Questie database. This will also reload the UI.'); end, + func = function (_, _) + if Questie.IsSoD then + Questie.db.global.sod.dbIsCompiled = false + else + Questie.db.global.dbIsCompiled = false + end + ReloadUI() + end, + }, + Spacer_F = QuestieOptionsUtils:Spacer(4.5), + openProfiler = { + type = "execute", + order = 4.6, + name = function() return l10n('Open Profiler'); end, + desc = function() return l10n('Open the Questie profiler, this is useful for tracking down the source of lag / frame spikes.'); end, + func = function (_, _) + QuestieLoader:ImportModule("Profiler"):Start() + end, + }, + Spacer_G = QuestieOptionsUtils:Spacer(4.7), + github_text = { + type = "description", + order = 4.8, + name = function() return Questie:Colorize(l10n('Questie is under active development for World of Warcraft: Classic. Please check GitHub for the latest alpha builds or to report issues. Or join us on our discord! (( https://github.com/Questie/Questie/ ))'), 'purple'); end, + fontSize = "medium", + }, + HeaderDev = { + type = "header", + order = 5, + name = l10n('Developer Options'), + }, + bugWorkarounds = { + type = "toggle", + order = 5.01, + name = function() return l10n('Enable bug workarounds'); end, + desc = function() return l10n('When enabled, Questie will hotfix vanilla UI bugs.'); end, + width = "full", + get = function() return Questie.db.profile.bugWorkarounds; end, + set = function (_, value) + Questie.db.profile.bugWorkarounds = value + end + }, + showItemIDs = { + type = "toggle", + order = 5.02, + name = function() return l10n('Show Item IDs'); end, + desc = function() return l10n('When this is checked, the ID of items will shown in tooltips.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsItemID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsItemID = value + end + }, + showNPCIDs = { + type = "toggle", + order = 5.03, + name = function() return l10n('Show NPC IDs'); end, + desc = function() return l10n('When this is checked, the ID of NPCs will be shown in tooltips.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsNPCID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsNPCID = value + end + }, + showObjectIDs = { + type = "toggle", + order = 5.04, + name = function() return l10n('Show Object IDs'); end, + desc = function() return l10n('When this is checked, the ID of objects will be shown in tooltips. These are guesses and only show the first matching ID in the QuestieDB.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsObjectID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsObjectID = value + end + }, + showQuestIDs = { + type = "toggle", + order = 5.05, + name = function() return l10n('Show Quest IDs'); end, + desc = function() return l10n('When this is checked, the ID of quests will show in tooltips and the tracker.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsQuestID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsQuestID = value + QuestieTracker:Update() + end + }, + debugEnabled = { + type = "toggle", + order = 5.06, + name = function() return l10n('Enable Debug'); end, + desc = function() return l10n('Enable or disable debug functionality.'); end, + width = "full", + get = function () return Questie.db.profile.debugEnabled; end, + set = function (_, value) + Questie.db.profile.debugEnabled = value + if Questie.db.profile.debugEnabled then + QuestieLoader:PopulateGlobals() + end + end, + }, + skipValidation = { + type = "toggle", + order = 5.07, + name = function() return l10n('Skip Validation'); end, + desc = function() return l10n('Skip database validation upon recompile. Validation is only present with debug enabled in the first place.'); end, + width = "full", + disabled = function() return not Questie.db.profile.debugEnabled; end, + get = function () return Questie.db.profile.skipValidation; end, + set = function (_, value) + Questie.db.profile.skipValidation = value + end, + }, + debugEnabledPrint = { + type = "toggle", + order = 5.08, + disabled = function() return not Questie.db.profile.debugEnabled; end, + name = function() return l10n('Enable Debug').."-PRINT" end, + desc = function() return l10n('Enable or disable debug functionality.').."-PRINT" end, + width = "full", + get = function () return Questie.db.profile.debugEnabledPrint; end, + set = function (_, value) + Questie.db.profile.debugEnabledPrint = value + end, + }, + debugLevel = { + type = "multiselect", + values = { + [0] = "DEBUG_CRITICAL", + [1] = "DEBUG_ELEVATED", + [2] = "DEBUG_INFO", + [3] = "DEBUG_DEVELOP", + [4] = "DEBUG_SPAM", + }, + order = 5.09, + name = function() return l10n('Debug level to print'); end, + width = "normal", + disabled = function() return not (Questie.db.profile.debugEnabledPrint and Questie.db.profile.debugEnabled); end, + get = function(_, key) + --Questie:Debug(Questie.DEBUG_SPAM, "Debug Key:", key, math.pow(2, key), state.option.values[key]) + --Questie:Debug(Questie.DEBUG_SPAM, "Debug Level:", Questie.db.profile.debugLevel, bit.band(Questie.db.profile.debugLevel, math.pow(2, key))) + return bit.band(Questie.db.profile.debugLevel, math.pow(2, key)) > 0 + end, + set = function (_, value) + local currentValue = Questie.db.profile.debugLevel + local flag = math.pow(2, value) + --Questie:Debug(Questie.DEBUG_SPAM, "Setting Debug:", currentValue, flag, bit.band(currentValue, flag)>0) + -- When current debug level is active, remove it + if (bit.band(currentValue, flag) > 0) then + Questie.db.profile.debugLevel = bit.bxor(flag, currentValue) + -- When current debug level is inactive, add it + else + Questie.db.profile.debugLevel = bit.bor(flag, currentValue) + end + end, + }, + }, + } +end + +_GetLanguages = function() + local languages = { + ['auto'] = l10n('Automatic'), + ['enUS'] = 'English', + ['esES'] = 'Español', + ['esMX'] = 'Español (América Latina)', + ['ptBR'] = 'Português', + ['frFR'] = 'Français', + ['deDE'] = 'Deutsch', + ['ruRU'] = 'Русский', + ['zhCN'] = '简体中文', + ['zhTW'] = '正體中文', + ['koKR'] = '한국어', + } + if QUESTIE_LOCALES_OVERRIDE ~= nil then + languages[QUESTIE_LOCALES_OVERRIDE.locale] = QUESTIE_LOCALES_OVERRIDE.localeName + end + return languages +end diff --git a/.history/Modules/Options/AdvancedTab/QuestieOptionsAdvanced_20260222003358.lua b/.history/Modules/Options/AdvancedTab/QuestieOptionsAdvanced_20260222003358.lua new file mode 100644 index 0000000..c0c4c82 --- /dev/null +++ b/.history/Modules/Options/AdvancedTab/QuestieOptionsAdvanced_20260222003358.lua @@ -0,0 +1,439 @@ +------------------------- +--Import modules. +------------------------- +---@type QuestieQuest +local QuestieQuest = QuestieLoader:ImportModule("QuestieQuest"); +---@type QuestieOptions +local QuestieOptions = QuestieLoader:ImportModule("QuestieOptions"); +---@type QuestieOptionsDefaults +local QuestieOptionsDefaults = QuestieLoader:ImportModule("QuestieOptionsDefaults"); +---@type QuestieOptionsUtils +local QuestieOptionsUtils = QuestieLoader:ImportModule("QuestieOptionsUtils"); +---@type QuestieTracker +local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker"); +---@type IsleOfQuelDanas +local IsleOfQuelDanas = QuestieLoader:ImportModule("IsleOfQuelDanas"); +---@type l10n +local l10n = QuestieLoader:ImportModule("l10n") + +QuestieOptions.tabs.advanced = {...} +local optionsDefaults = QuestieOptionsDefaults:Load() +local _GetLanguages + +function QuestieOptions.tabs.advanced:Initialize() + -- This needs to be called inside of the Init process for l10n to be fully loaded + StaticPopupDialogs["QUESTIE_LANG_CHANGED_RELOAD"] = { + button1 = l10n('Reload UI'), + button2 = l10n('Cancel'), + OnAccept = function() + ReloadUI() + end, + text = l10n('The database needs to be updated to change language. Press reload to apply the new language'), + OnShow = function(self) + self:SetFrameStrata("TOOLTIP") + end, + timeout = 0, + whileDead = true, + hideOnEscape = true, + preferredIndex = 3 + } + + return { + name = function() return l10n('Advanced'); end, + type = "group", + order = 7, + args = { + map_options = { + type = "header", + order = 1, + name = function() return l10n('Advanced Settings'); end, + }, + enableIconLimit = { + type = "toggle", + order = 1.1, + name = function() return l10n('Enable Icon Limit'); end, + desc = function() return l10n('Enable the limit of icons drawn per type.'); end, + width = "full", + get = function (info) return QuestieOptions:GetProfileValue(info); end, + set = function (info, value) + QuestieOptions:SetProfileValue(info, value) + QuestieOptionsUtils:Delay(0.5, QuestieQuest.SmoothReset, l10n('Setting icon limit value to %s : Redrawing!', value)) + end, + }, + iconLimit = { + type = "range", + order = 1.2, + name = function() return l10n('Icon Limit'); end, + desc = function() return l10n('Limits the amount of icons drawn per type. ( Default: %s )', optionsDefaults.profile.iconLimit); end, + width = 1.5, + min = 10, + max = 500, + step = 10, + disabled = function() return (not Questie.db.profile.enableIconLimit); end, + get = function(info) return QuestieOptions:GetProfileValue(info); end, + set = function (info, value) + QuestieOptions:SetProfileValue(info, value) + QuestieOptionsUtils:Delay(0.5, QuestieQuest.SmoothReset, l10n('Setting icon limit value to %s : Redrawing!', value)) + end, + }, + iconSpacer = { + type = "description", + order = 1.3, + name = "", + desc = "", + image = "", + imageWidth = 0.3, + width = 0.3, + func = function() end, + }, + clusterLevelHotzone = { + type = "range", + order = 1.4, + name = function() return l10n('Objective icon cluster amount'); end, + desc = function() return l10n('How much objective icons should cluster.'); end, + width = 1.5, + disabled = function() return (not Questie.db.profile.enabled); end, + min = 1, + max = 300, + step = 1, + get = function(info) return QuestieOptions:GetProfileValue(info); end, + set = function(info, value) + QuestieOptionsUtils:Delay(0.5, QuestieOptions.ClusterRedraw, l10n('Setting clustering value, clusterLevelHotzone set to %s : Redrawing!', value)) + QuestieOptions:SetProfileValue(info, value) + QuestieOptionsUtils.DetermineTheme() + end, + }, + quelDanasSpacer1 = QuestieOptionsUtils:Spacer(1.45, (not Questie.IsTBC)), + npcrules_group = { + type = "group", + order = 1.5, + inline = true, + width = 0.5, + hidden = (not Questie.IsTBC), + name = function() return l10n("Quel'Danas Settings"); end, + disabled = function() return not Questie.db.profile.autoaccept end, + args = { + isleOfQuelDanasPhase = { + type = "select", + order = 1.3, + width = 1.5, + values = IsleOfQuelDanas.localizedPhaseNames, + style = 'dropdown', + name = function() return l10n("Isle of Quel'Danas Phase") end, + desc = function() return l10n("Select the phase fitting your realm progress on the Isle of Quel'Danas"); end, + disabled = function() return (not Questie.IsWotlk) end, + get = function() return Questie.db.profile.isleOfQuelDanasPhase; end, + set = function(_, key) + Questie.db.profile.isleOfQuelDanasPhase = key + QuestieQuest:SmoothReset() + end, + }, + quelDanasSpacer2 = { + type = "description", + order = 1.4, + name = "", + desc = "", + image = "", + imageWidth = 0.2, + width = 0.2, + func = function() end, + }, + isleOfQuelDanasPhaseReminder = { + type = "toggle", + order = 1.5, + name = function() return l10n('Disable Phase reminder'); end, + desc = function() return l10n("Enable or disable the reminder on login to set the Isle of Quel'Danas phase"); end, + disabled = function() return (not Questie.IsWotlk) end, + width = 1, + get = function() return Questie.db.profile.isIsleOfQuelDanasPhaseReminderDisabled; end, + set = function(_, value) + Questie.db.profile.isIsleOfQuelDanasPhaseReminderDisabled = value + end, + }, + }, + }, + + Spacer_A = QuestieOptionsUtils:Spacer(2.9), + locale_header = { + type = "header", + order = 3, + name = function() return l10n('Localization Settings'); end, + }, + locale_dropdown = { + type = "select", + order = 3.1, + values = _GetLanguages(), + style = 'dropdown', + name = function() return l10n('Select UI Locale'); end, + get = function() + if not Questie.db.global.questieLocaleDiff then + return 'auto' + else + return l10n:GetUILocale(); + end + end, + set = function(_, lang) + local previousLocale = Questie.db.global.questieLocale + if lang == 'auto' then + local clientLocale = GetLocale() + if QUESTIE_LOCALES_OVERRIDE ~= nil then + clientLocale = QUESTIE_LOCALES_OVERRIDE.locale + end + l10n:SetUILocale(clientLocale) + Questie.db.global.questieLocale = clientLocale + Questie.db.global.questieLocaleDiff = false + else + l10n:SetUILocale(lang); + Questie.db.global.questieLocale = lang; + Questie.db.global.questieLocaleDiff = true; + end + + if previousLocale ~= Questie.db.global.questieLocale then + if Questie.IsSoD then + Questie.db.global.sod.dbIsCompiled = nil -- recompile db with new lang if locale changed + else + Questie.db.global.dbIsCompiled = nil -- recompile db with new lang if locale changed + end + StaticPopup_Show("QUESTIE_LANG_CHANGED_RELOAD") + end + end, + }, + Spacer_C = QuestieOptionsUtils:Spacer(3.9), + reset_header = { + type = "header", + order = 4, + name = function() return l10n('Reset Questie'); end, + }, + Spacer_D = QuestieOptionsUtils:Spacer(22), + reset_text = { + type = "description", + order = 4.1, + name = function() return l10n('Hitting this button will reset all of the Questie configuration settings back to their default values. (Excluding Localization)'); end, + fontSize = "medium", + }, + questieReset = { + type = "execute", + order = 4.2, + name = function() return l10n('Reset Questie'); end, + desc = function() return l10n('Reset Questie to the default values for all settings.'); end, + func = function (_, _) + -- update all values to default + for k,v in pairs(optionsDefaults.profile) do + Questie.db.profile[k] = v + end + + -- only toggle questie if it's off (must be called before resetting the value) + if (not Questie.db.profile.enabled) then + Questie.db.profile.enabled = true + --QuestieQuest:ToggleNotes(true); + end + + Questie.db.profile.enabled = optionsDefaults.profile.enabled; + Questie.db.profile.lowLevelStyle = optionsDefaults.profile.lowLevelStyle; + + Questie.db.profile.migrationVersion = nil + + Questie.db.profile.minimap.hide = optionsDefaults.profile.minimap.hide; + + if Questie.IsSoD then + Questie.db.global.sod.dbIsCompiled = false + else + Questie.db.global.dbIsCompiled = false + end + + Questie.db.char.hidden = nil + Questie.db.char.hiddenDailies = optionsDefaults.char.hiddenDailies; + + ReloadUI() + + end, + }, + Spacer_E = QuestieOptionsUtils:Spacer(4.3), + recompileDatabase = { + type = "execute", + order = 4.4, + name = function() return l10n('Recompile Database'); end, + desc = function() return l10n('Forces a recompile of the Questie database. This will also reload the UI.'); end, + func = function (_, _) + if Questie.IsSoD then + Questie.db.global.sod.dbIsCompiled = false + else + Questie.db.global.dbIsCompiled = false + end + ReloadUI() + end, + }, + Spacer_F = QuestieOptionsUtils:Spacer(4.5), + openProfiler = { + type = "execute", + order = 4.6, + name = function() return l10n('Open Profiler'); end, + desc = function() return l10n('Open the Questie profiler, this is useful for tracking down the source of lag / frame spikes.'); end, + func = function (_, _) + QuestieLoader:ImportModule("Profiler"):Start() + end, + }, + Spacer_G = QuestieOptionsUtils:Spacer(4.7), + github_text = { + type = "description", + order = 4.8, + name = function() return Questie:Colorize(l10n('Questie is under active development and being maintained by Xurkon. Please check my GitHub for the latest build or to report issues. (( https://github.com/Xurkon/PE-Questie ))'), 'purple'); end, + fontSize = "medium", + }, + HeaderDev = { + type = "header", + order = 5, + name = l10n('Developer Options'), + }, + bugWorkarounds = { + type = "toggle", + order = 5.01, + name = function() return l10n('Enable bug workarounds'); end, + desc = function() return l10n('When enabled, Questie will hotfix vanilla UI bugs.'); end, + width = "full", + get = function() return Questie.db.profile.bugWorkarounds; end, + set = function (_, value) + Questie.db.profile.bugWorkarounds = value + end + }, + showItemIDs = { + type = "toggle", + order = 5.02, + name = function() return l10n('Show Item IDs'); end, + desc = function() return l10n('When this is checked, the ID of items will shown in tooltips.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsItemID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsItemID = value + end + }, + showNPCIDs = { + type = "toggle", + order = 5.03, + name = function() return l10n('Show NPC IDs'); end, + desc = function() return l10n('When this is checked, the ID of NPCs will be shown in tooltips.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsNPCID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsNPCID = value + end + }, + showObjectIDs = { + type = "toggle", + order = 5.04, + name = function() return l10n('Show Object IDs'); end, + desc = function() return l10n('When this is checked, the ID of objects will be shown in tooltips. These are guesses and only show the first matching ID in the QuestieDB.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsObjectID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsObjectID = value + end + }, + showQuestIDs = { + type = "toggle", + order = 5.05, + name = function() return l10n('Show Quest IDs'); end, + desc = function() return l10n('When this is checked, the ID of quests will show in tooltips and the tracker.'); end, + disabled = function() return (not Questie.db.profile.enableTooltips); end, + width = "full", + get = function() return Questie.db.profile.enableTooltipsQuestID; end, + set = function (_, value) + Questie.db.profile.enableTooltipsQuestID = value + QuestieTracker:Update() + end + }, + debugEnabled = { + type = "toggle", + order = 5.06, + name = function() return l10n('Enable Debug'); end, + desc = function() return l10n('Enable or disable debug functionality.'); end, + width = "full", + get = function () return Questie.db.profile.debugEnabled; end, + set = function (_, value) + Questie.db.profile.debugEnabled = value + if Questie.db.profile.debugEnabled then + QuestieLoader:PopulateGlobals() + end + end, + }, + skipValidation = { + type = "toggle", + order = 5.07, + name = function() return l10n('Skip Validation'); end, + desc = function() return l10n('Skip database validation upon recompile. Validation is only present with debug enabled in the first place.'); end, + width = "full", + disabled = function() return not Questie.db.profile.debugEnabled; end, + get = function () return Questie.db.profile.skipValidation; end, + set = function (_, value) + Questie.db.profile.skipValidation = value + end, + }, + debugEnabledPrint = { + type = "toggle", + order = 5.08, + disabled = function() return not Questie.db.profile.debugEnabled; end, + name = function() return l10n('Enable Debug').."-PRINT" end, + desc = function() return l10n('Enable or disable debug functionality.').."-PRINT" end, + width = "full", + get = function () return Questie.db.profile.debugEnabledPrint; end, + set = function (_, value) + Questie.db.profile.debugEnabledPrint = value + end, + }, + debugLevel = { + type = "multiselect", + values = { + [0] = "DEBUG_CRITICAL", + [1] = "DEBUG_ELEVATED", + [2] = "DEBUG_INFO", + [3] = "DEBUG_DEVELOP", + [4] = "DEBUG_SPAM", + }, + order = 5.09, + name = function() return l10n('Debug level to print'); end, + width = "normal", + disabled = function() return not (Questie.db.profile.debugEnabledPrint and Questie.db.profile.debugEnabled); end, + get = function(_, key) + --Questie:Debug(Questie.DEBUG_SPAM, "Debug Key:", key, math.pow(2, key), state.option.values[key]) + --Questie:Debug(Questie.DEBUG_SPAM, "Debug Level:", Questie.db.profile.debugLevel, bit.band(Questie.db.profile.debugLevel, math.pow(2, key))) + return bit.band(Questie.db.profile.debugLevel, math.pow(2, key)) > 0 + end, + set = function (_, value) + local currentValue = Questie.db.profile.debugLevel + local flag = math.pow(2, value) + --Questie:Debug(Questie.DEBUG_SPAM, "Setting Debug:", currentValue, flag, bit.band(currentValue, flag)>0) + -- When current debug level is active, remove it + if (bit.band(currentValue, flag) > 0) then + Questie.db.profile.debugLevel = bit.bxor(flag, currentValue) + -- When current debug level is inactive, add it + else + Questie.db.profile.debugLevel = bit.bor(flag, currentValue) + end + end, + }, + }, + } +end + +_GetLanguages = function() + local languages = { + ['auto'] = l10n('Automatic'), + ['enUS'] = 'English', + ['esES'] = 'Español', + ['esMX'] = 'Español (América Latina)', + ['ptBR'] = 'Português', + ['frFR'] = 'Français', + ['deDE'] = 'Deutsch', + ['ruRU'] = 'Русский', + ['zhCN'] = '简体中文', + ['zhTW'] = '正體中文', + ['koKR'] = '한국어', + } + if QUESTIE_LOCALES_OVERRIDE ~= nil then + languages[QUESTIE_LOCALES_OVERRIDE.locale] = QUESTIE_LOCALES_OVERRIDE.localeName + end + return languages +end diff --git a/.history/Modules/Quest/QuestEventHandler_20260222005307.lua b/.history/Modules/Quest/QuestEventHandler_20260222005307.lua new file mode 100644 index 0000000..474cf1f --- /dev/null +++ b/.history/Modules/Quest/QuestEventHandler_20260222005307.lua @@ -0,0 +1,716 @@ +---@class QuestEventHandler +local QuestEventHandler = QuestieLoader:CreateModule("QuestEventHandler") +---@class QuestEventHandlerPrivate +local _QuestEventHandler = QuestEventHandler.private + +local _QuestLogUpdateQueue = {} -- Helper module +local questLogUpdateQueue = {} -- The actual queue + +---@type QuestEventHandlerPrivate +QuestEventHandler.private = QuestEventHandler.private or {} +---@type QuestLogCache +local QuestLogCache = QuestieLoader:ImportModule("QuestLogCache") +---@type QuestieQuest +local QuestieQuest = QuestieLoader:ImportModule("QuestieQuest") +---@type QuestieJourney +local QuestieJourney = QuestieLoader:ImportModule("QuestieJourney") +---@type QuestieNameplate +local QuestieNameplate = QuestieLoader:ImportModule("QuestieNameplate") +---@type QuestieLib +local QuestieLib = QuestieLoader:ImportModule("QuestieLib") +---@type QuestieDB +local QuestieDB = QuestieLoader:ImportModule("QuestieDB") +---@type QuestieAnnounce +local QuestieAnnounce = QuestieLoader:ImportModule("QuestieAnnounce") +---@type IsleOfQuelDanas +local IsleOfQuelDanas = QuestieLoader:ImportModule("IsleOfQuelDanas") +---@type QuestieCombatQueue +local QuestieCombatQueue = QuestieLoader:ImportModule("QuestieCombatQueue") +---@type QuestieTracker +local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker") +---@type l10n +local l10n = QuestieLoader:ImportModule("l10n") + +--- COMPATIBILITY --- +local C_Timer = QuestieCompat.C_Timer +local GetQuestLogTitle = QuestieCompat.GetQuestLogTitle +local GetItemInfo = QuestieCompat.GetItemInfo + +local tableRemove = table.remove + +local QUEST_LOG_STATES = { + QUEST_ACCEPTED = "QUEST_ACCEPTED", + QUEST_TURNED_IN = "QUEST_TURNED_IN", + QUEST_REMOVED = "QUEST_REMOVED", + QUEST_ABANDONED = "QUEST_ABANDONED" +} + +local eventFrame = CreateFrame("Frame", "QuestieQuestEventFrame") +local questLog = {} +local questLogUpdateQueueSize = 1 +local skipNextUQLCEvent = false +local doFullQuestLogScan = false +local deletedQuestItem = false + +--- Registers all events that are required for questing (accepting, removing, objective updates, ...) +function QuestEventHandler:RegisterEvents() + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] RegisterEvents") + eventFrame:RegisterEvent("QUEST_ACCEPTED") + eventFrame:RegisterEvent("QUEST_TURNED_IN") + eventFrame:RegisterEvent("QUEST_REMOVED") + eventFrame:RegisterEvent("QUEST_LOG_UPDATE") + eventFrame:RegisterEvent("QUEST_WATCH_UPDATE") + eventFrame:RegisterEvent("UNIT_QUEST_LOG_CHANGED") + eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA") + eventFrame:RegisterEvent("NEW_RECIPE_LEARNED") -- Spell objectives; Runes in SoD count as recipes because "Engraving" is a profession? + --eventFrame:RegisterEvent("SPELLS_CHANGED") -- Spell objectives + eventFrame:RegisterEvent("BAG_UPDATE_DELAYED") -- Catch quest item loots that bypass QUEST_WATCH_UPDATE (e.g. autoloot bots) + + eventFrame:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_HIDE") + + eventFrame:RegisterEvent("CHAT_MSG_COMBAT_FACTION_CHANGE") + eventFrame:SetScript("OnEvent", _QuestEventHandler.OnEvent) + + -- StaticPopup dialog hooks. Deleteing Quest items do not always trigger a Quest Log Update. + hooksecurefunc("StaticPopup_Show", function(...) + -- Hook StaticPopup_Show. If we find the "DELETE_ITEM" dialog, check for Quest Items and notify the player. + local which, text_arg1 = ... + if which == "DELETE_ITEM" then + local quest + local questName + local foundQuestItem = false + + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] StaticPopup_Show: Item Name: ", text_arg1) + + if deletedQuestItem == true then + deletedQuestItem = false + end + + for questLogIndex = 1, 75 do + local title, _, _, isHeader, _, _, _, questId = GetQuestLogTitle(questLogIndex) + + if (not title) then + break + end + + if (not isHeader) then + quest = QuestieDB.GetQuest(questId) + + if quest then + local info = StaticPopupDialogs[which] + local sourceItemId, soureItemName, sourceItemType, soureClassID + local reqSourceItemId, reqSoureItemName, reqSourceItemType, reqSoureClassID + + if quest.sourceItemId then + sourceItemId = quest.sourceItemId + + if sourceItemId then + soureItemName, _, _, _, _, sourceItemType, _, _, _, _, _, soureClassID = GetItemInfo(sourceItemId) + end + end + + if quest.requiredSourceItems then + reqSourceItemId = quest.requiredSourceItems[1] + + if reqSourceItemId then + reqSoureItemName, _, _, _, _, reqSourceItemType, _, _, _, _, _, reqSoureClassID = GetItemInfo(reqSourceItemId) + end + end + + if sourceItemId and soureItemName and sourceItemType and soureClassID and (sourceItemType == "Quest" or soureClassID == 12) and QuestieDB.QueryItemSingle(sourceItemId, "class") == 12 and text_arg1 == soureItemName then + questName = quest.name + foundQuestItem = true + break + elseif reqSourceItemId and reqSoureItemName and reqSourceItemType and reqSoureClassID and (reqSourceItemType == "Quest" or reqSoureClassID == 12) and QuestieDB.QueryItemSingle(reqSourceItemId, "class") == 12 and text_arg1 == reqSoureItemName then + questName = quest.name + foundQuestItem = true + break + else + if quest.Objectives and #quest.Objectives > 0 then + for _, objective in pairs(quest.Objectives) do + if text_arg1 == objective.Description then + questName = quest.name + foundQuestItem = true + break + end + end + end + end + end + end + end + + if foundQuestItem and quest and questName then + local frame, text + + for i = 1, STATICPOPUP_NUMDIALOGS do + frame = _G["StaticPopup" .. i] + if (frame:IsShown()) and ((frame.text.text_arg1 == text_arg1) or (string.find(frame.text:GetText(), text_arg1))) then + text = _G[frame:GetName() .. "Text"] + break + end + end + + if frame ~= nil and text ~= nil then + local updateText = l10n("Quest Item %%s might be needed for the quest %%s. \n\nAre you sure you want to delete this?") + text:SetFormattedText(updateText, text_arg1, questName) + text.text_arg1 = updateText + + StaticPopup_Resize(frame, which) + deletedQuestItem = true + + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] StaticPopup_Show: Quest Item Detected. Updating Static Popup.") + end + end + end + end) + + hooksecurefunc("DeleteCursorItem", function() + -- Hook DeleteCursorItem so we know when the player clicks the Accept button + if deletedQuestItem then + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] DeleteCursorItem: Quest Item deleted. Update all quests.") + + C_Timer.After(0.25, function() + _QuestEventHandler:UpdateAllQuests() + deletedQuestItem = false + end) + end + end) + + _QuestEventHandler:InitQuestLog() +end + +--- On Login mark all quests in the quest log with QUEST_ACCEPTED state +function _QuestEventHandler:InitQuestLog() + -- Fill the QuestLogCache for first time + local cacheMiss, changes = QuestLogCache.CheckForChanges(nil) + -- if cacheMiss then + -- TODO actually can happen in rare edge case if player accepts new quest during questie init. *cough* + -- or if someone managed to overflow game cache already at this point. + --Questie:Error("Did you accept a quest during InitQuestLog? Please report on Github or Discord. Game's quest log cache is not ok. This shouldn't happen. Questie may malfunction.") + -- end + + for questId, _ in pairs(changes) do + questLog[questId] = { + state = QUEST_LOG_STATES.QUEST_ACCEPTED + } + QuestieLib:CacheItemNames(questId) + end +end + +--- Fires when a quest is accepted in anyway. +---@param questLogIndex number +---@param questId number +function _QuestEventHandler:QuestAccepted(questLogIndex, questId) + questId = questId or select(8, GetQuestLogTitle(questLogIndex)) + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_ACCEPTED", questLogIndex, questId) + + if questLog[questId] and questLog[questId].timer then + -- We had a QUEST_REMOVED event which started this timer and now it was accepted again. + -- So the quest was abandoned before, because QUEST_TURNED_IN would have run before QUEST_ACCEPTED. + questLog[questId].timer:Cancel() + questLog[questId].timer = nil + QuestieCombatQueue:Queue(function() + _QuestEventHandler:MarkQuestAsAbandoned(questId) + end) + end + + questLog[questId] = {} + + -- Timed quests do not need a full Quest Log Update. + -- TODO: Add achievement timers later. + local questTimers = GetQuestTimers(questId) + if type(questTimers) == "number" then + skipNextUQLCEvent = false + else + skipNextUQLCEvent = true + end + + QuestieCombatQueue:Queue(function() + QuestieLib:CacheItemNames(questId) + _QuestEventHandler:HandleQuestAccepted(questId) + QuestieTracker:Update() + end) + + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_ACCEPTED - skipNextUQLCEvent - ", skipNextUQLCEvent) +end + +---@param questId number +---@return boolean true @if the function was successful, false otherwise +function _QuestEventHandler:HandleQuestAccepted(questId) + local idx = QuestieCompat.GetQuestLogIndexByID(questId) + if not idx then + _QuestLogUpdateQueue:Insert(function() + return _QuestEventHandler:HandleQuestAccepted(questId) + end) + return false + end + -- We first check the quest objectives and retry in the next QLU event if they are not correct yet + local cacheMiss, changes = QuestLogCache.CheckForChanges({ [questId] = true }) + if cacheMiss then + -- if cacheMiss, no need to check changes as only 1 questId + Questie:Debug(Questie.DEBUG_INFO, "Objectives are not cached yet") + _QuestLogUpdateQueue:Insert(function() + return _QuestEventHandler:HandleQuestAccepted(questId) + end) + + return false + end + + Questie:Debug(Questie.DEBUG_INFO, "Objectives are correct. Calling accept logic. quest:", questId) + questLog[questId].state = QUEST_LOG_STATES.QUEST_ACCEPTED + QuestieQuest:SetObjectivesDirty(questId) + + QuestieJourney:AcceptQuest(questId) + QuestieAnnounce:AcceptedQuest(questId) + + local isLastIslePhase = Questie.db.global.isleOfQuelDanasPhase == IsleOfQuelDanas.MAX_ISLE_OF_QUEL_DANAS_PHASES + if Questie.IsWotlk and (not isLastIslePhase) and IsleOfQuelDanas.CheckForActivePhase(questId) then + QuestieQuest:SmoothReset() + else + QuestieQuest:AcceptQuest(questId) + end + QuestieCompat.C_Timer.After(0.2, function() + QuestieTracker:Update() + end) + return true +end + +--- Fires when a quest is turned in +---@param questId number +---@param xpReward number +---@param moneyReward number +function _QuestEventHandler:QuestTurnedIn(questId, xpReward, moneyReward) + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_TURNED_IN", xpReward, moneyReward, questId) + + -- Block UpdateQuest from redrawing objective pins while completion is in-flight + QuestiePlayer.pendingCompleteQuestIds[questId] = true + + if questLog[questId] and questLog[questId].timer then + -- Cancel the timer so the quest is not marked as abandoned + questLog[questId].timer:Cancel() + questLog[questId].timer = nil + end + + Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "was turned in and is completed") + + if questLog[questId] then + -- There are quests which you just turn in so there is no preceding QUEST_ACCEPTED event and questLog[questId] + -- is empty + questLog[questId].state = QUEST_LOG_STATES.QUEST_TURNED_IN + elseif QuestieCompat.Is335 then + questLog[questId] = {state = QUEST_LOG_STATES.QUEST_TURNED_IN} + end + + local parentQuest = QuestieDB.QueryQuestSingle(questId, "parentQuest") + + if parentQuest and parentQuest > 0 then + -- Quests like "The Warsong Reports" have child quests which are just turned in. These child quests only + -- fire QUEST_TURNED_IN + QUEST_LOG_UPDATE + Questie:Debug(Questie.DEBUG_DEVELOP, "Quest:", questId, "Has a Parent Quest - do a full Quest Log check") + doFullQuestLogScan = true + end + + local itemName, _, _, quality, _, itemID = GetQuestLogRewardInfo(GetNumQuestLogRewards(questId), questId) + + if (itemID ~= nil or itemName ~= nil) and quality == 1 then + Questie:Debug(Questie.DEBUG_DEVELOP, "Quest:", questId, "Recieved a possible Quest Item - do a full Quest Log check") + doFullQuestLogScan = true + skipNextUQLCEvent = false + else + skipNextUQLCEvent = true + end + + QuestLogCache.RemoveQuest(questId) + QuestieQuest:SetObjectivesDirty(questId) -- is this necessary? should whole quest.Objectives be cleared at some point of quest removal? + + -- Don't immediately mark as complete, wait for QUEST_REMOVED to confirm it was actually turned in + -- This prevents completed quests from being saved as turned in when abandoned + -- QuestieQuest:CompleteQuest(questId) + -- QuestieJourney:CompleteQuest(questId) + -- QuestieAnnounce:CompletedQuest(questId) + -- questLog[questId] = nil + +QuestieCombatQueue:Queue(function() + QuestieTracker:Update() +end) +end + +--- Fires when a quest is removed from the quest log. This includes turning it in and abandoning it. +---@param questId number +function _QuestEventHandler:QuestRemoved(questId) + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_REMOVED", questId) + doFullQuestLogScan = false + + if (not questLog[questId]) then + questLog[questId] = {} + end + + -- The party members don't care whether a quest was turned in or abandoned, so we can just broadcast here + Questie:SendMessage("QC_ID_BROADCAST_QUEST_REMOVE", questId) + + -- QUEST_TURNED_IN was called before QUEST_REMOVED --> quest was turned in + if questLog[questId].state == QUEST_LOG_STATES.QUEST_TURNED_IN then + Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "was turned in before. Completing quest.") + + -- Ensure the guard is set in case QUEST_REMOVED fires before the combat queue drains + QuestiePlayer.pendingCompleteQuestIds[questId] = true + + -- Now that we confirmed the quest was actually turned in (not abandoned), mark it as complete + QuestieQuest:CompleteQuest(questId) + QuestieJourney:CompleteQuest(questId) + QuestieAnnounce:CompletedQuest(questId) + + questLog[questId] = nil + return + end + + -- QUEST_REMOVED can fire before QUEST_TURNED_IN. If QUEST_TURNED_IN is not called after X seconds the quest + -- was abandoned + questLog[questId] = { + state = QUEST_LOG_STATES.QUEST_REMOVED, + timer = C_Timer.NewTicker(1, function() + _QuestEventHandler:MarkQuestAsAbandoned(questId) + end, 1) + } + skipNextUQLCEvent = true + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_REMOVED - skipNextUQLCEvent - ", skipNextUQLCEvent) +end + +---@param questId number +function _QuestEventHandler:MarkQuestAsAbandoned(questId) + Questie:Debug(Questie.DEBUG_DEVELOP, "QuestEventHandler:MarkQuestAsAbandoned") + local questEntry = questLog[questId] + + -- so we don't attempt to index a nil value. + if (not questEntry) then + Questie:Debug(Questie.DEBUG_DEVELOP, "QuestEventHandler:MarkQuestAsAbandoned - questLog entry missing for", questId) + return + end + + if questEntry.state == QUEST_LOG_STATES.QUEST_REMOVED then + -- Check if objectives were completed before the quest was removed. + -- This handles auto-complete quests that disappear from the log without firing QUEST_TURNED_IN properly. + -- If objectives were complete, treat it as a completion (not abandonment) so objective pins get hidden. + local objectivesWereComplete = false + local quest = QuestieDB.GetQuest(questId) + if quest then + local isComplete = QuestieDB.IsComplete(questId) + objectivesWereComplete = (isComplete == 1) + + -- Check for Ebonhold auto-complete quests which might be removed before IsComplete returns 1 + local desc = quest.Description + if type(desc) == "table" then desc = desc[1] end + + if (not objectivesWereComplete) and desc and type(desc) == "string" then + if string.find(desc, "automatically rewarded") then + Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "detected as auto-complete by description") + objectivesWereComplete = true + end + end + end + + if objectivesWereComplete then + -- Objectives were complete, so this was likely an auto-complete quest. + -- Mark as complete instead of abandoned so objective pins get hidden. + Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "objectives were complete - treating as completed (auto-complete quest)") + questEntry.state = QUEST_LOG_STATES.QUEST_TURNED_IN + + QuestLogCache.RemoveQuest(questId) + QuestieQuest:CompleteQuest(questId) + QuestieJourney:CompleteQuest(questId) + QuestieAnnounce:CompletedQuest(questId) + questLog[questId] = nil + else + Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "was abandoned") + questEntry.state = QUEST_LOG_STATES.QUEST_ABANDONED + + QuestLogCache.RemoveQuest(questId) + QuestieQuest:SetObjectivesDirty(questId) -- is this necessary? should whole quest.Objectives be cleared at some point of quest removal? + + QuestieQuest:AbandonedQuest(questId) + QuestieJourney:AbandonQuest(questId) + QuestieAnnounce:AbandonedQuest(questId) + questLog[questId] = nil + end + end +end + +---Fires when the quest log changed in any way. This event fires very often! +function _QuestEventHandler:QuestLogUpdate() + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_LOG_UPDATE") + + local continueQueuing = true + -- Some of the other quest event didn't have the required information and ordered to wait for the next QLU. + -- We are now calling the function which the event added. + while continueQueuing and next(questLogUpdateQueue) do + continueQueuing = _QuestLogUpdateQueue:GetFirst()() + end + + if doFullQuestLogScan then + doFullQuestLogScan = false + -- Function call updates doFullQuestLogScan. Order matters. + _QuestEventHandler:UpdateAllQuests() + else + _QuestEventHandler:CleanupRemovedQuestsFallback() + QuestieCombatQueue:Queue(function() + QuestieTracker:Update() + end) + end +end + +--- Fires whenever a quest objective progressed +---@param questId number +function _QuestEventHandler:QuestWatchUpdate(questId) + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] QUEST_WATCH_UPDATE", questId) + + -- We do a full scan even though we have the questId because many QUEST_WATCH_UPDATE can fire before + -- a QUEST_LOG_UPDATE. Also not every QUEST_WATCH_UPDATE gets a single QUEST_LOG_UPDATE and doing a full + -- scan is less error prone + doFullQuestLogScan = true +end + +local _UnitQuestLogChangedCallback = function() + -- We also check in here because UNIT_QUEST_LOG_CHANGED is fired before the relevant events + -- (Accept, removed, ...) + if (not skipNextUQLCEvent) then + doFullQuestLogScan = true + else + doFullQuestLogScan = false + skipNextUQLCEvent = false + Questie:Debug(Questie.DEBUG_INFO, "Skipping UnitQuestLogChanged") + end + return true +end + +--- Fires when an objective changed in the quest log of the unitTarget. The required data is not available yet though +---@param unitTarget string +function _QuestEventHandler:UnitQuestLogChanged(unitTarget) + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] UNIT_QUEST_LOG_CHANGED", unitTarget) + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] UNIT_QUEST_LOG_CHANGED - skipNextUQLCEvent - ", skipNextUQLCEvent) + + -- There seem to be quests which don't trigger a QUEST_WATCH_UPDATE. + -- We don't add a full check to the queue if skipNextUQLCEvent == true (from QUEST_WATCH_UPDATE or QUEST_TURNED_IN) + if (not skipNextUQLCEvent) then + doFullQuestLogScan = true + _QuestLogUpdateQueue:Insert(_UnitQuestLogChangedCallback) + else + Questie:Debug(Questie.DEBUG_INFO, "Skipping UnitQuestLogChanged") + end + skipNextUQLCEvent = false +end + + +-- Fallback cleanup: some servers remove quests without firing QUEST_REMOVED reliably. +-- This compares Questie's currentQuestlog vs the game's quest log and removes stale quests. +function _QuestEventHandler:CleanupRemovedQuestsFallback() + local gameQuestIds = {} + local numEntries = select(1, GetNumQuestLogEntries()) or 0 + for questLogIndex = 1, numEntries do + local title, _, _, isHeader, _, _, _, qid = GetQuestLogTitle(questLogIndex) + if title and qid and qid > 0 and (not isHeader) then + gameQuestIds[qid] = true + end + end + + if QuestiePlayer and QuestiePlayer.currentQuestlog then + local removedQuestIds = {} + for questId in pairs(QuestiePlayer.currentQuestlog) do + if questId and questId > 0 and (not gameQuestIds[questId]) then + removedQuestIds[#removedQuestIds + 1] = questId + end + end + + for i = 1, #removedQuestIds do + local questId = removedQuestIds[i] + + -- Quest disappeared from log (abandoned or auto-turned-in) + -- Check if this quest was confirmed as turned in (not just objectives complete) + local wasTurnedIn = questLog[questId] and questLog[questId].state == QUEST_LOG_STATES.QUEST_TURNED_IN + local wasAlreadyComplete = Questie.db.char.complete and Questie.db.char.complete[questId] + + QuestLogCache.RemoveQuest(questId) + QuestieQuest:SetObjectivesDirty(questId) + + -- Only mark as complete if it was actually turned in OR already marked complete from previous session + -- Don't use quest.WasComplete because that's set when objectives complete, not when quest is turned in + if wasTurnedIn or wasAlreadyComplete then + QuestieQuest:CompleteQuest(questId) + else + QuestieQuest:AbandonedQuest(questId) + QuestieJourney:AbandonQuest(questId) + QuestieAnnounce:AbandonedQuest(questId) + end + + questLog[questId] = nil + end + + if #removedQuestIds > 0 then + QuestieNameplate:UpdateNameplate() + QuestieCombatQueue:Queue(function() + QuestieTracker:Update() + end) + end + end +end + +--- Does a full scan of the quest log and updates every quest that is in the QUEST_ACCEPTED state and which hash changed +--- since the last check +function _QuestEventHandler:UpdateAllQuests() + Questie:Debug(Questie.DEBUG_INFO, "Running full questlog check") + local questIdsToCheck = {} + + -- TODO replace with a ready table so no need to generate at each call + for questId, data in pairs(questLog) do + if data.state == QUEST_LOG_STATES.QUEST_ACCEPTED then + questIdsToCheck[questId] = true + end + end + + local cacheMiss, changes = QuestLogCache.CheckForChanges(questIdsToCheck) + + if next(changes) then + for questId, objIds in pairs(changes) do + --Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "objectives:", table.concat(objIds, ","), "will be updated") + Questie:Debug(Questie.DEBUG_INFO, "Quest:", questId, "will be updated") + QuestieQuest:SetObjectivesDirty(questId) + + QuestieNameplate:UpdateNameplate() + QuestieQuest:UpdateQuest(questId) + end + QuestieCombatQueue:Queue(function() + C_Timer.After(1.0, function() + QuestieTracker:Update() + end) + end) + else + Questie:Debug(Questie.DEBUG_INFO, "Nothing to update") + end + + + _QuestEventHandler:CleanupRemovedQuestsFallback() + + -- Do UpdateAllQuests() again at next QUEST_LOG_UPDATE if there was "cacheMiss" (game's cache and addon's cache didn't have all required data yet) + doFullQuestLogScan = doFullQuestLogScan or cacheMiss +end + +local lastTimeQuestRelatedFrameClosedEvent = -1 +--- Blizzard does not fire any event when quest items are received or retrieved from sources other than looting. +--- So we hook events which fires once or twice after closing certain frames and do a full quest log check. +function _QuestEventHandler:QuestRelatedFrameClosed(event) + local now = math.floor(GetTime()) + -- Don't do update if event fired twice + if lastTimeQuestRelatedFrameClosedEvent ~= now then + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event]", event) + + lastTimeQuestRelatedFrameClosedEvent = now + _QuestEventHandler:UpdateAllQuests() + QuestieTracker:Update() + end +end + +function _QuestEventHandler:ReputationChange() + Questie:Debug(Questie.DEBUG_DEVELOP, "[Quest Event] CHAT_MSG_COMBAT_FACTION_CHANGE") + + -- Reputational quest progression doesn't fire UNIT_QUEST_LOG_CHANGED event, only QUEST_LOG_UPDATE event. + doFullQuestLogScan = true +end + +--- Helper function to insert a callback to the questLogUpdateQueue and increase the index +function _QuestLogUpdateQueue:Insert(callback) + questLogUpdateQueue[questLogUpdateQueueSize] = callback + questLogUpdateQueueSize = questLogUpdateQueueSize + 1 +end + +--- Helper function to retrieve the first element of questLogUpdateQueue +---@return function @The callback that was inserted first into questLogUpdateQueue +function _QuestLogUpdateQueue:GetFirst() + questLogUpdateQueueSize = questLogUpdateQueueSize - 1 + return tableRemove(questLogUpdateQueue, 1) +end + +local trackerMinimizedByDungeon = false +function _QuestEventHandler:ZoneChangedNewArea() + Questie:Debug(Questie.DEBUG_DEVELOP, "[EVENT] ZONE_CHANGED_NEW_AREA") + -- By my tests it takes a full 6-7 seconds for the world to load. There are a lot of + -- backend Questie updates that occur when a player zones in/out of an instance. This + -- is necessary to get everything back into it's "normal" state after all the updates. + local isInInstance, instanceType = IsInInstance() + + if isInInstance then + C_Timer.After(8, function() + Questie:Debug(Questie.DEBUG_DEVELOP, "[EVENT] ZONE_CHANGED_NEW_AREA: Entering Instance") + if Questie.db.profile.hideTrackerInDungeons then + trackerMinimizedByDungeon = true + + QuestieCombatQueue:Queue(function() + QuestieTracker:Collapse() + end) + end + end) + + -- We only want this to fire outside of an instance if the player isn't dead and we need to reset the Tracker + elseif (not Questie.db.char.isTrackerExpanded and not UnitIsGhost("player")) and trackerMinimizedByDungeon == true then + C_Timer.After(8, function() + Questie:Debug(Questie.DEBUG_DEVELOP, "[EVENT] ZONE_CHANGED_NEW_AREA: Exiting Instance") + if Questie.db.profile.hideTrackerInDungeons then + trackerMinimizedByDungeon = false + + QuestieCombatQueue:Queue(function() + QuestieTracker:Expand() + end) + end + end) + end +end + +--- Is executed whenever an event is fired and triggers relevant event handling. +---@param event string +function _QuestEventHandler:OnEvent(event, ...) + if event == "QUEST_ACCEPTED" then + _QuestEventHandler:QuestAccepted(...) + elseif event == "QUEST_TURNED_IN" then + _QuestEventHandler:QuestTurnedIn(...) + elseif event == "QUEST_REMOVED" then + _QuestEventHandler:QuestRemoved(...) + elseif event == "QUEST_LOG_UPDATE" then + _QuestEventHandler:QuestLogUpdate() + elseif event == "QUEST_WATCH_UPDATE" then + _QuestEventHandler:QuestWatchUpdate(...) + elseif event == "UNIT_QUEST_LOG_CHANGED" and select(1, ...) == "player" then + _QuestEventHandler:UnitQuestLogChanged(...) + elseif event == "ZONE_CHANGED_NEW_AREA" then + _QuestEventHandler:ZoneChangedNewArea() + elseif event == "NEW_RECIPE_LEARNED" then + Questie:Debug(Questie.DEBUG_DEVELOP, "[EVENT] NEW_RECIPE_LEARNED (QuestEventHandler)") + doFullQuestLogScan = true -- If this event is related to a spell objective, a QUEST_LOG_UPDATE will be fired afterwards + elseif event == "BAG_UPDATE_DELAYED" then + -- Autoloot bots can bypass the standard loot frame, skipping QUEST_WATCH_UPDATE for the last items looted. + -- Flag a full scan and trigger the update immediately so the tracker refreshes. + doFullQuestLogScan = true + _QuestEventHandler:QuestLogUpdate() + elseif event == "PLAYER_INTERACTION_MANAGER_FRAME_HIDE" then + local eventType = select(1, ...) + if eventType == 1 then + event = "TRADE_CLOSED" + elseif eventType == 5 then + event = "MERCHANT_CLOSED" + elseif eventType == 8 then + event = "BANKFRAME_CLOSED" + elseif eventType == 10 then + event = "GUILDBANKFRAME_CLOSED" + elseif eventType == 12 then + event = "VENDOR_CLOSED" + elseif eventType == 17 then + event = "MAIL_CLOSED" + elseif eventType == 21 then + event = "AUCTION_HOUSE_CLOSED" + else + -- Unknown event which we will simply ignore + return + end + _QuestEventHandler:QuestRelatedFrameClosed(event) + elseif event == "CHAT_MSG_COMBAT_FACTION_CHANGE" then + _QuestEventHandler:ReputationChange() + end +end diff --git a/.history/Modules/Quest/QuestieQuest_20260222005948.lua b/.history/Modules/Quest/QuestieQuest_20260222005948.lua new file mode 100644 index 0000000..4e2f727 --- /dev/null +++ b/.history/Modules/Quest/QuestieQuest_20260222005948.lua @@ -0,0 +1,1733 @@ +--- COMPATIBILITY --- +local IsQuestFlaggedCompleted = QuestieCompat.IsQuestFlaggedCompleted or C_QuestLog.IsQuestFlaggedCompleted + +---@class QuestieQuest +local QuestieQuest = QuestieLoader:CreateModule("QuestieQuest") +---@type QuestieQuestPrivate +QuestieQuest.private = QuestieQuest.private or {} +local _QuestieQuest = QuestieQuest.private +------------------------- +--Import modules. +------------------------- +---@type QuestieProfessions +local QuestieProfessions = QuestieLoader:ImportModule("QuestieProfessions") +---@type QuestieReputation +local QuestieReputation = QuestieLoader:ImportModule("QuestieReputation") +---@type QuestieTooltips +local QuestieTooltips = QuestieLoader:ImportModule("QuestieTooltips") +---@type QuestieTracker +local QuestieTracker = QuestieLoader:ImportModule("QuestieTracker") +---@type QuestieDBMIntegration +local QuestieDBMIntegration = QuestieLoader:ImportModule("QuestieDBMIntegration") +---@type QuestieMap +local QuestieMap = QuestieLoader:ImportModule("QuestieMap") +---@type QuestieLib +local QuestieLib = QuestieLoader:ImportModule("QuestieLib") +---@type QuestiePlayer +local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer") +---@type TaskQueue +local TaskQueue = QuestieLoader:ImportModule("TaskQueue") +---@type QuestieDB +local QuestieDB = QuestieLoader:ImportModule("QuestieDB") +---@type ZoneDB +local ZoneDB = QuestieLoader:ImportModule("ZoneDB") +---@type QuestieCombatQueue +local QuestieCombatQueue = QuestieLoader:ImportModule("QuestieCombatQueue") +---@type QuestieAnnounce +local QuestieAnnounce = QuestieLoader:ImportModule("QuestieAnnounce") +---@type QuestieMenu +local QuestieMenu = QuestieLoader:ImportModule("QuestieMenu") +---@type l10n +local l10n = QuestieLoader:ImportModule("l10n") +---@type QuestLogCache +local QuestLogCache = QuestieLoader:ImportModule("QuestLogCache") +---@type ThreadLib +local ThreadLib = QuestieLoader:ImportModule("ThreadLib") +---@type AvailableQuests +local AvailableQuests = QuestieLoader:ImportModule("AvailableQuests") + +--- COMPATIBILITY --- +local C_Timer = QuestieCompat.C_Timer +local GetQuestsCompleted = QuestieCompat.GetQuestsCompleted +local xpcall = QuestieCompat.xpcall + +--We should really try and squeeze out all the performance we can, especially in this. +local tostring = tostring; +local tinsert = table.insert; +local pairs = pairs; +local ipairs = ipairs; +local yield = coroutine.yield +local NewThread = ThreadLib.ThreadSimple + +local NOP_FUNCTION = function() +end +local ERR_FUNCTION = function(err) + print(err) + print(debugstack()) +end + +-- forward declaration +local _UnloadAlreadySpawnedIcons +local _RegisterObjectiveTooltips, _DetermineIconsToDraw, _GetIconsSortedByDistance +local _DrawObjectiveIcons, _DrawObjectiveWaypoints + +local HBD = QuestieCompat.HBD or LibStub("HereBeDragonsQuestie-2.0") + +function QuestieQuest:Initialize() + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest]: Getting all completed quests") + Questie.db.char.complete = GetQuestsCompleted() + + QuestieProfessions:Update() + QuestieReputation:Update(true) +end + +---@param category AutoBlacklistString +function QuestieQuest.ResetAutoblacklistCategory(category) + Questie:Debug(Questie.DEBUG_SPAM, "[QuestieQuest]: Resetting autoblacklist category", category) + for questId, questCategory in pairs(QuestieDB.autoBlacklist) do + if questCategory == category then + QuestieDB.autoBlacklist[questId] = nil + end + end +end + +function QuestieQuest:ToggleNotes(showIcons) + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:ToggleNotes] showIcons:", showIcons) + QuestieQuest:GetAllQuestIds() -- add notes that weren't added from previous hidden state + + if showIcons then + _QuestieQuest:ShowQuestIcons() + _QuestieQuest:ShowManualIcons() + else + _QuestieQuest:HideQuestIcons() + _QuestieQuest:HideManualIcons() + end +end + +function _QuestieQuest:ShowQuestIcons() + local trackerHiddenQuests = Questie.db.char.TrackerHiddenQuests + for questId, frameList in pairs(QuestieMap.questIdFrames) do + if (not trackerHiddenQuests) or (not trackerHiddenQuests[questId]) then -- Skip quests which are completely hidden from the Tracker menu + for _, frameName in pairs(frameList) do -- this may seem a bit expensive, but its actually really fast due to the order things are checked + ---@type IconFrame + local icon = _G[frameName]; + if not icon.data then + error("Desync! Icon has not been removed correctly, but has already been reset. Skipping frame \"" .. + frameName .. "\" for quest " .. questId) + else + local objectiveString = tostring(questId) .. " " .. tostring(icon.data.ObjectiveIndex) + if (not Questie.db.char.TrackerHiddenObjectives) or (not Questie.db.char.TrackerHiddenObjectives[objectiveString]) then + if icon ~= nil and icon.hidden and (not icon:ShouldBeHidden()) then + icon:FakeShow() + + if icon.data.lineFrames then + for _, lineIcon in pairs(icon.data.lineFrames) do + lineIcon:FakeShow() + end + end + end + if (icon.data.QuestData.FadeIcons or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then + icon:FadeOut() + else + icon:FadeIn() + end + end + end + end + end + end +end + +function _QuestieQuest:ShowManualIcons() + for _, frameList in pairs(QuestieMap.manualFrames) do + for _, frameName in pairs(frameList) do + local icon = _G[frameName]; + if icon ~= nil and icon.hidden and (not icon:ShouldBeHidden()) then -- check for function to make sure its a frame + icon:FakeShow() + end + end + end +end + +function _QuestieQuest:HideQuestIcons() + for _, frameList in pairs(QuestieMap.questIdFrames) do + for _, frameName in pairs(frameList) do -- this may seem a bit expensive, but its actually really fast due to the order things are checked + local icon = _G[frameName]; + if icon ~= nil and (not icon.hidden) and icon:ShouldBeHidden() then -- check for function to make sure its a frame + -- Hides Objective Icons + icon:FakeHide() + + -- Hides Objective Tooltips + QuestieTooltips:RemoveQuest(icon.data.Id) + + if icon.data.lineFrames then + for _, lineIcon in pairs(icon.data.lineFrames) do + lineIcon:FakeHide() + end + end + end + if (icon.data.QuestData.FadeIcons or (icon.data.ObjectiveData and icon.data.ObjectiveData.FadeIcons)) and icon.data.Type ~= "complete" then + icon:FadeOut() + else + icon:FadeIn() + end + end + end +end + +function _QuestieQuest:HideManualIcons() + for _, frameList in pairs(QuestieMap.manualFrames) do + for _, frameName in pairs(frameList) do + local icon = _G[frameName]; + if icon ~= nil and (not icon.hidden) and icon:ShouldBeHidden() then -- check for function to make sure its a frame + icon:FakeHide() + end + end + end +end + +function QuestieQuest:ClearAllNotes() + for questId in pairs(QuestiePlayer.currentQuestlog) do + local quest = QuestieDB.GetQuest(questId) + + if not quest then + return + end + + for _, s in pairs(quest.Objectives) do + s.AlreadySpawned = {} + end + + if next(quest.SpecialObjectives) then + for _, s in pairs(quest.SpecialObjectives) do + s.AlreadySpawned = {} + end + end + end + + for _, frameList in pairs(QuestieMap.questIdFrames) do + for _, frameName in pairs(frameList) do + local icon = _G[frameName] + if icon and icon.Unload then + icon:Unload() + end + end + end + + QuestieMap.questIdFrames = {} +end + +function QuestieQuest:ClearAllToolTips() + for questId in pairs(QuestiePlayer.currentQuestlog) do + local quest = QuestieDB.GetQuest(questId) + + if not quest then + return + end + + if quest.Objectives then + for _, objective in pairs(quest.Objectives) do + if objective.hasRegisteredTooltips then + objective.hasRegisteredTooltips = false + end + + if objective.registeredItemTooltips then + objective.registeredItemTooltips = false + end + end + end + + if quest.ObjectiveData then + for _, objective in pairs(quest.ObjectiveData) do + if objective.hasRegisteredTooltips then + objective.hasRegisteredTooltips = false + end + + if objective.registeredItemTooltips then + objective.registeredItemTooltips = false + end + end + end + + if next(quest.SpecialObjectives) then + for _, objective in pairs(quest.SpecialObjectives) do + if objective.hasRegisteredTooltips then + objective.hasRegisteredTooltips = false + end + + if objective.registeredItemTooltips then + objective.registeredItemTooltips = false + end + end + end + end + + QuestieTooltips.lookupByKey = {} + QuestieTooltips.lookupKeyByQuestId = {} +end + +-- This is only needed for SmoothReset(), normally special objectives don't need to update +---@param questId number +local function _UpdateSpecials(questId) + local quest = QuestieDB.GetQuest(questId) + if quest and next(quest.SpecialObjectives) then + for _, objective in pairs(quest.SpecialObjectives) do + local result, err = xpcall(QuestieQuest.PopulateObjective, ERR_FUNCTION, QuestieQuest, quest, 0, objective, + true) + if not result then + Questie:Error("[QuestieQuest]: [SpecialObjectives] " .. + l10n("There was an error populating objectives for %s %s %s %s", quest.name or "No quest name", + quest.Id or "No quest id", 0 or "No objective", err or "No error")); + end + end + end +end + +function QuestieQuest:SmoothReset() + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:SmoothReset]") + if QuestieQuest._isResetting then + QuestieQuest._resetAgain = true + return + end + QuestieQuest._isResetting = true + QuestieQuest._resetNeedsAvailables = false + + -- bit of a hack (there has to be a better way to do logic like this + QuestieDBMIntegration:ClearAll() + local stepTable = { + function() + -- Wait until game cache has quest log okay. + return QuestLogCache.TestGameCache() + end, + function() + return #QuestieMap._mapDrawQueue == 0 and + #QuestieMap._minimapDrawQueue == 0 -- wait until draw queue is finished + end, + function() + QuestieQuest:ClearAllNotes() + QuestieQuest:ClearAllToolTips() + return true + end, + function() + QuestieMenu:OnLogin(true) -- remove icons + return true + end, + function() + return #QuestieMap._mapDrawQueue == 0 and + #QuestieMap._minimapDrawQueue == 0 -- wait until draw queue is finished + end, + function() + -- reset quest log + QuestiePlayer.currentQuestlog = {} + + --- reset the blacklist + QuestieDB.autoBlacklist = {} + + -- make sure complete db is correct + Questie.db.char.complete = GetQuestsCompleted() + QuestieProfessions:Update() + QuestieReputation:Update(true) + + -- populate QuestiePlayer.currentQuestlog + QuestieQuest:GetAllQuestIdsNoObjectives() + QuestieQuest._nextRestQuest = next(QuestiePlayer.currentQuestlog) + return true + end, + function() + QuestieMenu:OnLogin() + return true + end, + function() + QuestieQuest._resetNeedsAvailables = true + AvailableQuests.CalculateAndDrawAll(function() QuestieQuest._resetNeedsAvailables = false end) + return true + end, + function() + for _ = 1, 64 do + if QuestieQuest._nextRestQuest then + QuestieQuest:UpdateQuest(QuestieQuest._nextRestQuest) + _UpdateSpecials(QuestieQuest._nextRestQuest) + QuestieQuest._nextRestQuest = next(QuestiePlayer.currentQuestlog, QuestieQuest._nextRestQuest) + else + QuestieCombatQueue:Queue(function() + C_Timer.After(2.0, function() + QuestieTracker:Update() + end) + end) + break + end + end + return not QuestieQuest._nextRestQuest + end, + function() + return (not QuestieQuest._resetNeedsAvailables) and #QuestieMap._mapDrawQueue == 0 and + #QuestieMap._minimapDrawQueue == 0 + end, + function() + QuestieQuest._isResetting = nil + if QuestieQuest._resetAgain then + QuestieQuest._resetAgain = nil + QuestieQuest:SmoothReset() + end + return true + end + } + local step = 1 + local ticker + ticker = C_Timer.NewTicker(0.01, function() + if stepTable[step]() then + step = step + 1 + if not stepTable[step] then + ticker:Cancel() + end + end + if QuestieQuest._resetAgain and not QuestieQuest._resetNeedsAvailables then -- we can stop the current reset + ticker:Cancel() + QuestieQuest._resetAgain = nil + QuestieQuest._isResetting = nil + QuestieQuest:SmoothReset() + end + end) +end + +---@param questId number +---@return boolean +function QuestieQuest:ShouldShowQuestNotes(questId) + if not Questie.db.profile.hideUntrackedQuestsMapIcons then + return true + end + + local autoWatch = Questie.db.profile.autoTrackQuests + local trackedAuto = autoWatch and + (not Questie.db.char.AutoUntrackedQuests or not Questie.db.char.AutoUntrackedQuests[questId]) + local trackedManual = not autoWatch and (Questie.db.char.TrackedQuests and Questie.db.char.TrackedQuests[questId]) + return trackedAuto or trackedManual +end + +function QuestieQuest:HideQuest(id) + Questie.db.char.hidden[id] = true + QuestieMap:UnloadQuestFrames(id) + QuestieTooltips:RemoveQuest(id) +end + +function QuestieQuest:UnhideQuest(id) + Questie.db.char.hidden[id] = nil + AvailableQuests.CalculateAndDrawAll() +end + +local allianceTournamentMarkerQuests = { [13684] = true, [13685] = true, [13688] = true, [13689] = true, [13690] = true, + [13593] = true, [13703] = true, [13704] = true, [13705] = true, [13706] = true } +local hordeTournamentMarkerQuests = { [13691] = true, [13693] = true, [13694] = true, [13695] = true, [13696] = true, + [13707] = true, [13708] = true, [13709] = true, [13710] = true, [13711] = true } + +---@param questId number +function QuestieQuest:AcceptQuest(questId) + local quest = QuestieDB.GetQuest(questId) + + if quest then + local complete = QuestieDB.IsComplete(questId) + -- If any of these flags exsist then this quest has already once been accepted and is probobly in a failed state + if (quest.WasComplete or quest.isComplete or complete == 0 or complete == -1) and (QuestiePlayer.currentQuestlog[questId]) then + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Accepted Quest:", questId, + " Warning: This quest was once accepted and needs to be reset.") + + -- Reset quest log + QuestiePlayer.currentQuestlog[questId] = nil + + -- Reset quest objectives + quest.Objectives = {} + + -- Reset quest flags + quest.WasComplete = nil + quest.isComplete = nil + + -- Reset tooltips + QuestieTooltips:RemoveQuest(questId) + end + + if not QuestiePlayer.currentQuestlog[questId] then + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Accepted Quest:", questId) + + QuestiePlayer.currentQuestlog[questId] = quest + + if allianceTournamentMarkerQuests[questId] then + Questie.db.char.complete[13686] = true -- Alliance Tournament Eligibility Marker + elseif hordeTournamentMarkerQuests[questId] then + Questie.db.char.complete[13687] = true -- Horde Tournament Eligibility Marker + end + + TaskQueue:Queue( + -- Get all the Frames for the quest and unload them, the available quest icon for example. + function() QuestieMap:UnloadQuestFrames(questId) end, + -- Make sure there isn't any lingering tooltip data hanging around in the quest table. + function() QuestieTooltips:RemoveQuest(questId) end, + function() + -- Re-accepted quest can be collapsed. Expand it. Especially dailies. + if Questie.db.char.collapsedQuests then + Questie.db.char.collapsedQuests[questId] = nil + end + -- Re-accepted quest can be untracked. Clear it. Especially timed quests. + if Questie.db.char.AutoUntrackedQuests[questId] then + Questie.db.char.AutoUntrackedQuests[questId] = nil + end + end, + function() QuestieQuest:PopulateQuestLogInfo(quest) end, + function() + -- This needs to happen after QuestieQuest:PopulateQuestLogInfo because that is the place where quest.Objectives is generated + Questie:SendMessage("QC_ID_BROADCAST_QUEST_UPDATE", questId) + end, + function() QuestieQuest:PopulateObjectiveNotes(quest) end, + function() AvailableQuests.CalculateAndDrawAll() end, + function() + QuestieCombatQueue:Queue(function() + QuestieTracker:Update() + end) + end + ) + else + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Accepted Quest:", questId, + " Warning: Quest already exists, not adding") + end + end +end + +local allianceChampionMarkerQuests = { [13699] = true, [13713] = true, [13723] = true, [13724] = true, [13725] = true } +local hordeChampionMarkerQuests = { [13726] = true, [13727] = true, [13728] = true, [13729] = true, [13731] = true } + +---@param questId number +function QuestieQuest:CompleteQuest(questId) + -- Skip quests which are turn in only and are not added to the quest log in the first place + local questLogEntry = QuestiePlayer.currentQuestlog[questId] + if questLogEntry then + -- Only reset flags if the entry is a table (not a legacy number) + if type(questLogEntry) == "table" then + questLogEntry.WasComplete = nil + questLogEntry.isComplete = nil + end + QuestiePlayer.currentQuestlog[questId] = nil; + end + + -- Only quests that are daily quests or aren't repeatable should be marked complete, + -- otherwise objectives for repeatable quests won't track correctly - #1433 + if QuestieCompat.Is335 then + QuestieCompat.SetQuestComplete(questId) + else + Questie.db.char.complete[questId] = (not QuestieDB.IsRepeatable(questId)) or QuestieDB.IsDailyQuest(questId) or + QuestieDB.IsWeeklyQuest(questId); + end + + if allianceChampionMarkerQuests[questId] then + Questie.db.char.complete[13700] = true -- Alliance Champion Marker + Questie.db.char.complete[13686] = nil -- Alliance Tournament Eligibility Marker + elseif hordeChampionMarkerQuests[questId] then + Questie.db.char.complete[13701] = true -- Horde Champion Marker + Questie.db.char.complete[13687] = nil -- Horde Tournament Eligibility Marker + end + QuestieMap:UnloadQuestFrames(questId) + + -- Clear the pending-complete guard now that frames are unloaded + if QuestiePlayer.pendingCompleteQuestIds then + QuestiePlayer.pendingCompleteQuestIds[questId] = nil + end + + if (QuestieMap.questIdFrames[questId]) then + Questie:Error("Just removed all frames but the framelist seems to still be there!", questId) + end + + -- Delayed verification to ensure all objective icons are removed + -- This handles race conditions where AvailableQuests might redraw icons or UnloadQuestFrames misses some + C_Timer.After(0.5, function() + if QuestieMap.questIdFrames[questId] then + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:CompleteQuest] Lingering frames detected for quest:", + questId, "- forcing cleanup") + QuestieMap:UnloadQuestFrames(questId) + end + end) + + QuestieTooltips:RemoveQuest(questId) + QuestieTracker:RemoveQuest(questId) + QuestieCombatQueue:Queue(function() + QuestieTracker:Update() + end) + + -- TODO: Should this be done first? Because CalculateAndDrawAll looks at QuestieMap.questIdFrames[QuestId] to add available + AvailableQuests.CalculateAndDrawAll() + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Completed Quest:", questId) +end + +---@param questId number +function QuestieQuest:AbandonedQuest(questId) + -- NOTE: Some servers remove quests without firing QUEST_REMOVED reliably. + -- Always cleanup frames/tracker even if the quest isn't currently in QuestiePlayer.currentQuestlog. + if QuestiePlayer.currentQuestlog then + QuestiePlayer.currentQuestlog[questId] = nil + end + + QuestieMap:UnloadQuestFrames(questId) + + local quest = QuestieDB.GetQuest(questId) + if quest then + -- Reset quest objectives + quest.Objectives = {} + + -- Reset quest flags + quest.WasComplete = nil + quest.isComplete = nil + + if allianceTournamentMarkerQuests[questId] then + Questie.db.char.complete[13686] = nil -- Alliance Tournament Eligibility Marker + elseif hordeTournamentMarkerQuests[questId] then + Questie.db.char.complete[13687] = nil -- Horde Tournament Eligibility Marker + end + + local childQuests = QuestieDB.QueryQuestSingle(questId, "childQuests") + if childQuests then + for _, childQuestId in pairs(childQuests) do + Questie.db.char.complete[childQuestId] = nil + QuestLogCache.RemoveQuest(childQuestId) + end + end + end + + AvailableQuests.UnloadUndoable() + + QuestieTracker:RemoveQuest(questId) + QuestieTooltips:RemoveQuest(questId) + QuestieCombatQueue:Queue(function() + QuestieTracker:Update() + end) + + AvailableQuests.CalculateAndDrawAll() + + -- Delayed verification to ensure all objective icons are removed + -- This handles race conditions where QuestieQuest:UpdateQuest might redraw icons + -- just as we are abandoning the quest + C_Timer.After(0.5, function() + if QuestieMap.questIdFrames[questId] then + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:AbandonedQuest] Lingering frames detected for quest:", + questId, "- forcing cleanup") + QuestieMap:UnloadQuestFrames(questId) + end + end) + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Abandoned Quest:", questId) +end + +---@param questId number +function QuestieQuest:UpdateQuest(questId) + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest]", questId) + + ---@type Quest + local quest = QuestieDB.GetQuest(questId) + + local sourceItemId = (quest and tonumber(quest.sourceItemId)) or 0 + + if quest and (not Questie.db.char.complete[questId] or QuestiePlayer.currentQuestlog[questId]) then + -- Skip this update if the quest is mid-completion to avoid redrawing objective pins + -- that CompleteQuest is about to remove via UnloadQuestFrames. + if QuestiePlayer.pendingCompleteQuestIds and QuestiePlayer.pendingCompleteQuestIds[questId] then + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] Skipping - quest is pending completion:", questId) + return + end + QuestieQuest:PopulateQuestLogInfo(quest) + + if QuestieQuest:ShouldShowQuestNotes(questId) then + QuestieQuest:UpdateObjectiveNotes(quest) + else + QuestieTooltips:RemoveQuest(questId) + end + + local isComplete = QuestieDB.IsComplete(questId) + + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] QuestDB:IsComplete() flag is: " .. isComplete) + + if isComplete == 1 then + -- Quest is complete + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] Quest is: Complete!") + + QuestieMap:UnloadQuestFrames(questId) + QuestieQuest:AddFinisher(quest) + quest.WasComplete = true + elseif isComplete == -1 then + -- Failed quests should be shown as available again + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest:UpdateQuest] Quest has: Failed!") + + QuestieMap:UnloadQuestFrames(questId) + QuestieTooltips:RemoveQuest(questId) + AvailableQuests.DrawAvailableQuest(quest) + + -- Reset any collapsed quest flags + if Questie.db.char.collapsedQuests then + Questie.db.char.collapsedQuests[questId] = nil + end + elseif isComplete == 0 then + -- Quest was somehow reset back to incomplete after being completed (quest.WasComplete == true). + -- The "or" check looks for a sourceItemId then checks to see if it's NOT in the players bag. + -- Player destroyed quest items? Or some other quest mechanic removed the needed quest item. + -- Check if all objectives are already complete before treating a missing source item as a reset. + -- Some quests use consumable key items (e.g. Cold Iron Key for quest 12843). After using the key + -- the item leaves the bag, making CheckQuestSourceItem return false. Without this guard, Questie + -- would reset the quest and draw the key-drop NPC on the map even though all objectives are done. + local allObjectivesComplete = false + if quest.Objectives and #quest.Objectives > 0 then + local doneCount = 0 + for i = 1, #quest.Objectives do + if quest.Objectives[i] and quest.Objectives[i].Completed == true then + doneCount = doneCount + 1 + end + end + allObjectivesComplete = (doneCount == #quest.Objectives) + end + + if quest and not allObjectivesComplete and (quest.WasComplete or (sourceItemId > 0 and QuestieQuest:CheckQuestSourceItem(questId) == false)) then + Questie:Debug(Questie.DEBUG_DEVELOP, + "[QuestieQuest:UpdateQuest] Quest was once complete or Quest Item(s) were removed. Resetting quest.") + + -- Reset quest objectives + quest.Objectives = {} + + -- Reset quest flags + quest.WasComplete = nil + quest.isComplete = nil + + -- Reset tooltips + QuestieTooltips:RemoveQuest(questId) + + QuestieQuest:CheckQuestSourceItem(questId, true) + QuestieMap:UnloadQuestFrames(questId) + + -- Reset any collapsed quest flags + if Questie.db.char.collapsedQuests then + Questie.db.char.collapsedQuests[questId] = nil + end + + QuestieQuest:PopulateQuestLogInfo(quest) + QuestieQuest:PopulateObjectiveNotes(quest) + AvailableQuests.CalculateAndDrawAll() + else + -- Sometimes objective(s) are all complete but the quest doesn't get flagged as "1". So far the only + -- quests I've found that does this are quests involving an item(s). Checks all objective(s) and if they + -- are all complete, simulate a "Complete Quest" so the quest finisher appears on the map. + if quest.Objectives and #quest.Objectives > 0 then + local numCompleteObjectives = 0 + + for i = 1, #quest.Objectives do + if quest.Objectives[i] and quest.Objectives[i].Completed and quest.Objectives[i].Completed == true then + numCompleteObjectives = numCompleteObjectives + 1 + end + end + + if numCompleteObjectives == #quest.Objectives then + Questie:Debug(Questie.DEBUG_DEVELOP, + "[QuestieQuest:UpdateQuest] All Quest Objective(s) are Complete! Manually setting quest to Complete!") + QuestieMap:UnloadQuestFrames(questId) + QuestieQuest:AddFinisher(quest) + quest.WasComplete = true + quest.isComplete = true + else + Questie:Debug(Questie.DEBUG_DEVELOP, + "[QuestieQuest:UpdateQuest] Quest Objective Status is: " .. + numCompleteObjectives .. ", out of: " .. #quest.Objectives .. ". No updates required.") + end + end + end + end + + Questie:SendMessage("QC_ID_BROADCAST_QUEST_UPDATE", questId) + end +end + +---@param questId number +function QuestieQuest:SetObjectivesDirty(questId) + local quest = QuestieDB.GetQuest(questId) + + if quest then + for _, objective in pairs(quest.Objectives) do + objective.isUpdated = false + end + end +end + +--Run this if you want to update the entire table +function QuestieQuest:GetAllQuestIds() + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Getting all quests") + + QuestiePlayer.currentQuestlog = {} + + for questId, data in pairs(QuestLogCache.questLog_DO_NOT_MODIFY) do -- DO NOT MODIFY THE RETURNED TABLE + local quest = QuestieDB.GetQuest(questId) + + if not quest then + if not Questie._sessionWarnings[questId] then + if not Questie.IsSoD then + Questie:Error(l10n( + "The quest %s is missing from Questie's database. Please report this on GitHub or Discord!", + tostring(questId))) + end + Questie._sessionWarnings[questId] = true + end + + QuestiePlayer.currentQuestlog[questId] = questId -- legacy behavior + else + local complete = QuestieDB.IsComplete(questId) + + QuestiePlayer.currentQuestlog[questId] = quest + quest.LocalizedName = data.title + + if complete == -1 then + QuestieQuest:UpdateQuest(questId) + else + -- Only draw the source item objective when the quest is not yet complete. + -- If the quest is complete (complete == 1), the source item was consumed during + -- the quest (e.g. Cold Iron Key for quest 12843) and should not draw its drop NPC. + if complete == 1 then + -- Mark the quest object as complete so QuestieArrow collects finisher spawns + -- instead of objective spawns. Without this, the arrow falls through to the + -- objective collection path and picks up any stale fake item objectives. + quest.isComplete = true + quest.WasComplete = true + else + QuestieQuest:CheckQuestSourceItem(questId, true) + end + QuestieQuest:PopulateQuestLogInfo(quest) + + if QuestieQuest:ShouldShowQuestNotes(questId) then + QuestieQuest:PopulateObjectiveNotes(quest) + else + QuestieTooltips:RemoveQuest(questId) + end + end + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Adding the quest", questId, + QuestiePlayer.currentQuestlog[questId]) + end + end + + QuestieCombatQueue:Queue(function() + QuestieTracker:Update() + end) +end + +-- This checks and manually adds quest item tooltips for sourceItems +local function _AddSourceItemObjective(quest) + local sourceItemId = tonumber(quest.sourceItemId) or 0 + if sourceItemId <= 0 then + return + end + + local questObjectives = QuestieDB.QueryQuestSingle(quest.Id, "objectives") + local itemObjectives = questObjectives and questObjectives[3] + + -- If sourceItemId is already part of an item objective, do nothing + if itemObjectives then + for _, itemObjectiveIndex in pairs(itemObjectives) do + for _, itemObjectiveId in pairs(itemObjectiveIndex) do + if itemObjectiveId == sourceItemId then + Questie:Debug(Questie.DEBUG_INFO, + "[QuestieQuest:_AddSourceItemObjective] This item is already part of a quest objective.") + return + end + end + end + end + + local itemName = QuestieDB.QueryItemSingle(sourceItemId, "name") + if not itemName then + return + end + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_AddSourceItemObjective] Adding Source Item Id for:", sourceItemId) + + local fakeObjective = { + Id = quest.Id, + IsSourceItem = true, + QuestData = quest, + Index = 1, + Needed = 1, + Collected = 1, + text = itemName, + Description = itemName + } + + QuestieTooltips:RegisterObjectiveTooltip(quest.Id, "i_" .. sourceItemId, fakeObjective); +end + +-- This checks and manually adds quest item tooltips for SpellItems +local function _AddSpellItemObjective(quest) + if not quest.SpellItemId then + return + end + + local questObjectives = QuestieDB.QueryQuestSingle(quest.Id, "objectives") + local spellObjectives = questObjectives and questObjectives[6] + if not spellObjectives then + return + end + + local depthIndex = 1 -- TODO: What is better for this? + local needed = (quest.Objectives and quest.Objectives[depthIndex] and quest.Objectives[depthIndex].Needed) or 0 + local collected = (quest.Objectives and quest.Objectives[depthIndex] and quest.Objectives[depthIndex].Collected) or 0 + local desc = (quest.Objectives and quest.Objectives[depthIndex] and quest.Objectives[depthIndex].Description) or "" + + local fakeObjective = { + Id = quest.Id, + IsSourceItem = true, + QuestData = quest, + Index = 1, + Needed = needed, + Collected = collected, + text = nil, + Description = desc, + } + + QuestieTooltips:RegisterObjectiveTooltip(quest.Id, "i_" .. quest.SpellItemId, fakeObjective); +end + + +-- This checks and manually adds quest item tooltips for requiredSourceItems +local function _AddRequiredSourceItemObjective(quest) + if not quest.requiredSourceItems then + return + end + + local questObjectives = QuestieDB.QueryQuestSingle(quest.Id, "objectives") + local itemObjectives = questObjectives and questObjectives[3] + + for index, requiredSourceItemId in pairs(quest.requiredSourceItems) do + local alreadyInObjectives = false + + if itemObjectives then + for _, itemObjectiveIndex in pairs(itemObjectives) do + for _, itemObjectiveId in pairs(itemObjectiveIndex) do + if itemObjectiveId == requiredSourceItemId or quest.sourceItemId == requiredSourceItemId then + Questie:Debug(Questie.DEBUG_INFO, + "[QuestieQuest:_AddRequiredSourceItemObjective] This item is already part of a quest objective.") + alreadyInObjectives = true + break + end + end + if alreadyInObjectives then + break + end + end + end + + if not alreadyInObjectives then + local itemName = QuestieDB.QueryItemSingle(requiredSourceItemId, "name") + if itemName then + Questie:Debug(Questie.DEBUG_INFO, + "[QuestieQuest:_AddRequiredSourceItemObjective] Adding Source Item Id for:", requiredSourceItemId) + + local fakeObjective = { + Id = quest.Id, + IsRequiredSourceItem = true, + QuestData = quest, + Index = index, + text = itemName, + Description = itemName + } + + QuestieTooltips:RegisterObjectiveTooltip(quest.Id, "i_" .. requiredSourceItemId, fakeObjective); + end + end + end +end + + + +function QuestieQuest:GetAllQuestIdsNoObjectives() + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Getting all quests without objectives") + QuestiePlayer.currentQuestlog = {} + + for questId, data in pairs(QuestLogCache.questLog_DO_NOT_MODIFY) do -- DO NOT MODIFY THE RETURNED TABLE + local quest = QuestieDB.GetQuest(questId) + + if not quest then + if not Questie._sessionWarnings[questId] then + if not Questie.IsSoD then + Questie:Error(l10n( + "The quest %s is missing from Questie's database. Please report this on GitHub or Discord!", + tostring(questId))) + end + Questie._sessionWarnings[questId] = true + end + + QuestiePlayer.currentQuestlog[questId] = questId + else + QuestiePlayer.currentQuestlog[questId] = quest + quest.LocalizedName = data.title + _AddSourceItemObjective(quest) + _AddRequiredSourceItemObjective(quest) + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Adding the quest", questId, + QuestiePlayer.currentQuestlog[questId]) + end + end +end + +-- iterate all notes, update / remove as needed +---@param quest Quest +function QuestieQuest:UpdateObjectiveNotes(quest) + if (not QuestiePlayer.currentQuestlog) or (not QuestiePlayer.currentQuestlog[quest.Id]) then + return + end + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] UpdateObjectiveNotes:", quest.Id) + for objectiveIndex, objective in pairs(quest.Objectives) do + local result, err = xpcall(QuestieQuest.PopulateObjective, ERR_FUNCTION, QuestieQuest, quest, objectiveIndex, + objective, false) + if (not result) then + Questie:Debug(Questie.DEBUG_ELEVATED, "[QuestieQuest] There was an error populating objectives for", + quest.name, quest.Id, objectiveIndex, err) + end + end + + if next(quest.SpecialObjectives) then + for _, objective in pairs(quest.SpecialObjectives) do + local result, err = xpcall(QuestieQuest.PopulateObjective, ERR_FUNCTION, QuestieQuest, quest, 0, objective, + true) + if not result then + Questie:Error("[QuestieQuest]: [SpecialObjectives] " .. + l10n("There was an error populating objectives for %s %s %s %s", quest.name or "No quest name", + quest.Id or "No quest id", 0 or "No objective", err or "No error")); + end + end + end +end + +-- This function is used to check the players bags for an item that matches quest.sourceItemId. +-- A good example for this edge case is [18] The Price of Shoes (118) where upon acceptance, Verner's Note (1283) is given +-- to the player and the Quest is immediately flagged as Complete. If the note is destroyed then a slightly modified version +-- of QuestieDB.IsComplete() that uses this function, returns zero allowing the quest updates to properly set the quests state. +---@param questId number @QuestID +---@param makeObjective boolean @If set to true, then this will create an incomplete objective for the missing quest item +---@return boolean @Returns true if quest.sourceItemId matches an item in a players bag +function QuestieQuest:CheckQuestSourceItem(questId, makeObjective) + local quest = QuestieDB.GetQuest(questId) + local sourceItem = true + + -- Ascension/custom quests may omit sourceItemId (nil). Normalize to 0. + local sourceItemId = (quest and tonumber(quest.sourceItemId)) or 0 + + if quest and sourceItemId > 0 then + for bag = -2, 4 do + local numSlots = QuestieCompat.GetContainerNumSlots(bag) or 0 + for slot = 1, numSlots do + local itemId = select(10, QuestieCompat.GetContainerItemInfo(bag, slot)) + if itemId == sourceItemId then + return true + end + end + + sourceItem = false + end + + -- If we are missing the sourceItem for zero objective quests then make an objective for it so the + -- player has a visual indication as to what item is missing and so the quest has a "tag" of some kind. + -- Also double check the quests leaderboard and make sure an objective doesn't already exist. + if (not sourceItem) and makeObjective and (not QuestieQuest:GetAllLeaderBoardDetails(quest.Id)[1]) then + local itemName = QuestieDB.QueryItemSingle(sourceItemId, "name") or ("Item " .. tostring(sourceItemId)) + quest.Objectives = { + [1] = { + Description = itemName, + Type = "item", + Needed = 1, + Collected = 0, + Completed = false, + Id = sourceItemId, + questId = quest.Id + } + } + end + else + return true + end + + return false +end + +local function _GetIconScaleForAvailable() + return Questie.db.profile.availableScale or 1.3 +end + +---@param quest Quest +function QuestieQuest:AddFinisher(quest) + --We should never ever add the quest if IsQuestFlaggedComplete true. + local questId = quest.Id + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest] Adding finisher for quest", questId) + + local complete = QuestieDB.IsComplete(questId) + + if (QuestiePlayer.currentQuestlog[questId] and (IsQuestFlaggedCompleted(questId) == false) and (complete == 1 or complete == 0) and (not Questie.db.char.complete[questId])) then + local finisher, key + + if quest.Finisher ~= nil then + if quest.Finisher.Type == "monster" then + finisher = QuestieDB:GetNPC(quest.Finisher.Id) + key = "m_" .. quest.Finisher.Id + elseif quest.Finisher.Type == "object" then + finisher = QuestieDB:GetObject(quest.Finisher.Id) + key = "o_" .. quest.Finisher.Id + else + Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieQuest] Unhandled finisher type:", quest.Finisher.Type, + questId, quest.name) + end + else + Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieQuest] Quest has no finisher:", questId, quest.name) + end + + if finisher ~= nil then + -- Certain race conditions can occur when the NPC/Objects are both the Quest Starter and Quest Finisher + -- which can result in duplicate Quest Title tooltips appearing. DrawAvailableQuest() would have already + -- registered this NPC/Object so, the appropriate tooltip lines are already present. This checks and clears + -- any duplicate keys before registering the Quest Finisher. + + -- Clear duplicate keys if they exist + if QuestieTooltips.lookupByKey[key] then + if QuestieTooltips:GetTooltip(key) ~= nil and #QuestieTooltips:GetTooltip(key) > 1 then + for ttline = 1, #QuestieTooltips:GetTooltip(key) do + for index, line in pairs(QuestieTooltips:GetTooltip(key)) do + if (ttline == index) then + Questie:Debug(Questie.DEBUG_DEVELOP, + "[QuestieQuest] AddFinisher - Removing duplicate Quest Title!") + + -- Remove duplicate Quest Title + QuestieTooltips.lookupByKey[key][tostring(questId) .. " " .. finisher.name] = nil + + -- Now check to see if the dup has a Special Objective + local objText = string.match(line, ".*|cFFcbcbcb.*") + + if objText then + local objIndex + + -- Grab the Special Objective index + if quest.SpecialObjectives[1] then + objIndex = quest.SpecialObjectives[1].Index + end + + if objIndex then + Questie:Debug(Questie.DEBUG_DEVELOP, + "[QuestieQuest] AddFinisher - Removing Special Objective!") + + -- Remove Special Objective Text + QuestieTooltips.lookupByKey[key][tostring(questId) .. " " .. objIndex] = nil + end + end + end + end + end + end + end + + QuestieTooltips:RegisterQuestStartTooltip(questId, finisher.name, finisher.id, key) + + local finisherIcons = {} + local finisherLocs = {} + + for finisherZone, spawns in pairs(finisher.spawns or {}) do + if (finisherZone ~= nil and spawns ~= nil) then + for _, coords in ipairs(spawns) do + local data = { + Id = questId, + Icon = Questie.ICON_TYPE_COMPLETE, + GetIconScale = _GetIconScaleForAvailable, + IconScale = _GetIconScaleForAvailable(), + Type = "complete", + QuestData = quest, + Name = finisher.name, + IsObjectiveNote = false, + } + + if QuestieDB.IsActiveEventQuest(quest.Id) then + data.Icon = Questie.ICON_TYPE_EVENTQUEST_COMPLETE + elseif QuestieDB.IsPvPQuest(quest.Id) then + data.Icon = Questie.ICON_TYPE_PVPQUEST_COMPLETE + elseif quest.IsRepeatable then + data.Icon = Questie.ICON_TYPE_REPEATABLE_COMPLETE + end + + if (coords[1] == -1 or coords[2] == -1) then + local dungeonLocation = ZoneDB:GetDungeonLocation(finisherZone) + if dungeonLocation ~= nil then + for _, value in ipairs(dungeonLocation) do + local zone = value[1]; + local x = value[2]; + local y = value[3]; + + QuestieMap:DrawWorldIcon(data, zone, x, y) + end + end + else + local x = coords[1]; + local y = coords[2]; + + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Adding world icon as finisher:", + finisherZone, x, y) + finisherIcons[finisherZone] = QuestieMap:DrawWorldIcon(data, finisherZone, x, y) + + if not finisherLocs[finisherZone] then + finisherLocs[finisherZone] = { x, y } + end + end + end + end + end + + if finisher.waypoints then + for zone, waypoints in pairs(finisher.waypoints) do + if (not ZoneDB.IsDungeonZone(zone)) then + if not finisherIcons[zone] and waypoints[1] and waypoints[1][1] and waypoints[1][1][1] then + local data = { + Id = questId, + Icon = Questie.ICON_TYPE_COMPLETE, + GetIconScale = _GetIconScaleForAvailable, + IconScale = _GetIconScaleForAvailable(), + Type = "complete", + QuestData = quest, + Name = finisher.name, + IsObjectiveNote = false, + } + + if QuestieDB.IsActiveEventQuest(quest.Id) then + data.Icon = Questie.ICON_TYPE_EVENTQUEST_COMPLETE + elseif QuestieDB.IsPvPQuest(quest.Id) then + data.Icon = Questie.ICON_TYPE_PVPQUEST_COMPLETE + elseif quest.IsRepeatable then + data.Icon = Questie.ICON_TYPE_REPEATABLE_COMPLETE + end + + finisherIcons[zone] = QuestieMap:DrawWorldIcon(data, zone, waypoints[1][1][1], + waypoints[1][1][2]) + finisherLocs[zone] = { waypoints[1][1][1], waypoints[1][1][2] } + end + + QuestieMap:DrawWaypoints(finisherIcons[zone], waypoints, zone) + end + end + end + else + Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieQuest] finisher or finisher.spawns == nil for questId", + questId) + end + end +end + +---@param quest Quest +---@param objectiveIndex ObjectiveIndex +---@param objective QuestObjective +---@param blockItemTooltips any +function QuestieQuest:PopulateObjective(quest, objectiveIndex, objective, blockItemTooltips) -- must be p-called + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjective]", objective.Description) + + if (not objective.Update) then + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjective] - Quest is already updated. --> Exiting!") + return + end + + objective:Update() + local completed = objective.Completed + local objectiveData = quest.ObjectiveData[objective.Index] or + objective -- the reason for "or objective" is to handle "SpecialObjectives" aka non-listed objectives (demonic runestones for closing the portal) + + if (not objective.spawnList or (not next(objective.spawnList))) and _QuestieQuest.objectiveSpawnListCallTable[objectiveData.Type] then + objective.spawnList = _QuestieQuest.objectiveSpawnListCallTable[objectiveData.Type](objective.Id, objective, + objectiveData); + end + + -- Tooltips should always show. + -- For completed and uncompleted objectives + _RegisterObjectiveTooltips(objective, quest.Id, blockItemTooltips) + + if completed or quest.isComplete then + _UnloadAlreadySpawnedIcons(objective) + return + end + + if (not objective.Color) then + objective.Color = QuestieLib:ColorWheel() + end + + if objective.spawnList and next(objective.spawnList) then + local maxPerType = 300 + + if Questie.db.profile.enableIconLimit and Questie.db.profile.iconLimit < maxPerType then + maxPerType = Questie.db.profile.iconLimit + end + + local closestStarter = QuestieMap:FindClosestStarter() + local objectiveCenter = closestStarter[quest.Id] + + local zoneCount = 0 + local zones = {} + local objectiveZone + + for _, spawnData in pairs(objective.spawnList) do + for zone in pairs(spawnData.Spawns) do + zones[zone] = true + end + end + + for zone in pairs(zones) do + objectiveZone = zone + zoneCount = zoneCount + 1 + end + + if zoneCount == 1 then -- this objective happens in 1 zone, clustering should be relative to that zone + local x, y = HBD:GetWorldCoordinatesFromZone(0.5, 0.5, ZoneDB:GetUiMapIdByAreaId(objectiveZone)) + objectiveCenter = { x = x, y = y } + end + + local iconsToDraw, _ = _DetermineIconsToDraw(quest, objective, objectiveIndex, objectiveCenter) + local icon, iconPerZone = _DrawObjectiveIcons(quest.Id, iconsToDraw, objective, maxPerType) + _DrawObjectiveWaypoints(objective, icon, iconPerZone) + end +end + +_RegisterObjectiveTooltips = function(objective, questId, blockItemTooltips) + Questie:Debug(Questie.DEBUG_INFO, "Registering objective tooltips for", objective.Description) + + if objective.spawnList then + if (not objective.hasRegisteredTooltips) then + for id, spawnData in pairs(objective.spawnList) do + if spawnData.TooltipKey and (not objective.AlreadySpawned[id]) then + QuestieTooltips:RegisterObjectiveTooltip(questId, spawnData.TooltipKey, objective) + end + end + + objective.hasRegisteredTooltips = true + end + else + -- No spawnList and no Id means there is nothing Questie can draw for this objective. + -- This covers server-tracked trigger objectives (e.g. "complete N quests in zone" for + -- quest 50150) which may have any objectiveType from the server, not just "event". + if not objective.Id then + objective.hasRegisteredTooltips = true + return + end + Questie:Error("[QuestieQuest]: [Tooltips] " .. + l10n("There was an error populating objectives for %s %s %s %s", objective.Description or "No objective text", + questId or "No quest id", 0 or "No objective", "No error")) + end + + if (not objective.registeredItemTooltips) and objective.Type == "item" and (not blockItemTooltips) and objective.Id then + local itemName = QuestieDB.QueryItemSingle(objective.Id, "name") + + if itemName then + QuestieTooltips:RegisterObjectiveTooltip(questId, "i_" .. objective.Id, objective) + end + + objective.registeredItemTooltips = true + end +end + +_UnloadAlreadySpawnedIcons = function(objective) + if objective.AlreadySpawned and next(objective.AlreadySpawned) then + for id, spawn in pairs(objective.AlreadySpawned) do + if spawn then + for _, mapIcon in pairs(spawn.mapRefs) do + mapIcon:Unload() + end + for _, minimapIcon in pairs(spawn.minimapRefs) do + minimapIcon:Unload() + end + spawn.mapRefs = {} + spawn.minimapRefs = {} + end + end + objective.AlreadySpawned = {} + end +end + +---@param quest Quest +---@param objective QuestObjective +---@param objectiveIndex ObjectiveIndex +---@param objectiveCenter {x:X, y:Y} +_DetermineIconsToDraw = function(quest, objective, objectiveIndex, objectiveCenter) + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_DetermineIconsToDraw]") + + local iconsToDraw = {} + local spawnItemId + + for id, spawnData in pairs(objective.spawnList) do + if spawnData.ItemId then + spawnItemId = spawnData.ItemId + end + + if (not objective.Icon) and spawnData.Icon then + objective.Icon = spawnData.Icon + end + + if (not objective.AlreadySpawned[id]) and (not objective.Completed) and Questie.db.profile.enableObjectives then + local data = { + Id = quest.Id, + ObjectiveIndex = objectiveIndex, + QuestData = quest, + ObjectiveData = objective, + Icon = spawnData.Icon, + IconColor = quest.Color, + GetIconScale = spawnData.GetIconScale, + IconScale = spawnData.GetIconScale(), + Name = spawnData.Name, + Type = objective.Type, + ObjectiveTargetId = spawnData.Id + } + + objective.AlreadySpawned[id] = { + data = data, + minimapRefs = {}, + mapRefs = {}, + } + + for zone, spawns in pairs(spawnData.Spawns) do + local uiMapId = ZoneDB:GetUiMapIdByAreaId(zone) + for _, spawn in pairs(spawns) do + if (spawn[1] and spawn[2]) then + local drawIcon = { + AlreadySpawnedId = id, + data = data, + zone = zone, + AreaID = zone, + UiMapID = uiMapId, + x = spawn[1], + y = spawn[2], + worldX = 0, + worldY = 0, + distance = 0, + touched = nil, -- TODO change. This is meant to let lua reserve memory for all keys needed for sure. + } + local x, y, _ = HBD:GetWorldCoordinatesFromZone(drawIcon.x / 100, drawIcon.y / 100, uiMapId) + x = x or 0 + y = y or 0 + -- Cache world coordinates for clustering calculations + drawIcon.worldX = x + drawIcon.worldY = y + -- There are instances when X and Y are not in the same map such as in dungeons etc, we default to 0 if it is not set + -- This will create a distance of 0 but it doesn't matter. + local distance = QuestieLib:Euclid(objectiveCenter.x or 0, objectiveCenter.y or 0, x, y); + drawIcon.distance = distance or 0 -- cache for clustering + -- there can be multiple icons at same distance at different directions + --local distance = floor(distance) + local iconList = iconsToDraw[distance] + if iconList then + iconList[#iconList + 1] = drawIcon + else + iconsToDraw[distance] = { drawIcon } + end + end + end + end + end + end + + return iconsToDraw, spawnItemId +end + +_DrawObjectiveIcons = function(questId, iconsToDraw, objective, maxPerType) + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_DrawObjectiveIcons] Adding Icons for quest:", questId) + + local spawnedIconCount = 0 + local icon + local iconPerZone = {} + + local range = Questie.db.profile.clusterLevelHotzone + + local iconCount, orderedList = _GetIconsSortedByDistance(iconsToDraw) + + if orderedList[1] and orderedList[1].Icon == Questie.ICON_TYPE_OBJECT then -- new clustering / limit code should prevent problems, always show all object notes + range = range * 0.2; -- Only use 20% of the default range. + end + + local hotzones = QuestieMap.utils:CalcHotzones(orderedList, range, iconCount); + + for i = 1, #hotzones do + local hotzone = hotzones[i] + if (spawnedIconCount > maxPerType) then + Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Too many icons for quest:", questId) + break; + end + + --Any icondata will do because they are all the same + icon = hotzone[1]; + + local spawnsMapRefs = objective.AlreadySpawned[icon.AlreadySpawnedId].mapRefs + local spawnsMinimapRefs = objective.AlreadySpawned[icon.AlreadySpawnedId].minimapRefs + + local centerX, centerY = QuestieMap.utils.CenterPoint(hotzone) + + local dungeonLocation = ZoneDB:GetDungeonLocation(icon.zone) + + if dungeonLocation and centerX == -1 and centerY == -1 then + if dungeonLocation[2] then -- We have more than 1 instance entrance (e.g. Blackrock dungeons) + local secondDungeonLocation = dungeonLocation[2] + + icon.zone = secondDungeonLocation[1] + centerX = secondDungeonLocation[2] + centerY = secondDungeonLocation[3] + + local iconMap, iconMini = QuestieMap:DrawWorldIcon(icon.data, icon.zone, centerX, centerY) -- clustering code takes care of duplicates as long as min-dist is more than 0 + + if iconMap and iconMini then + iconPerZone[icon.zone] = { iconMap, centerX, centerY } + spawnsMapRefs[#spawnsMapRefs + 1] = iconMap + spawnsMinimapRefs[#spawnsMinimapRefs + 1] = iconMini + end + + spawnedIconCount = spawnedIconCount + 1; + end + + local firstDungeonLocation = dungeonLocation[1] + icon.zone = firstDungeonLocation[1] + centerX = firstDungeonLocation[2] + centerY = firstDungeonLocation[3] + end + + local iconMap, iconMini = QuestieMap:DrawWorldIcon(icon.data, icon.zone, centerX, centerY) -- clustering code takes care of duplicates as long as min-dist is more than 0 + + if iconMap and iconMini then + iconPerZone[icon.zone] = { iconMap, centerX, centerY } + spawnsMapRefs[#spawnsMapRefs + 1] = iconMap + spawnsMinimapRefs[#spawnsMinimapRefs + 1] = iconMini + end + + spawnedIconCount = spawnedIconCount + 1; + end + + return icon, iconPerZone +end + +_GetIconsSortedByDistance = function(icons) + local iconCount = 0; + local orderedList = {} + local distances = {} + + local i = 0 + + for distance in pairs(icons) do + i = i + 1 + distances[i] = distance + end + + table.sort(distances) + + -- use the keys to retrieve the values in the sorted order + for distIndex = 1, #distances do + local iconsAtDisntace = icons[distances[distIndex]] + + for iconIndex = 1, #iconsAtDisntace do + local icon = iconsAtDisntace[iconIndex] + + iconCount = iconCount + 1 + orderedList[iconCount] = icon + end + end + + return iconCount, orderedList +end + +_DrawObjectiveWaypoints = function(objective, icon, iconPerZone) + for _, spawnData in pairs(objective.spawnList) do -- spawnData.Name, spawnData.Spawns + if spawnData.Waypoints then + for zone, waypoints in pairs(spawnData.Waypoints) do + local firstWaypoint = waypoints[1][1] + + if (not iconPerZone[zone]) and icon and firstWaypoint[1] ~= -1 and firstWaypoint[2] ~= -1 then -- spawn an icon in this zone for the mob + local iconMap, iconMini = QuestieMap:DrawWorldIcon(icon.data, zone, firstWaypoint[1], + firstWaypoint[2]) -- clustering code takes care of duplicates as long as min-dist is more than 0 + + if iconMap and iconMini then + iconPerZone[zone] = { iconMap, firstWaypoint[1], firstWaypoint[2] } + tinsert(objective.AlreadySpawned[icon.AlreadySpawnedId].mapRefs, iconMap); + tinsert(objective.AlreadySpawned[icon.AlreadySpawnedId].minimapRefs, iconMini); + end + end + + local ipz = iconPerZone[zone] + + if ipz then + QuestieMap:DrawWaypoints(ipz[1], waypoints, zone, spawnData.Hostile and { 1, 0.2, 0, 0.7 } or nil) + end + end + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:_DrawObjectiveWaypoints]") + end + end +end + +---@param quest Quest +function QuestieQuest:PopulateObjectiveNotes(quest) -- this should be renamed to PopulateNotes as it also handles finishers now + if (not quest) then + return + end + + if QuestieDB.IsComplete(quest.Id) == 1 then + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjectiveNotes] Quest Complete! Adding Finisher for:", + quest.Id) + + QuestieQuest:UpdateQuest(quest.Id) + _AddSourceItemObjective(quest) + _AddRequiredSourceItemObjective(quest) + _AddSpellItemObjective(quest) + + return + end + + if (not quest.Color) then + quest.Color = QuestieLib:ColorWheel() + end + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateObjectiveNotes] Populating objectives for:", quest.Id) + + QuestieQuest:UpdateObjectiveNotes(quest) + _AddSourceItemObjective(quest) + _AddRequiredSourceItemObjective(quest) + _AddSpellItemObjective(quest) +end + +---@param quest Quest +---@return true? +function QuestieQuest:PopulateQuestLogInfo(quest) + if (not quest) then + return nil + end + + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:PopulateQuestLogInfo] ", quest.Id) + + local questLogEngtry = QuestLogCache.GetQuest(quest.Id) -- DO NOT MODIFY THE RETURNED TABLE + + if (not questLogEngtry) then return end + + if questLogEngtry.isComplete ~= nil and questLogEngtry.isComplete == 1 then + quest.isComplete = true + end + + --Uses the category order to draw the quests and trusts the database order. + + local questObjectives = QuestieQuest:GetAllLeaderBoardDetails(quest.Id) or {} -- DO NOT MODIFY THE RETURNED TABLE + + for objectiveIndex, objective in pairs(questObjectives) do + if objective.type and string.len(objective.type) > 1 then + if (not quest.ObjectiveData) or (not quest.ObjectiveData[objectiveIndex]) then + Questie:Error(l10n("Missing objective data for quest "), quest.Id, " ", objective.text) + else + if not quest.Objectives[objectiveIndex] then + quest.Objectives[objectiveIndex] = { + Id = quest.ObjectiveData[objectiveIndex].Id, + Index = objectiveIndex, + questId = quest.Id, + _lastUpdate = 0, + Description = objective.text, + spawnList = {}, + AlreadySpawned = {}, + Update = _QuestieQuest.ObjectiveUpdate, + Coordinates = quest.ObjectiveData[objectiveIndex].Coordinates, -- Only for type "event" + RequiredRepValue = quest.ObjectiveData[objectiveIndex].RequiredRepValue + } + end + + quest.Objectives[objectiveIndex]:Update() + end + end + + if (not quest.Objectives[objectiveIndex]) or (not quest.Objectives[objectiveIndex].Id) then + Questie:Debug(Questie.DEBUG_DEVELOP, + "[QuestieQuest:PopulateQuestLogInfo] Error finding entry ID for objective", objectiveIndex, + objective.type, objective.text, "of questId:", quest.Id) + end + end + + -- find special unlisted objectives + if next(quest.SpecialObjectives) then + for index, specialObjective in pairs(quest.SpecialObjectives) do + if (not specialObjective.Description) then + specialObjective.Description = "Special objective" + end + + specialObjective.questId = quest.Id + + if specialObjective.RealObjectiveIndex and quest.Objectives[specialObjective.RealObjectiveIndex] then + -- This specialObjective is an extraObjective and has a RealObjectiveIndex set + specialObjective.Completed = quest.Objectives[specialObjective.RealObjectiveIndex].Completed + specialObjective.Update = function(self) + self.Completed = quest.Objectives[self.RealObjectiveIndex].Completed + end + elseif specialObjective.Type == "item" and specialObjective.Id then + specialObjective.Completed = GetItemCount(specialObjective.Id) > 0 + specialObjective.Update = function(self) + self.Completed = GetItemCount(self.Id) > 0 + end + else + specialObjective.Update = NOP_FUNCTION + end + + specialObjective.Index = 64 + index -- offset to not conflict with real objectives + specialObjective.AlreadySpawned = specialObjective.AlreadySpawned or {} + end + end + + if #quest.Objectives == 0 and #quest.SpecialObjectives == 0 and (not quest.ObjectiveData or #quest.ObjectiveData == 0) and ((quest.triggerEnd and #quest.triggerEnd > 0) or (quest.Finisher and quest.Finisher.Id ~= nil)) then + -- Some quests when picked up will be flagged isComplete == 0 but the quest.Objective table or quest.SpecialObjectives table is nil. This + -- check assumes the Quest should have been flagged questLogEngtry.isComplete == 1. We're specifically looking for a quest.triggerEnd or + -- a quest.Finisher.Id because this might throw an error if there is nothing to populate when we call QuestieQuest:AddFinisher(). + -- We added a check for quest.ObjectiveData to ensure we don't prematurely complete quests that are just waiting for the server to sync their objectives. + QuestieMap:UnloadQuestFrames(quest.Id) + QuestieQuest:AddFinisher(quest) + quest.isComplete = true + end + + return true +end + +---@param self QuestObjective @quest.Objectives[] entry +function _QuestieQuest.ObjectiveUpdate(self) + if self.isUpdated then + return + end + + local questObjectives = QuestieQuest:GetAllLeaderBoardDetails(self.questId) -- DO NOT MODIFY THE RETURNED TABLE + + if questObjectives and questObjectives[self.Index] then + local obj = questObjectives[self.Index] -- DO NOT EDIT THE TABLE + if (obj.type) then + -- fixes for api bug + local numFulfilled = obj.numFulfilled or 0 + local numRequired = obj.numRequired or 0 + local finished = obj.finished or false -- ensure its boolean false and not nil (hack) + + self.Type = obj.type; + self.Description = obj.text + self.Collected = tonumber(numFulfilled); + self.Needed = tonumber(numRequired); + self.Completed = (self.Needed == self.Collected and self.Needed > 0) or + (finished and (self.Needed == 0 or (not self.Needed))) -- some objectives get removed on PLAYER_LOGIN because isComplete is set to true at random???? + -- Mark objective updated + self.isUpdated = true + end + end +end + +---@param questId number +---@return table|nil @DO NOT EDIT RETURNED TABLE +function QuestieQuest:GetAllLeaderBoardDetails(questId) + Questie:Debug(Questie.DEBUG_INFO, "[QuestieQuest:GetAllLeaderBoardDetails] for questId", questId) + + local questObjectives = QuestLogCache.GetQuestObjectives(questId) -- DO NOT MODIFY THE RETURNED TABLE + if (not questObjectives) then return end + + for _, objective in pairs(questObjectives) do -- DO NOT MODIFY THE RETURNED TABLE + -- TODO Move this to QuestEventHandler module or QuestieQuest:AcceptQuest( ) + QuestieQuest:UpdateQuest( ) (accept quest one required to register objectives without progress) + -- TODO After ^^^ moving remove this function and use "QuestLogCache.GetQuest(questId).objectives -- DO NOT MODIFY THE RETURNED TABLE" in place of it. + QuestieAnnounce:ObjectiveChanged(questId, objective.text, objective.numFulfilled, objective.numRequired) + end + + return questObjectives +end + +function QuestieQuest.DrawDailyQuest(questId) + if QuestieDB.IsDoable(questId) then + local quest = QuestieDB.GetQuest(questId) + AvailableQuests.DrawAvailableQuest(quest) + end +end + +return QuestieQuest diff --git a/.history/Questie-335_20260222010131.toc b/.history/Questie-335_20260222010131.toc new file mode 100644 index 0000000..8785210 --- /dev/null +++ b/.history/Questie-335_20260222010131.toc @@ -0,0 +1,250 @@ +## Interface: 30300 +## Title: Questie - Ebonhold +## Author: Questie Team +## Notes: A standalone Classic QuestHelper +## Notes-esMX: Ayundante de misión +## Notes-esES: Ayundante de misión +## Notes-ptBR: Ajudante de missão +## Notes-frFR: Assistant de quête +## Version: 9.8.0 +## RequiredDeps: +## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu +## SavedVariables: QuestieConfig +## SavedVariablesPerCharacter: QuestieConfigCharacter +## X-Curse-Project-ID: 334372 +## X-Wago-ID: qv634BKb +## X-WOW_PROJECT_ID: 11 + +# Loader module +Modules\Libs\QuestieLoader.lua + +# COMPATIBILITY +Modules\QuestieCompat.lua +Compat\embeds.xml + +Modules\VersionCheck.lua + +# Thread Manager +Modules\Libs\ThreadLib.lua +#Message Handler +Modules\Libs\MessageHandler.lua + +#Quest XP +Database\QuestXP\QuestieXP.lua +Database\QuestXP\DB\xpDB-wotlk.lua + +# stream module (used by DB) +Modules\QuestieStream.lua + +# Zones +Database\Zones\zoneTables.lua +# Ascension Custom Zones +Database\Ascension\Zones\AscensionZoneTables.lua +Database\Ascension\Zones\AscensionUiMapData.lua +# Ebonhold Custom Zones +Database\Ebonhold\Zones\EbonholdZoneTables.lua +Database\Ebonhold\Zones\EbonholdUiMapData.lua +Database\Zones\zoneDB.lua + + +# Databases +Database\Wotlk\wotlkItemDB.lua +Database\Wotlk\wotlkNpcDB.lua +Database\Wotlk\wotlkObjectDB.lua +Database\Wotlk\wotlkQuestDB.lua +Database\QuestieDB.lua +Database\questDB.lua +Database\objectDB.lua +Database\npcDB.lua +Database\itemDB.lua +Database\Constants.lua +Database\MeetingStones.lua + +# Ascension Custom Database +Database\Ascension\Bronzebeard\AscensionNpcDB.lua +Database\Ascension\Bronzebeard\AscensionObjectDB.lua +Database\Ascension\Bronzebeard\AscensionItemDB.lua +Database\Ascension\Bronzebeard\AscensionQuestDB.lua +Database\Ascension\AscensionLoader.lua + +# Ebonhold Custom Database +Database\Ebonhold\Ebonhold\EbonholdNpcDB.lua +Database\Ebonhold\Ebonhold\EbonholdObjectDB.lua +Database\Ebonhold\Ebonhold\EbonholdItemDB.lua +Database\Ebonhold\Ebonhold\EbonholdQuestDB.lua +Database\Ebonhold\EbonholdLoader.lua + + +# Corrections +Database\Corrections\AutoTableUpdates.lua +Database\Corrections\QuestieCorrections.lua +Database\Corrections\QuestieItemBlacklist.lua +Database\Corrections\QuestieNPCBlacklist.lua +Database\Corrections\QuestieQuestBlacklist.lua +#Database\Corrections\SoMPhases.lua +Database\Corrections\QuestieEvent.lua + +# Automatic General Corrections +Database\Corrections\Automatic\itemStartFixes.lua +Database\Corrections\Automatic\classicQuestReputationFixes.lua + +# Classic Corrections +Database\Corrections\classicQuestFixes.lua +Database\Corrections\classicNPCFixes.lua +Database\Corrections\classicItemFixes.lua +Database\Corrections\classicObjectFixes.lua + +# TBC Corrections +Database\Corrections\tbcQuestFixes.lua +Database\Corrections\tbcNPCFixes.lua +Database\Corrections\tbcItemFixes.lua +Database\Corrections\tbcObjectFixes.lua + +# Wotlk Corrections +Database\Corrections\wotlkQuestFixes.lua +Database\Corrections\wotlkNPCFixes.lua +Database\Corrections\wotlkItemFixes.lua +Database\Corrections\wotlkObjectFixes.lua + +# Compiler +Database\compiler.lua + +# Localization +Localization\l10n.lua +Localization\Translations\Translations.xml + +Localization\lookups\lookupQuestCategories.lua +Localization\lookups\lookupZones.lua +Localization\lookups\Wotlk\lookupItems\lookupItems.xml +Localization\lookups\Wotlk\lookupNpcs\lookupNpcs.xml +Localization\lookups\Wotlk\lookupObjects\lookupObjects.xml +Localization\lookups\Wotlk\lookupQuests\lookupQuests.xml + +# Libs +Modules\Libs\QuestieLib.lua +Modules\Libs\QuestieSerializer.lua +Modules\Libs\QuestieCombatQueue.lua +Modules\Libs\RamerDouglasPeucker.lua + +# Modules +Modules\QuestieValidateGameCache.lua +Modules\Arrow\QuestieArrow.lua +Modules\QuestieInit.lua +Modules\MinimapIcon.lua +Modules\QuestieProfessions.lua +Modules\QuestieReputation.lua +Modules\QuestieEventHandler.lua +Modules\QuestieLearner.lua +Modules\Migration.lua +Modules\QuestieMenu\Townsfolk.lua +Modules\QuestieMenu\ClassTrainers.lua +Modules\QuestieMenu\Mailboxes.lua +Modules\QuestieMenu\MeetingStones.lua +Modules\QuestieMenu\ProfessionTrainers.lua +Modules\QuestieMenu\QuestieMenu.lua +Modules\QuestieAnnounce.lua +Modules\QuestieShutUp.lua +Modules\Sounds.lua +Modules\TaskQueue.lua +Modules\QuestiePlayer.lua +#Modules\QuestieDebugOffer.lua +Modules\WorldMapButton\WorldMapButton.lua +#Modules\WorldMapButton\QuestieWorldMapButtonTemplate.xml + +# QuestLinks +Modules\QuestLinks\ChatFilter.lua +Modules\QuestLinks\Hooks.lua +Modules\QuestLinks\Link.lua + +# Tooltips +Modules\Tooltips\Tooltip.lua +Modules\Tooltips\MapIconTooltip.lua +Modules\Tooltips\TooltipHandler.lua + +# Auto +Modules\Auto\QuestieAuto.lua +Modules\Auto\Privates.lua +Modules\Auto\DisallowedIDs.lua + +# FramePool +Modules\FramePool\QuestieFramePool.lua +Modules\FramePool\QuestieFrame.lua + +# Map +Modules\Map\QuestieMap.lua +Modules\Map\QuestieMapUtils.lua +#Modules\Map\HBDHooks.lua +Modules\Map\WeaponMasterSkills.lua + + +# Quest +Modules\Quest\AvailableQuests.lua +Modules\Quest\QuestLogCache.lua +Modules\Quest\DailyQuests.lua +Modules\Quest\IsleOfQuelDanas.lua +Modules\Quest\QuestEventHandler.lua +Modules\Quest\QuestgiverFrame.lua +Modules\Quest\QuestieQuest.lua +Modules\Quest\QuestieQuestPrivates.lua + +Modules\QuestieNameplate.lua +Modules\QuestieCoordinates.lua + +# Network +Modules\Network\QuestieComms.lua +Modules\Network\QuestieCommsData.lua + +# Journey +Modules\Journey\QuestieJourney.lua +Modules\Journey\QuestieJourneyPrivates.lua +Modules\Journey\QuestieJourneyUtils.lua +Modules\Journey\History.lua +Modules\Journey\QuestDetailsFrame.lua +Modules\Journey\tabs\MyJourney\Note.lua +Modules\Journey\tabs\MyJourney\MyJourney.lua +Modules\Journey\tabs\MyJourney\MyJourneyTab.lua +Modules\Journey\tabs\QuestsByZone\QuestsByZone.lua +Modules\Journey\tabs\QuestsByZone\QuestsByZoneTab.lua +#Modules\Journey\tabs\Search\Search.lua +#Modules\Journey\tabs\Search\SearchTab.lua +Modules\Journey\QuestieSearch.lua +Modules\Journey\QuestieSearchResults.lua + +# Tracker +Modules\Tracker\QuestieTracker.lua +Modules\Tracker\TrackerUtils.lua +Modules\Tracker\TrackerMenu.lua +Modules\Tracker\TrackerFadeTicker.lua +Modules\Tracker\TrackerBaseFrame.lua +Modules\Tracker\TrackerHeaderFrame.lua +Modules\Tracker\TrackerQuestFrame.lua +Modules\Tracker\TrackerQuestTimers.lua +Modules\Tracker\TrackerLinePool.lua + +# Tutorial +Modules\Tutorial\ChooseObjectiveType.lua +Modules\Tutorial\Tutorial.lua + +#Modules\QuestieDBMIntegration.lua +Modules\QuestieSlash.lua + +# Options +Modules\Options\QuestieOptions.lua +Modules\Options\QuestieOptionsDefaults.lua +Modules\Options\QuestieOptionsUtils.lua +Modules\Options\AdvancedTab\QuestieOptionsAdvanced.lua +Modules\Options\AutoTab\QuestieOptionsAuto.lua +Modules\Options\DBMTab\QuestieOptionsDBM.lua +Modules\Options\GeneralTab\QuestieOptionsGeneral.lua +Modules\Options\IconsTab\QuestieOptionsIcons.lua +Modules\Options\NameplateTab\QuestieOptionsNameplate.lua +Modules\Options\TrackerTab\QuestieOptionsTracker.lua + +# Cleanup +Modules\QuestieCleanup.lua + +# Profiler +Modules\QuestieProfiler.lua + +# Main +Questie.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index a25c407..2ca30dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v9.8.0 + +### Fixes +- **[Quest]** Modified `BAG_UPDATE_DELAYED` to trigger a quest log update and instantly refresh the tracker when items are deposited directly into bags by autoloot bots that bypass standard item events. +- **[Map]** Fixed an issue where "Special Objectives" (e.g., source item drops) failed to check player bag contents upon completion, causing their map icons to linger indefinitely until the main quest was turned in. + ## v9.7.12 ### Fixes diff --git a/Localization/Translations/Options/Advanced.lua b/Localization/Translations/Options/Advanced.lua index 7a5bc62..9daa5ad 100644 --- a/Localization/Translations/Options/Advanced.lua +++ b/Localization/Translations/Options/Advanced.lua @@ -302,7 +302,7 @@ local advancedOptionsLocales = { ["esES"] = "Es necesario actualizar la base de datos para cambiar el idioma. Presiona recargar para aplicar el nuevo idioma", ["frFR"] = "La base de données doit être mise à jour pour changer la langue. Appuyez sur Recharger pour appliquer la nouvelle langue.", }, - ["Questie is under active development for World of Warcraft: Classic. Please check GitHub for the latest alpha builds or to report issues. Or join us on our discord! (( https://github.com/Questie/Questie/ ))"] = { + ["Questie is under active development and being maintained by Xurkon. Please check my GitHub for the latest build or to report issues. (( https://github.com/Xurkon/PE-Questie ))"] = { ["ptBR"] = "Questie está em desenvolvimento para World of Warcraft: Classic. Verifique o GitHub para obter as versões mais recentes ou relatar problemas. Ou junte-se a nós em nossa discórdia!(( https://github.com/Questie/Questie/ )) ", ["ruRU"] = "Questie находится в стадии активной разработки для World of Warcraft: Classic. Проверяйте наличие обновлений на сайте GitHub (https://github.com/Questie/Questie/) или присоединяйтесь к нашему серверу в Discord (https://discord.gg/phKnHVu)", ["deDE"] = "Questie befindet sich noch aktiv in der Entwicklungsphase für World of Warcraft: Classic. Für Neuigkeiten und aktuelle Versionen schau auf Github vorbei oder trete unserem Discord Server bei! (( https://github.com/Questie/Questie/ ))", diff --git a/Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua b/Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua index 0b3f2dc..c0c4c82 100644 --- a/Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua +++ b/Modules/Options/AdvancedTab/QuestieOptionsAdvanced.lua @@ -277,7 +277,7 @@ function QuestieOptions.tabs.advanced:Initialize() github_text = { type = "description", order = 4.8, - name = function() return Questie:Colorize(l10n('Questie is under active development for World of Warcraft: Classic. Please check GitHub for the latest alpha builds or to report issues. Or join us on our discord! (( https://github.com/Questie/Questie/ ))'), 'purple'); end, + name = function() return Questie:Colorize(l10n('Questie is under active development and being maintained by Xurkon. Please check my GitHub for the latest build or to report issues. (( https://github.com/Xurkon/PE-Questie ))'), 'purple'); end, fontSize = "medium", }, HeaderDev = { diff --git a/Modules/Quest/QuestEventHandler.lua b/Modules/Quest/QuestEventHandler.lua index c0382b5..474cf1f 100644 --- a/Modules/Quest/QuestEventHandler.lua +++ b/Modules/Quest/QuestEventHandler.lua @@ -686,8 +686,9 @@ function _QuestEventHandler:OnEvent(event, ...) doFullQuestLogScan = true -- If this event is related to a spell objective, a QUEST_LOG_UPDATE will be fired afterwards elseif event == "BAG_UPDATE_DELAYED" then -- Autoloot bots can bypass the standard loot frame, skipping QUEST_WATCH_UPDATE for the last items looted. - -- Flag a full scan so the tracker refreshes on the next QUEST_LOG_UPDATE. + -- Flag a full scan and trigger the update immediately so the tracker refreshes. doFullQuestLogScan = true + _QuestEventHandler:QuestLogUpdate() elseif event == "PLAYER_INTERACTION_MANAGER_FRAME_HIDE" then local eventType = select(1, ...) if eventType == 1 then diff --git a/Modules/Quest/QuestieQuest.lua b/Modules/Quest/QuestieQuest.lua index 399a19a..4e2f727 100644 --- a/Modules/Quest/QuestieQuest.lua +++ b/Modules/Quest/QuestieQuest.lua @@ -1651,6 +1651,11 @@ function QuestieQuest:PopulateQuestLogInfo(quest) specialObjective.Update = function(self) self.Completed = quest.Objectives[self.RealObjectiveIndex].Completed end + elseif specialObjective.Type == "item" and specialObjective.Id then + specialObjective.Completed = GetItemCount(specialObjective.Id) > 0 + specialObjective.Update = function(self) + self.Completed = GetItemCount(self.Id) > 0 + end else specialObjective.Update = NOP_FUNCTION end diff --git a/Questie-335.toc b/Questie-335.toc index 45aae64..8785210 100644 --- a/Questie-335.toc +++ b/Questie-335.toc @@ -6,7 +6,7 @@ ## Notes-esES: Ayundante de misión ## Notes-ptBR: Ajudante de missão ## Notes-frFR: Assistant de quête -## Version: 9.7.12 +## Version: 9.8.0 ## RequiredDeps: ## OptionalDeps: Ace3, CallbackHandler-1.0, HereBeDragons, LibDataBroker-1.1, LibDBIcon-1.0, LibSharedMedia-3.0, LibStub, LibUIDropDownMenu ## SavedVariables: QuestieConfig diff --git a/release_notes.txt b/release_notes.txt new file mode 100644 index 0000000..2026b9c --- /dev/null +++ b/release_notes.txt @@ -0,0 +1,3 @@ +### Fixes +- **[Quest]** Fixed an issue where tracking icons for dynamically updated custom quests like Peak Predators would prematurely disappear due to sync delays on WotLK servers. +- **[Map]** Resolved a frame pool leak that prevented the yellow Finisher icon from appearing immediately on the map after turning in or completing a quest.