diff --git a/Gromissingcrafts/CHANGES.md b/Gromissingcrafts/CHANGES.md new file mode 100644 index 0000000..f5b9238 --- /dev/null +++ b/Gromissingcrafts/CHANGES.md @@ -0,0 +1,16 @@ +### v1.0 (2026/08/06) +- Initial release: from-scratch rebuild of MissingCrafts v1.4.1 with no Ace3 dependency (AceAddon/AceDB/AceGUI/AceHook/AceLocale all replaced with native vanilla frames and a small local helper layer) to avoid the cross-addon conflicts Ace3 causes on OctoWoW/Turtle WoW +- Add support for Turtle WoW's Survival profession (starter recipe data set — see README's "Survival profession data" section) +- Add `/gmc dump` developer command to help complete the Survival (and any other profession's) recipe data from the live client +- Keep pfUI window placement support +- Rebuild every interactive widget without Blizzard UI templates and with explicit frame strata, after pfUI's skinning repeatedly broke click handling on templated widgets +- Rebuild the recipe list on a fixed-size row pool with a custom scrollbar (drag, click, and mouse wheel) instead of a native ScrollFrame, which wasn't clipping its content reliably on this client +- The window now anchors directly to the profession frame it's attached to and moves with it automatically, instead of being independently draggable +- Remove the profession/character filter dropdowns — the window is already scoped to whichever profession frame it's open from, and always your current character; search by recipe name instead +- Fix `/gmc` slash command never registering (it was writing SLASH_* into the addon's isolated environment instead of the real global namespace the client actually scans) +- Expand the Survival starter recipe set to 7 confirmed recipes (Dim Torch, Survivalist's Skinning Knife, Driftwood Fishing Pole, Traveler's Tent, Fishing Boat, Murloc's Flippers, Repaired Electro-Lantern) using a full player-provided skill-up table cross-checked against octowow.st's database; corrected two skill levels that earlier web research had gotten wrong (Traveler's Tent 50→80, Fishing Boat 125→115) +- Add `/gmc dump` support for the trainer window (lists every recipe on offer, not just ones already known) +- Expand Survival to 44 recipes (skill 1-150) using real names/skill levels/result item ids from a player-provided `/gmc dump`; recipes without a confirmed real spell id use a safe placeholder instead (see README's "Survival profession data" section) — also corrected Traveler's Tent's result item (51283, not 50234, which turned out to be an unrelated "outline" item) +- Fix `/gmc dump` not detecting the trainer window on this client (it's `ClassTrainerFrame`, not the vanilla-standard `TrainerFrame`) — the command now also self-diagnoses (reports what it did/didn't find) when nothing matches, instead of just failing silently +- Expand Survival to the full 87-recipe list (skill 1-300), using a trainer-service-list `/gmc dump` to confirm every remaining recipe's name and skill level +- Fill in real result item ids for all 87 Survival recipes (previously 44 had one), sourced by hand from octowow.st's database; also corrected Iron Lantern's item (2714, not 81187, which turned out to be an unrelated "outline" item — same category of mistake as the earlier Traveler's Tent fix) diff --git a/Gromissingcrafts/CLAUDE.md b/Gromissingcrafts/CLAUDE.md new file mode 100644 index 0000000..c77dff6 --- /dev/null +++ b/Gromissingcrafts/CLAUDE.md @@ -0,0 +1,104 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Gromissingcrafts is a World of Warcraft Classic (Turtle WoW/OctoWoW) addon that helps players track crafting recipes they haven't learned yet on their current character. It is a from-scratch, Ace3-free rebuild of [MissingCrafts](https://github.com/refaim/MissingCrafts), built because Turtle-style 1.12 clients frequently break when multiple addons each bundle their own copy of Ace3 (AceAddon/AceDB/AceGUI/AceHook/AceLocale share a single global registry, so mismatched versions/instances taint or clobber each other). Everything here is built on native `CreateFrame` + manual scripting, with no Blizzard UI templates and no Ace3. + +## Architecture + +### Environment Layer +- **`src/Environment.lua`**: Creates an isolated global environment using `setfenv(1, Gromissingcrafts)` that prevents global namespace pollution while maintaining access to WoW API +- Every `.lua` file must begin with `setfenv(1, Gromissingcrafts)` to work within this isolated environment +- Provides utility functions: `erase()` for table cleanup, `tpop()` for safe key removal + +### Database Layer (`src/db/`) +Repository pattern implementation with clean domain models: + +- **`Database.lua`**: Manages the `GromissingcraftsDatabase` SavedVariable directly (no AceDB) via a small get-or-create helper that auto-vivifies `global.realmToNameToCharacter[realm][name]` with default fields +- **`Character.lua`**: Domain model representing a character with profession knowledge and skill level checking +- **`CharacterRepository.lua`**: Data access layer for character CRUD operations +- **`CraftRepository.lua`**: Complex repository finding missing recipes based on character filters, handles craft sources mapping + +### UI Layer (`src/ui/`) +Native-frame component system, no widget framework, and **no Blizzard UI templates on anything interactive** (`UIDropDownMenuTemplate`, `UIPanelButtonTemplate`, `InputBoxTemplate`, `UIPanelScrollFrameTemplate` were all tried and all got their input handling broken by pfUI's skinning — see git history). Every interactive frame is hand-built (manual backdrop/textures) and every one of them sets `SetFrameStrata("DIALOG")` explicitly, because **strata is not inherited from a parent frame** on this client — leaving it unset was silently losing click priority to whatever else was on screen underneath. + +- **`UIManager.lua`**: Central coordinator managing window lifecycle, profession frame events, and component interactions. Hooks `MovePanelToLeft`/`MovePanelToCenter` via the local `hookGlobalFunction` helper (see `ui/Tools.lua`) — **not** the native `hooksecurefunc`, which isn't reliably present on true-1.12-era clients +- **`window/Window.lua`**: Main window — a plain `CreateFrame("Frame", ..., UIParent)` with a `DialogBox` backdrop, fixed `"DIALOG"` strata, **anchored directly to the profession frame** (`SetPoint("TOPLEFT", professionFrame.widget, "TOPRIGHT", ...)`) so it moves with it automatically via WoW's native anchor propagation — no drag support of its own, not meant to be repositioned independently +- **`filters/FiltersPanel.lua`**: Plain frame holding just a search box (the profession/character dropdown filters were removed by request — the window is already scoped to whichever profession frame it's attached to, and always the current character) +- **`filters/SearchField.lua`**: Hand-built `EditBox`, no template +- **`list/CraftsList.lua`**: **Not** a native `ScrollFrame` + `SetScrollChild` — that combination wasn't clipping its content reliably on this client (rows kept spilling past the frame's own height regardless of template). Instead it's a fixed pool of row widgets sized to exactly fill the visible area (the same technique Blizzard's own `FauxScrollFrame`-based lists use), with a scroll offset into the data. The scrollbar (bigger than Blizzard's default, click-and-drag capable) is fully custom too, built the same template-free way. +- **`list/CraftsListItem.lua`**: Individual recipe row — always was plain frames, unchanged from upstream +- **`TooltipEnhancer.lua`**: Enhances item tooltips with recipe learning status for each character (via `LibItemTooltip`, which was never Ace-based) +- **`window/OpenButton.lua`**: Toggle button that appears on profession frames to show/hide the main window +- **`DataStateManager.lua`**: State management for UI data synchronization and event coordination — tracks current character/profession/search query and re-queries `CraftRepository` on change +- **`VanillaFramePool.lua`**: Efficient frame reuse system for performance optimization +- **`window/PlacementPolicy.lua`**: Geometry/anchor rules per profession-frame type (now relative offsets from the profession frame's `TOPRIGHT`, not absolute screen coordinates), including pfUI detection (`IsAddOnLoaded("pfUI")`) + +### Localization (`src/locale/`) +- **`Tools.lua`**: Minimal AceLocale-free replacement — one shared `L` table. `enUS.lua` loads **first** (see the `.toc` order) and populates every key as the fallback; every other locale file only receives the live table (and thus only gets to override keys) when `GetLocale()` matches its own code +- Multiple language files (enUS, ruRU, deDE, frFR, esES, ptBR, koKR, zhCN, zhTW) + +### Dev tooling +- **`src/DevTools.lua`**: `/gromissingcrafts` (alias `/gmc`) slash command. `dump` walks whichever trade skill/craft window is open (`GetTradeSkillInfo`/`GetCraftInfo` + item-link parsing) and prints each recipe's exact name and result item id, since neither spell id nor required skill level is exposed by the client API for known recipes. Used to grow `lib/LibCrafts-1.0/Professions/Turtle/Survival.lua` with confirmed data instead of guesses. + +## Key Technical Features + +### Event-Driven Architecture +- Uses LibCraftingProfessions events (`LCP_SKILLS_UPDATE`, `LCP_FRAME_SHOW`, `LCP_FRAME_CLOSE`) for real-time updates +- Automatic profession data synchronization when skills are learned +- Dynamic UI updates when profession frames open/close + +### Data Persistence +- Cross-realm character data storage with automatic cleanup, stored directly in `GromissingcraftsDatabase` +- Profession knowledge tracking with skill level requirements +- Recipe source mapping from LibCrafts constants to internal enums + +## External Dependencies + +### Kept from MissingCrafts (none of these were ever Ace3-dependent) +- **LibStub**: tiny library-versioning shim, not part of the Ace3 conflict surface +- **LibCraftingProfessions-1.0**: Profession frame detection and skill tracking across multiple addon types (extended here with `Survival`) +- **LibCrafts-1.0**: Comprehensive crafting recipe database with source information (extended here with `Professions/Turtle/Survival.lua`) +- **LibItemTooltip-1.0**: Tooltip enhancement framework (hooks `GameTooltip`/`ItemRefTooltip` methods directly) + +### Removed entirely +- AceAddon-3.0, AceDB-3.0, AceGUI-3.0, AceHook-3.0, AceLocale-3.0 — see `README.md` and the plan history for the replacement mapping + +## Supported Profession Addons + +- Default WoW profession frames +- AdvancedTradeSkillWindow (ATSW) +- AdvancedTradeSkillWindow2 (ATSW2) +- Artisan +- pfUI + +Different frame types require different initialization delays and positioning strategies — see `PlacementPolicy.lua`. + +## Development Guidelines + +### Code Style +- Extensive use of Lua `---@` type annotations for documentation and IDE support +- Object-oriented patterns using metatables with `self` parameters +- Acquire/release pattern for UI components to prevent memory leaks +- Repository pattern for clean data access abstraction + +### File Loading Order +Files must load in the order given in `Gromissingcrafts.toc`: +1. `LibStub` + the three data libraries +2. `src/Environment.lua`, `src/Compatibility.lua` +3. Database layer files +4. Locale files — **`enUS.lua` must stay first** among locale files (see `locale/Tools.lua`) +5. UI component files +6. `src/DevTools.lua` +7. `src/Gromissingcrafts.lua` — main addon bootstrap (plain `ADDON_LOADED` event frame, no AceAddon) + +### No live-client testing available in this environment +Changes here are verified statically (Lua syntax checks, manual trace-throughs, cross-checking renamed globals). Real acceptance testing has to happen in-game on OctoWoW/Turtle WoW. + +### Lessons learned the hard way (read before touching UI code) +- **No Blizzard UI templates on interactive frames.** pfUI (and likely other skinning addons) hook/reskin recognized templates and this repeatedly broke click handling for dynamically-created widgets — first `UIDropDownMenuTemplate`, then `UIPanelButtonTemplate`, then `InputBoxTemplate`. Build interactive widgets from bare `CreateFrame` + manual backdrop/textures instead (see `Dropdown` history, `SearchField.lua`, `OpenButton.lua`). +- **Set `SetFrameStrata` explicitly on every interactive frame**, not just the top-level window. It is not inherited from a parent in this client; an unset child silently defaults to `"MEDIUM"` and can lose click priority to something else on screen even while it visually renders on top. +- **Native `ScrollFrame` + `SetScrollChild` did not reliably clip its content** on this client, template or no template. `CraftsList.lua` uses a fixed-size row pool instead (see that file's header comment). +- **Anchor to the frame you want to track, not to a computed absolute position.** The window used to snapshot the profession frame's screen coordinates once and reposition on a few known hooks; now it's anchored directly via `SetPoint(..., professionFrame.widget, ...)` so it tracks automatically, including drags, with no extra code. diff --git a/Gromissingcrafts/Gromissingcrafts.toc b/Gromissingcrafts/Gromissingcrafts.toc new file mode 100644 index 0000000..972d007 --- /dev/null +++ b/Gromissingcrafts/Gromissingcrafts.toc @@ -0,0 +1,48 @@ +## Interface: 11200 +## Title: Gromissingcrafts +## Notes: Shows the missing crafts for your crafting professions and how to obtain them. Ace3-free, pfUI-friendly fork of MissingCrafts with Survival profession support. +## Author: Refaim, Grom +## Version: 1.0 +## SavedVariables: GromissingcraftsDatabase +## OptionalDeps: AdvancedTradeSkillWindow, AdvancedTradeSkillWindow2, Artisan, pfUI + +lib\LibStub\LibStub.lua + +lib\LibCraftingProfessions-1.0\LibCraftingProfessions-1.0.xml +lib\LibCrafts-1.0\LibCrafts-1.0.xml +lib\LibItemTooltip-1.0\LibItemTooltip-1.0.xml + +src\Environment.lua +src\Compatibility.lua + +src\db\Database.lua +src\db\Character.lua +src\db\CharacterRepository.lua +src\db\CraftRepository.lua + +src\locale\Tools.lua +src\locale\enUS.lua +src\locale\deDE.lua +src\locale\esES.lua +src\locale\frFR.lua +src\locale\koKR.lua +src\locale\ptBR.lua +src\locale\ruRU.lua +src\locale\zhCN.lua +src\locale\zhTW.lua + +src\ui\Tools.lua +src\ui\VanillaFramePool.lua +src\ui\list\CraftsListItem.lua +src\ui\list\CraftsList.lua +src\ui\filters\SearchField.lua +src\ui\filters\FiltersPanel.lua +src\ui\window\PlacementPolicy.lua +src\ui\window\OpenButton.lua +src\ui\window\Window.lua +src\ui\TooltipEnhancer.lua +src\ui\DataStateManager.lua +src\ui\UIManager.lua + +src\DevTools.lua +src\Gromissingcrafts.lua diff --git a/Gromissingcrafts/LICENSE b/Gromissingcrafts/LICENSE new file mode 100644 index 0000000..6689603 --- /dev/null +++ b/Gromissingcrafts/LICENSE @@ -0,0 +1,26 @@ +MIT License + +Copyright (c) 2025 Roman Kharitonov +Copyright (c) 2026 Grom (Gromissingcrafts changes) + +Gromissingcrafts is a derivative work of MissingCrafts (https://github.com/refaim/MissingCrafts) +by Roman Kharitonov, rebuilt without the Ace3 framework and with added Survival +profession support, distributed under the same license below. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Gromissingcrafts/README.md b/Gromissingcrafts/README.md new file mode 100644 index 0000000..82e4d3d --- /dev/null +++ b/Gromissingcrafts/README.md @@ -0,0 +1,52 @@ +# Gromissingcrafts + +A World of Warcraft addon for Turtle WoW (and OctoWoW) that helps players track crafting recipes they haven't learned yet across their characters. + +Gromissingcrafts is a from-scratch rebuild of [MissingCrafts](https://github.com/refaim/MissingCrafts) with **no Ace3** anywhere in the addon — Ace3's embedded framework (AceAddon/AceDB/AceGUI/AceHook/AceLocale) is a common source of cross-addon conflicts on non-standard 1.12 clients like Turtle WoW/OctoWoW, since every addon that bundles its own copy shares the same global Ace3 registry. Gromissingcrafts is built entirely on native vanilla WoW frames instead, and adds support for Turtle WoW's custom **Survival** profession. + +## Features + +- **Integrated Profession Window**: Attaches directly to the profession frame you have open, and moves with it +- **Enhanced Item Tooltips**: Shows which of your characters can learn recipes from items and their current learning status +- **Search**: Filter the current profession's missing recipes by name +- **Recipe Source Information**: Displays how to obtain each missing recipe (Vendor, Quest, Drop, etc.) where known +- **Multiple Profession Frame Support**: Works with default WoW frames and popular profession addons +- **All Vanilla Crafting Professions**, plus Turtle WoW's Jewelcrafting, Disguise, and **Survival** +- **pfUI-friendly**: Detects pfUI and anchors/sizes itself accordingly +- **No Ace3**: nothing here can collide with another addon's bundled Ace3 libraries +- **Multi-Language Support**: English, German, French, Spanish, Portuguese, Russian, Korean, Chinese (Simplified), Chinese (Traditional) + +## How to Use + +1. Open any crafting profession window +2. Click the recipe button in the upper right corner of the profession frame +3. The Gromissingcrafts window opens attached to it, showing every recipe for that profession you haven't learned on your current character +4. Type in the search box to filter by recipe name +5. Hover over recipes to see their source information + +## Survival profession data + +Turtle WoW's Survival profession has no public numeric recipe database that could be reliably scraped for this addon, so `lib/LibCrafts-1.0/Professions/Turtle/Survival.lua` is built entirely from a real player's own data: a skill-up table, a `/gmc dump` of their known trade skill entries (real result item ids), and a `/gmc dump` of both trainer NPCs' full service lists (every recipe on offer, known or not). Between the three, it's now the **full recipe list, skill 1 through 300** — 87 recipes. The only known gap is two skill-295 items taught by a recipe item rather than the trainer directly (Oil-Powered Cooker, Prospector's Magnifying Lens), left out rather than mis-tagged. + +**All 87 now have a real result item id** — the last 43 (skill 155-300) were confirmed by hand against octowow.st's database. 7 recipes also have a confirmed real spell id; the other 80 use a safe placeholder (900001+, nowhere near any real spell id range) since the client API has no way to read a recipe's actual spell id under any window — only its name and, for known trade-skill entries, its result item. This doesn't affect accuracy: the addon matches "known vs. missing" by recipe **name**, and the recipe tooltip always prefers a real result item id over the spell id, which every entry now has — a placeholder spell id never actually surfaces anywhere. + +The one remaining gap: two skill-295 items (Oil-Powered Cooker, Prospector's Magnifying Lens) are taught by a recipe item rather than the trainer directly, and neither their spell id nor item id is confirmed, so they're left out rather than mis-tagged. + +If you ever want to confirm one of the placeholder spell ids for real: open the Survival trade skill window in-game once you've learned it and run `/gmc dump`, or look it up directly at `octowow.st/db/?spell=` if you find one — **grab the Item ID, not the Display ID shown on the same page**, they're different numbers. + +## Supported Profession Windows + +- Default WoW profession windows +- Turtle WoW improved Trade Skill window +- [**AdvancedTradeSkillWindow**](https://github.com/laytya/AdvancedTradeSkillWindow-vanilla) +- [**AdvancedTradeSkillWindow2**](https://github.com/Shellyoung/AdvancedTradeSkillWindow2) +- [**Artisan**](https://github.com/Otari98/Artisan) +- [**pfUI**](https://github.com/shagu/pfUI)'s trade skill frame styling + +## Relationship to MissingCrafts + +Gromissingcrafts started as a straight port of [MissingCrafts](https://github.com/refaim/MissingCrafts) v1.4.1, replacing every Ace3-backed piece with a native equivalent and adding Survival. `LibCrafts-1.0`, `LibCraftingProfessions-1.0`, and `LibItemTooltip-1.0` — the three data/plumbing libraries MissingCrafts already built on top of — were never Ace3-dependent to begin with, so they carry over largely unchanged (plus the Survival additions above). + +## Acknowledgments +- Built on top of [MissingCrafts](https://github.com/refaim/MissingCrafts) by Roman Kharitonov +- Inspired by [MissingTradeSkillsList](https://github.com/refaim/MissingTradeSkillsList) diff --git a/Gromissingcrafts/lib/LibCraftingProfessions-1.0/LibCraftingProfessions-1.0.lua b/Gromissingcrafts/lib/LibCraftingProfessions-1.0/LibCraftingProfessions-1.0.lua new file mode 100644 index 0000000..f1ac0a3 --- /dev/null +++ b/Gromissingcrafts/lib/LibCraftingProfessions-1.0/LibCraftingProfessions-1.0.lua @@ -0,0 +1,872 @@ +--[[ + Name: LibCraftingProfessions-1.0 + Developed by: Refaim (rkharito@yandex.ru, https://github.com/refaim/) + Website: https://github.com/refaim/LibCraftingProfessions-1.0 + Description: A library designed to provide a universal interface for crafting professions. + Dependencies: LibStub + Compatibility: World of Warcraft Vanilla (1.12.1), Turtle (1.18.0) +]] + +---@type LibStubDef +local LibStub = getglobal("LibStub") +assert(LibStub ~= nil) + +local untyped_lib, _ = LibStub:NewLibrary("LibCraftingProfessions-1.0", 19) +if not untyped_lib then + return +end + +---@class LibCraftingProfessions +---@field event_to_handlers table +---@field event_frame Frame + +local lib = --[[---@type LibCraftingProfessions]] untyped_lib +if lib.event_to_handlers == nil then + lib.event_to_handlers = {} +end + +local IS_TURTLE_WOW = getglobal("TURTLE_WOW_VERSION") ~= nil + +---@shape LcpProfessionProps +---@field icon string + +---@type table +local P = {} +P["Alchemy"] = {icon = [[Interface\Icons\trade_alchemy]]} +P["Blacksmithing"] = {icon = [[Interface\Icons\trade_blacksmithing]]} +P["Cooking"] = {icon = [[Interface\Icons\inv_misc_food_15]]} +P["Enchanting"] = {icon = [[Interface\Icons\trade_engraving]]} +P["Engineering"] = {icon = [[Interface\Icons\trade_engineering]]} +P["First Aid"] = {icon = [[Interface\Icons\spell_holy_sealofsacrifice]]} +P["Leatherworking"] = {icon = [[Interface\Icons\inv_misc_armorkit_17]]} +P["Mining"] = {icon = [[Interface\Icons\spell_fire_flameblades]]} +P["Poisons"] = {icon = [[Interface\Icons\trade_brewpoison]]} +P["Tailoring"] = {icon = [[Interface\Icons\trade_tailoring]]} +if IS_TURTLE_WOW then + P["Jewelcrafting"] = {icon = [[Interface\Icons\inv_helmet_44]]} + P["Survival"] = {icon = [[Interface\Icons\INV_Misc_Rope_01]]} +end +local PROFESSION_TO_PROPS = P +P = {} + +---@type table +local ENGLISH_TO_LOCALIZED = {} +---@type table +local LOCALIZED_TO_ENGLISH = {} + +---@shape LcpProfession +---@field english_name string +---@field localized_name string +---@field icon_texture_path string + +---@shape LcpKnownProfession: LcpProfession +---@field cur_rank number + +---@alias LcpProfessionFrameType "VanillaCraftFrame" | "VanillaTradeSkillFrame" | "TurtleTradeSkillFrame" | "Artisan" | "AdvancedTradeSkillWindow" | "AdvancedTradeSkillWindow2" + +---@type {FrameType: table} +LibCraftingProfessionsConstants = { + FrameType = { + VanillaCraftFrame = "VanillaCraftFrame", + VanillaTradeSkillFrame = "VanillaTradeSkillFrame", + TurtleTradeSkillFrame = "TurtleTradeSkillFrame", + Artisan = "Artisan", + AdvancedTradeSkillWindow = "AdvancedTradeSkillWindow", + AdvancedTradeSkillWindow2 = "AdvancedTradeSkillWindow2", + } +} +local FrameType = LibCraftingProfessionsConstants.FrameType + +---@shape LcpProfessionFrame +---@field frame Frame +---@field type LcpProfessionFrameType + +---@alias LcpSkillDifficulty "trivial" | "easy" | "medium" | "optimal" | "difficult" + +---@shape LcpKnownSkill +---@field localized_name string +---@field item_link string +---@field difficulty LcpSkillDifficulty +---@field num_available number + +---@alias LcpScanSkillsHandler fun(profession: LcpKnownProfession, skills: LcpKnownSkill[]):void + +---@type table +local skills_by_english_profession_name = {} + +---@return boolean +local function ready(value) + if value == nil then + return false + end + if type(value) == "table" then + return next(value) ~= nil + end + return value ~= "" and value ~= 0 +end + +--- +--- Retrieves a list of all supported crafting professions in the game. +--- +---@return LcpProfession[] +function lib:GetSupportedProfessions() + ---@type LcpProfession[] + local professions = {} + for english_name, props in pairs(PROFESSION_TO_PROPS) do + tinsert(professions, { + english_name = english_name, + localized_name = ENGLISH_TO_LOCALIZED[english_name], + icon_texture_path = props.icon, + }) + end + return professions +end + +--- +--- Retrieves a list of the player's known crafting professions. +--- Returns a table of known professions, or nil if game data is not yet available. +--- Use RegisterEvent("LCP_SKILLS_UPDATE", ...) to ensure data availability. +--- +---@return LcpKnownProfession[]|nil +function lib:GetPlayerProfessions() + local num_of_professions = GetNumSkillLines() + if not ready(num_of_professions) then + return nil + end + + ---@type LcpKnownProfession[] + local professions = {} + for i = 1, num_of_professions do + local localized_name, is_header, _, rank, _, _, _, _, _, _, _, _, _ = GetSkillLineInfo(i) + if not is_header then + if not ready(localized_name) then + return nil + end + + local english_name = LOCALIZED_TO_ENGLISH[localized_name] + local crafting_props = PROFESSION_TO_PROPS[english_name] + if crafting_props ~= nil then + if not ready(rank) then + return nil + end + tinsert(professions, { + english_name = english_name, + localized_name = localized_name, + icon_texture_path = crafting_props.icon, + cur_rank = rank, + }) + end + end + end + + return professions +end + +--- +--- Retrieves a list of the player's skills for a specific profession. +--- Returns a table of known skills for the specified profession, or nil if game data is not yet available +--- Use RegisterEvent("LCP_SKILLS_UPDATE", ...) to ensure data availability. +--- +--- @param profession_name string +--- @return LcpKnownSkill[]|nil +function lib:GetPlayerProfessionSkills(profession_name) + return skills_by_english_profession_name[profession_name] or skills_by_english_profession_name[LOCALIZED_TO_ENGLISH[profession_name]] +end + +--- +--- Registers a handler for the specific library event. +--- LCP_SKILLS_UPDATE is fired when skill data is updated and ready for retrieval. +--- LCP_FRAME_SHOW is fired when a crafting profession frame is shown. +--- LCP_FRAME_CLOSE is fired when a crafting profession frame is closed. +---@param event string +---@param handler function +---@overload fun(event: "LCP_SKILLS_UPDATE", handler: fun(profession: LcpKnownProfession, skills: LcpKnownSkill[]):void) +---@overload fun(event: "LCP_FRAME_SHOW", handler: fun(profession: LcpKnownProfession, frame: Frame, frame_type: LcpProfessionFrameType):void) +---@overload fun(event: "LCP_FRAME_CLOSE", handler: fun(frame: Frame, frame_type: LcpProfessionFrameType):void) +function lib:RegisterEvent(event, handler) + if lib.event_to_handlers[event] == nil then + lib.event_to_handlers[event] = {} + end + tinsert(lib.event_to_handlers[event], --[[---@type function]] handler) +end + +---@shape _LcpFilterOption +---@field name string +---@field type string + +---@shape _LcpProfessionAdapter +---@field GetProfessionInfo fun():string|nil, string|nil, number|nil, number|nil +---@field GetNumSkills fun():number|nil +---@field GetSkillInfo fun(index:number):string|nil, string|nil, number|nil, wowboolean +---@field GetSkillItemLink fun(index:number):string|nil +---@field ExpandHeader fun(index:number):void +---@field CollapseHeader fun(index:number):void +---@field DisableFilters fun():_LcpFilterOption[] +---@field EnableFilters fun(options:_LcpFilterOption[]):void + +---@param adapter _LcpProfessionAdapter +---@return LcpKnownSkill[]|nil +local function scan_skills(adapter) + local localized_profession, english_profession, cur_rank, max_rank = adapter.GetProfessionInfo() + if not ready(localized_profession) or not ready(english_profession) or not ready(cur_rank) or not ready(max_rank) then + return nil + end + + local disabled_filters = adapter.DisableFilters() + + local num_of_skills_before_expansion = adapter.GetNumSkills() + if not ready(num_of_skills_before_expansion) then + return nil + end + + ---@type table + local set_of_headers_to_collapse = {} + for i = 1, num_of_skills_before_expansion do + local skill_name, skill_type_or_difficulty, _, is_expanded = adapter.GetSkillInfo(i) + if not ready(skill_name) or not ready(skill_type_or_difficulty) then + return nil + end + if skill_type_or_difficulty == "header" and is_expanded == nil then + set_of_headers_to_collapse[ --[[---@not nil]] skill_name] = true + end + end + adapter.ExpandHeader(0) + + local num_of_skills_after_expansion = adapter.GetNumSkills() + if not ready(num_of_skills_after_expansion) then + return nil + end + + ---@type LcpKnownSkill[] + local skills = {} + for i = 1, num_of_skills_after_expansion do + local skill_name, skill_type_or_difficulty, num_available, _ = adapter.GetSkillInfo(i) + if not ready(skill_name) or not ready(skill_type_or_difficulty) then + return nil + end + if skill_type_or_difficulty ~= "header" then + local item_link = adapter.GetSkillItemLink(i) + if num_available == nil or not ready(item_link) then + return nil + end + + ---@type LcpKnownSkill + local skill = { + localized_name = --[[---@not nil]] skill_name, + item_link = --[[---@not nil]] item_link, + difficulty = --[[---@type LcpSkillDifficulty]] skill_type_or_difficulty, + num_available = --[[---@not nil]] num_available, + } + tinsert(skills, skill) + end + end + + for i = adapter.GetNumSkills(), 1, -1 do + local skill_name, skill_type_or_difficulty, _, _ = adapter.GetSkillInfo(i) + if skill_type_or_difficulty == "header" and set_of_headers_to_collapse[ --[[---@not nil]] skill_name] ~= nil then + adapter.CollapseHeader(i) + end + end + + adapter.EnableFilters(disabled_filters) + + return skills +end + +---@param known_professions LcpKnownProfession[]|nil +local function forget_obsolete_professions(known_professions) + if known_professions == nil then + return + end + + ---@type table + local known_profession_names_set = {} + for _, profession in ipairs(--[[---@not nil]] known_professions) do + known_profession_names_set[profession.english_name] = true + end + + for english_name, _ in pairs(skills_by_english_profession_name) do + if known_profession_names_set[english_name] == nil then + skills_by_english_profession_name[english_name] = nil + end + end +end + +---@param known_professions LcpKnownProfession[]|nil +---@param name string +---@return LcpKnownProfession|nil +local function find_known_profession(known_professions, name) + local profession + for _, candidate in ipairs(known_professions or {}) do + if candidate.localized_name == name or candidate.english_name == name then + profession = candidate + break + end + end + return profession +end + +---@param english_name string +---@param skills LcpKnownSkill[] +local function save_skills(english_name, skills) + skills_by_english_profession_name[english_name] = skills + + local known_professions = lib:GetPlayerProfessions() + forget_obsolete_professions(known_professions) + + local profession = find_known_profession(known_professions, english_name) + if profession == nil then + return + end + + for _, handler in ipairs(lib.event_to_handlers["LCP_SKILLS_UPDATE"] or {}) do + handler(profession, skills) + end +end + +---@shape LcpThirdPartyProfessionFrameAddon +---@field name string +---@field frame_name string +---@field opens_and_closes_vanilla_profession_frames boolean + +---@type LcpThirdPartyProfessionFrameAddon +local artisan = {name = "Artisan", frame_name = "ArtisanFrame", opens_and_closes_vanilla_profession_frames = true} +---@type LcpThirdPartyProfessionFrameAddon +local atsw = {name = "AdvancedTradeSkillWindow", frame_name = "ATSWFrame", opens_and_closes_vanilla_profession_frames = false} +---@type LcpThirdPartyProfessionFrameAddon +local atsw2 = {name = "AdvancedTradeSkillWindow2", frame_name = "ATSWFrame", opens_and_closes_vanilla_profession_frames = true} + +local SUPPORTED_THIRD_PARTY_ADDONS = {artisan, atsw, atsw2} + +---@param addon LcpThirdPartyProfessionFrameAddon +local function is_third_party_loaded(addon) + return IsAddOnLoaded(addon.name) == 1 and getglobal(addon.frame_name) ~= nil +end + +---@return LcpProfessionFrame +local function detect_profession_frame(professionFrame) + ---@type Frame + local atsw1_or_atsw2_frame = getglobal(atsw2.frame_name) + if atsw1_or_atsw2_frame ~= nil then + ---@type LcpProfessionFrameType + local frame_type = FrameType.AdvancedTradeSkillWindow + if is_third_party_loaded(atsw2) then + frame_type = FrameType.AdvancedTradeSkillWindow2 + end + return {frame = atsw1_or_atsw2_frame, type = frame_type} + end + + if is_third_party_loaded(artisan) then + return {frame = getglobal(artisan.frame_name), type = FrameType.Artisan} + end + + if professionFrame == TradeSkillFrame then + local frame_type = IS_TURTLE_WOW and FrameType.TurtleTradeSkillFrame or FrameType.VanillaTradeSkillFrame + return {frame = professionFrame, type = frame_type} + end + + return {frame = professionFrame, type = FrameType.VanillaCraftFrame} +end + +---@param english_name string +---@param localized_name string +---@param cur_rank number +---@param frame Frame +local function send_frame_show_event(english_name, localized_name, cur_rank, frame) + local profession = { + english_name = english_name, + localized_name = localized_name, + cur_rank = cur_rank, + } + local real_frame = detect_profession_frame(frame) + for _, handler in ipairs(lib.event_to_handlers["LCP_FRAME_SHOW"] or {}) do + handler(profession, real_frame.frame, real_frame.type) + end +end + +---@param frame Frame +local function send_frame_close_event(frame) + local real_frame = detect_profession_frame(frame) + for _, handler in ipairs(lib.event_to_handlers["LCP_FRAME_CLOSE"] or {}) do + handler(real_frame.frame, real_frame.type) + end +end + +---@return CraftFrame +local function get_craft_frame() + if CraftFrame == nil then + LoadAddOn("Blizzard_CraftUI") + end + return CraftFrame +end + +---@type _LcpProfessionAdapter +local craft_adapter = { + GetProfessionInfo = function() + local name, cur_rank, max_rank = GetCraftDisplaySkillLine() + if name == nil then -- GetCraftDisplaySkillLine() returns nil for the "Beast Training" frame + name = GetCraftName() + end + return name, LOCALIZED_TO_ENGLISH[--[[---@type string]] name], cur_rank, max_rank + end, + GetNumSkills = GetNumCrafts, + GetSkillInfo = function(i) + local name, _, type, num_available, is_expanded = GetCraftInfo(i) + return name, type, num_available, is_expanded + end, + GetSkillItemLink = GetCraftItemLink, + ExpandHeader = ExpandCraftSkillLine, + CollapseHeader = CollapseCraftSkillLine, + DisableFilters = function() return {} end, + EnableFilters = function(options) end, +} + +local function try_send_craft_frame_show_event() + local localized_name, english_name, cur_rank, _ = craft_adapter.GetProfessionInfo() + if not ready(localized_name) or not ready(english_name) or not ready(cur_rank) then + return + end + send_frame_show_event(--[[---@not nil]] english_name, --[[---@not nil]] localized_name, --[[---@not nil]] cur_rank, get_craft_frame()) +end + +local craft_scan_in_progress = false + +local function scan_craft_frame() + craft_scan_in_progress = true + + local localized_name, english_name, _, _ = craft_adapter.GetProfessionInfo() + if not ready(localized_name) or not ready(english_name) then + craft_scan_in_progress = false + return + end + + local skills = scan_skills(craft_adapter) + if not ready(skills) then + craft_scan_in_progress = false + return + end + + save_skills(--[[---@not nil]] english_name, --[[---@not nil]] skills) + craft_scan_in_progress = false +end + +---@shape _LcpSkillFilterAdapter +---@field GetType fun(): "inv_slot" | "subclass" +---@field GetOptions fun(): string... +---@field GetOptionState fun(index:number): wowboolean +---@field SetOptionState fun(index:number, enable:wowboolean, exclusive:wowboolean): void + +---@param adapter _LcpSkillFilterAdapter +---@return _LcpFilterOption[] +local function disable_trade_skill_filters(adapter) + if adapter.GetOptionState(0) == 1 then + return {} + end + + ---@type _LcpFilterOption[] + local selected_options = {} + for i, option in ipairs({adapter.GetOptions()}) do + if adapter.GetOptionState(i) == 1 then + tinsert(selected_options, {name = option, type = adapter.GetType()}) + end + end + + adapter.SetOptionState(0, 1, 1) + + return selected_options +end + +---@param adapter _LcpSkillFilterAdapter +---@param options _LcpFilterOption[] +local function enable_trade_skill_filters(adapter, options) + ---@type table + local options_set = {} + for _, option in ipairs(options) do + if option.type == adapter.GetType() then + options_set[option.name] = true + end + end + + for i, option in ipairs({adapter.GetOptions()}) do + if options_set[option] then + adapter.SetOptionState(i, 1, 1) + end + end +end + +local function get_trade_skill_frame() + if TradeSkillFrame == nil then + LoadAddOn("Blizzard_TradeSkillUI") + end + return TradeSkillFrame +end + +---@type _LcpSkillFilterAdapter +local inv_slot_filter_adapter = { + GetType = function() return "inv_slot" end, + GetOptions = GetTradeSkillInvSlots, + GetOptionState = GetTradeSkillInvSlotFilter, + SetOptionState = SetTradeSkillInvSlotFilter, +} + +---@type _LcpSkillFilterAdapter +local subclass_filter_adapter = { + GetType = function() return "subclass" end, + GetOptions = GetTradeSkillSubClasses, + GetOptionState = GetTradeSkillSubClassFilter, + SetOptionState = SetTradeSkillSubClassFilter, +} + +---@type _LcpProfessionAdapter +local trade_skill_adapter = { + GetProfessionInfo = function() + local name, cur_rank, max_rank = GetTradeSkillLine() + return name, LOCALIZED_TO_ENGLISH[--[[---@type string]] name], cur_rank, max_rank + end, + GetNumSkills = GetNumTradeSkills, + GetSkillInfo = GetTradeSkillInfo, + GetSkillItemLink = GetTradeSkillItemLink, + ExpandHeader = ExpandTradeSkillSubClass, + CollapseHeader = CollapseTradeSkillSubClass, + DisableFilters = function() + local result = {} + for _, adapter in ipairs({inv_slot_filter_adapter, subclass_filter_adapter}) do + for _, option in ipairs(disable_trade_skill_filters(adapter)) do + tinsert(result, option) + end + end + return result + end, + EnableFilters = function(options) + for _, adapter in ipairs({inv_slot_filter_adapter, subclass_filter_adapter}) do + enable_trade_skill_filters(adapter, options) + end + end, +} + +local function try_send_trade_skill_frame_show_event() + local localized_name, english_name, cur_rank, _ = trade_skill_adapter.GetProfessionInfo() + if not ready(localized_name) or not ready(english_name) or not ready(cur_rank) then + return false + end + send_frame_show_event(--[[---@not nil]] english_name, --[[---@not nil]] localized_name, --[[---@not nil]] cur_rank, get_trade_skill_frame()) +end + +local trade_skill_scan_in_progress = false + +---@return boolean +local function scan_trade_skill_frame() + trade_skill_scan_in_progress = true + + local localized_name, english_name, _, _ = trade_skill_adapter.GetProfessionInfo() + if not ready(localized_name) or not ready(english_name) then + trade_skill_scan_in_progress = false + return false + end + + local skills = scan_skills(trade_skill_adapter) + if not ready(skills) then + trade_skill_scan_in_progress = false + return false + end + + save_skills(--[[---@not nil]] english_name, --[[---@not nil]] skills) + + trade_skill_scan_in_progress = false + return true +end + +local L = ENGLISH_TO_LOCALIZED + +local game_locale = GetLocale() +if game_locale == "enUS" or game_locale == "enGB" then + L["Alchemy"] = "Alchemy" + L["Blacksmithing"] = "Blacksmithing" + L["Cooking"] = "Cooking" + L["Enchanting"] = "Enchanting" + L["Engineering"] = "Engineering" + L["First Aid"] = "First Aid" + L["Leatherworking"] = "Leatherworking" + L["Mining"] = "Mining" + L["Poisons"] = "Poisons" + L["Tailoring"] = "Tailoring" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "Jewelcrafting" + L["Survival"] = "Survival" + end +elseif game_locale == "deDE" then + L["Alchemy"] = "Alchimie" + L["Blacksmithing"] = "Schmiedekunst" + L["Cooking"] = "Kochkunst" + L["Enchanting"] = "Verzauberkunst" + L["Engineering"] = "Ingenieurskunst" + L["First Aid"] = "Erste Hilfe" + L["Leatherworking"] = "Lederverarbeitung" + L["Mining"] = "Bergbau" + L["Poisons"] = "Gifte" + L["Tailoring"] = "Schneiderei" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "Juwelenschleifen" + L["Survival"] = "Survival" + end +elseif game_locale == "esES" then + L["Alchemy"] = "Alquimia" + L["Blacksmithing"] = "Herrería" + L["Cooking"] = "Cocina" + L["Enchanting"] = "Encantamiento" + L["Engineering"] = "Ingeniería" + L["First Aid"] = "Primeros auxilios" + L["Leatherworking"] = "Peletería" + L["Mining"] = "Minería" + L["Poisons"] = "Venenos" + L["Tailoring"] = "Sastrería" + if IS_TURTLE_WOW then + L["Blacksmithing"] = "Ferraria" + L["Cooking"] = "Culinária" + L["Enchanting"] = "Encantamento" + L["Engineering"] = "Engenharia" + L["First Aid"] = "Primeiros Socorros" + L["Jewelcrafting"] = "Joalheria" + L["Survival"] = "Survival" + L["Leatherworking"] = "Couraria" + L["Mining"] = "Mineração" + L["Tailoring"] = "Alfaiataria" + end +elseif game_locale == "esMX" then + L["Alchemy"] = "Alquimia" + L["Blacksmithing"] = "Herrería" + L["Cooking"] = "Cocina" + L["Enchanting"] = "Encantamiento" + L["Engineering"] = "Ingeniería" + L["First Aid"] = "Primeros auxilios" + L["Leatherworking"] = "Peletería" + L["Mining"] = "Minería" + L["Poisons"] = "Venenos" + L["Tailoring"] = "Sastrería" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "Joyería" + L["Survival"] = "Survival" + end +elseif game_locale == "frFR" then + L["Alchemy"] = "Alchimie" + L["Blacksmithing"] = "Forge" + L["Cooking"] = "Cuisine" + L["Enchanting"] = "Enchantement" + L["Engineering"] = "Ingénieur" + L["First Aid"] = "Premiers soins" + L["Leatherworking"] = "Travail du cuir" + L["Mining"] = "Minage" + L["Poisons"] = "Poisons" + L["Tailoring"] = "Couture" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "Joaillerie" + L["Survival"] = "Survival" + end +elseif game_locale == "koKR" then + L["Alchemy"] = "연금술" + L["Blacksmithing"] = "대장기술" + L["Cooking"] = "요리" + L["Enchanting"] = "마법부여" + L["Engineering"] = "기계공학" + L["First Aid"] = "응급치료" + L["Leatherworking"] = "가죽세공" + L["Mining"] = "채광" + L["Poisons"] = "독 조제" + L["Tailoring"] = "재봉술" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "보석세공" + L["Survival"] = "Survival" + end +elseif game_locale == "ptBR" then + L["Alchemy"] = "Alquimia" + L["Blacksmithing"] = "Ferraria" + L["Cooking"] = "Culinária" + L["Enchanting"] = "Encantamento" + L["Engineering"] = "Engenharia" + L["First Aid"] = "Primeiros Socorros" + L["Leatherworking"] = "Couraria" + L["Mining"] = "Mineração" + L["Poisons"] = "Venenos" + L["Tailoring"] = "Alfaiataria" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "Joalheria" + L["Survival"] = "Survival" + end +elseif game_locale == "ruRU" then + L["Alchemy"] = "Алхимия" + L["Blacksmithing"] = "Кузнечное дело" + L["Cooking"] = "Кулинария" + L["Enchanting"] = "Наложение чар" + L["Engineering"] = "Инженерное дело" + L["First Aid"] = "Первая помощь" + L["Leatherworking"] = "Кожевничество" + L["Mining"] = "Горное дело" + L["Poisons"] = "Яды" + L["Tailoring"] = "Портняжное дело" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "Ювелирное дело" + L["Survival"] = "Survival" + end +elseif game_locale == "zhCN" then + L["Alchemy"] = "炼金术" + L["Blacksmithing"] = "锻造" + L["Cooking"] = "烹饪" + L["Enchanting"] = "附魔" + L["Engineering"] = "工程学" + L["First Aid"] = "急救" + L["Leatherworking"] = "制皮" + L["Mining"] = "采矿" + L["Poisons"] = "毒药" + L["Tailoring"] = "裁缝" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "珠宝加工" + L["Survival"] = "Survival" + end +elseif game_locale == "zhTW" then + L["Alchemy"] = "煉金術" + L["Blacksmithing"] = "鍛造" + L["Cooking"] = "烹飪" + L["Enchanting"] = "附魔" + L["Engineering"] = "工程學" + L["First Aid"] = "急救" + L["Leatherworking"] = "製皮" + L["Mining"] = "採礦" + L["Poisons"] = "毒藥" + L["Tailoring"] = "裁縫" + if IS_TURTLE_WOW then + L["Jewelcrafting"] = "珠寶設計" + L["Survival"] = "Survival" + end +end + +for english_name, localized_name in pairs(ENGLISH_TO_LOCALIZED) do + LOCALIZED_TO_ENGLISH[localized_name] = english_name +end + +---@param frame Frame +---@return table +local function as_table(frame) + return --[[---@type table]] frame +end + +local was_third_party_on_hide_hooked = false + +---@param addon LcpThirdPartyProfessionFrameAddon +local function hook_third_party_on_hide(addon) + if not is_third_party_loaded(addon) then + return + end + + ---@type Frame + local frame = getglobal(addon.frame_name) + if frame == nil then + return + end + if as_table(frame).is_hooked_by_lcp == true then + was_third_party_on_hide_hooked = true + return + end + + local on_hide = frame:GetScript("OnHide") + frame:SetScript("OnHide", function() + if on_hide ~= nil then + (--[[---@not nil]] on_hide)() + end + send_frame_close_event(frame) + end) + + as_table(frame).is_hooked_by_lcp = true + was_third_party_on_hide_hooked = true +end + +if lib.event_frame ~= nil then + lib.event_frame:UnregisterAllEvents() + lib.event_frame:SetScript("OnUpdate", nil) + lib.event_frame:SetScript("OnEvent", nil) +end + +local render_pseudo_time = 0 +local craft_frame_successfully_scanned_at = 0 +local trade_skill_frame_successfully_scanned_at = 0 + +lib.event_frame = CreateFrame("Frame") + +lib.event_frame:SetScript("OnUpdate", function () + -- Third-party addons can trigger multiple UPDATE events + -- so we count rendered frames to not scan skills more than once per frame + render_pseudo_time = render_pseudo_time + 1 + if render_pseudo_time >= 100 then + render_pseudo_time = 1 + end +end) + +lib.event_frame:SetScript("OnEvent", function() + local do_craft_scan = false + local do_trade_skill_scan = false + + if event == "ADDON_LOADED" then + for _, addon in ipairs(SUPPORTED_THIRD_PARTY_ADDONS) do + if addon.name == arg1 and addon.opens_and_closes_vanilla_profession_frames then + hook_third_party_on_hide(addon) + end + end + elseif event == "CRAFT_SHOW" then + try_send_craft_frame_show_event() + do_craft_scan = true + elseif event == "CRAFT_UPDATE" then + if not craft_scan_in_progress then + -- Learned new skill AND craft frame is opened + do_craft_scan = true + end + elseif event == "CRAFT_CLOSE" then + if not was_third_party_on_hide_hooked then + send_frame_close_event(get_craft_frame()) + end + elseif event == "TRADE_SKILL_SHOW" then + try_send_trade_skill_frame_show_event() + do_trade_skill_scan = true + elseif event == "TRADE_SKILL_UPDATE" then + if not trade_skill_scan_in_progress then + -- Learned new skill AND trade skill frame is opened + do_trade_skill_scan = true + end + elseif event == "TRADE_SKILL_CLOSE" then + if not was_third_party_on_hide_hooked then + send_frame_close_event(get_trade_skill_frame()) + end + elseif event == "CHARACTER_POINTS_CHANGED" then + -- Learned or unlearned profession/specialization + forget_obsolete_professions(lib:GetPlayerProfessions()) + elseif event == "SKILL_LINES_CHANGED" then + -- Raised the profession skill level OR learned/unlearned profession/specialization + do_craft_scan = true + do_trade_skill_scan = true + end + + if do_craft_scan and craft_frame_successfully_scanned_at ~= render_pseudo_time then + if scan_craft_frame() then + craft_frame_successfully_scanned_at = render_pseudo_time + end + end + + if do_trade_skill_scan and trade_skill_frame_successfully_scanned_at ~= render_pseudo_time then + if scan_trade_skill_frame() then + trade_skill_frame_successfully_scanned_at = render_pseudo_time + end + end +end) +lib.event_frame:RegisterEvent("ADDON_LOADED") +lib.event_frame:RegisterEvent("CRAFT_SHOW") +lib.event_frame:RegisterEvent("CRAFT_UPDATE") +lib.event_frame:RegisterEvent("CRAFT_CLOSE") +lib.event_frame:RegisterEvent("TRADE_SKILL_SHOW") +lib.event_frame:RegisterEvent("TRADE_SKILL_UPDATE") +lib.event_frame:RegisterEvent("TRADE_SKILL_CLOSE") +lib.event_frame:RegisterEvent("CHARACTER_POINTS_CHANGED") +lib.event_frame:RegisterEvent("SKILL_LINES_CHANGED") + +for _, addon in ipairs(SUPPORTED_THIRD_PARTY_ADDONS) do + if is_third_party_loaded(addon) and addon.opens_and_closes_vanilla_profession_frames then + hook_third_party_on_hide(addon) + end +end diff --git a/Gromissingcrafts/lib/LibCraftingProfessions-1.0/LibCraftingProfessions-1.0.xml b/Gromissingcrafts/lib/LibCraftingProfessions-1.0/LibCraftingProfessions-1.0.xml new file mode 100644 index 0000000..85d2719 --- /dev/null +++ b/Gromissingcrafts/lib/LibCraftingProfessions-1.0/LibCraftingProfessions-1.0.xml @@ -0,0 +1,4 @@ + +