Initial commit
This commit is contained in:
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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=<id>` 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)
|
||||
@@ -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<string, function[]>
|
||||
---@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<string, LcpProfessionProps>
|
||||
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<string, string>
|
||||
local ENGLISH_TO_LOCALIZED = {}
|
||||
---@type table<string, string>
|
||||
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<LcpProfessionFrameType, LcpProfessionFrameType>}
|
||||
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<string, LcpKnownSkill[]>
|
||||
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<string, true>
|
||||
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<string, boolean>
|
||||
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<string, boolean>
|
||||
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
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<Script file="LibStub.lua" />
|
||||
<Script file="LibCraftingProfessions-1.0.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,63 @@
|
||||
### Revision 14 (2025/12/21)
|
||||
- Turtle: Update localization
|
||||
|
||||
### Revision 14 (2025/09/09)
|
||||
- Turtle: Add a lot of missing recipes
|
||||
- Fix a bunch of data errors
|
||||
- Add craft spell id to craft objects
|
||||
- Fix debug mode
|
||||
|
||||
### Revision 13 (2025/09/05)
|
||||
- Turtle: Add new 1.18 alchemy concoction recipes
|
||||
- Turtle: Add localization updates from patch 1.18.7230
|
||||
|
||||
### Revision 13 (2025/08/17)
|
||||
- Turtle: Support the latest localization updates (including deDE game client)
|
||||
|
||||
### Revision 12 (2025/08/16)
|
||||
- Turtle: Fix Jewelcrafting database errors
|
||||
- Turtle: Add some missing 1.17.2 recipes
|
||||
- Turtle: Add some 1.18.0 recipe changes
|
||||
- Turtle: Support Disguise profession
|
||||
|
||||
### Revision 11 (2025/06/11)
|
||||
- Fix a lot of database errors
|
||||
|
||||
### Revision 10 (2025/06/10)
|
||||
- Fix localized profession names
|
||||
|
||||
### Revision 9 (2025/06/10)
|
||||
- Vanilla: Fix a bunch of localization mistakes
|
||||
- Turtle: Fully support esES, ptBR and zhCN locales
|
||||
- Turtle: Add several missing crafts
|
||||
- Turtle: Fix and rework Jewelcrafting
|
||||
|
||||
### Revision 8 (2025/06/07)
|
||||
- Attempt to work around a supposed bug in SpellInfo SuperWoW function
|
||||
|
||||
### Revision 7 (2025/06/04)
|
||||
- Significantly improve localization support by using SuperWoW (if available)
|
||||
|
||||
### Revision 6 (2025/06/01)
|
||||
- Add a couple of missing Turtle-specific recipes
|
||||
- Add GetCraftsByRecipeId to public API
|
||||
|
||||
### Revision 5 (2025/05/03)
|
||||
- Support Turtle WoW Jewelcrafting profession (by [KasVital](https://github.com/KasVital))
|
||||
|
||||
### Revision 4 (2024/11/09)
|
||||
- Add locale modules for profession names
|
||||
- Add GetCraftsByProfession() API
|
||||
- Fix First Aid profession name
|
||||
- Fix library in-memory upgrade process
|
||||
|
||||
### Revision 3 (2024/11/03)
|
||||
- Add new Turtle WoW recipes for Vanilla professions
|
||||
- Fix craft indentation
|
||||
- Update some translations
|
||||
|
||||
### Revision 2 (2024/11/02)
|
||||
- Disable debug mode
|
||||
|
||||
### Revision 1 (2024/11/02)
|
||||
- Release the first version
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Roman Kharitonov
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,482 @@
|
||||
--[[
|
||||
Name: LibCrafts-1.0
|
||||
Developed by: Refaim (rkharito@yandex.ru, https://github.com/refaim/)
|
||||
Website: https://github.com/refaim/LibCrafts-1.0
|
||||
Description: A library designed to provide a database crafting spells, recipes, reagents etc.
|
||||
Dependencies: LibStub
|
||||
Compatibility: Vanilla (1.12.1), Turtle (1.18.0)
|
||||
]]
|
||||
|
||||
---@type LibStubDef
|
||||
local LibStub = getglobal("LibStub")
|
||||
assert(LibStub ~= nil)
|
||||
|
||||
local untyped_lib, _ = LibStub:NewLibrary("LibCrafts-1.0", 20)
|
||||
if not untyped_lib then return end
|
||||
|
||||
---@class LibCrafts
|
||||
---@field env LcEnvironment
|
||||
---@field constants LcConstants
|
||||
---@field modules_by_name table<string, LcModule>
|
||||
---@field spell_id_to_craft table<number, LcCraft>
|
||||
---@field reagent_id_to_spell_ids_set table<number, table<number, boolean>>
|
||||
---@field recipe_id_to_spell_ids_set table<number, table<number, boolean>>
|
||||
---@field localized_profession_name_to_spell_ids_set table<string, table<number, boolean>>
|
||||
|
||||
local lib = --[[---@type LibCrafts]] untyped_lib
|
||||
lib.modules_by_name = lib.modules_by_name or {}
|
||||
lib.spell_id_to_craft = lib.spell_id_to_craft or {}
|
||||
lib.reagent_id_to_spell_ids_set = lib.reagent_id_to_spell_ids_set or {}
|
||||
lib.recipe_id_to_spell_ids_set = lib.recipe_id_to_spell_ids_set or {}
|
||||
lib.localized_profession_name_to_spell_ids_set = lib.localized_profession_name_to_spell_ids_set or {}
|
||||
|
||||
---@type table[]
|
||||
local data_tables = {
|
||||
lib.modules_by_name,
|
||||
lib.spell_id_to_craft,
|
||||
lib.reagent_id_to_spell_ids_set,
|
||||
lib.recipe_id_to_spell_ids_set,
|
||||
lib.localized_profession_name_to_spell_ids_set,
|
||||
}
|
||||
|
||||
local all_data_tables_filled = true
|
||||
for _, data_table in ipairs(data_tables) do
|
||||
if type(data_table) ~= "table" or next(data_table) == nil then
|
||||
all_data_tables_filled = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not all_data_tables_filled then
|
||||
lib.modules_by_name = {}
|
||||
lib.spell_id_to_craft = {}
|
||||
lib.reagent_id_to_spell_ids_set = {}
|
||||
lib.recipe_id_to_spell_ids_set = {}
|
||||
lib.localized_profession_name_to_spell_ids_set = {}
|
||||
end
|
||||
|
||||
lib.env = {
|
||||
is_debug = false,
|
||||
is_turtle_wow = getglobal("TURTLE_WOW_VERSION") ~= nil,
|
||||
is_super_wow_loaded = getglobal("SpellInfo") ~= nil,
|
||||
}
|
||||
|
||||
lib.constants = {
|
||||
qualities = {
|
||||
Poor = 101,
|
||||
Common = 102,
|
||||
Uncommon = 103,
|
||||
Rare = 104,
|
||||
Epic = 105,
|
||||
Legendary = 106,
|
||||
},
|
||||
spell_sources = {
|
||||
LearnedAutomatically = 201,
|
||||
Quest = 202,
|
||||
Trainer = 203,
|
||||
WorldObject = 204,
|
||||
},
|
||||
recipe_sources = {
|
||||
Chest = 301,
|
||||
CraftedByEngineer = 302,
|
||||
Drop = 303,
|
||||
Fishing = 304,
|
||||
GiftedToReturningEngineers = 305,
|
||||
Pickpocketing = 306,
|
||||
Quest = 307,
|
||||
Vendor = 308,
|
||||
},
|
||||
}
|
||||
|
||||
---@shape LcEnvironment
|
||||
---@field is_debug boolean
|
||||
---@field is_turtle_wow boolean
|
||||
---@field is_super_wow_loaded boolean
|
||||
|
||||
---@shape LcConstants
|
||||
---@field qualities LcItemQualities
|
||||
---@field spell_sources LcSpellSources
|
||||
---@field recipe_sources LcRecipeSources
|
||||
|
||||
---@shape LcItemQualities
|
||||
---@field Poor 101
|
||||
---@field Common 102
|
||||
---@field Uncommon 103
|
||||
---@field Rare 104
|
||||
---@field Epic 105
|
||||
---@field Legendary 106
|
||||
|
||||
---@alias LcItemQuality 101 | 102 | 103 | 104 | 105 | 106
|
||||
|
||||
---@shape LcSpellSources
|
||||
---@field LearnedAutomatically 201
|
||||
---@field Quest 202
|
||||
---@field Trainer 203
|
||||
---@field WorldObject 204
|
||||
|
||||
---@alias LcSpellSource 201 | 202 | 203 | 204
|
||||
|
||||
---@shape LcRecipeSources
|
||||
---@field Chest 301
|
||||
---@field CraftedByEngineer 302
|
||||
---@field Drop 303
|
||||
---@field Fishing 304
|
||||
---@field GiftedToReturningEngineers 305
|
||||
---@field Pickpocketing 306
|
||||
---@field Quest 307
|
||||
---@field Vendor 308
|
||||
|
||||
---@alias LcRecipeSource 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308
|
||||
|
||||
---@shape LcItem
|
||||
---@field id number
|
||||
|
||||
---@shape LcRecipe: LcItem
|
||||
---@field quality LcItemQuality
|
||||
---@field sources LcRecipeSource[]
|
||||
|
||||
---@class LcCraft
|
||||
---@field spell_id number
|
||||
---@field en_profession_name string
|
||||
---@field localized_profession_name string
|
||||
---@field spell_id number
|
||||
---@field localized_spell_name string
|
||||
---@field skill_level number
|
||||
---@field character_level number
|
||||
---@field result LcItem|nil
|
||||
---@field sources LcSpellSource[]
|
||||
---@field recipes LcRecipe[]
|
||||
---@field reagent_id_to_count table<number, number>
|
||||
---@field was_enriched boolean
|
||||
local Craft = {}
|
||||
|
||||
---@param value string
|
||||
---@param locale_module_name string
|
||||
---@return string
|
||||
local function translate_from_en_to_game_locale(value, locale_module_name)
|
||||
---@type table<string, string|boolean>
|
||||
local L = {}
|
||||
local locale_module = --[[---@type LcLocaleModule]] lib.modules_by_name[locale_module_name]
|
||||
if locale_module ~= nil then
|
||||
L = locale_module:GetStrings()
|
||||
end
|
||||
|
||||
local localized_value = L[value]
|
||||
if type(localized_value) ~= "string" or localized_value == "" then
|
||||
localized_value = value
|
||||
end
|
||||
|
||||
return --[[---@type string]] localized_value
|
||||
end
|
||||
|
||||
---@param craft LcCraft
|
||||
---@return LcCraft
|
||||
local function enrich(craft)
|
||||
if not lib.env.is_super_wow_loaded or craft.was_enriched then
|
||||
return craft
|
||||
end
|
||||
|
||||
local spell_name, _, _, _, _ = SpellInfo(craft.spell_id)
|
||||
if type(spell_name) == "string" then
|
||||
craft.localized_spell_name = spell_name
|
||||
craft.was_enriched = true
|
||||
end
|
||||
|
||||
return craft
|
||||
end
|
||||
|
||||
---
|
||||
--- Returns a list of crafts by reagent item id.
|
||||
---
|
||||
---@param item_id number
|
||||
---@return LcCraft[]
|
||||
function lib:GetCraftsByReagentId(item_id)
|
||||
local crafts = {}
|
||||
for spell_id, _ in pairs(self.reagent_id_to_spell_ids_set[item_id] or {}) do
|
||||
tinsert(crafts, enrich(self.spell_id_to_craft[spell_id]))
|
||||
end
|
||||
return crafts
|
||||
end
|
||||
|
||||
---
|
||||
--- Returns a list of crafts by recipe item id.
|
||||
---
|
||||
---@param item_id number
|
||||
---@return LcCraft[]
|
||||
function lib:GetCraftsByRecipeId(item_id)
|
||||
local crafts = {}
|
||||
for spell_id, _ in pairs(self.recipe_id_to_spell_ids_set[item_id] or {}) do
|
||||
tinsert(crafts, enrich(self.spell_id_to_craft[spell_id]))
|
||||
end
|
||||
return crafts
|
||||
end
|
||||
|
||||
---
|
||||
--- Returns a list of crafts by profession name in current game locale (or in English).
|
||||
---
|
||||
---@param profession string
|
||||
---@return LcCraft[]
|
||||
function lib:GetCraftsByProfession(profession)
|
||||
local d = self.localized_profession_name_to_spell_ids_set
|
||||
local spell_ids_set = d[profession] or d[translate_from_en_to_game_locale(profession, "Locales-Professions")] or {}
|
||||
local crafts = {}
|
||||
for spell_id, _ in pairs(spell_ids_set) do
|
||||
tinsert(crafts, enrich(self.spell_id_to_craft[spell_id]))
|
||||
end
|
||||
return crafts
|
||||
end
|
||||
|
||||
---@class LcModule
|
||||
---@field name string
|
||||
---@field version number
|
||||
|
||||
---@class LcProfessionModule: LcModule
|
||||
---@field en_profession_name string
|
||||
---@field localized_profession_name string
|
||||
local ProfessionModule = {}
|
||||
|
||||
---@class LcLocaleModule: LcModule
|
||||
---@field en_to_any table<string, string|boolean>
|
||||
local LocaleModule = {}
|
||||
|
||||
---@return table<string, string|boolean>
|
||||
function LocaleModule:GetStrings()
|
||||
return self.en_to_any
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param version number
|
||||
---@return boolean
|
||||
local function module_registered(name, version)
|
||||
local current_version = (lib.modules_by_name[name] or {}).version
|
||||
if current_version ~= nil and current_version >= version then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param version number
|
||||
---@param en_profession_name string
|
||||
---@return LcProfessionModule|nil
|
||||
function lib:RegisterProfessionModule(name, version, en_profession_name)
|
||||
if self.env.is_debug then
|
||||
assert(type(name) == "string" and name ~= "")
|
||||
assert(type(version) == "number" and version > 0)
|
||||
assert(type(en_profession_name) == "string" and en_profession_name ~= "")
|
||||
end
|
||||
|
||||
if module_registered(name, version) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local object = {}
|
||||
setmetatable(object, {__index = ProfessionModule})
|
||||
|
||||
local module = --[[---@type LcProfessionModule]] object
|
||||
module.name = name
|
||||
module.version = version
|
||||
module.en_profession_name = en_profession_name
|
||||
module.localized_profession_name = translate_from_en_to_game_locale(en_profession_name, "Locales-Professions")
|
||||
|
||||
self.modules_by_name[name] = module
|
||||
return module
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param locale string
|
||||
---@param version number
|
||||
---@return LcLocaleModule|nil
|
||||
function lib:RegisterLocaleModule(name, locale, version)
|
||||
if self.env.is_debug then
|
||||
assert(type(name) == "string" and locale ~= "")
|
||||
assert(type(locale) == "string" and locale ~= "")
|
||||
assert(type(version) == "number" and version > 0)
|
||||
end
|
||||
|
||||
if locale == "enGB" then
|
||||
locale = "enUS"
|
||||
end
|
||||
|
||||
local game_locale = GetLocale()
|
||||
if locale ~= game_locale or module_registered(name, version) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local object = {}
|
||||
setmetatable(object, {__index = LocaleModule})
|
||||
|
||||
local module = --[[---@type LcLocaleModule]] object
|
||||
module.name = name
|
||||
module.version = version
|
||||
module.en_to_any = {}
|
||||
|
||||
self.modules_by_name[name] = module
|
||||
return module
|
||||
end
|
||||
|
||||
---@type table<number, boolean>
|
||||
local SPELL_SOURCE_SET = {}
|
||||
for _, source in pairs(lib.constants.spell_sources) do
|
||||
SPELL_SOURCE_SET[source] = true
|
||||
end
|
||||
|
||||
---@param spell_id number
|
||||
---@param spell_name string
|
||||
---@param skill_level number
|
||||
---@param sources LcSpellSource[]
|
||||
---@return LcCraft
|
||||
function ProfessionModule:NewCraft(spell_id, spell_name, skill_level, sources)
|
||||
if lib.env.is_debug then
|
||||
assert(type(spell_id) == "number" and spell_id > 0)
|
||||
assert(type(spell_name) == "string" and spell_name ~= "")
|
||||
assert(type(skill_level) == "number" and skill_level > 0)
|
||||
assert(type(sources) == "table")
|
||||
for _, source in ipairs(sources) do
|
||||
assert(type(source) == "number" and SPELL_SOURCE_SET[source] ~= nil)
|
||||
end
|
||||
end
|
||||
|
||||
local object = {}
|
||||
setmetatable(object, {__index = Craft})
|
||||
|
||||
local craft = --[[---@type LcCraft]] object
|
||||
craft.spell_id = spell_id
|
||||
craft.en_profession_name = self.en_profession_name
|
||||
craft.localized_profession_name = self.localized_profession_name
|
||||
craft.spell_id = spell_id
|
||||
craft.localized_spell_name = translate_from_en_to_game_locale(spell_name, "Locales-Spells")
|
||||
craft.skill_level = skill_level
|
||||
craft.character_level = 1
|
||||
craft.sources = sources
|
||||
craft.recipes = {}
|
||||
craft.reagent_id_to_count = {}
|
||||
|
||||
return craft
|
||||
end
|
||||
|
||||
---@type table<number, boolean>
|
||||
local ITEM_QUALITY_SET = {}
|
||||
for _, quality in pairs(lib.constants.qualities) do
|
||||
ITEM_QUALITY_SET[quality] = true
|
||||
end
|
||||
|
||||
---@type table<number, boolean>
|
||||
local RECIPE_SOURCE_SET = {}
|
||||
for _, source in pairs(lib.constants.recipe_sources) do
|
||||
RECIPE_SOURCE_SET[source] = true
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param quality LcItemQuality
|
||||
---@param sources LcRecipeSource[]
|
||||
---@return self
|
||||
function Craft:AddRecipe(id, quality, sources)
|
||||
if lib.env.is_debug then
|
||||
assert(type(id) == "number" and id > 0)
|
||||
assert(type(quality) == "number" and ITEM_QUALITY_SET[quality] ~= nil)
|
||||
assert(type(sources) == "table")
|
||||
for _, source in ipairs(sources) do
|
||||
assert(type(source) == "number" and RECIPE_SOURCE_SET[source] ~= nil)
|
||||
end
|
||||
for _, recipe in ipairs(self.recipes) do
|
||||
assert(recipe.id ~= id)
|
||||
end
|
||||
end
|
||||
|
||||
tinsert(self.recipes, {id = id, quality = quality, sources = sources})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param item_id number
|
||||
---@return self
|
||||
function Craft:SetResult(item_id)
|
||||
if lib.env.is_debug then
|
||||
assert(type(item_id) == "number" and item_id > 0)
|
||||
assert(self.result == nil)
|
||||
end
|
||||
|
||||
self.result = {id = item_id }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@param count number
|
||||
---@return self
|
||||
function Craft:AddReagent(id, count)
|
||||
if lib.env.is_debug then
|
||||
assert(type(id) == "number" and id > 0)
|
||||
assert(type(count) == "number" and count > 0)
|
||||
assert(self.reagent_id_to_count[id] == nil)
|
||||
end
|
||||
|
||||
self.reagent_id_to_count[id] = count
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param level number
|
||||
---@return self
|
||||
function Craft:SetMinCharacterLevel(level)
|
||||
if lib.env.is_debug then
|
||||
assert(type(level) == "number" and level > 0)
|
||||
end
|
||||
self.character_level = level
|
||||
return self
|
||||
end
|
||||
|
||||
---@generic K
|
||||
---@param t table<K, table<number, boolean>>
|
||||
---@param key K
|
||||
---@return table<number, boolean>
|
||||
local function get_or_create_set(t, key)
|
||||
local set = t[key]
|
||||
if set == nil then
|
||||
set = {}
|
||||
t[key] = set
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
function Craft:Save()
|
||||
if lib.env.is_debug then
|
||||
assert(next(self.reagent_id_to_count) ~= nil)
|
||||
end
|
||||
|
||||
self.was_enriched = false
|
||||
if lib.env.is_super_wow_loaded then
|
||||
enrich(self)
|
||||
end
|
||||
|
||||
local old_craft = lib.spell_id_to_craft[self.spell_id]
|
||||
if old_craft ~= nil then
|
||||
for reagent_id, _ in pairs(old_craft.reagent_id_to_count) do
|
||||
lib.reagent_id_to_spell_ids_set[reagent_id][self.spell_id] = nil
|
||||
end
|
||||
|
||||
for _, recipe in ipairs(old_craft.recipes) do
|
||||
lib.recipe_id_to_spell_ids_set[recipe.id][self.spell_id] = nil
|
||||
end
|
||||
|
||||
lib.localized_profession_name_to_spell_ids_set[old_craft.localized_profession_name][self.spell_id] = nil
|
||||
end
|
||||
|
||||
lib.spell_id_to_craft[self.spell_id] = self
|
||||
|
||||
for reagent_id, _ in pairs(self.reagent_id_to_count) do
|
||||
get_or_create_set(lib.reagent_id_to_spell_ids_set, reagent_id)[self.spell_id] = true
|
||||
end
|
||||
|
||||
for _, recipe in ipairs(self.recipes) do
|
||||
get_or_create_set(lib.recipe_id_to_spell_ids_set, recipe.id)[self.spell_id] = true
|
||||
end
|
||||
|
||||
get_or_create_set(lib.localized_profession_name_to_spell_ids_set, self.localized_profession_name)[self.spell_id] = true
|
||||
end
|
||||
|
||||
---@return LibCrafts
|
||||
function LibCraftsGetLibrary()
|
||||
return lib
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<Script file="LibStub.lua" />
|
||||
<Script file="LibCrafts-1.0.lua" />
|
||||
<Include file="Locales\Locales.xml"/>
|
||||
<Include file="Professions\Professions.xml"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,33 @@
|
||||
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
|
||||
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
||||
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
|
||||
local _G = getfenv()
|
||||
local strfind, strfmt = string.find, string.format
|
||||
local LibStub = _G[LIBSTUB_MAJOR]
|
||||
|
||||
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
||||
LibStub = LibStub or { libs = {}, minors = {} }
|
||||
_G[LIBSTUB_MAJOR] = LibStub
|
||||
LibStub.minor = LIBSTUB_MINOR
|
||||
|
||||
function LibStub:NewLibrary(major, minor)
|
||||
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
||||
local _,_,num = strfind(minor, "(%d+)")
|
||||
minor = assert(tonumber(num), "Minor version must either be a number or contain a number.")
|
||||
|
||||
local oldminor = self.minors[major]
|
||||
if oldminor and oldminor >= minor then return nil end
|
||||
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
||||
return self.libs[major], oldminor
|
||||
end
|
||||
|
||||
function LibStub:GetLibrary(major, silent)
|
||||
if not self.libs[major] and not silent then
|
||||
error(strfmt("Cannot find a library instance of %q.", tostring(major)), 2)
|
||||
end
|
||||
return self.libs[major], self.minors[major]
|
||||
end
|
||||
|
||||
function LibStub:IterateLibraries() return pairs(self.libs) end
|
||||
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<Script file="deDE-Professions.lua" />
|
||||
<Script file="deDE-Spells.lua" />
|
||||
<Script file="enUS-Professions.lua" />
|
||||
<Script file="enUS-Spells.lua" />
|
||||
<Script file="esES-Professions.lua" />
|
||||
<Script file="esES-Spells.lua" />
|
||||
<Script file="esMX-Professions.lua" />
|
||||
<Script file="esMX-Spells.lua" />
|
||||
<Script file="frFR-Professions.lua" />
|
||||
<Script file="frFR-Spells.lua" />
|
||||
<Script file="koKR-Professions.lua" />
|
||||
<Script file="koKR-Spells.lua" />
|
||||
<Script file="ptBR-Professions.lua" />
|
||||
<Script file="ptBR-Spells.lua" />
|
||||
<Script file="ruRU-Professions.lua" />
|
||||
<Script file="ruRU-Spells.lua" />
|
||||
<Script file="zhCN-Professions.lua" />
|
||||
<Script file="zhCN-Spells.lua" />
|
||||
<Script file="zhTW-Professions.lua" />
|
||||
<Script file="zhTW-Spells.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "deDE", 5
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
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 lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "Verkleiden"
|
||||
L["Jewelcrafting"] = "Juwelenschleifen"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "enUS", 5
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
L["Alchemy"] = true
|
||||
L["Blacksmithing"] = true
|
||||
L["Cooking"] = true
|
||||
L["Enchanting"] = true
|
||||
L["Engineering"] = true
|
||||
L["First Aid"] = true
|
||||
L["Leatherworking"] = true
|
||||
L["Mining"] = true
|
||||
L["Poisons"] = true
|
||||
L["Tailoring"] = true
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
L["Disguise"] = true
|
||||
L["Jewelcrafting"] = true
|
||||
L["Survival"] = true
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "esES", 7
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
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 lib.env.is_turtle_wow then
|
||||
L["Blacksmithing"] = "Ferraria"
|
||||
L["Cooking"] = "Culinária"
|
||||
L["Disguise"] = "Disfarce"
|
||||
L["Enchanting"] = "Encantamento"
|
||||
L["Engineering"] = "Engenharia"
|
||||
L["First Aid"] = "Primeiros Socorros"
|
||||
L["Jewelcrafting"] = "Joalheria"
|
||||
L["Leatherworking"] = "Couraria"
|
||||
L["Mining"] = "Mineração"
|
||||
L["Tailoring"] = "Alfaiataria"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "esMX", 4
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
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 lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "Disfarce"
|
||||
L["Jewelcrafting"] = "Joalheria"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "frFR", 5
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
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 lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "Déguisement"
|
||||
L["Jewelcrafting"] = "Joaillerie"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "koKR", 5
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
L["Alchemy"] = "연금술"
|
||||
L["Blacksmithing"] = "대장기술"
|
||||
L["Cooking"] = "요리"
|
||||
L["Enchanting"] = "마법부여"
|
||||
L["Engineering"] = "기계공학"
|
||||
L["First Aid"] = "응급치료"
|
||||
L["Leatherworking"] = "가죽세공"
|
||||
L["Mining"] = "채광"
|
||||
L["Poisons"] = "독 조제"
|
||||
L["Tailoring"] = "재봉술"
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "변장술"
|
||||
L["Jewelcrafting"] = "보석세공"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "ptBR", 7
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
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 lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "Disfarce"
|
||||
L["Jewelcrafting"] = "Joalheria"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "ruRU", 4
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
L["Alchemy"] = "Алхимия"
|
||||
L["Blacksmithing"] = "Кузнечное дело"
|
||||
L["Cooking"] = "Кулинария"
|
||||
L["Enchanting"] = "Наложение чар"
|
||||
L["Engineering"] = "Инженерное дело"
|
||||
L["First Aid"] = "Первая помощь"
|
||||
L["Leatherworking"] = "Кожевничество"
|
||||
L["Mining"] = "Горное дело"
|
||||
L["Poisons"] = "Яды"
|
||||
L["Tailoring"] = "Портняжное дело"
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "Маскировка"
|
||||
L["Jewelcrafting"] = "Ювелирное дело"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "zhCN", 4
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
L["Alchemy"] = "炼金术"
|
||||
L["Blacksmithing"] = "锻造"
|
||||
L["Cooking"] = "烹饪"
|
||||
L["Enchanting"] = "附魔"
|
||||
L["Engineering"] = "工程学"
|
||||
L["First Aid"] = "急救"
|
||||
L["Leatherworking"] = "制皮"
|
||||
L["Mining"] = "采矿"
|
||||
L["Poisons"] = "毒药"
|
||||
L["Tailoring"] = "裁缝"
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "伪装术"
|
||||
L["Jewelcrafting"] = "珠宝加工"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, locale, version = "Locales-Professions", "zhTW", 4
|
||||
local module = --[[---@type LcLocaleModule]] lib:RegisterLocaleModule(name, locale, version)
|
||||
if not module then return end
|
||||
|
||||
local L = module:GetStrings()
|
||||
L["Alchemy"] = "煉金術"
|
||||
L["Blacksmithing"] = "鍛造"
|
||||
L["Cooking"] = "烹飪"
|
||||
L["Enchanting"] = "附魔"
|
||||
L["Engineering"] = "工程學"
|
||||
L["First Aid"] = "急救"
|
||||
L["Leatherworking"] = "製皮"
|
||||
L["Mining"] = "採礦"
|
||||
L["Poisons"] = "毒藥"
|
||||
L["Tailoring"] = "裁縫"
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
L["Disguise"] = "偽裝術"
|
||||
L["Jewelcrafting"] = "珠寶設計"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,657 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, version = "Professions-Cooking", 4
|
||||
local module = --[[---@type LcProfessionModule]] lib:RegisterProfessionModule(name, version, "Cooking")
|
||||
if not module then return end
|
||||
|
||||
local Quality = lib.constants.qualities
|
||||
local SpellSource = lib.constants.spell_sources
|
||||
local RecipeSource = lib.constants.recipe_sources
|
||||
|
||||
module:NewCraft(2538, "Charred Wolf Meat", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetResult(2679)
|
||||
:AddReagent(2672, 1) -- Stringy Wolf Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2539, "Spiced Wolf Meat", 10, {SpellSource.Trainer})
|
||||
:SetResult(2680)
|
||||
:AddReagent(2672, 1) -- Stringy Wolf Meat
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2540, "Roasted Boar Meat", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetResult(2681)
|
||||
:AddReagent(769, 1) -- Chunk of Boar Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2541, "Coyote Steak", 50, {SpellSource.Trainer})
|
||||
:SetResult(2684)
|
||||
:AddReagent(2673, 1) -- Coyote Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2542, "Goretusk Liver Pie", 50, {})
|
||||
:SetResult(724)
|
||||
:AddRecipe(2697, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(723, 1) -- Goretusk Liver
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2543, "Westfall Stew", 75, {})
|
||||
:SetResult(733)
|
||||
:AddRecipe(728, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(729, 1) -- Stringy Vulture Meat
|
||||
:AddReagent(730, 1) -- Murloc Eye
|
||||
:AddReagent(731, 1) -- Goretusk Snout
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2544, "Crab Cake", 75, {SpellSource.Trainer})
|
||||
:SetResult(2683)
|
||||
:AddReagent(2674, 1) -- Crawler Meat
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2545, "Cooked Crab Claw", 85, {})
|
||||
:SetResult(2682)
|
||||
:AddRecipe(2698, Quality.Common, {RecipeSource.Drop, RecipeSource.Vendor})
|
||||
:AddReagent(2675, 1) -- Crawler Claw
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2546, "Dry Pork Ribs", 80, {SpellSource.Trainer})
|
||||
:SetResult(2687)
|
||||
:AddReagent(2677, 1) -- Boar Ribs
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2547, "Redridge Goulash", 100, {})
|
||||
:SetResult(1082)
|
||||
:AddRecipe(2699, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(1080, 1) -- Tough Condor Meat
|
||||
:AddReagent(1081, 1) -- Crisp Spider Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2548, "Succulent Pork Ribs", 110, {})
|
||||
:SetResult(2685)
|
||||
:AddRecipe(2700, Quality.Common, {RecipeSource.Drop, RecipeSource.Vendor})
|
||||
:AddReagent(2677, 2) -- Boar Ribs
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2549, "Seasoned Wolf Kabob", 100, {})
|
||||
:SetResult(1017)
|
||||
:AddRecipe(2701, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(1015, 2) -- Lean Wolf Flank
|
||||
:AddReagent(2665, 1) -- Stormwind Seasoning Herbs
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2795, "Beer Basted Boar Ribs", 25, {})
|
||||
:SetResult(2888)
|
||||
:AddRecipe(2889, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2886, 1) -- Crag Boar Rib
|
||||
:AddReagent(2894, 1) -- Rhapsody Malt
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3370, "Crocolisk Steak", 80, {})
|
||||
:SetResult(3662)
|
||||
:AddRecipe(3678, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(2924, 1) -- Crocolisk Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3371, "Blood Sausage", 60, {})
|
||||
:SetResult(3220)
|
||||
:AddRecipe(3679, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(3172, 1) -- Boar Intestines
|
||||
:AddReagent(3173, 1) -- Bear Meat
|
||||
:AddReagent(3174, 1) -- Spider Ichor
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3372, "Murloc Fin Soup", 90, {})
|
||||
:SetResult(3663)
|
||||
:AddRecipe(3680, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(1468, 2) -- Murloc Fin
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3373, "Crocolisk Gumbo", 120, {})
|
||||
:SetResult(3664)
|
||||
:AddRecipe(3681, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3667, 1) -- Tender Crocolisk Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3376, "Curiously Tasty Omelet", 130, {})
|
||||
:SetResult(3665)
|
||||
:AddRecipe(3682, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3685, 1) -- Raptor Egg
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3377, "Gooey Spider Cake", 110, {})
|
||||
:SetResult(3666)
|
||||
:AddRecipe(3683, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2251, 2) -- Gooey Spider Leg
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3397, "Big Bear Steak", 110, {})
|
||||
:SetResult(3726)
|
||||
:AddRecipe(3734, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3730, 1) -- Big Bear Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3398, "Hot Lion Chops", 125, {})
|
||||
:SetResult(3727)
|
||||
:AddRecipe(3735, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3731, 1) -- Lion Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3399, "Tasty Lion Steak", 150, {})
|
||||
:SetResult(3728)
|
||||
:AddRecipe(3736, Quality.Common, {RecipeSource.Quest})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(3731, 2) -- Lion Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3400, "Soothing Turtle Bisque", 175, {})
|
||||
:SetResult(3729)
|
||||
:AddRecipe(3737, Quality.Common, {RecipeSource.Quest})
|
||||
:AddReagent(3712, 1) -- Turtle Meat
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(4094, "Barbecued Buzzard Wing", 175, {SpellSource.Trainer})
|
||||
:SetResult(4457)
|
||||
:AddRecipe(4609, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3404, 1) -- Buzzard Wing
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6412, "Kaldorei Spider Kabob", 10, {})
|
||||
:SetResult(5472)
|
||||
:AddRecipe(5482, Quality.Common, {RecipeSource.Quest})
|
||||
:AddReagent(5465, 1) -- Small Spider Leg
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6413, "Scorpid Surprise", 20, {})
|
||||
:SetResult(5473)
|
||||
:AddRecipe(5483, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(5466, 1) -- Scorpid Stinger
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6414, "Roasted Kodo Meat", 35, {})
|
||||
:SetResult(5474)
|
||||
:AddRecipe(5484, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(5467, 1) -- Kodo Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6415, "Fillet of Frenzy", 50, {})
|
||||
:SetResult(5476)
|
||||
:AddRecipe(5485, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(5468, 1) -- Soft Frenzy Flesh
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6416, "Strider Stew", 50, {})
|
||||
:SetResult(5477)
|
||||
:AddRecipe(5486, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(4536, 1) -- Shiny Red Apple
|
||||
:AddReagent(5469, 1) -- Strider Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6417, "Dig Rat Stew", 90, {})
|
||||
:SetResult(5478)
|
||||
:AddRecipe(5487, Quality.Common, {RecipeSource.Quest})
|
||||
:AddReagent(5051, 1) -- Dig Rat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6418, "Crispy Lizard Tail", 100, {})
|
||||
:SetResult(5479)
|
||||
:AddRecipe(5488, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(5470, 1) -- Thunder Lizard Tail
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6419, "Lean Venison", 110, {})
|
||||
:SetResult(5480)
|
||||
:AddRecipe(5489, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2678, 4) -- Mild Spices
|
||||
:AddReagent(5471, 1) -- Stag Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6499, "Boiled Clams", 50, {SpellSource.Trainer})
|
||||
:SetResult(5525)
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(5503, 1) -- Clam Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6500, "Goblin Deviled Clams", 125, {SpellSource.Trainer})
|
||||
:SetResult(5527)
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(5504, 1) -- Tangy Clam Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6501, "Clam Chowder", 90, {})
|
||||
:SetResult(5526)
|
||||
:AddRecipe(5528, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(1179, 1) -- Ice Cold Milk
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(5503, 1) -- Clam Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7213, "Giant Clam Scorcho", 175, {})
|
||||
:SetResult(6038)
|
||||
:AddRecipe(6039, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(4655, 1) -- Giant Clam Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7751, "Brilliant Smallfish", 1, {})
|
||||
:SetResult(6290)
|
||||
:AddRecipe(6325, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6291, 1) -- Raw Brilliant Smallfish
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7752, "Slitherskin Mackerel", 1, {})
|
||||
:SetResult(787)
|
||||
:AddRecipe(6326, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6303, 1) -- Raw Slitherskin Mackerel
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7753, "Longjaw Mud Snapper", 50, {})
|
||||
:SetResult(4592)
|
||||
:AddRecipe(6328, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6289, 1) -- Raw Longjaw Mud Snapper
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7754, "Loch Frenzy Delight", 50, {})
|
||||
:SetResult(6316)
|
||||
:AddRecipe(6329, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(6317, 1) -- Raw Loch Frenzy
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7755, "Bristle Whisker Catfish", 100, {})
|
||||
:SetResult(4593)
|
||||
:AddRecipe(6330, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6308, 1) -- Raw Bristle Whisker Catfish
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7827, "Rainbow Fin Albacore", 50, {})
|
||||
:SetResult(5095)
|
||||
:AddRecipe(6368, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6361, 1) -- Raw Rainbow Fin Albacore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7828, "Rockscale Cod", 175, {})
|
||||
:SetResult(4594)
|
||||
:AddRecipe(6369, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6362, 1) -- Raw Rockscale Cod
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8238, "Savory Deviate Delight", 85, {})
|
||||
:SetResult(6657)
|
||||
:AddRecipe(6661, Quality.Uncommon, {RecipeSource.Drop})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(6522, 1) -- Deviate Fish
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8604, "Herb Baked Egg", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetResult(6888)
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(6889, 1) -- Small Egg
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8607, "Smoked Bear Meat", 40, {})
|
||||
:SetResult(6890)
|
||||
:AddRecipe(6892, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3173, 1) -- Bear Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(9513, "Thistle Tea", 60, {})
|
||||
:SetResult(7676)
|
||||
:AddRecipe(7678, Quality.Common, {RecipeSource.Quest})
|
||||
:AddRecipe(18160, Quality.Uncommon, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(2452, 1) -- Swiftthistle
|
||||
:Save()
|
||||
|
||||
module:NewCraft(13028, "Goldthorn Tea", 175, {SpellSource.Trainer})
|
||||
:SetResult(10841)
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(3821, 1) -- Goldthorn
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15853, "Lean Wolf Steak", 125, {})
|
||||
:SetResult(12209)
|
||||
:AddRecipe(12227, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(1015, 1) -- Lean Wolf Flank
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15855, "Roast Raptor", 175, {})
|
||||
:SetResult(12210)
|
||||
:AddRecipe(12228, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(12184, 1) -- Raptor Flesh
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15856, "Hot Wolf Ribs", 175, {})
|
||||
:SetResult(13851)
|
||||
:AddRecipe(12229, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(12203, 1) -- Red Wolf Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15861, "Jungle Stew", 175, {})
|
||||
:SetResult(12212)
|
||||
:AddRecipe(12231, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(4536, 2) -- Shiny Red Apple
|
||||
:AddReagent(12202, 1) -- Tiger Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15863, "Carrion Surprise", 175, {})
|
||||
:SetResult(12213)
|
||||
:AddRecipe(12232, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(12037, 1) -- Mystery Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15865, "Mystery Stew", 175, {})
|
||||
:SetResult(12214)
|
||||
:AddRecipe(12233, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2596, 1) -- Skin of Dwarven Stout
|
||||
:AddReagent(12037, 1) -- Mystery Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15906, "Dragonbreath Chili", 200, {})
|
||||
:SetResult(12217)
|
||||
:AddRecipe(12239, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(4402, 1) -- Small Flame Sac
|
||||
:AddReagent(12037, 1) -- Mystery Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15910, "Heavy Kodo Stew", 200, {})
|
||||
:SetResult(12215)
|
||||
:AddRecipe(12240, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(12204, 2) -- Heavy Kodo Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15915, "Spiced Chili Crab", 225, {})
|
||||
:SetResult(12216)
|
||||
:AddRecipe(16111, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 2) -- Hot Spices
|
||||
:AddReagent(12206, 1) -- Tender Crab Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15933, "Monster Omelet", 225, {})
|
||||
:SetResult(12218)
|
||||
:AddRecipe(16110, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3713, 2) -- Soothing Spices
|
||||
:AddReagent(12207, 1) -- Giant Egg
|
||||
:Save()
|
||||
|
||||
module:NewCraft(15935, "Crispy Bat Wing", 1, {})
|
||||
:SetResult(12224)
|
||||
:AddRecipe(12226, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(12223, 1) -- Meaty Bat Wing
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18238, "Spotted Yellowtail", 225, {})
|
||||
:SetResult(6887)
|
||||
:AddRecipe(13939, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(4603, 1) -- Raw Spotted Yellowtail
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18239, "Cooked Glossy Mightfish", 225, {})
|
||||
:SetResult(13927)
|
||||
:AddRecipe(13940, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13754, 1) -- Raw Glossy Mightfish
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18240, "Grilled Squid", 240, {})
|
||||
:SetResult(13928)
|
||||
:AddRecipe(13942, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13755, 1) -- Winter Squid
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18241, "Filet of Redgill", 225, {})
|
||||
:SetResult(13930)
|
||||
:AddRecipe(13941, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(13758, 1) -- Raw Redgill
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18242, "Hot Smoked Bass", 240, {})
|
||||
:SetResult(13929)
|
||||
:AddRecipe(13943, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 2) -- Hot Spices
|
||||
:AddReagent(13756, 1) -- Raw Summer Bass
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18243, "Nightfin Soup", 250, {})
|
||||
:SetResult(13931)
|
||||
:AddRecipe(13945, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(13759, 1) -- Raw Nightfin Snapper
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18244, "Poached Sunscale Salmon", 250, {})
|
||||
:SetResult(13932)
|
||||
:AddRecipe(13946, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(13760, 1) -- Raw Sunscale Salmon
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18245, "Lobster Stew", 275, {})
|
||||
:SetResult(13933)
|
||||
:AddRecipe(13947, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(13888, 1) -- Darkclaw Lobster
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18246, "Mightfish Steak", 275, {})
|
||||
:SetResult(13934)
|
||||
:AddRecipe(13948, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13893, 1) -- Large Raw Mightfish
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18247, "Baked Salmon", 275, {})
|
||||
:SetResult(13935)
|
||||
:AddRecipe(13949, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13889, 1) -- Raw Whitescale Salmon
|
||||
:Save()
|
||||
|
||||
module:NewCraft(20626, "Undermine Clam Chowder", 225, {})
|
||||
:SetResult(16766)
|
||||
:AddRecipe(16767, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(1179, 1) -- Ice Cold Milk
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(7974, 2) -- Zesty Clam Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(20916, "Mithril Headed Trout", 175, {})
|
||||
:SetResult(8364)
|
||||
:AddRecipe(17062, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(8365, 1) -- Raw Mithril Head Trout
|
||||
:Save()
|
||||
|
||||
module:NewCraft(21143, "Gingerbread Cookie", 1, {})
|
||||
:SetResult(17197)
|
||||
:AddRecipe(17200, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(6889, 1) -- Small Egg
|
||||
:AddReagent(17194, 1) -- Holiday Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(21144, "Egg Nog", 35, {})
|
||||
:SetResult(17198)
|
||||
:AddRecipe(17201, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(1179, 1) -- Ice Cold Milk
|
||||
:AddReagent(6889, 1) -- Small Egg
|
||||
:AddReagent(17194, 1) -- Holiday Spices
|
||||
:AddReagent(17196, 1) -- Holiday Spirits
|
||||
:Save()
|
||||
|
||||
module:NewCraft(21175, "Spider Sausage", 200, {SpellSource.Trainer})
|
||||
:SetResult(17222)
|
||||
:AddReagent(12205, 2) -- White Spider Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(22480, "Tender Wolf Steak", 225, {})
|
||||
:SetResult(18045)
|
||||
:AddRecipe(18046, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(12208, 1) -- Tender Wolf Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(22761, "Runn Tum Tuber Surprise", 275, {})
|
||||
:SetResult(18254)
|
||||
:AddRecipe(18267, Quality.Uncommon, {RecipeSource.Drop})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(18255, 1) -- Runn Tum Tuber
|
||||
:Save()
|
||||
|
||||
module:NewCraft(24418, "Heavy Crocolisk Stew", 150, {})
|
||||
:SetResult(20074)
|
||||
:AddRecipe(20075, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(3667, 2) -- Tender Crocolisk Meat
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:Save()
|
||||
|
||||
module:NewCraft(24801, "Smoked Desert Dumplings", 285, {SpellSource.Quest})
|
||||
:SetResult(20452)
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(20424, 1) -- Sandworm Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(25659, "Dirge's Kickin' Chimaerok Chops", 300, {})
|
||||
:SetResult(21023)
|
||||
:AddRecipe(21025, Quality.Epic, {RecipeSource.Quest})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(8150, 1) -- Deeprock Salt
|
||||
:AddReagent(9061, 1) -- Goblin Rocket Fuel
|
||||
:AddReagent(21024, 1) -- Chimaerok Tenderloin
|
||||
:Save()
|
||||
|
||||
module:NewCraft(25704, "Smoked Sagefish", 80, {})
|
||||
:SetResult(21072)
|
||||
:AddRecipe(21099, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2678, 1) -- Mild Spices
|
||||
:AddReagent(21071, 1) -- Raw Sagefish
|
||||
:Save()
|
||||
|
||||
module:NewCraft(25954, "Sagefish Delight", 175, {})
|
||||
:SetResult(21217)
|
||||
:AddRecipe(21219, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(21153, 1) -- Raw Greater Sagefish
|
||||
:Save()
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
module:NewCraft(4094, "Barbecued Buzzard Wing", 175, {})
|
||||
:SetResult(4457)
|
||||
:AddRecipe(4609, Quality.Common, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3404, 1) -- Buzzard Wing
|
||||
:Save()
|
||||
|
||||
module:NewCraft(9513, "Thistle Tea", 60, {})
|
||||
:SetResult(7676)
|
||||
:AddRecipe(18160, Quality.Uncommon, {RecipeSource.Quest, RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(2452, 1) -- Swiftthistle
|
||||
:Save()
|
||||
|
||||
module:NewCraft(45054, "Maritime Gumbo", 35, {})
|
||||
:SetResult(30818)
|
||||
:AddRecipe(30819, Quality.Common, {RecipeSource.Quest})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(2674, 1) -- Crawler Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(45625, "Le Fishe Au Chocolat", 300, {})
|
||||
:SetResult(84040)
|
||||
:AddRecipe(61666, Quality.Epic, {RecipeSource.Quest})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13464, 1) -- Golden Sansam
|
||||
:AddReagent(13889, 1) -- Raw Whitescale Salmon
|
||||
:AddReagent(61173, 1) -- Premium Chocolate
|
||||
:Save()
|
||||
|
||||
module:NewCraft(45627, "Gilneas Hot Stew", 200, {})
|
||||
:SetResult(84041)
|
||||
:AddRecipe(61676, Quality.Uncommon, {RecipeSource.Vendor})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(12203, 1) -- Red Wolf Meat
|
||||
:AddReagent(12205, 1) -- White Spider Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(46085, "Gurubashi Gumbo", 300, {})
|
||||
:SetResult(53015)
|
||||
:AddRecipe(53016, Quality.Rare, {RecipeSource.Quest})
|
||||
:AddReagent(159, 1) -- Refreshing Spring Water
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(3667, 1) -- Tender Crocolisk Meat
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(12037, 2) -- Mystery Meat
|
||||
:AddReagent(12202, 1) -- Tiger Meat
|
||||
:Save()
|
||||
|
||||
module:NewCraft(49551, "Empowering Herbal Salad", 300, {})
|
||||
:SetResult(83309)
|
||||
:AddRecipe(92045, Quality.Rare, {RecipeSource.Quest})
|
||||
:AddReagent(8838, 1) -- Sungrass
|
||||
:AddReagent(22529, 1) -- Savage Frond
|
||||
:AddReagent(51714, 2) -- Sweet Mountain Berry
|
||||
:Save()
|
||||
|
||||
module:NewCraft(57047, "Danonzo's Tel'Abim Surprise", 300, {})
|
||||
:SetResult(60976)
|
||||
:AddRecipe(60979, Quality.Rare, {RecipeSource.Quest})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(10286, 1) -- Heart of the Wild
|
||||
:AddReagent(60955, 1) -- Gargantuan Tel'Abim Banana
|
||||
:Save()
|
||||
|
||||
module:NewCraft(57049, "Danonzo's Tel'Abim Delight", 300, {})
|
||||
:SetResult(60977)
|
||||
:AddRecipe(60980, Quality.Rare, {RecipeSource.Quest})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13467, 1) -- Icecap
|
||||
:AddReagent(60955, 1) -- Gargantuan Tel'Abim Banana
|
||||
:Save()
|
||||
|
||||
module:NewCraft(57051, "Danonzo's Tel'Abim Medley", 300, {})
|
||||
:SetResult(60978)
|
||||
:AddRecipe(60981, Quality.Rare, {RecipeSource.Quest})
|
||||
:AddReagent(3713, 1) -- Soothing Spices
|
||||
:AddReagent(13464, 2) -- Golden Sansam
|
||||
:AddReagent(60955, 1) -- Gargantuan Tel'Abim Banana
|
||||
:Save()
|
||||
|
||||
module:NewCraft(58044, "Ambersap Glazed Boar Ribs", 175, {})
|
||||
:SetResult(41674)
|
||||
:AddRecipe(19670, Quality.Uncommon, {RecipeSource.Vendor})
|
||||
:AddReagent(2677, 1) -- Boar Ribs
|
||||
:AddReagent(2692, 1) -- Hot Spices
|
||||
:AddReagent(41675, 1) -- Ambersap
|
||||
:Save()
|
||||
|
||||
module:NewCraft(58046, "Crawford Apple Tarte", 175, {})
|
||||
:SetResult(41673)
|
||||
:AddRecipe(19671, Quality.Uncommon, {}) -- TODO add source
|
||||
:AddReagent(1179, 1) -- Ice Cold Milk
|
||||
:AddReagent(4539, 1) -- Goldenbark Apple
|
||||
:AddReagent(41677, 1) -- Northwind Flour
|
||||
:Save()
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, version = "Professions-FirstAid", 4
|
||||
local module = --[[---@type LcProfessionModule]] lib:RegisterProfessionModule(name, version, "First Aid")
|
||||
if not module then return end
|
||||
|
||||
local Quality = lib.constants.qualities
|
||||
local SpellSource = lib.constants.spell_sources
|
||||
local RecipeSource = lib.constants.recipe_sources
|
||||
|
||||
module:NewCraft(3275, "Linen Bandage", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetResult(1251)
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3276, "Heavy Linen Bandage", 40, {SpellSource.Trainer})
|
||||
:SetResult(2581)
|
||||
:AddReagent(2589, 2) -- Linen Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3277, "Wool Bandage", 80, {SpellSource.Trainer})
|
||||
:SetResult(3530)
|
||||
:AddReagent(2592, 1) -- Wool Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3278, "Heavy Wool Bandage", 115, {SpellSource.Trainer})
|
||||
:SetResult(3531)
|
||||
:AddReagent(2592, 2) -- Wool Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7928, "Silk Bandage", 150, {SpellSource.Trainer})
|
||||
:SetResult(6450)
|
||||
:AddReagent(4306, 1) -- Silk Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7929, "Heavy Silk Bandage", 180, {})
|
||||
:SetResult(6451)
|
||||
:AddRecipe(16112, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(4306, 2) -- Silk Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7934, "Anti-Venom", 80, {SpellSource.Trainer})
|
||||
:SetResult(6452)
|
||||
:AddReagent(1475, 1) -- Small Venom Sac
|
||||
:Save()
|
||||
|
||||
module:NewCraft(7935, "Strong Anti-Venom", 130, {})
|
||||
:SetResult(6453)
|
||||
:AddRecipe(6454, Quality.Uncommon, {RecipeSource.Chest, RecipeSource.Drop})
|
||||
:AddReagent(1288, 1) -- Large Venom Sac
|
||||
:Save()
|
||||
|
||||
module:NewCraft(10840, "Mageweave Bandage", 210, {})
|
||||
:SetResult(8544)
|
||||
:AddRecipe(16113, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(4338, 1) -- Mageweave Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(10841, "Heavy Mageweave Bandage", 240, {SpellSource.Trainer})
|
||||
:SetResult(8545)
|
||||
:AddReagent(4338, 2) -- Mageweave Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18629, "Runecloth Bandage", 260, {SpellSource.Trainer})
|
||||
:SetResult(14529)
|
||||
:AddReagent(14047, 1) -- Runecloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(18630, "Heavy Runecloth Bandage", 290, {SpellSource.Trainer})
|
||||
:SetResult(14530)
|
||||
:AddReagent(14047, 2) -- Runecloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(23787, "Powerful Anti-Venom", 300, {})
|
||||
:SetResult(19440)
|
||||
:AddRecipe(19442, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(19441, 1) -- Huge Venom Sac
|
||||
:Save()
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
module:NewCraft(10844, "Powerful Smelling Salts", 250, {})
|
||||
:SetResult(8546)
|
||||
:AddRecipe(8547, Quality.Rare, {RecipeSource.Drop})
|
||||
:AddReagent(7078, 2) -- Essence of Fire
|
||||
:AddReagent(8150, 4) -- Deeprock Salt
|
||||
:AddReagent(18512, 1) -- Larval Acid
|
||||
:Save()
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, version = "Professions-Mining", 2
|
||||
local module = --[[---@type LcProfessionModule]] lib:RegisterProfessionModule(name, version, "Mining")
|
||||
if not module then return end
|
||||
|
||||
local Quality = lib.constants.qualities
|
||||
local SpellSource = lib.constants.spell_sources
|
||||
local RecipeSource = lib.constants.recipe_sources
|
||||
|
||||
module:NewCraft(2657, "Smelt Copper", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetResult(2840)
|
||||
:AddReagent(2770, 1) -- Copper Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2658, "Smelt Silver", 75, {SpellSource.Trainer})
|
||||
:SetResult(2842)
|
||||
:AddReagent(2775, 1) -- Silver Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2659, "Smelt Bronze", 65, {SpellSource.Trainer})
|
||||
:SetResult(2841)
|
||||
:AddReagent(2840, 1) -- Copper Bar
|
||||
:AddReagent(3576, 1) -- Tin Bar
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3304, "Smelt Tin", 65, {SpellSource.Trainer})
|
||||
:SetResult(3576)
|
||||
:AddReagent(2771, 1) -- Tin Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3307, "Smelt Iron", 125, {SpellSource.Trainer})
|
||||
:SetResult(3575)
|
||||
:AddReagent(2772, 1) -- Iron Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3308, "Smelt Gold", 155, {SpellSource.Trainer})
|
||||
:SetResult(3577)
|
||||
:AddReagent(2776, 1) -- Gold Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3569, "Smelt Steel", 165, {SpellSource.Trainer})
|
||||
:SetResult(3859)
|
||||
:AddReagent(3575, 1) -- Iron Bar
|
||||
:AddReagent(3857, 1) -- Coal
|
||||
:Save()
|
||||
|
||||
module:NewCraft(10097, "Smelt Mithril", 175, {SpellSource.Trainer})
|
||||
:SetResult(3860)
|
||||
:AddReagent(3858, 1) -- Mithril Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(10098, "Smelt Truesilver", 230, {SpellSource.Trainer})
|
||||
:SetResult(6037)
|
||||
:AddReagent(7911, 1) -- Truesilver Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(14891, "Smelt Dark Iron", 230, {SpellSource.Quest})
|
||||
:SetResult(11371)
|
||||
:AddReagent(11370, 8) -- Dark Iron Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(16153, "Smelt Thorium", 250, {SpellSource.Trainer})
|
||||
:SetResult(12359)
|
||||
:AddReagent(10620, 1) -- Thorium Ore
|
||||
:Save()
|
||||
|
||||
module:NewCraft(22967, "Smelt Elementium", 300, {SpellSource.Trainer})
|
||||
:SetResult(17771)
|
||||
:AddReagent(12360, 10) -- Arcanite Bar
|
||||
:AddReagent(17010, 1) -- Fiery Core
|
||||
:AddReagent(18562, 1) -- Elementium Ore
|
||||
:AddReagent(18567, 3) -- Elemental Flux
|
||||
:Save()
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
module:NewCraft(45451, "Smelt Dreamsteel", 300, {})
|
||||
:SetResult(61216)
|
||||
:AddRecipe(61226, Quality.Uncommon, {RecipeSource.Quest})
|
||||
:AddReagent(3859, 1) -- Steel Bar
|
||||
:AddReagent(20381, 1) -- Dreamscale
|
||||
:AddReagent(61198, 1) -- Small Dream Shard
|
||||
:Save()
|
||||
end
|
||||
@@ -0,0 +1,178 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, version = "Professions-Poisons", 4
|
||||
local module = --[[---@type LcProfessionModule]] lib:RegisterProfessionModule(name, version, "Poisons")
|
||||
if not module then return end
|
||||
|
||||
local Quality = lib.constants.qualities
|
||||
local SpellSource = lib.constants.spell_sources
|
||||
local RecipeSource = lib.constants.recipe_sources
|
||||
|
||||
module:NewCraft(2835, "Deadly Poison", 130, {SpellSource.Trainer})
|
||||
:SetResult(2892)
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:AddReagent(5173, 1) -- Deathweed
|
||||
:Save()
|
||||
|
||||
module:NewCraft(2837, "Deadly Poison II", 170, {SpellSource.Trainer})
|
||||
:SetResult(2893)
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:AddReagent(5173, 2) -- Deathweed
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3420, "Crippling Poison", 1, {SpellSource.Trainer})
|
||||
:SetResult(3775)
|
||||
:AddReagent(2930, 1) -- Essence of Pain
|
||||
:AddReagent(3371, 1) -- Empty Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(3421, "Crippling Poison II", 230, {SpellSource.Trainer})
|
||||
:SetResult(3776)
|
||||
:AddReagent(8923, 3) -- Essence of Agony
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5763, "Mind-numbing Poison", 100, {SpellSource.Trainer})
|
||||
:SetResult(5237)
|
||||
:AddReagent(2928, 1) -- Dust of Decay
|
||||
:AddReagent(2930, 1) -- Essence of Pain
|
||||
:AddReagent(3371, 1) -- Empty Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(6510, "Blinding Powder", 150, {SpellSource.Trainer})
|
||||
:SetResult(5530)
|
||||
:AddReagent(3818, 1) -- Fadeleaf
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8681, "Instant Poison", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetResult(6947)
|
||||
:AddReagent(2928, 1) -- Dust of Decay
|
||||
:AddReagent(3371, 1) -- Empty Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8687, "Instant Poison II", 120, {SpellSource.Trainer})
|
||||
:SetResult(6949)
|
||||
:AddReagent(2928, 3) -- Dust of Decay
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8691, "Instant Poison III", 160, {SpellSource.Trainer})
|
||||
:SetResult(6950)
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:AddReagent(8924, 1) -- Dust of Deterioration
|
||||
:Save()
|
||||
|
||||
module:NewCraft(8694, "Mind-numbing Poison II", 170, {SpellSource.Trainer})
|
||||
:SetResult(6951)
|
||||
:AddReagent(2928, 4) -- Dust of Decay
|
||||
:AddReagent(2930, 4) -- Essence of Pain
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(11341, "Instant Poison IV", 200, {SpellSource.Trainer})
|
||||
:SetResult(8926)
|
||||
:AddReagent(8924, 2) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(11342, "Instant Poison V", 240, {SpellSource.Trainer})
|
||||
:SetResult(8927)
|
||||
:AddReagent(8924, 3) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(11343, "Instant Poison VI", 280, {SpellSource.Trainer})
|
||||
:SetResult(8928)
|
||||
:AddReagent(8924, 4) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(11357, "Deadly Poison III", 210, {SpellSource.Trainer})
|
||||
:SetResult(8984)
|
||||
:AddReagent(5173, 3) -- Deathweed
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(11358, "Deadly Poison IV", 250, {SpellSource.Trainer})
|
||||
:SetResult(8985)
|
||||
:AddReagent(5173, 5) -- Deathweed
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(11400, "Mind-numbing Poison III", 240, {SpellSource.Trainer})
|
||||
:SetResult(9186)
|
||||
:AddReagent(8923, 2) -- Essence of Agony
|
||||
:AddReagent(8924, 2) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(13220, "Wound Poison", 140, {SpellSource.Trainer})
|
||||
:SetResult(10918)
|
||||
:AddReagent(2930, 1) -- Essence of Pain
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:AddReagent(5173, 1) -- Deathweed
|
||||
:Save()
|
||||
|
||||
module:NewCraft(13228, "Wound Poison II", 180, {SpellSource.Trainer})
|
||||
:SetResult(10920)
|
||||
:AddReagent(2930, 1) -- Essence of Pain
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:AddReagent(5173, 2) -- Deathweed
|
||||
:Save()
|
||||
|
||||
module:NewCraft(13229, "Wound Poison III", 220, {SpellSource.Trainer})
|
||||
:SetResult(10921)
|
||||
:AddReagent(5173, 2) -- Deathweed
|
||||
:AddReagent(8923, 1) -- Essence of Agony
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(13230, "Wound Poison IV", 260, {SpellSource.Trainer})
|
||||
:SetResult(10922)
|
||||
:AddReagent(5173, 2) -- Deathweed
|
||||
:AddReagent(8923, 2) -- Essence of Agony
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(25347, "Deadly Poison V", 300, {})
|
||||
:SetResult(20844)
|
||||
:AddRecipe(21302, Quality.Rare, {RecipeSource.Drop})
|
||||
:AddReagent(5173, 7) -- Deathweed
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
module:NewCraft(45611, "Agitating Poison", 300, {SpellSource.Trainer})
|
||||
:SetResult(65032)
|
||||
:AddReagent(2931, 2) -- Maiden's Anguish
|
||||
:AddReagent(3372, 1) -- Leaded Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(45878, "Dissolvent Poison", 240, {SpellSource.Trainer})
|
||||
:SetResult(54009)
|
||||
:AddReagent(2931, 3) -- Maiden's Anguish
|
||||
:AddReagent(8924, 2) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(45882, "Dissolvent Poison II", 300, {SpellSource.Trainer})
|
||||
:SetResult(54010)
|
||||
:AddReagent(2931, 4) -- Maiden's Anguish
|
||||
:AddReagent(8924, 3) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(51924, "Corrosive Poison", 260, {SpellSource.Trainer})
|
||||
:SetResult(47408)
|
||||
:AddReagent(5173, 3) -- Deathweed
|
||||
:AddReagent(8924, 3) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
|
||||
module:NewCraft(52576, "Corrosive Poison II", 300, {})
|
||||
:SetResult(47409)
|
||||
:AddRecipe(21302, Quality.Rare, {RecipeSource.Drop})
|
||||
:AddReagent(5173, 3) -- Deathweed
|
||||
:AddReagent(8924, 3) -- Dust of Deterioration
|
||||
:AddReagent(8925, 1) -- Crystal Vial
|
||||
:Save()
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<Script file="Alchemy.lua" />
|
||||
<Script file="Blacksmithing.lua" />
|
||||
<Script file="Cooking.lua" />
|
||||
<Script file="Enchanting.lua" />
|
||||
<Script file="Engineering.lua" />
|
||||
<Script file="FirstAid.lua" />
|
||||
<Script file="Leatherworking.lua" />
|
||||
<Script file="Mining.lua" />
|
||||
<Script file="Poisons.lua" />
|
||||
<Script file="Tailoring.lua" />
|
||||
<Include file="Turtle\Professions.xml"/>
|
||||
</Ui>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, version = "Professions-Disguise", 1
|
||||
local module = --[[---@type LcProfessionModule]] lib:RegisterProfessionModule(name, version, "Disguise")
|
||||
if not module then return end
|
||||
|
||||
local Quality = lib.constants.qualities
|
||||
local SpellSource = lib.constants.spell_sources
|
||||
local RecipeSource = lib.constants.recipe_sources
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
module:NewCraft(5169, "Defias Disguise", 1, {})
|
||||
:SetMinCharacterLevel(13)
|
||||
:AddRecipe(5126, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:AddReagent(7997, 1) -- Red Defias Mask
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5264, "South Seas Pirate Disguise", 1, {})
|
||||
:SetMinCharacterLevel(13)
|
||||
:AddRecipe(5127, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:AddReagent(5107, 1) -- Deckhand's Shirt
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5265, "Stonesplinter Trogg Disguise", 1, {})
|
||||
:SetMinCharacterLevel(13)
|
||||
:AddRecipe(5131, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:AddReagent(5109, 1) -- Stonesplinter Rags
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5266, "Syndicate Disguise", 1, {})
|
||||
:SetMinCharacterLevel(22)
|
||||
:AddRecipe(5132, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2592, 1) -- Wool Cloth
|
||||
:AddReagent(5113, 1) -- Mark of the Syndicate
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5267, "Dalaran Wizard Disguise", 1, {})
|
||||
:SetMinCharacterLevel(13)
|
||||
:AddRecipe(5130, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:AddReagent(5110, 1) -- Dalaran Wizard's Robe
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5268, "Dark Iron Dwarf Disguise", 1, {})
|
||||
:SetMinCharacterLevel(22)
|
||||
:AddRecipe(5129, Quality.Common, {RecipeSource.Vendor})
|
||||
:AddReagent(2592, 1) -- Wool Cloth
|
||||
:AddReagent(5108, 1) -- Dark Iron Leather
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5668, "Peasant Disguise", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetMinCharacterLevel(2)
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:Save()
|
||||
|
||||
module:NewCraft(5669, "Peon Disguise", 1, {SpellSource.LearnedAutomatically})
|
||||
:SetMinCharacterLevel(2)
|
||||
:AddReagent(2589, 1) -- Linen Cloth
|
||||
:Save()
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<Script file="Disguise.lua" />
|
||||
<Script file="Jewelcrafting.lua" />
|
||||
<Script file="Survival.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,265 @@
|
||||
local lib = LibCraftsGetLibrary()
|
||||
local name, version = "Professions-Survival", 6
|
||||
local module = --[[---@type LcProfessionModule]] lib:RegisterProfessionModule(name, version, "Survival")
|
||||
if not module then return end
|
||||
|
||||
local SpellSource = lib.constants.spell_sources
|
||||
|
||||
--[[
|
||||
Survival is a Turtle WoW secondary profession, trained at Nesingwary's
|
||||
Expedition (Stranglethorn Vale). This is now the full recipe list, skill 1
|
||||
through 300, triple-confirmed: a player-provided skill-up table, that same
|
||||
player's own "/gmc dump" of their known trade skill entries (real result
|
||||
item ids), and a follow-up "/gmc dump" of both trainer NPCs' full service
|
||||
lists (every recipe on offer, known or not, with skill requirements - just
|
||||
no item ids, since training a spell has no crafted-item preview). Two
|
||||
skill-295 entries the original table listed with source "Recipe" rather than
|
||||
"Trainer" (Oil-Powered Cooker, Prospector's Magnifying Lens) are the only
|
||||
known gap - they're taught by a recipe item this addon has no id for, not by
|
||||
the trainer directly, so they're left out rather than mis-tagged.
|
||||
|
||||
The client API has no way to read a recipe's actual spell id under any of
|
||||
the three windows (trade skill, craft, or trainer) - only its name and,
|
||||
for known trade-skill/craft entries, the crafted item's id. The skill
|
||||
155-300 result items were instead confirmed by hand against octowow.st
|
||||
(https://octowow.st/db/?item=<id>) - note that db shows both an Item ID and
|
||||
an unrelated Display ID (a 3D model reference) on the same page; it's the
|
||||
Item ID this needs.
|
||||
|
||||
Because CraftRepository matches "known vs missing" by recipe NAME (see
|
||||
CharacterRepository/CraftRepository), not by spell id, every recipe below
|
||||
still uses a safe placeholder spell id (900001+, an id range nowhere near
|
||||
any real Blizzard or Turtle spell) where the real one isn't confirmed -
|
||||
7 recipes have a real one, the rest don't. That doesn't affect accuracy:
|
||||
every recipe below (all 87) now has a real result item id, and the tooltip
|
||||
always prefers that over the spell id (see CraftsListItem:_DrawTooltip), so
|
||||
a placeholder spell id never actually surfaces anywhere.
|
||||
]]
|
||||
|
||||
if lib.env.is_turtle_wow then
|
||||
-- Confirmed real spell ids
|
||||
module:NewCraft(46064, "Dim Torch", 1, {SpellSource.Trainer})
|
||||
:SetResult(6182)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(47101, "Survivalist's Skinning Knife", 10, {SpellSource.Trainer})
|
||||
:SetResult(7009)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(47103, "Driftwood Fishing Pole", 10, {SpellSource.Trainer})
|
||||
:SetResult(7010)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(46072, "Traveler's Tent", 80, {SpellSource.Trainer})
|
||||
:SetResult(51283)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(46073, "Fishing Boat", 115, {SpellSource.Trainer})
|
||||
:SetResult(51282)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(46066, "Murloc's Flippers", 125, {SpellSource.Trainer})
|
||||
:SetResult(65028)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(46077, "Repaired Electro-Lantern", 230, {SpellSource.Trainer})
|
||||
:SetResult(65030)
|
||||
:Save()
|
||||
|
||||
-- Placeholder spell ids (900001+) - real names/skill levels/result items,
|
||||
-- see header comment
|
||||
module:NewCraft(900001, "Bundle of Simple Sticks", 5, {SpellSource.Trainer})
|
||||
:SetResult(42149)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900002, "Crude Hunting Bow", 15, {SpellSource.Trainer})
|
||||
:SetResult(42092)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900003, "Crude Hatchet", 15, {SpellSource.Trainer})
|
||||
:SetResult(42091)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900004, "Crude Machete", 15, {SpellSource.Trainer})
|
||||
:SetResult(42090)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900005, "Crude Walking Stick", 15, {SpellSource.Trainer})
|
||||
:SetResult(42089)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900006, "Copper Lantern", 20, {SpellSource.Trainer})
|
||||
:SetResult(42093)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900007, "Simple Slingshot", 30, {SpellSource.Trainer})
|
||||
:SetResult(42229)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900008, "Simple Herbalist's Backpack", 40, {SpellSource.Trainer})
|
||||
:SetResult(33369)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900009, "Makeshift Rations Bag", 40, {SpellSource.Trainer})
|
||||
:SetResult(33370)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900010, "Weak Healing Salve", 50, {SpellSource.Trainer})
|
||||
:SetResult(42233)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900011, "Makeshift Knife", 60, {SpellSource.Trainer})
|
||||
:SetResult(42094)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900012, "Gardening Gloves", 65, {SpellSource.Trainer})
|
||||
:SetResult(42095)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900013, "Crude Fishing Rod", 70, {SpellSource.Trainer})
|
||||
:SetResult(42096)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900014, "Hunting Spear", 90, {SpellSource.Trainer})
|
||||
:SetResult(42097)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900015, "Gardening Broom", 95, {SpellSource.Trainer})
|
||||
:SetResult(42098)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900016, "Healing Salve", 100, {SpellSource.Trainer})
|
||||
:SetResult(42125)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900017, "Oakwood Bow", 100, {SpellSource.Trainer})
|
||||
:SetResult(42099)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900018, "Simple Fishing Lure", 100, {SpellSource.Trainer})
|
||||
:SetResult(114)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900019, "Fishing Bag", 105, {SpellSource.Trainer})
|
||||
:SetResult(33371)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900020, "Skinner's Pack", 110, {SpellSource.Trainer})
|
||||
:SetResult(33372)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900021, "Gardening Pitchfork", 120, {SpellSource.Trainer})
|
||||
:SetResult(42100)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900022, "Blackmouth Fishing Trap", 125, {SpellSource.Trainer})
|
||||
:SetResult(42329)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900023, "Bundle of Bright Wood Sticks", 125, {SpellSource.Trainer})
|
||||
:SetResult(42150)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900024, "Murloc Scale Coat", 125, {SpellSource.Trainer})
|
||||
:SetResult(42101)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900025, "Sturdy Blade", 130, {SpellSource.Trainer})
|
||||
:SetResult(42104)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900026, "Sturdy Cane", 130, {SpellSource.Trainer})
|
||||
:SetResult(42102)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900027, "Sturdy Knife", 130, {SpellSource.Trainer})
|
||||
:SetResult(42103)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900028, "Sturdy Net", 130, {SpellSource.Trainer})
|
||||
:SetResult(42154)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900029, "Reliable Fishing Rod", 135, {SpellSource.Trainer})
|
||||
:SetResult(42105)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900030, "Hat of the Junior Chef", 140, {SpellSource.Trainer})
|
||||
:SetResult(42106)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900031, "Rugged Mining Sack", 140, {SpellSource.Trainer})
|
||||
:SetResult(42294)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900032, "Throwable Net", 140, {SpellSource.Trainer})
|
||||
:SetResult(42132)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900033, "Bright Wood Arrows", 145, {SpellSource.Trainer})
|
||||
:SetResult(42198)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900034, "Treasure Compass", 145, {SpellSource.Trainer})
|
||||
:SetResult(42107)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900035, "Potent Healing Salve", 150, {SpellSource.Trainer})
|
||||
:SetResult(42126)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900036, "Spicy Fishing Lure", 150, {SpellSource.Trainer})
|
||||
:SetResult(125)
|
||||
:Save()
|
||||
|
||||
module:NewCraft(900037, "Studded Rations Bag", 150, {SpellSource.Trainer})
|
||||
:SetResult(33373)
|
||||
:Save()
|
||||
|
||||
-- Placeholder spell ids (skill 155+), but real result items now -
|
||||
-- confirmed via a player pulling Item IDs (not Display IDs - a
|
||||
-- different number on the same db page) off octowow.st
|
||||
module:NewCraft(900038, "Slowing Bolas", 155, {SpellSource.Trainer}):SetResult(106):Save()
|
||||
module:NewCraft(900039, "Iron Lantern", 160, {SpellSource.Trainer}):SetResult(2714):Save()
|
||||
module:NewCraft(900040, "Snap Trap", 160, {SpellSource.Trainer}):SetResult(42328):Save()
|
||||
module:NewCraft(900041, "Edged Machete", 165, {SpellSource.Trainer}):SetResult(42108):Save()
|
||||
module:NewCraft(900042, "Iron Spear", 165, {SpellSource.Trainer}):SetResult(42109):Save()
|
||||
module:NewCraft(900043, "Reinforced Fishing Rod", 170, {SpellSource.Trainer}):SetResult(42110):Save()
|
||||
module:NewCraft(900044, "Bundle of Shade Wood Sticks", 175, {SpellSource.Trainer}):SetResult(42151):Save()
|
||||
module:NewCraft(900045, "Firefin Fishing Trap", 175, {SpellSource.Trainer}):SetResult(42330):Save()
|
||||
module:NewCraft(900046, "Water Trudgers", 175, {SpellSource.Trainer}):SetResult(42111):Save()
|
||||
module:NewCraft(900047, "Cleaning Cloth", 180, {SpellSource.Trainer}):SetResult(60001):Save()
|
||||
module:NewCraft(900048, "Sharpened Herb Sickle", 180, {SpellSource.Trainer}):SetResult(42112):Save()
|
||||
module:NewCraft(900049, "Lined Wintercloak", 185, {SpellSource.Trainer}):SetResult(42113):Save()
|
||||
module:NewCraft(900050, "Sleek Pinewood Bow", 190, {SpellSource.Trainer}):SetResult(42114):Save()
|
||||
module:NewCraft(900051, "Savory Fishing Lure", 200, {SpellSource.Trainer}):SetResult(133):Save()
|
||||
module:NewCraft(900052, "Superior Healing Salve", 200, {SpellSource.Trainer}):SetResult(42127):Save()
|
||||
module:NewCraft(900053, "Nutritious Rations", 205, {SpellSource.Trainer}):SetResult(42155):Save()
|
||||
module:NewCraft(900054, "Shade Wood Arrows", 205, {SpellSource.Trainer}):SetResult(42199):Save()
|
||||
module:NewCraft(900055, "Hiking Staff", 215, {SpellSource.Trainer}):SetResult(42116):Save()
|
||||
module:NewCraft(900056, "Tree Hatchet", 215, {SpellSource.Trainer}):SetResult(42117):Save()
|
||||
module:NewCraft(900057, "Vine Cutter", 215, {SpellSource.Trainer}):SetResult(42115):Save()
|
||||
module:NewCraft(900058, "Bundle of Tropical Sticks", 225, {SpellSource.Trainer}):SetResult(42152):Save()
|
||||
module:NewCraft(900059, "Sunshade Hat", 235, {SpellSource.Trainer}):SetResult(42118):Save()
|
||||
module:NewCraft(900060, "Thick Rations Bag", 235, {SpellSource.Trainer}):SetResult(33374):Save()
|
||||
module:NewCraft(900061, "Aromatic Berries", 240, {SpellSource.Trainer}):SetResult(42131):Save()
|
||||
module:NewCraft(900062, "Spiced Berries", 240, {SpellSource.Trainer}):SetResult(42130):Save()
|
||||
module:NewCraft(900063, "Warped Recurve Bow", 240, {SpellSource.Trainer}):SetResult(42119):Save()
|
||||
module:NewCraft(900064, "Advanced Camouflage", 245, {SpellSource.Trainer}):SetResult(42230):Save()
|
||||
module:NewCraft(900065, "Emergency Parachute", 250, {SpellSource.Trainer}):SetResult(42156):Save()
|
||||
module:NewCraft(900066, "Premium Fishing Lure", 250, {SpellSource.Trainer}):SetResult(145):Save()
|
||||
module:NewCraft(900067, "Stabilizing Healing Salve", 250, {SpellSource.Trainer}):SetResult(42128):Save()
|
||||
module:NewCraft(900068, "Smooth Ironfeather Arrows", 255, {SpellSource.Trainer}):SetResult(42200):Save()
|
||||
module:NewCraft(900069, "Heavy Duty Machete", 260, {SpellSource.Trainer}):SetResult(42120):Save()
|
||||
module:NewCraft(900070, "Thorium Edged Machete", 260, {SpellSource.Trainer}):SetResult(42121):Save()
|
||||
module:NewCraft(900071, "Thorium Spear", 265, {SpellSource.Trainer}):SetResult(42122):Save()
|
||||
module:NewCraft(900072, "Bundle of Star Wood Sticks", 270, {SpellSource.Trainer}):SetResult(42153):Save()
|
||||
module:NewCraft(900073, "Razor-sharp Skinning Knife", 270, {SpellSource.Trainer}):SetResult(42124):Save()
|
||||
module:NewCraft(900074, "Mastercraft Fishing Rod", 275, {SpellSource.Trainer}):SetResult(42123):Save()
|
||||
module:NewCraft(900075, "Stonescale Fishing Trap", 275, {SpellSource.Trainer}):SetResult(42331):Save()
|
||||
module:NewCraft(900076, "Miner's Rucksack", 280, {SpellSource.Trainer}):SetResult(33375):Save()
|
||||
module:NewCraft(900077, "Herbalist's Knapsack", 285, {SpellSource.Trainer}):SetResult(33376):Save()
|
||||
module:NewCraft(900078, "Skinner's Carryall", 285, {SpellSource.Trainer}):SetResult(33378):Save()
|
||||
module:NewCraft(900079, "Cooling Rations Bag", 290, {SpellSource.Trainer}):SetResult(33379):Save()
|
||||
module:NewCraft(900080, "Major Healing Salve", 300, {SpellSource.Trainer}):SetResult(42129):Save()
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
# LibCrafts
|
||||
|
||||
A modern, lightweight embeddable database library for crafting professions in Vanilla WoW (1.12.1) and [Turtle WoW](https://turtle-wow.org). Designed as an replacement (not a drop-in) for [ReagentData](https://github.com/refaim/ReagentData), [TradeSkillsData](https://github.com/refaim/TradeSkillsData) and [TradeSkillsData-turtle](https://github.com/refaim/TradeSkillsData-turtle).
|
||||
|
||||
**Notice to players:** This is **NOT a standalone addon** and provides no functionality on its own. **Do not install this library directly.** This is a developer resource meant to be embedded inside other addons. If you're looking for a crafting addon, this is not it. You might be looking for [MasterTradeSkills](https://github.com/refaim/MasterTradeSkills) or [MissingCrafts](https://github.com/refaim/MissingCrafts) instead.
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight ID-based database architecture
|
||||
- Complete crafting system mapping (spells, recipes, reagents, results, sources etc)
|
||||
- Developer-friendly database structure
|
||||
- Vanilla WoW and Turtle WoW compatibility
|
||||
- Localization support
|
||||
|
||||
## Usage
|
||||
|
||||
```lua
|
||||
local LibCrafts = LibStub("LibCrafts-1.0")
|
||||
|
||||
-- Query crafts using reagent ID
|
||||
local reagentId = 2318 -- Light Leather, see https://www.wowhead.com/classic/item=2318/light-leather
|
||||
local crafts = LibCrafts:GetCraftsByReagentId(reagentId) -- Light Leather
|
||||
|
||||
---Query crafts using recipe ID
|
||||
local recipeId = 4408 -- Mechanical Squirrel, see https://www.wowhead.com/classic/item=4408/schematic-mechanical-squirrel
|
||||
local crafts = LibCrafts:GetCraftsByRecipeId(recipeId) -- Mechanical Squirrel
|
||||
|
||||
--- Query crafts using english or localized profession name
|
||||
local crafts = LibCrafts:GetCraftsByProfession("Leatherworking")
|
||||
local crafts = LibCrafts:GetCraftsByProfession("制皮")
|
||||
```
|
||||
|
||||
## Changes
|
||||
|
||||
See [CHANGES](CHANGES.md) for the full list of changes.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you have suggestions for improvements or have found a bug, please create an issue or submit a pull request.
|
||||
|
||||
## License
|
||||
|
||||
LibCrafts is distributed under the MIT License. For details, see the [LICENSE](LICENSE) file.
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Roman Kharitonov
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,219 @@
|
||||
--[[
|
||||
Name: LibItemTooltip-1.0
|
||||
Developed by: Refaim (rkharito@yandex.ru, https://github.com/refaim/)
|
||||
Description: A library that helps to hook into item tooltips.
|
||||
Dependencies: LibStub
|
||||
Compatibility: Vanilla (1.12.1), Turtle (1.17.2+)
|
||||
]]
|
||||
|
||||
---@type LibStubDef
|
||||
local LibStub = getglobal("LibStub")
|
||||
assert(LibStub ~= nil)
|
||||
|
||||
local untyped_lib, old_lib_version = LibStub:NewLibrary("LibItemTooltip-1.0", 2)
|
||||
if not untyped_lib then
|
||||
return
|
||||
end
|
||||
|
||||
---@alias LitEventHandler fun(tooltip: GameTooltip, item_link: string, item_id: number)
|
||||
|
||||
---@shape LitHook
|
||||
---@field lib_version number
|
||||
---@field name string
|
||||
---@field object table
|
||||
---@field orig function
|
||||
---@field hook function
|
||||
|
||||
---@class LibItemTooltip
|
||||
---@field game_tooltip_event_frame Frame
|
||||
---@field item_ref_tooltip_event_frame Frame
|
||||
---@field game_tooltip_item_link string|nil
|
||||
---@field item_ref_tooltip_link string|nil
|
||||
---@field event_to_handlers table<string, LitEventHandler[]>
|
||||
---@field object_to_name_to_hook table<string, table<string, LitHook>>
|
||||
|
||||
local lib = --[[---@type LibItemTooltip]] untyped_lib
|
||||
if lib.event_to_handlers == nil then
|
||||
lib.event_to_handlers = {}
|
||||
end
|
||||
if lib.object_to_name_to_hook == nil then
|
||||
lib.object_to_name_to_hook = {}
|
||||
end
|
||||
|
||||
---@type table<string, string>
|
||||
local event_upgrade_map = {
|
||||
OnShow = "OnShow2",
|
||||
}
|
||||
|
||||
---@param event "OnShow"
|
||||
---@param handler LitEventHandler
|
||||
function lib:RegisterEvent(event, handler)
|
||||
assert(type(event) == "string", "Event name must be a string")
|
||||
assert(type(handler) == "function", "Handler must be a function")
|
||||
|
||||
local new_event = event_upgrade_map[event] or event
|
||||
if self.event_to_handlers[new_event] == nil then
|
||||
self.event_to_handlers[new_event] = {}
|
||||
end
|
||||
tinsert(self.event_to_handlers[new_event], handler)
|
||||
end
|
||||
|
||||
---@param item_link string
|
||||
---@return number
|
||||
local function extract_id(item_link)
|
||||
local _, _, id = string.find(item_link, "item:(%d+)")
|
||||
return --[[---@not nil]] tonumber(--[[---@not nil]] id)
|
||||
end
|
||||
|
||||
---@param tooltip GameTooltip
|
||||
---@param item_link string
|
||||
local function on_show(tooltip, item_link)
|
||||
for _, handler in ipairs(lib.event_to_handlers["OnShow2"] or {}) do
|
||||
handler(tooltip, item_link, extract_id(item_link))
|
||||
end
|
||||
end
|
||||
|
||||
---@param frame Frame
|
||||
local function delete_event_frame(frame)
|
||||
frame:SetScript("OnShow", nil)
|
||||
frame:SetScript("OnHide", nil)
|
||||
frame:SetParent(nil)
|
||||
end
|
||||
|
||||
if old_lib_version ~= nil and old_lib_version < 2 then
|
||||
local old_frame = --[[---@type Frame]] (--[[---@type table]] lib).event_frame
|
||||
delete_event_frame(old_frame)
|
||||
|
||||
for old_event, new_event in pairs(event_upgrade_map) do
|
||||
if lib.event_to_handlers[new_event] == nil then
|
||||
lib.event_to_handlers[new_event] = {}
|
||||
end
|
||||
for _, handler in ipairs(lib.event_to_handlers[old_event] or {}) do
|
||||
tinsert(lib.event_to_handlers[new_event], handler)
|
||||
end
|
||||
lib.event_to_handlers[old_event] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if lib.game_tooltip_event_frame ~= nil then
|
||||
delete_event_frame(lib.game_tooltip_event_frame)
|
||||
end
|
||||
lib.game_tooltip_event_frame = CreateFrame("Frame", nil, GameTooltip)
|
||||
lib.game_tooltip_event_frame:SetScript("OnShow", function()
|
||||
if lib.game_tooltip_item_link ~= nil then
|
||||
on_show(GameTooltip, --[[---@not nil]] lib.game_tooltip_item_link)
|
||||
end
|
||||
end)
|
||||
lib.game_tooltip_event_frame:SetScript("OnHide", function()
|
||||
lib.game_tooltip_item_link = nil
|
||||
end)
|
||||
|
||||
if lib.item_ref_tooltip_event_frame ~= nil then
|
||||
delete_event_frame(lib.item_ref_tooltip_event_frame)
|
||||
end
|
||||
lib.item_ref_tooltip_event_frame = CreateFrame("Frame", nil, ItemRefTooltip)
|
||||
lib.item_ref_tooltip_event_frame:SetScript("OnShow", function()
|
||||
if lib.item_ref_tooltip_link ~= nil and not IsAltKeyDown() and not IsControlKeyDown() and not IsShiftKeyDown() then
|
||||
on_show(ItemRefTooltip, --[[---@not nil]] lib.item_ref_tooltip_link)
|
||||
end
|
||||
end)
|
||||
lib.item_ref_tooltip_event_frame:SetScript("OnHide", function()
|
||||
lib.item_ref_tooltip_link = nil
|
||||
end)
|
||||
|
||||
---@param object table
|
||||
---@param name string
|
||||
---@param on_hook function
|
||||
local function hook(object, name, on_hook)
|
||||
assert(type(object[name]) == "function")
|
||||
|
||||
local object_id = tostring(object)
|
||||
local name_to_hook = lib.object_to_name_to_hook[object_id]
|
||||
if name_to_hook == nil then
|
||||
name_to_hook = {}
|
||||
end
|
||||
|
||||
local old_hook = name_to_hook[name]
|
||||
if old_hook ~= nil then
|
||||
old_hook.object[name] = old_hook.orig
|
||||
end
|
||||
|
||||
---@type function
|
||||
local orig = object[name]
|
||||
object[name] = function(...)
|
||||
on_hook(unpack(arg))
|
||||
return orig(unpack(arg))
|
||||
end
|
||||
|
||||
name_to_hook[name] = {
|
||||
lib_version = 2,
|
||||
name = name,
|
||||
object = object,
|
||||
orig = orig,
|
||||
hook = on_hook
|
||||
}
|
||||
|
||||
lib.object_to_name_to_hook[object_id] = name_to_hook
|
||||
end
|
||||
|
||||
hook(getfenv(), "SetItemRef", function(link, text, button)
|
||||
lib.item_ref_tooltip_link = link
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetHyperlink", function(self, link)
|
||||
if string.find(link, "item:") then
|
||||
lib.game_tooltip_item_link = link
|
||||
end
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetBagItem", function(self, container, slot)
|
||||
lib.game_tooltip_item_link = GetContainerItemLink(container, slot)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetCraftItem", function(self, skill, slot)
|
||||
lib.game_tooltip_item_link = GetCraftReagentItemLink(skill, slot)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetCraftSpell", function(self, slot)
|
||||
lib.game_tooltip_item_link = GetCraftItemLink(slot)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetInventoryItem", function(self, unit, slot)
|
||||
lib.game_tooltip_item_link = GetInventoryItemLink(unit, slot)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetLootItem", function(self, slot)
|
||||
lib.game_tooltip_item_link = GetLootSlotLink(slot)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetLootRollItem", function(self, id)
|
||||
lib.game_tooltip_item_link = GetLootRollItemLink(id)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetMerchantItem", function(self, item_index)
|
||||
lib.game_tooltip_item_link = GetMerchantItemLink(item_index)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetQuestItem", function(self, item_type, index)
|
||||
lib.game_tooltip_item_link = GetQuestItemLink(item_type, index)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetQuestLogItem", function(self, item_type, index)
|
||||
lib.game_tooltip_item_link = GetQuestLogItemLink(item_type, index)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetTradePlayerItem", function(self, index)
|
||||
lib.game_tooltip_item_link = GetTradePlayerItemLink(index)
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetTradeSkillItem", function(self, skill_index, reagent_index)
|
||||
if reagent_index then
|
||||
lib.game_tooltip_item_link = GetTradeSkillReagentItemLink(skill_index, reagent_index)
|
||||
else
|
||||
lib.game_tooltip_item_link = GetTradeSkillItemLink(skill_index)
|
||||
end
|
||||
end)
|
||||
|
||||
hook(GameTooltip, "SetTradeTargetItem", function(self, index)
|
||||
lib.game_tooltip_item_link = GetTradeTargetItemLink(index)
|
||||
end)
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
|
||||
<Script file="LibStub.lua" />
|
||||
<Script file="LibItemTooltip-1.0.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,33 @@
|
||||
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
|
||||
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
||||
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
|
||||
local _G = getfenv()
|
||||
local strfind, strfmt = string.find, string.format
|
||||
local LibStub = _G[LIBSTUB_MAJOR]
|
||||
|
||||
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
||||
LibStub = LibStub or { libs = {}, minors = {} }
|
||||
_G[LIBSTUB_MAJOR] = LibStub
|
||||
LibStub.minor = LIBSTUB_MINOR
|
||||
|
||||
function LibStub:NewLibrary(major, minor)
|
||||
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
||||
local _,_,num = strfind(minor, "(%d+)")
|
||||
minor = assert(tonumber(num), "Minor version must either be a number or contain a number.")
|
||||
|
||||
local oldminor = self.minors[major]
|
||||
if oldminor and oldminor >= minor then return nil end
|
||||
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
||||
return self.libs[major], oldminor
|
||||
end
|
||||
|
||||
function LibStub:GetLibrary(major, silent)
|
||||
if not self.libs[major] and not silent then
|
||||
error(strfmt("Cannot find a library instance of %q.", tostring(major)), 2)
|
||||
end
|
||||
return self.libs[major], self.minors[major]
|
||||
end
|
||||
|
||||
function LibStub:IterateLibraries() return pairs(self.libs) end
|
||||
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
|
||||
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
||||
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
|
||||
local _G = getfenv()
|
||||
local strfind, strfmt = string.find, string.format
|
||||
local LibStub = _G[LIBSTUB_MAJOR]
|
||||
|
||||
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
||||
LibStub = LibStub or { libs = {}, minors = {} }
|
||||
_G[LIBSTUB_MAJOR] = LibStub
|
||||
LibStub.minor = LIBSTUB_MINOR
|
||||
|
||||
function LibStub:NewLibrary(major, minor)
|
||||
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
||||
local _,_,num = strfind(minor, "(%d+)")
|
||||
minor = assert(tonumber(num), "Minor version must either be a number or contain a number.")
|
||||
|
||||
local oldminor = self.minors[major]
|
||||
if oldminor and oldminor >= minor then return nil end
|
||||
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
||||
return self.libs[major], oldminor
|
||||
end
|
||||
|
||||
function LibStub:GetLibrary(major, silent)
|
||||
if not self.libs[major] and not silent then
|
||||
error(strfmt("Cannot find a library instance of %q.", tostring(major)), 2)
|
||||
end
|
||||
return self.libs[major], self.minors[major]
|
||||
end
|
||||
|
||||
function LibStub:IterateLibraries() return pairs(self.libs) end
|
||||
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@return boolean
|
||||
local function IsSuperWoWActive()
|
||||
return getglobal("SetAutoloot") ~= nil or SUPERWOW_VERSION ~= nil
|
||||
end
|
||||
|
||||
---@return number
|
||||
local function GetSuperWoWVersion()
|
||||
return tonumber(SUPERWOW_VERSION or "0.0") or 0
|
||||
end
|
||||
|
||||
---@param spellId number
|
||||
---@return string
|
||||
function GetSpellLink(spellId)
|
||||
assert(type(spellId) == "number")
|
||||
|
||||
local prefix = "enchant:"
|
||||
if IsSuperWoWActive() and GetSuperWoWVersion() <= 1.2 then
|
||||
-- https://github.com/balakethelock/SuperWoW/wiki/Changelog#07112024--12
|
||||
prefix = "spell:"
|
||||
end
|
||||
|
||||
return prefix .. tostring(spellId)
|
||||
end
|
||||
@@ -0,0 +1,168 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
--[[
|
||||
Turtle WoW's own trade skill / craft / trainer windows don't expose a
|
||||
recipe's spell id or required skill level through the client API - the only
|
||||
reliably scriptable facts are a recipe's exact name, and (trade skill/craft
|
||||
only) its resulting item id. "/gmc dump" walks whichever of those three
|
||||
windows is open and prints what it can, so gaps in lib/LibCrafts-1.0
|
||||
(Survival's especially) can be filled in with precise lookups instead of
|
||||
fuzzy name searches. The trainer window is the most complete source - it
|
||||
lists every recipe the trainer offers whether you've learned it or not -
|
||||
but only the trade skill/craft windows expose item ids, and only for
|
||||
recipes you already know.
|
||||
]]
|
||||
|
||||
---@class DevTools
|
||||
DevTools = {}
|
||||
|
||||
---@param itemLink string|nil
|
||||
---@return number|nil
|
||||
local function extractItemId(itemLink)
|
||||
if itemLink == nil then
|
||||
return nil
|
||||
end
|
||||
local _, _, id = strfind(itemLink, "item:(%d+)")
|
||||
return id and tonumber(id) or nil
|
||||
end
|
||||
|
||||
---@param text string
|
||||
local function printLine(text)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(text)
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
local function dumpTradeSkillFrame()
|
||||
local name, curRank, maxRank = GetTradeSkillLine()
|
||||
if name == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
printLine(format("|cff40bf40Gromissingcrafts|r: dumping '%s' (rank %d/%d)", name, curRank or 0, maxRank or 0))
|
||||
|
||||
for i = 1, GetNumTradeSkills() do
|
||||
local skillName, skillType = GetTradeSkillInfo(i)
|
||||
if skillType ~= "header" then
|
||||
local itemId = extractItemId(GetTradeSkillItemLink(i))
|
||||
printLine(format(" [%d] %s -> item %s (%s)", i, skillName or "?", itemId and tostring(itemId) or "?", skillType or "?"))
|
||||
end
|
||||
end
|
||||
|
||||
printLine("Look each item id up on your realm's database to get the exact spell id and skill level, then add:")
|
||||
printLine(" module:NewCraft(spellId, \"Recipe Name\", skillLevel, {SpellSource.Trainer}):SetResult(itemId):Save()")
|
||||
return true
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
local function dumpCraftFrame()
|
||||
local name, curRank, maxRank = GetCraftDisplaySkillLine()
|
||||
if name == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
printLine(format("|cff40bf40Gromissingcrafts|r: dumping '%s' (rank %d/%d)", name, curRank or 0, maxRank or 0))
|
||||
|
||||
for i = 1, GetNumCrafts() do
|
||||
local skillName, _, skillType = GetCraftInfo(i)
|
||||
if skillType ~= "header" then
|
||||
local itemId = extractItemId(GetCraftItemLink(i))
|
||||
printLine(format(" [%d] %s -> item %s (%s)", i, skillName or "?", itemId and tostring(itemId) or "?", skillType or "?"))
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
The trainer window lists every service the trainer offers, known or not -
|
||||
unlike the trade skill window (which only ever shows recipes you've already
|
||||
learned). No item link exists for a trainer entry though (you're learning a
|
||||
spell, not previewing a crafted item), so this only gets names, not ids.
|
||||
GetTrainerServiceInfo's exact return order isn't confirmed for this client,
|
||||
so both candidate "type" slots are printed and checked for the header filter.
|
||||
]]
|
||||
---@return boolean
|
||||
local function dumpTrainerFrame()
|
||||
local count = GetNumTrainerServices()
|
||||
if count == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
printLine(format("|cff40bf40Gromissingcrafts|r: dumping trainer list (%d services)", count))
|
||||
|
||||
for i = 1, count do
|
||||
local serviceName, b, c = GetTrainerServiceInfo(i)
|
||||
if b ~= "header" and c ~= "header" then
|
||||
printLine(format(" [%d] %s (%s / %s)", i, serviceName or "?", tostring(b), tostring(c)))
|
||||
end
|
||||
end
|
||||
|
||||
printLine("No item ids here - cross-reference these names against your realm's database, or check the trade skill window (item ids) after learning each one.")
|
||||
return true
|
||||
end
|
||||
|
||||
---@param frameName string
|
||||
local function reportFrame(frameName)
|
||||
local frame = getglobal(frameName)
|
||||
if frame == nil then
|
||||
printLine(format(" %s: does not exist on this client", frameName))
|
||||
return
|
||||
end
|
||||
local ok, visible = pcall(function() return frame:IsVisible() end)
|
||||
printLine(format(" %s: exists, IsVisible()=%s", frameName, ok and tostring(visible) or "error calling IsVisible"))
|
||||
end
|
||||
|
||||
--[[
|
||||
Nothing matched. Rather than guess again blind, report exactly what does and
|
||||
doesn't exist on this client so the real cause (wrong frame name? visibility
|
||||
check failing? something else entirely?) can be fixed instead of guessed at.
|
||||
]]
|
||||
local function diagnose()
|
||||
printLine("|cffff8040Gromissingcrafts|r: no supported window detected. Diagnostics:")
|
||||
reportFrame("TradeSkillFrame")
|
||||
reportFrame("CraftFrame")
|
||||
reportFrame("TrainerFrame")
|
||||
reportFrame("ClassTrainerFrame")
|
||||
local ok, numServices = pcall(GetNumTrainerServices)
|
||||
if not ok then
|
||||
printLine(" GetNumTrainerServices() = error calling it")
|
||||
else
|
||||
printLine(format(" GetNumTrainerServices() = %s", numServices == nil and "nil" or tostring(numServices)))
|
||||
end
|
||||
end
|
||||
|
||||
local function dump()
|
||||
if getglobal("TradeSkillFrame") ~= nil and TradeSkillFrame:IsVisible() and dumpTradeSkillFrame() then
|
||||
return
|
||||
end
|
||||
if getglobal("CraftFrame") ~= nil and CraftFrame:IsVisible() and dumpCraftFrame() then
|
||||
return
|
||||
end
|
||||
if getglobal("TrainerFrame") ~= nil and TrainerFrame:IsVisible() and dumpTrainerFrame() then
|
||||
return
|
||||
end
|
||||
-- This client names the trainer window ClassTrainerFrame (used for both
|
||||
-- class and profession trainers), not the vanilla-standard TrainerFrame -
|
||||
-- confirmed via /gmc dump's own diagnostics.
|
||||
if getglobal("ClassTrainerFrame") ~= nil and ClassTrainerFrame:IsVisible() and dumpTrainerFrame() then
|
||||
return
|
||||
end
|
||||
diagnose()
|
||||
end
|
||||
|
||||
function DevTools:Create()
|
||||
-- Must land in the REAL global table, not this addon's isolated
|
||||
-- environment - the client's slash-command dispatcher scans real _G for
|
||||
-- SLASH_* variables, and a bare assignment here would only ever set
|
||||
-- Gromissingcrafts.SLASH_GROMISSINGCRAFTS1 (writes don't fall through
|
||||
-- the environment's __index like reads do).
|
||||
setglobal("SLASH_GROMISSINGCRAFTS1", "/gromissingcrafts")
|
||||
setglobal("SLASH_GROMISSINGCRAFTS2", "/gmc")
|
||||
SlashCmdList["GROMISSINGCRAFTS"] = function(msg)
|
||||
local command = strlower(msg or "")
|
||||
if command == "dump" then
|
||||
dump()
|
||||
else
|
||||
printLine("|cff40bf40Gromissingcrafts|r: /gmc dump - print recipe names (+ item ids where available) from the open trade skill, craft, or trainer window")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
--[[
|
||||
Use the addon's private table as an isolated environment.
|
||||
By using {__index = _G} metatable we're allowing all global lookups to transparently fallback to the game-wide
|
||||
globals table, while the private table itself will act as a thin layer on top of the game-wide globals table,
|
||||
allowing us to have our own global variables isolated from the rest of the game.
|
||||
|
||||
This accomplishes several goals:
|
||||
1. Prevents addon-specific "globals" from leaking to game-wide global namespace _G
|
||||
2. Optionally retains the ability to access these "globals" via the only exposed global variable "Gromissingcrafts"
|
||||
3. Allows us to make overrides for WoW API global functions and variables without actually touching
|
||||
the real global namespace, making these overrides visible only to this addon.
|
||||
|
||||
setfenv(1, Gromissingcrafts) must be added to every .lua file to allow it to work within this environment,
|
||||
and this Environment file must be loaded before all others
|
||||
]]
|
||||
|
||||
local _G = getfenv(0)
|
||||
Gromissingcrafts = setmetatable({_G = _G}, {__index = _G})
|
||||
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
ADDON_NAME = "Gromissingcrafts"
|
||||
ADDON_VERSION = "1.0"
|
||||
|
||||
---@param t table
|
||||
function erase(t)
|
||||
setmetatable(t, nil)
|
||||
for key, _ in pairs(t) do
|
||||
t[key] = nil
|
||||
end
|
||||
table.setn(t, 0)
|
||||
end
|
||||
|
||||
---@generic K
|
||||
---@generic T
|
||||
---@param t table<K, T>
|
||||
---@param key K
|
||||
---@return T|nil
|
||||
function tpop(t, key)
|
||||
local value = t[key]
|
||||
t[key] = nil
|
||||
return value
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@shape AddonInfo
|
||||
---@field name string
|
||||
---@field version string
|
||||
|
||||
---@type AddonInfo
|
||||
local addonInfo = {
|
||||
name = ADDON_NAME,
|
||||
version = ADDON_VERSION
|
||||
}
|
||||
|
||||
---@type LibStubDef
|
||||
local LibStub = getglobal("LibStub")
|
||||
assert(LibStub ~= nil, "LibStub is required to run this addon")
|
||||
|
||||
local LibCraftingProfessions = --[[---@type LibCraftingProfessions]] LibStub("LibCraftingProfessions-1.0")
|
||||
local LibCrafts = --[[---@type LibCrafts]] LibStub("LibCrafts-1.0")
|
||||
local LibItemTooltip = --[[---@type LibItemTooltip]] LibStub("LibItemTooltip-1.0")
|
||||
|
||||
---@shape Repositories
|
||||
---@field characterRepository CharacterRepository
|
||||
---@field craftRepository CraftRepository
|
||||
|
||||
---@class Gromissingcrafts
|
||||
---@field database Database
|
||||
---@field uiManager UIManager
|
||||
|
||||
---@type Gromissingcrafts
|
||||
local addon = --[[---@type Gromissingcrafts]] {}
|
||||
|
||||
function addon:OnEnable()
|
||||
self.database = Database:Create()
|
||||
|
||||
local characterRepository = CharacterRepository:Create(self.database)
|
||||
---@type Repositories
|
||||
local repositories = {
|
||||
characterRepository = characterRepository,
|
||||
craftRepository = CraftRepository:Create(self.database, characterRepository, LibCrafts)
|
||||
}
|
||||
|
||||
-- L is the shared locale table populated by src/locale/*.lua, loaded earlier in the .toc
|
||||
self.uiManager = UIManager:Create(addonInfo, repositories, --[[---@type GromissingcraftsLocale]] L, LibItemTooltip)
|
||||
|
||||
LibCraftingProfessions:RegisterEvent("LCP_SKILLS_UPDATE", function(profession, skills)
|
||||
local skillNames = {}
|
||||
for _, skill in ipairs(skills) do
|
||||
tinsert(skillNames, skill.localized_name)
|
||||
end
|
||||
self.database:SaveCurrentPlayerSkills(profession.localized_name, profession.cur_rank, skillNames)
|
||||
|
||||
local playerProfessions = LibCraftingProfessions:GetPlayerProfessions()
|
||||
if playerProfessions ~= nil then
|
||||
local playerProfessionNames = {}
|
||||
for _, playerProfession in ipairs(playerProfessions or {}) do
|
||||
tinsert(playerProfessionNames, playerProfession.localized_name)
|
||||
end
|
||||
self.database:SaveCurrentPlayerProfessions(playerProfessionNames)
|
||||
end
|
||||
|
||||
self.uiManager:OnDataUpdated(profession.localized_name)
|
||||
end)
|
||||
|
||||
LibCraftingProfessions:RegisterEvent("LCP_FRAME_SHOW", function(profession, frame, frameType)
|
||||
self.uiManager:OnProfessionFrameOpened(profession.localized_name, frame, frameType)
|
||||
end)
|
||||
|
||||
LibCraftingProfessions:RegisterEvent("LCP_FRAME_CLOSE", function(frame, frameType)
|
||||
self.uiManager:OnProfessionFrameClosed(frame, frameType)
|
||||
end)
|
||||
|
||||
DevTools:Create()
|
||||
end
|
||||
|
||||
Addon = addon
|
||||
|
||||
local bootstrapFrame = CreateFrame("Frame")
|
||||
bootstrapFrame:RegisterEvent("ADDON_LOADED")
|
||||
bootstrapFrame:SetScript("OnEvent", function()
|
||||
if event == "ADDON_LOADED" and arg1 == ADDON_NAME then
|
||||
bootstrapFrame:UnregisterEvent("ADDON_LOADED")
|
||||
addon:OnEnable()
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,68 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@shape CharacterProfession
|
||||
---@field localizedName string
|
||||
---@field rank number
|
||||
---@field knownLocalizedSkillNamesSet table<string, boolean>
|
||||
|
||||
---@class Character
|
||||
---@field name string
|
||||
---@field professionLocalizedNameToProfession table<string, CharacterProfession>
|
||||
Character = {}
|
||||
|
||||
---@param dbCharacter DatabaseCharacter
|
||||
---@return self
|
||||
function Character:Create(dbCharacter)
|
||||
---@type table<string, CharacterProfession>
|
||||
local nameToProfession = {}
|
||||
|
||||
for professionName, profession in pairs(dbCharacter.professionsByLocalizedName) do
|
||||
---@type table<string, boolean>
|
||||
local skillSet = {}
|
||||
for _, skillName in ipairs(profession.knownLocalizedSkillNames) do
|
||||
skillSet[skillName] = true
|
||||
end
|
||||
|
||||
nameToProfession[professionName] = {
|
||||
localizedName = professionName,
|
||||
rank = profession.rank,
|
||||
knownLocalizedSkillNamesSet = skillSet,
|
||||
}
|
||||
end
|
||||
|
||||
local object = --[[---@type self]] {}
|
||||
setmetatable(object, {__index = Character})
|
||||
object.name = dbCharacter.name
|
||||
object.professionLocalizedNameToProfession = nameToProfession
|
||||
return object
|
||||
end
|
||||
|
||||
---@param localizedName string
|
||||
---@return number
|
||||
function Character:GetProfessionRank(localizedName)
|
||||
local profession = self.professionLocalizedNameToProfession[localizedName]
|
||||
if profession == nil then
|
||||
return 0
|
||||
end
|
||||
return profession.rank
|
||||
end
|
||||
|
||||
---@param craft Craft
|
||||
---@return boolean
|
||||
function Character:Knows(craft)
|
||||
local profession = self.professionLocalizedNameToProfession[craft.localizedProfessionName]
|
||||
if profession == nil then
|
||||
return false
|
||||
end
|
||||
return profession.knownLocalizedSkillNamesSet[craft.localizedName] == true
|
||||
end
|
||||
|
||||
---@param craft Craft
|
||||
---@return boolean
|
||||
function Character:CanLearnNow(craft)
|
||||
local profession = self.professionLocalizedNameToProfession[craft.localizedProfessionName]
|
||||
if profession == nil then
|
||||
return false
|
||||
end
|
||||
return not self:Knows(craft) and profession.rank >= craft.skillLevel
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class CharacterRepository
|
||||
---@field _db Database
|
||||
CharacterRepository = {}
|
||||
|
||||
---@param database Database
|
||||
---@return self
|
||||
function CharacterRepository:Create(database)
|
||||
self._db = database
|
||||
return self
|
||||
end
|
||||
|
||||
---@param exceptName ?string|nil
|
||||
---@return Character[]
|
||||
function CharacterRepository:FindAll(exceptName)
|
||||
---@type Character[]
|
||||
local characters = {}
|
||||
for _, dbCharacter in ipairs(self._db:GetCharacters()) do
|
||||
if dbCharacter.name ~= exceptName then
|
||||
tinsert(characters, Character:Create(dbCharacter))
|
||||
end
|
||||
end
|
||||
return characters
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@return Character|nil
|
||||
function CharacterRepository:Find(name)
|
||||
for _, dbCharacter in ipairs(self._db:GetCharacters()) do
|
||||
if dbCharacter.name == name then
|
||||
return Character:Create(dbCharacter)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
@@ -0,0 +1,185 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class CraftRepository
|
||||
---@field _db Database
|
||||
---@field _characterRepository CharacterRepository
|
||||
---@field _libCrafts LibCrafts
|
||||
CraftRepository = {}
|
||||
|
||||
---@alias CraftSource "Chest" | "Craft" | "Drop" | "Fishing" | "Gift" | "Pickpocketing" | "Quest" | "Trainer" | "Vendor" | "WorldObject" | "Unknown"
|
||||
---@type table<CraftSource, CraftSource>
|
||||
CraftSource = {
|
||||
Chest = "Chest",
|
||||
Craft = "Craft",
|
||||
Drop = "Drop",
|
||||
Fishing = "Fishing",
|
||||
Gift = "Gift",
|
||||
Pickpocketing = "Pickpocketing",
|
||||
Quest = "Quest",
|
||||
Trainer = "Trainer",
|
||||
Vendor = "Vendor",
|
||||
WorldObject = "WorldObject",
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
---@shape Craft
|
||||
---@field spellId number
|
||||
---@field localizedProfessionName string
|
||||
---@field localizedName string
|
||||
---@field skillLevel number
|
||||
---@field isAvailable boolean
|
||||
---@field recipeId number|nil
|
||||
---@field resultId number|nil
|
||||
---@field sources CraftSource[]
|
||||
|
||||
---@param database Database
|
||||
---@param LibCrafts LibCrafts
|
||||
function CraftRepository:Create(database, characterRepository, LibCrafts)
|
||||
self._db = database
|
||||
self._characterRepository = characterRepository
|
||||
self._libCrafts = LibCrafts
|
||||
return self
|
||||
end
|
||||
|
||||
---@type table<LcRecipeSource, CraftSource>
|
||||
local RECIPE_SOURCE_TO_CRAFT_SOURCE = {}
|
||||
---@type table<LcSpellSource, CraftSource>
|
||||
local SPELL_SOURCE_TO_CRAFT_SOURCE = {}
|
||||
|
||||
---@param craft LcCraft
|
||||
---@param LibCrafts LibCrafts
|
||||
---@return CraftSource[]
|
||||
local function parseSources(craft, LibCrafts)
|
||||
if next(RECIPE_SOURCE_TO_CRAFT_SOURCE) == nil then
|
||||
local RecipeSource = LibCrafts.constants.recipe_sources
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.Chest] = CraftSource.Chest
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.CraftedByEngineer] = CraftSource.Craft
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.Drop] = CraftSource.Drop
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.Fishing] = CraftSource.Fishing
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.GiftedToReturningEngineers] = CraftSource.Gift
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.Pickpocketing] = CraftSource.Pickpocketing
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.Quest] = CraftSource.Quest
|
||||
RECIPE_SOURCE_TO_CRAFT_SOURCE[RecipeSource.Vendor] = CraftSource.Vendor
|
||||
end
|
||||
|
||||
if next(SPELL_SOURCE_TO_CRAFT_SOURCE) == nil then
|
||||
local SpellSource = LibCrafts.constants.spell_sources
|
||||
SPELL_SOURCE_TO_CRAFT_SOURCE[SpellSource.LearnedAutomatically] = CraftSource.Trainer
|
||||
SPELL_SOURCE_TO_CRAFT_SOURCE[SpellSource.Quest] = CraftSource.Quest
|
||||
SPELL_SOURCE_TO_CRAFT_SOURCE[SpellSource.Trainer] = CraftSource.Trainer
|
||||
SPELL_SOURCE_TO_CRAFT_SOURCE[SpellSource.WorldObject] = CraftSource.WorldObject
|
||||
end
|
||||
|
||||
---@type table<CraftSource, boolean>
|
||||
local craftSourcesSet = {}
|
||||
for _, recipe in ipairs(craft.recipes) do
|
||||
for _, recipeSource in ipairs(recipe.sources) do
|
||||
local craftSource = RECIPE_SOURCE_TO_CRAFT_SOURCE[recipeSource]
|
||||
if craftSource ~= nil then
|
||||
craftSourcesSet[craftSource] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, spellSource in ipairs(craft.sources) do
|
||||
local craftSource = SPELL_SOURCE_TO_CRAFT_SOURCE[spellSource]
|
||||
if craftSource ~= nil then
|
||||
craftSourcesSet[craftSource] = true
|
||||
end
|
||||
end
|
||||
|
||||
---@type CraftSource[]
|
||||
local craftSources = {}
|
||||
for source, _ in pairs(craftSourcesSet) do
|
||||
tinsert(craftSources, source)
|
||||
end
|
||||
return craftSources
|
||||
end
|
||||
|
||||
---@param craft LcCraft
|
||||
---@param professionRank number
|
||||
---@param LibCrafts LibCrafts
|
||||
---@return Craft
|
||||
local function create(craft, professionRank, LibCrafts)
|
||||
---@type number|nil
|
||||
local recipeId
|
||||
for _, recipe in ipairs(craft.recipes) do
|
||||
recipeId = recipe.id
|
||||
break
|
||||
end
|
||||
|
||||
---@type number|nil
|
||||
local resultId
|
||||
if craft.result ~= nil then
|
||||
resultId = (--[[---@not nil]] craft.result).id
|
||||
end
|
||||
|
||||
return {
|
||||
spellId = craft.spell_id,
|
||||
localizedProfessionName = craft.localized_profession_name,
|
||||
localizedName = craft.localized_spell_name,
|
||||
skillLevel = craft.skill_level,
|
||||
isAvailable = craft.skill_level <= professionRank,
|
||||
recipeId = recipeId,
|
||||
resultId = resultId,
|
||||
sources = parseSources(craft, LibCrafts)
|
||||
}
|
||||
end
|
||||
|
||||
---@param characterName string
|
||||
---@param localizedProfessionName string
|
||||
---@param searchQuery string
|
||||
---@return Craft[]
|
||||
function CraftRepository:FindMissing(characterName, localizedProfessionName, searchQuery)
|
||||
local professionRank = 0
|
||||
local character = self._characterRepository:Find(characterName)
|
||||
if character ~= nil then
|
||||
professionRank = (--[[---@not nil]] character):GetProfessionRank(localizedProfessionName)
|
||||
end
|
||||
|
||||
---@type table<string, boolean>
|
||||
local characterSkillSet = {}
|
||||
for _, skillName in ipairs(self._db:GetLocalizedSkillNames(characterName, localizedProfessionName)) do
|
||||
characterSkillSet[skillName] = true
|
||||
end
|
||||
|
||||
local lcSearchQuery = strlower(searchQuery)
|
||||
|
||||
---@type Craft[]
|
||||
local crafts = {}
|
||||
for _, craft in ipairs(self._libCrafts:GetCraftsByProfession(localizedProfessionName)) do
|
||||
if characterSkillSet[craft.localized_spell_name] == nil then
|
||||
local match = false
|
||||
if lcSearchQuery ~= "" then
|
||||
local lcSpellName = strlower(craft.localized_spell_name)
|
||||
local a, b = strfind(lcSpellName, lcSearchQuery, 1, true)
|
||||
match = a ~= nil and b ~= nil
|
||||
else
|
||||
match = true
|
||||
end
|
||||
if match then
|
||||
tinsert(crafts, create(craft, professionRank, self._libCrafts))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return crafts
|
||||
end
|
||||
|
||||
---@param itemId number
|
||||
---@return Craft[]
|
||||
function CraftRepository:FindByRecipeId(itemId)
|
||||
local playerName, _ = UnitName("player")
|
||||
local character = self._characterRepository:Find(playerName)
|
||||
|
||||
---@type Craft[]
|
||||
local crafts = {}
|
||||
for _, craft in ipairs(self._libCrafts:GetCraftsByRecipeId(itemId)) do
|
||||
local professionRank = 0
|
||||
if character ~= nil then
|
||||
professionRank = (--[[---@not nil]] character):GetProfessionRank(craft.localized_profession_name)
|
||||
end
|
||||
tinsert(crafts, create(craft, professionRank, self._libCrafts))
|
||||
end
|
||||
|
||||
return crafts
|
||||
end
|
||||
@@ -0,0 +1,146 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class Database
|
||||
---@field _db DatabaseSavedVariable
|
||||
Database = {}
|
||||
|
||||
---@shape DatabaseProfession
|
||||
---@field rank number
|
||||
---@field knownLocalizedSkillNames string[]
|
||||
|
||||
---@shape DatabaseCharacter
|
||||
---@field realm string
|
||||
---@field english_faction string
|
||||
---@field localized_faction string
|
||||
---@field name string
|
||||
---@field professionsByLocalizedName table<string, DatabaseProfession>
|
||||
|
||||
---@shape DatabaseSavedVariable
|
||||
---@field global {realmToNameToCharacter: table<string, table<string, DatabaseCharacter>>}
|
||||
|
||||
local SAVED_VARIABLE_NAME = "GromissingcraftsDatabase"
|
||||
|
||||
---@return DatabaseCharacter
|
||||
local function newCharacterDefaults()
|
||||
return {
|
||||
realm = "",
|
||||
english_faction = "",
|
||||
localized_faction = "",
|
||||
name = "",
|
||||
professionsByLocalizedName = {},
|
||||
}
|
||||
end
|
||||
|
||||
---@return self
|
||||
function Database:Create()
|
||||
---@type DatabaseSavedVariable
|
||||
local saved = getglobal(SAVED_VARIABLE_NAME)
|
||||
if type(saved) ~= "table" then
|
||||
saved = --[[---@type DatabaseSavedVariable]] {}
|
||||
setglobal(SAVED_VARIABLE_NAME, saved)
|
||||
end
|
||||
if type(saved.global) ~= "table" then
|
||||
saved.global = {realmToNameToCharacter = {}}
|
||||
end
|
||||
if type(saved.global.realmToNameToCharacter) ~= "table" then
|
||||
saved.global.realmToNameToCharacter = {}
|
||||
end
|
||||
|
||||
self._db = saved
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param realm string
|
||||
---@return table<string, DatabaseCharacter>
|
||||
function Database:_GetOrCreateRealm(realm)
|
||||
local realms = self._db.global.realmToNameToCharacter
|
||||
local nameToCharacter = realms[realm]
|
||||
if nameToCharacter == nil then
|
||||
nameToCharacter = {}
|
||||
realms[realm] = nameToCharacter
|
||||
end
|
||||
return nameToCharacter
|
||||
end
|
||||
|
||||
---@param realm string
|
||||
---@param name string
|
||||
---@return DatabaseCharacter
|
||||
function Database:_GetOrCreateCharacter(realm, name)
|
||||
local nameToCharacter = self:_GetOrCreateRealm(realm)
|
||||
local character = nameToCharacter[name]
|
||||
if character == nil then
|
||||
character = newCharacterDefaults()
|
||||
nameToCharacter[name] = character
|
||||
end
|
||||
return character
|
||||
end
|
||||
|
||||
---@return DatabaseCharacter[]
|
||||
function Database:GetCharacters()
|
||||
self:_SavePlayer()
|
||||
|
||||
---@type DatabaseCharacter[]
|
||||
local characters = {}
|
||||
for _, nameToCharacter in pairs(self._db.global.realmToNameToCharacter) do
|
||||
for _, character in pairs(nameToCharacter) do
|
||||
tinsert(characters, character)
|
||||
end
|
||||
end
|
||||
return characters
|
||||
end
|
||||
|
||||
---@param characterName string
|
||||
---@param localizedProfessionName string
|
||||
---@return string[]
|
||||
function Database:GetLocalizedSkillNames(characterName, localizedProfessionName)
|
||||
---@type DatabaseProfession
|
||||
local profession
|
||||
for _, nameToCharacter in pairs(self._db.global.realmToNameToCharacter) do
|
||||
if nameToCharacter[characterName] ~= nil then
|
||||
profession = nameToCharacter[characterName].professionsByLocalizedName[localizedProfessionName]
|
||||
end
|
||||
end
|
||||
return (profession or {}).knownLocalizedSkillNames or {}
|
||||
end
|
||||
|
||||
---@param localizedProfessionName string
|
||||
---@param professionRank number
|
||||
---@param localizedSkillNames string[]
|
||||
function Database:SaveCurrentPlayerSkills(localizedProfessionName, professionRank, localizedSkillNames)
|
||||
local player = self:_SavePlayer()
|
||||
player.professionsByLocalizedName[localizedProfessionName] = {
|
||||
rank = professionRank,
|
||||
knownLocalizedSkillNames = localizedSkillNames
|
||||
}
|
||||
end
|
||||
|
||||
---@param localizedProfessionNames string[]
|
||||
function Database:SaveCurrentPlayerProfessions(localizedProfessionNames)
|
||||
---@type table<string, boolean>
|
||||
local set = {}
|
||||
for _, localizedProfessionName in ipairs(localizedProfessionNames) do
|
||||
set[localizedProfessionName] = true
|
||||
end
|
||||
|
||||
local player = self:_SavePlayer()
|
||||
for localizedProfessionName, _ in pairs(player.professionsByLocalizedName) do
|
||||
if set[localizedProfessionName] == nil then
|
||||
player.professionsByLocalizedName[localizedProfessionName] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@return DatabaseCharacter
|
||||
function Database:_SavePlayer()
|
||||
local realm = GetRealmName()
|
||||
local name, _ = UnitName("player")
|
||||
local english_faction, localized_faction = UnitFactionGroup("player")
|
||||
|
||||
local player = self:_GetOrCreateCharacter(realm, name)
|
||||
player.realm = realm
|
||||
player.english_faction = english_faction
|
||||
player.localized_faction = localized_faction
|
||||
player.name = name
|
||||
return player
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@shape GromissingcraftsLocale
|
||||
---@field recipe_tooltip_already_known string
|
||||
---@field recipe_tooltip_can_learn_now string
|
||||
---@field recipe_tooltip_can_learn_later string
|
||||
---@field craft_tooltip_source string
|
||||
---@field craft_source_chest string
|
||||
---@field craft_source_craft string
|
||||
---@field craft_source_drop string
|
||||
---@field craft_source_fishing string
|
||||
---@field craft_source_gift string
|
||||
---@field craft_source_pickpocketing string
|
||||
---@field craft_source_quest string
|
||||
---@field craft_source_trainer string
|
||||
---@field craft_source_unknown string
|
||||
---@field craft_source_vendor string
|
||||
---@field craft_source_world_object string
|
||||
|
||||
--[[
|
||||
Minimal AceLocale-free replacement. enUS.lua must load first (see the .toc) and
|
||||
always populates every key, acting as the fallback. Every other locale file only
|
||||
receives the shared table (and thus only gets to override keys) when the running
|
||||
client's GetLocale() matches its own code; otherwise NewLocale returns nil and the
|
||||
file's `if L == nil then return end` guard skips it entirely.
|
||||
]]
|
||||
|
||||
---@type GromissingcraftsLocale
|
||||
L = L or --[[---@type GromissingcraftsLocale]] {}
|
||||
|
||||
---@alias LocaleCode "deDE" | "enUS" | "esES" | "frFR" | "koKR" | "ptBR" | "ruRU" | "zhCN" | "zhTW"
|
||||
|
||||
---@param code LocaleCode
|
||||
---@param is_default boolean
|
||||
---@return GromissingcraftsLocale|nil
|
||||
function NewLocale(code, is_default)
|
||||
if is_default then
|
||||
return L
|
||||
end
|
||||
if GetLocale() == code then
|
||||
return L
|
||||
end
|
||||
return nil
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("deDE", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "Bereits bekannt"
|
||||
L.recipe_tooltip_can_learn_now = "Kann jetzt lernen"
|
||||
L.recipe_tooltip_can_learn_later = "Kann später lernen"
|
||||
|
||||
L.craft_tooltip_source = "Quelle"
|
||||
L.craft_source_chest = "Truhe"
|
||||
L.craft_source_craft = "Handwerk"
|
||||
L.craft_source_drop = "Drop"
|
||||
L.craft_source_fishing = "Angeln"
|
||||
L.craft_source_gift = "Geschenk"
|
||||
L.craft_source_pickpocketing = "Taschendiebstahl"
|
||||
L.craft_source_quest = "Quest"
|
||||
L.craft_source_trainer = "Lehrer"
|
||||
L.craft_source_unknown = "Unbekannt"
|
||||
L.craft_source_vendor = "H\195\164ndler"
|
||||
L.craft_source_world_object = "Objekt"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("enUS", true)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "Already known"
|
||||
L.recipe_tooltip_can_learn_now = "Can learn now"
|
||||
L.recipe_tooltip_can_learn_later = "Can learn later"
|
||||
|
||||
L.craft_tooltip_source = "Source"
|
||||
L.craft_source_chest = "Chest"
|
||||
L.craft_source_craft = "Craft"
|
||||
L.craft_source_drop = "Drop"
|
||||
L.craft_source_fishing = "Fishing"
|
||||
L.craft_source_gift = "Gift"
|
||||
L.craft_source_pickpocketing = "Pickpocketing"
|
||||
L.craft_source_quest = "Quest"
|
||||
L.craft_source_trainer = "Trainer"
|
||||
L.craft_source_unknown = "Unknown"
|
||||
L.craft_source_vendor = "Vendor"
|
||||
L.craft_source_world_object = "Object"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("esES", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "Ya conoce"
|
||||
L.recipe_tooltip_can_learn_now = "Puede aprender"
|
||||
L.recipe_tooltip_can_learn_later = "Podrá aprender más tarde"
|
||||
|
||||
L.craft_tooltip_source = "Fuente"
|
||||
L.craft_source_chest = "Cofre"
|
||||
L.craft_source_craft = "Artesanía"
|
||||
L.craft_source_drop = "Botín"
|
||||
L.craft_source_fishing = "Pesca"
|
||||
L.craft_source_gift = "Regalo"
|
||||
L.craft_source_pickpocketing = "Carterista"
|
||||
L.craft_source_quest = "Misión"
|
||||
L.craft_source_trainer = "Instructor"
|
||||
L.craft_source_unknown = "Desconocido"
|
||||
L.craft_source_vendor = "Vendedor"
|
||||
L.craft_source_world_object = "Objeto"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("frFR", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "Déjà connu"
|
||||
L.recipe_tooltip_can_learn_now = "Peut apprendre"
|
||||
L.recipe_tooltip_can_learn_later = "Pourra apprendre plus tard"
|
||||
|
||||
L.craft_tooltip_source = "Source"
|
||||
L.craft_source_chest = "Boîte"
|
||||
L.craft_source_craft = "Artisanat"
|
||||
L.craft_source_drop = "Ramass\195\169"
|
||||
L.craft_source_fishing = "Pêche"
|
||||
L.craft_source_gift = "Cadeau"
|
||||
L.craft_source_pickpocketing = "Vol à la tire"
|
||||
L.craft_source_quest = "Qu\195\170te"
|
||||
L.craft_source_trainer = "Instructeur"
|
||||
L.craft_source_unknown = "Inconnu"
|
||||
L.craft_source_vendor = "Marchand"
|
||||
L.craft_source_world_object = "Objet"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("koKR", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "이미 알고 있음"
|
||||
L.recipe_tooltip_can_learn_now = "지금 배울 수 있음"
|
||||
L.recipe_tooltip_can_learn_later = "나중에 배울 수 있음"
|
||||
|
||||
L.craft_tooltip_source = "소스"
|
||||
L.craft_source_chest = "상자"
|
||||
L.craft_source_craft = "제작"
|
||||
L.craft_source_drop = "드롭"
|
||||
L.craft_source_fishing = "낙시"
|
||||
L.craft_source_gift = "선물"
|
||||
L.craft_source_pickpocketing = "소매치기"
|
||||
L.craft_source_quest = "퀴스트"
|
||||
L.craft_source_trainer = "훈련사"
|
||||
L.craft_source_unknown = "알 수 없음"
|
||||
L.craft_source_vendor = "상인"
|
||||
L.craft_source_world_object = "오브젝트"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("ptBR", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "Já conhece"
|
||||
L.recipe_tooltip_can_learn_now = "Pode aprender"
|
||||
L.recipe_tooltip_can_learn_later = "Poderá aprender depois"
|
||||
|
||||
L.craft_tooltip_source = "Fonte"
|
||||
L.craft_source_chest = "Baú"
|
||||
L.craft_source_craft = "Artesanato"
|
||||
L.craft_source_drop = "Drop"
|
||||
L.craft_source_fishing = "Pesca"
|
||||
L.craft_source_gift = "Presente"
|
||||
L.craft_source_pickpocketing = "Furto"
|
||||
L.craft_source_quest = "Missão"
|
||||
L.craft_source_trainer = "Instrutor"
|
||||
L.craft_source_unknown = "Desconhecido"
|
||||
L.craft_source_vendor = "Vendedor"
|
||||
L.craft_source_world_object = "Objeto"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("ruRU", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "Уже знает"
|
||||
L.recipe_tooltip_can_learn_now = "Может изучить"
|
||||
L.recipe_tooltip_can_learn_later = "Сможет изучить позже"
|
||||
|
||||
L.craft_tooltip_source = "Источник"
|
||||
L.craft_source_chest = "Сундуки"
|
||||
L.craft_source_craft = "Крафт"
|
||||
L.craft_source_drop = "Дроп"
|
||||
L.craft_source_fishing = "Рыбалка"
|
||||
L.craft_source_gift = "Подарок"
|
||||
L.craft_source_pickpocketing = "Воровство"
|
||||
L.craft_source_quest = "Задание"
|
||||
L.craft_source_trainer = "Тренер"
|
||||
L.craft_source_unknown = "Неизвестно"
|
||||
L.craft_source_vendor = "Продавец"
|
||||
L.craft_source_world_object = "Объект"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("zhCN", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "已经学会"
|
||||
L.recipe_tooltip_can_learn_now = "可以学习"
|
||||
L.recipe_tooltip_can_learn_later = "以后可以学习"
|
||||
|
||||
L.craft_tooltip_source = "来源"
|
||||
L.craft_source_chest = "宝箱"
|
||||
L.craft_source_craft = "制作"
|
||||
L.craft_source_drop = "掉落"
|
||||
L.craft_source_fishing = "钓鱼"
|
||||
L.craft_source_gift = "礼物"
|
||||
L.craft_source_pickpocketing = "撓窃"
|
||||
L.craft_source_quest = "任务"
|
||||
L.craft_source_trainer = "训练师"
|
||||
L.craft_source_unknown = "未知"
|
||||
L.craft_source_vendor = "商人"
|
||||
L.craft_source_world_object = "物体"
|
||||
@@ -0,0 +1,21 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
local L = --[[---@type GromissingcraftsLocale]] NewLocale("zhTW", false)
|
||||
if L == nil then return end
|
||||
|
||||
L.recipe_tooltip_already_known = "已經學會"
|
||||
L.recipe_tooltip_can_learn_now = "可以學習"
|
||||
L.recipe_tooltip_can_learn_later = "以後可以學習"
|
||||
|
||||
L.craft_tooltip_source = "來源"
|
||||
L.craft_source_chest = "寶箱"
|
||||
L.craft_source_craft = "製作"
|
||||
L.craft_source_drop = "掉落"
|
||||
L.craft_source_fishing = "釣魚"
|
||||
L.craft_source_gift = "禮物"
|
||||
L.craft_source_pickpocketing = "撓窃"
|
||||
L.craft_source_quest = "任務"
|
||||
L.craft_source_trainer = "訓練師"
|
||||
L.craft_source_unknown = "未知"
|
||||
L.craft_source_vendor = "商人"
|
||||
L.craft_source_world_object = "物體"
|
||||
@@ -0,0 +1,58 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class DataStateManager
|
||||
---@field _craftRepository CraftRepository
|
||||
---@field _updateCrafts fun(crafts: Craft[]): void
|
||||
---@field _selectedCharacter string|nil
|
||||
---@field _selectedProfession string|nil
|
||||
---@field _searchQuery string
|
||||
DataStateManager = {}
|
||||
|
||||
---@param craftRepository CraftRepository
|
||||
---@param updateCrafts fun(crafts: Craft[]): void
|
||||
function DataStateManager:Create(craftRepository, updateCrafts)
|
||||
self._craftRepository = craftRepository
|
||||
self._updateCrafts = updateCrafts
|
||||
return self
|
||||
end
|
||||
|
||||
---@param profession string
|
||||
function DataStateManager:OnWindowOpened(profession)
|
||||
local player, _ = UnitName("player")
|
||||
self._selectedCharacter = player
|
||||
self._selectedProfession = profession
|
||||
self._searchQuery = ""
|
||||
self:UpdateCrafts()
|
||||
end
|
||||
|
||||
function DataStateManager:OnWindowClosed()
|
||||
self._selectedCharacter = nil
|
||||
self._selectedProfession = nil
|
||||
self._searchQuery = ""
|
||||
end
|
||||
|
||||
---@param newProfession string
|
||||
function DataStateManager:OnProfessionSwitchedInFrame(newProfession)
|
||||
self._selectedProfession = newProfession
|
||||
self:UpdateCrafts()
|
||||
end
|
||||
|
||||
---@param profession string
|
||||
function DataStateManager:OnCraftsUpdated(profession)
|
||||
if profession == self._selectedProfession then
|
||||
self:UpdateCrafts()
|
||||
end
|
||||
end
|
||||
|
||||
---@param filters Filters
|
||||
function DataStateManager:OnFiltersChanged(filters)
|
||||
self._searchQuery = filters.searchQuery
|
||||
self:UpdateCrafts()
|
||||
end
|
||||
|
||||
function DataStateManager:UpdateCrafts()
|
||||
if self._selectedCharacter == nil or self._selectedProfession == nil then
|
||||
return
|
||||
end
|
||||
self._updateCrafts(self._craftRepository:FindMissing(self._selectedCharacter, self._selectedProfession, self._searchQuery or ""))
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@type ScriptType[]
|
||||
local SCRIPT_TYPES = {
|
||||
"OnAnimFinished",
|
||||
"OnChar",
|
||||
"OnClick",
|
||||
"OnColorSelect",
|
||||
"OnCursorChanged",
|
||||
"OnDoubleClick",
|
||||
"OnDragStart",
|
||||
"OnDragStop",
|
||||
"OnEditFocusGained",
|
||||
"OnEditFocusLost",
|
||||
"OnEnter",
|
||||
"OnEnterPressed",
|
||||
"OnEscapePressed",
|
||||
"OnEvent",
|
||||
"OnHide",
|
||||
"OnHorizontalScroll",
|
||||
"OnHyperlinkClick",
|
||||
"OnHyperlinkEnter",
|
||||
"OnHyperlinkLeave",
|
||||
"OnInputLanguageChanged",
|
||||
"OnKeyDown",
|
||||
"OnKeyUp",
|
||||
"OnLeave",
|
||||
"OnLoad",
|
||||
"OnMessageScrollChanged",
|
||||
"OnMouseDown",
|
||||
"OnMouseUp",
|
||||
"OnMouseWheel",
|
||||
"OnReceiveDrag",
|
||||
"OnScrollRangeChanged",
|
||||
"OnShow",
|
||||
"OnSizeChanged",
|
||||
"OnSpacePressed",
|
||||
"OnTabPressed",
|
||||
"OnTextChanged",
|
||||
"OnTextSet",
|
||||
"OnTooltipAddMoney",
|
||||
"OnTooltipCleared",
|
||||
"OnTooltipSetDefaultAnchor",
|
||||
"OnUpdate",
|
||||
"OnUpdateModel",
|
||||
"OnValueChanged",
|
||||
"OnVerticalScroll",
|
||||
}
|
||||
|
||||
---@param frame Frame
|
||||
function clearFrame(frame)
|
||||
for _, scriptType in ipairs(SCRIPT_TYPES) do
|
||||
if frame:HasScript(scriptType) then
|
||||
frame:SetScript(scriptType, nil)
|
||||
end
|
||||
end
|
||||
frame:UnregisterAllEvents()
|
||||
frame:ClearAllPoints()
|
||||
frame:Hide()
|
||||
frame:SetParent(nil)
|
||||
end
|
||||
|
||||
---@param frame Frame
|
||||
---@return string
|
||||
function getFrameId(frame)
|
||||
assert(type(frame) == "table")
|
||||
local _, _, address = strfind(tostring(frame), "table: (%x+)")
|
||||
assert(type(address) == "string")
|
||||
return --[[---@type string]] address
|
||||
end
|
||||
|
||||
---@param delayInFrames 0|1
|
||||
---@param callback fun():void
|
||||
function callDelayed(delayInFrames, callback)
|
||||
if delayInFrames == 0 then
|
||||
callback()
|
||||
else
|
||||
local delayFrame = CreateFrame("Frame")
|
||||
local function onUpdate()
|
||||
delayFrame:SetScript("OnUpdate", nil)
|
||||
clearFrame(delayFrame)
|
||||
callback()
|
||||
end
|
||||
delayFrame:SetScript("OnUpdate", onUpdate)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Vanilla 1.12 has no reliable native hooksecurefunc (that's a post-1.12 Blizzard
|
||||
API tied to the secure-execution/taint system Turtle WoW's client may or may
|
||||
not back-port). Replace the target global function with a wrapper that calls
|
||||
our callback and then the original, same technique LibItemTooltip already
|
||||
uses to hook GameTooltip methods.
|
||||
]]
|
||||
---@param funcName string
|
||||
---@param callback fun(...): void
|
||||
function hookGlobalFunction(funcName, callback)
|
||||
local original = getglobal(funcName)
|
||||
if type(original) ~= "function" then
|
||||
return
|
||||
end
|
||||
|
||||
setglobal(funcName, function()
|
||||
callback(unpack(arg))
|
||||
return original(unpack(arg))
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,149 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@alias RecipeStatus "IsLearned" | "CanLearnNow" | "CanLearnLater" | "CannotLearn"
|
||||
|
||||
---@type table<RecipeStatus, RecipeStatus>
|
||||
local RecipeStatus = {
|
||||
IsLearned = "IsLearned",
|
||||
CanLearnNow = "CanLearnNow",
|
||||
CanLearnLater = "CanLearnLater",
|
||||
CannotLearn = "CannotLearn",
|
||||
}
|
||||
|
||||
---@shape TooltipItem
|
||||
---@field characterName string
|
||||
---@field characterProfessionRank number
|
||||
---@field skillLevel number
|
||||
---@field status RecipeStatus
|
||||
|
||||
---@class TooltipEnhancer
|
||||
---@field _craftRepository CraftRepository
|
||||
---@field _characterRepository CharacterRepository
|
||||
---@field _locale GromissingcraftsLocale
|
||||
TooltipEnhancer = {}
|
||||
|
||||
---@param craftRepository CraftRepository
|
||||
---@param characterRepository CharacterRepository
|
||||
---@param locale GromissingcraftsLocale
|
||||
---@param LibItemTooltip LibItemTooltip
|
||||
---@return self
|
||||
function TooltipEnhancer:Create(craftRepository, characterRepository, locale, LibItemTooltip)
|
||||
self._craftRepository = craftRepository
|
||||
self._characterRepository = characterRepository
|
||||
self._locale = locale
|
||||
|
||||
LibItemTooltip:RegisterEvent("OnShow", function(tooltip, itemLink, itemId)
|
||||
if self:EnhanceTooltip(tooltip, itemId) then
|
||||
tooltip:Show()
|
||||
end
|
||||
end)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param tooltip GameTooltip
|
||||
---@param itemId number
|
||||
---@return boolean
|
||||
function TooltipEnhancer:EnhanceTooltip(tooltip, itemId)
|
||||
local items = self:CreateItems(itemId)
|
||||
items = self:FilterItems(items)
|
||||
if getn(items) == 0 then
|
||||
return false
|
||||
end
|
||||
self:SortItems(items)
|
||||
self:DrawItems(tooltip, items)
|
||||
return true
|
||||
end
|
||||
|
||||
---@param itemId number
|
||||
---@return TooltipItem[]
|
||||
function TooltipEnhancer:CreateItems(itemId)
|
||||
local crafts = self._craftRepository:FindByRecipeId(itemId)
|
||||
if getn(crafts) == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Assume that if single recipe teaches multiple spells that they are all have the same requirements.
|
||||
local craft = crafts[1]
|
||||
|
||||
local items = {}
|
||||
local player, _ = UnitName("player")
|
||||
for _, character in ipairs(self._characterRepository:FindAll(player)) do
|
||||
tinsert(items, {
|
||||
characterName = character.name,
|
||||
characterProfessionRank = character:GetProfessionRank(craft.localizedProfessionName),
|
||||
skillLevel = craft.skillLevel,
|
||||
status = self:GetRecipeStatus(character, craft),
|
||||
})
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
---@param items TooltipItem[]
|
||||
---@return TooltipItem[]
|
||||
function TooltipEnhancer:FilterItems(items)
|
||||
local new_items = {}
|
||||
for _, item in ipairs(items) do
|
||||
if item.characterProfessionRank > 0 and item.status ~= RecipeStatus.CannotLearn then
|
||||
tinsert(new_items, item)
|
||||
end
|
||||
end
|
||||
return new_items
|
||||
end
|
||||
|
||||
---@param items TooltipItem[]
|
||||
function TooltipEnhancer:SortItems(items)
|
||||
table.sort(items, function(a, b)
|
||||
return a.characterName < b.characterName
|
||||
end)
|
||||
end
|
||||
|
||||
---@param tooltip GameTooltip
|
||||
---@param items TooltipItem[]
|
||||
function TooltipEnhancer:DrawItems(tooltip, items)
|
||||
---@type table<RecipeStatus, string>
|
||||
local statusToColor = {
|
||||
[RecipeStatus.IsLearned] = "|cff808080",
|
||||
[RecipeStatus.CanLearnNow] = "|cff40bf40",
|
||||
[RecipeStatus.CanLearnLater] = "|cffff8040",
|
||||
}
|
||||
|
||||
---@type table<RecipeStatus, string>
|
||||
local statusToText = {
|
||||
[RecipeStatus.IsLearned] = self._locale.recipe_tooltip_already_known,
|
||||
[RecipeStatus.CanLearnNow] = self._locale.recipe_tooltip_can_learn_now,
|
||||
[RecipeStatus.CanLearnLater] = self._locale.recipe_tooltip_can_learn_later,
|
||||
}
|
||||
|
||||
tooltip:AddLine(" ")
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local color = statusToColor[item.status]
|
||||
local message = statusToText[item.status]
|
||||
|
||||
local leftText = format("%s%s|r", color, message)
|
||||
local rightText = format("%s%s (%d)|r", color, item.characterName, item.characterProfessionRank)
|
||||
|
||||
tooltip:AddDoubleLine(leftText, rightText)
|
||||
end
|
||||
end
|
||||
|
||||
---@param character Character
|
||||
---@param craft Craft
|
||||
---@return RecipeStatus
|
||||
function TooltipEnhancer:GetRecipeStatus(character, craft)
|
||||
if character:Knows(craft) then
|
||||
return RecipeStatus.IsLearned
|
||||
end
|
||||
|
||||
if character:CanLearnNow(craft) then
|
||||
return RecipeStatus.CanLearnNow
|
||||
end
|
||||
|
||||
if character:GetProfessionRank(craft.localizedProfessionName) > 0 then
|
||||
return RecipeStatus.CanLearnLater
|
||||
end
|
||||
|
||||
return RecipeStatus.CannotLearn
|
||||
end
|
||||
@@ -0,0 +1,206 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@shape ProfessionFrame
|
||||
---@field id string
|
||||
---@field type LcpProfessionFrameType
|
||||
---@field profession string
|
||||
---@field widget Frame
|
||||
|
||||
---@shape WindowState
|
||||
---@field widget Window
|
||||
---@field opened boolean
|
||||
---@field frameId string|nil
|
||||
|
||||
---@class UIManager
|
||||
---@field _addonInfo AddonInfo
|
||||
---@field _repos Repositories
|
||||
---@field _locale GromissingcraftsLocale
|
||||
---@field _placementPolicy PlacementPolicy
|
||||
---@field _vanillaFramePool VanillaFramePool
|
||||
---@field _buttonsByFrameId table<string, OpenButton>
|
||||
---@field _windowState WindowState
|
||||
---@field _filtersPanel FiltersPanel
|
||||
---@field _craftsList CraftsList
|
||||
---@field _dataStateManager DataStateManager
|
||||
UIManager = {}
|
||||
|
||||
---@param addonInfo AddonInfo
|
||||
---@param repositories Repositories
|
||||
---@param locale GromissingcraftsLocale
|
||||
---@param LibItemTooltip LibItemTooltip
|
||||
---@return self
|
||||
function UIManager:Create(addonInfo, repositories, locale, LibItemTooltip)
|
||||
self._addonInfo = addonInfo
|
||||
self._repos = repositories
|
||||
self._locale = locale
|
||||
self._placementPolicy = PlacementPolicy
|
||||
self._vanillaFramePool = VanillaFramePool:Create()
|
||||
self._tooltipEnhancer = TooltipEnhancer:Create(self._repos.craftRepository, self._repos.characterRepository, locale, LibItemTooltip)
|
||||
self._buttonsByFrameId = {}
|
||||
|
||||
---@param crafts Craft[]
|
||||
local function updateCrafts(crafts)
|
||||
if self._craftsList ~= nil then
|
||||
self._craftsList:PopulateInterface(crafts)
|
||||
end
|
||||
end
|
||||
|
||||
self._dataStateManager = DataStateManager:Create(self._repos.craftRepository, updateCrafts)
|
||||
|
||||
hookGlobalFunction("MovePanelToLeft", function() self:UpdateGeometry() end)
|
||||
hookGlobalFunction("MovePanelToCenter", function() self:UpdateGeometry() end)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param profession string
|
||||
function UIManager:OnDataUpdated(profession)
|
||||
self._dataStateManager:OnCraftsUpdated(profession)
|
||||
end
|
||||
|
||||
---@param profession string
|
||||
---@param frame Frame
|
||||
---@param frameType LcpProfessionFrameType
|
||||
function UIManager:OnProfessionFrameOpened(profession, frame, frameType)
|
||||
---@type ProfessionFrame
|
||||
local professionFrame = {
|
||||
id = getFrameId(frame),
|
||||
type = frameType,
|
||||
profession = profession,
|
||||
widget = frame,
|
||||
}
|
||||
|
||||
if self:IsWindowAttachedTo(professionFrame.id) then
|
||||
self._dataStateManager:OnProfessionSwitchedInFrame(profession)
|
||||
else
|
||||
self:DestroyButton(professionFrame.id)
|
||||
self:CreateButton(professionFrame)
|
||||
end
|
||||
|
||||
self:UpdateGeometry()
|
||||
end
|
||||
|
||||
---@param frame Frame
|
||||
---@param frameType LcpProfessionFrameType
|
||||
function UIManager:OnProfessionFrameClosed(frame, frameType)
|
||||
local frameId = getFrameId(frame)
|
||||
self:DestroyButton(frameId)
|
||||
if self:IsWindowAttachedTo(frameId) then
|
||||
self:CloseWindow()
|
||||
else
|
||||
self:UpdateGeometry()
|
||||
end
|
||||
end
|
||||
|
||||
---@param frame ProfessionFrame
|
||||
function UIManager:OnWindowOpened(frame)
|
||||
self._windowState.opened = true
|
||||
self._windowState.frameId = frame.id
|
||||
if self._filtersPanel ~= nil then
|
||||
self._filtersPanel:Clear()
|
||||
end
|
||||
self._dataStateManager:OnWindowOpened(frame.profession)
|
||||
self:CheckButtons()
|
||||
end
|
||||
|
||||
function UIManager:OnWindowClosed()
|
||||
self._windowState.opened = false
|
||||
self._windowState.frameId = nil
|
||||
self._dataStateManager:OnWindowClosed()
|
||||
self:CheckButtons()
|
||||
end
|
||||
|
||||
---@param frame ProfessionFrame
|
||||
function UIManager:OnButtonClicked(frame)
|
||||
if self:IsWindowAttachedTo(frame.id) then
|
||||
self:CloseWindow()
|
||||
else
|
||||
self:OpenWindow(frame)
|
||||
end
|
||||
end
|
||||
|
||||
---@param professionFrame ProfessionFrame
|
||||
function UIManager:CreateButton(professionFrame)
|
||||
local FrameType = LibCraftingProfessionsConstants.FrameType
|
||||
|
||||
---@type 0|1
|
||||
local delayInFrames = 0
|
||||
if professionFrame.type == FrameType.AdvancedTradeSkillWindow2 or professionFrame.type == FrameType.Artisan then
|
||||
delayInFrames = 1 -- Work around async window initialization behavior
|
||||
end
|
||||
|
||||
local onClick = function()
|
||||
self:OnButtonClicked(professionFrame)
|
||||
end
|
||||
|
||||
callDelayed(delayInFrames, function()
|
||||
self._buttonsByFrameId[professionFrame.id] = OpenButton:Create(onClick, professionFrame, self._placementPolicy)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param frameId string
|
||||
function UIManager:DestroyButton(frameId)
|
||||
local button = tpop(self._buttonsByFrameId, frameId)
|
||||
if button ~= nil then
|
||||
(--[[---@not nil]] button):Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
function UIManager:CheckButtons()
|
||||
local activeId
|
||||
if self._windowState ~= nil and self._windowState.opened then
|
||||
activeId = self._windowState.frameId
|
||||
end
|
||||
for frameId, button in pairs(self._buttonsByFrameId) do
|
||||
button:SetChecked(frameId == activeId)
|
||||
end
|
||||
end
|
||||
|
||||
---@param frame ProfessionFrame
|
||||
function UIManager:OpenWindow(frame)
|
||||
if self:IsWindowAttachedTo(frame.id) then
|
||||
return
|
||||
end
|
||||
|
||||
if self._windowState == nil then
|
||||
local onClose = function()
|
||||
self:OnWindowClosed()
|
||||
end
|
||||
|
||||
local filtersPanel = FiltersPanel:Create()
|
||||
local craftsList = CraftsList:Create(self._locale, self._vanillaFramePool)
|
||||
local window = Window:Create(self._addonInfo, onClose, filtersPanel, craftsList, frame, self._placementPolicy)
|
||||
|
||||
filtersPanel:OnChange(function(filters)
|
||||
self._dataStateManager:OnFiltersChanged(filters)
|
||||
end)
|
||||
|
||||
self._windowState = {
|
||||
widget = window,
|
||||
frameId = frame.id,
|
||||
opened = true
|
||||
}
|
||||
self._filtersPanel = filtersPanel
|
||||
self._craftsList = craftsList
|
||||
end
|
||||
|
||||
self._windowState.widget:Show(frame)
|
||||
self:OnWindowOpened(frame)
|
||||
end
|
||||
|
||||
function UIManager:CloseWindow()
|
||||
self._windowState.widget:Hide()
|
||||
self:OnWindowClosed()
|
||||
end
|
||||
|
||||
---@param frameId string
|
||||
---@return boolean
|
||||
function UIManager:IsWindowAttachedTo(frameId)
|
||||
return self._windowState ~= nil and self._windowState.opened and self._windowState.frameId == frameId
|
||||
end
|
||||
|
||||
function UIManager:UpdateGeometry()
|
||||
if self._windowState ~= nil and self._windowState.opened then
|
||||
self._windowState.widget:UpdateGeometry()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class VanillaFramePool
|
||||
---@field _framesByType table<string, Frame[]>
|
||||
VanillaFramePool = {}
|
||||
|
||||
local RELEASED_FIELD = "__released_to_pool__"
|
||||
|
||||
---@return self
|
||||
function VanillaFramePool:Create()
|
||||
local object = {}
|
||||
setmetatable(object, {__index = VanillaFramePool})
|
||||
|
||||
local result = --[[---@type self]] object
|
||||
result._framesByType = {}
|
||||
return result
|
||||
end
|
||||
|
||||
function VanillaFramePool:Destroy()
|
||||
for _, pool in pairs(self._framesByType or {}) do
|
||||
erase(pool)
|
||||
end
|
||||
erase(self)
|
||||
end
|
||||
|
||||
---@param frameType "Frame"
|
||||
---@return Frame
|
||||
---@overload fun(frameType: "Button"): Button
|
||||
function VanillaFramePool:Acquire(frameType)
|
||||
local pool = self._framesByType[frameType] or {}
|
||||
|
||||
local frame
|
||||
if next(pool) == nil then
|
||||
frame = CreateFrame(frameType, nil, nil, nil)
|
||||
else
|
||||
frame = tremove(pool)
|
||||
local frameAsTable = --[[---@type table<string, any>]] frame
|
||||
frameAsTable[RELEASED_FIELD] = false
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
---@param frame Frame
|
||||
function VanillaFramePool:Release(frame)
|
||||
local frameAsTable = --[[---@type table<string, any>]] frame
|
||||
if frameAsTable[RELEASED_FIELD] then
|
||||
return
|
||||
end
|
||||
|
||||
local frameType = frame:GetFrameType()
|
||||
|
||||
local pool = self._framesByType[frameType]
|
||||
if pool == nil then
|
||||
pool = {}
|
||||
self._framesByType[frameType] = pool
|
||||
end
|
||||
|
||||
for _, child in ipairs({frame:GetChildren()}) do
|
||||
self:Release(child)
|
||||
end
|
||||
|
||||
clearFrame(frame)
|
||||
|
||||
frameAsTable[RELEASED_FIELD] = true
|
||||
tinsert(pool, frame)
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
--[[
|
||||
Search-only now - the profession/character dropdowns were removed. The
|
||||
window is already scoped to whichever profession frame it's attached to and
|
||||
always shows the current logged-in character, so nothing is lost: this panel
|
||||
just holds the recipe-name search box.
|
||||
]]
|
||||
|
||||
---@class FiltersPanel
|
||||
---@field _frame Frame
|
||||
---@field _searchField SearchField
|
||||
---@field _onChange fun()
|
||||
FiltersPanel = {}
|
||||
|
||||
---@shape Filters
|
||||
---@field searchQuery string
|
||||
|
||||
local PANEL_WIDTH = 352
|
||||
local PANEL_HEIGHT = 24
|
||||
local SEARCH_FIELD_MARGIN = 8
|
||||
|
||||
FiltersPanel.WIDTH = PANEL_WIDTH
|
||||
FiltersPanel.HEIGHT = PANEL_HEIGHT
|
||||
|
||||
---@return self
|
||||
function FiltersPanel:Create()
|
||||
local panel = --[[---@type self]] {}
|
||||
setmetatable(panel, {__index = FiltersPanel})
|
||||
|
||||
local frame = CreateFrame("Frame", nil, nil)
|
||||
frame:SetWidth(PANEL_WIDTH)
|
||||
frame:SetHeight(PANEL_HEIGHT)
|
||||
frame:SetFrameStrata("DIALOG")
|
||||
|
||||
local searchField = SearchField:Create(PANEL_WIDTH - (2 * SEARCH_FIELD_MARGIN), function()
|
||||
if panel._onChange ~= nil then
|
||||
panel._onChange()
|
||||
end
|
||||
end)
|
||||
|
||||
local searchFieldFrame = searchField:GetFrame()
|
||||
searchFieldFrame:SetParent(frame)
|
||||
searchFieldFrame:ClearAllPoints()
|
||||
searchFieldFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", SEARCH_FIELD_MARGIN, 0)
|
||||
searchFieldFrame:SetFrameStrata("DIALOG")
|
||||
|
||||
panel._frame = frame
|
||||
panel._searchField = searchField
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
---@return Frame
|
||||
function FiltersPanel:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
---@param callback fun(filters: Filters)
|
||||
function FiltersPanel:OnChange(callback)
|
||||
self._onChange = function()
|
||||
callback(self:GetFilters())
|
||||
end
|
||||
end
|
||||
|
||||
---@return Filters
|
||||
function FiltersPanel:GetFilters()
|
||||
return {
|
||||
searchQuery = self._searchField:GetText(),
|
||||
}
|
||||
end
|
||||
|
||||
function FiltersPanel:Clear()
|
||||
self._searchField:SetText("")
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
--[[
|
||||
Not InputBoxTemplate - same reasoning as Dropdown.lua: pfUI (and similar
|
||||
skinning addons) appear to hook known Blizzard template names and that broke
|
||||
click/focus handling for other widgets here. A bare EditBox with a manual
|
||||
backdrop avoids giving it anything to key off of.
|
||||
]]
|
||||
|
||||
---@class SearchField
|
||||
---@field _frame EditBox
|
||||
---@field _text string
|
||||
SearchField = {}
|
||||
|
||||
---@param width number pixel width
|
||||
---@param onChange fun()
|
||||
---@return self
|
||||
function SearchField:Create(width, onChange)
|
||||
local object = --[[---@type self]] {}
|
||||
setmetatable(object, {__index = SearchField})
|
||||
|
||||
local frame = CreateFrame("EditBox", nil, nil)
|
||||
frame:SetWidth(width)
|
||||
frame:SetHeight(20)
|
||||
frame:SetFrameStrata("DIALOG")
|
||||
frame:SetAutoFocus(false)
|
||||
frame:SetMaxLetters(250)
|
||||
frame:EnableMouse(true)
|
||||
|
||||
frame:SetBackdrop({
|
||||
bgFile = [[Interface\Tooltips\UI-Tooltip-Background]],
|
||||
edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
|
||||
tile = true,
|
||||
tileSize = 16,
|
||||
edgeSize = 12,
|
||||
insets = {left = 3, right = 3, top = 3, bottom = 3},
|
||||
})
|
||||
frame:SetBackdropColor(0, 0, 0, 0.8)
|
||||
|
||||
frame:SetFontObject(GameFontHighlightSmall)
|
||||
frame:SetTextInsets(6, 6, 0, 0)
|
||||
frame:SetJustifyH("LEFT")
|
||||
|
||||
frame:SetScript("OnTextChanged", function()
|
||||
object._text = frame:GetText()
|
||||
onChange()
|
||||
end)
|
||||
frame:SetScript("OnEscapePressed", function()
|
||||
frame:ClearFocus()
|
||||
end)
|
||||
frame:SetScript("OnEnterPressed", function()
|
||||
frame:ClearFocus()
|
||||
end)
|
||||
frame:SetScript("OnMouseDown", function()
|
||||
frame:SetFocus()
|
||||
end)
|
||||
|
||||
object._frame = frame
|
||||
object._text = ""
|
||||
return object
|
||||
end
|
||||
|
||||
---@return Frame
|
||||
function SearchField:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
---@return string
|
||||
function SearchField:GetText()
|
||||
return self._text
|
||||
end
|
||||
|
||||
---@param text string
|
||||
function SearchField:SetText(text)
|
||||
self._frame:SetText(text)
|
||||
self._text = text
|
||||
end
|
||||
@@ -0,0 +1,282 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
--[[
|
||||
Fixed pool of row widgets sized to exactly fill the visible area (see the
|
||||
earlier note in git history / CHANGES about why - a real ScrollFrame wasn't
|
||||
clipping content reliably on this client). Scrolling changes which slice of
|
||||
the data those rows display; the scrollbar here is a fully custom, drag/click
|
||||
capable widget built the same template-free way as Dropdown.lua, and every
|
||||
piece gets an explicit "DIALOG" strata - strata is NOT inherited from a
|
||||
parent frame in this client, so anything left unset can lose input priority
|
||||
to whatever else is on screen at that spot.
|
||||
]]
|
||||
|
||||
---@class CraftsList
|
||||
---@field _frame Frame
|
||||
---@field _rows Frame
|
||||
---@field _track Frame
|
||||
---@field _thumb Button
|
||||
---@field _locale GromissingcraftsLocale
|
||||
---@field _framePool VanillaFramePool
|
||||
---@field _items CraftsListItem[]
|
||||
---@field _crafts Craft[]
|
||||
---@field _scrollOffset number
|
||||
---@field _visibleRowCount number
|
||||
CraftsList = {}
|
||||
|
||||
local CONTENT_WIDTH = 352
|
||||
local SCROLLBAR_WIDTH = 24 -- 1.5x the original 16px
|
||||
local SCROLLBAR_GAP = 4
|
||||
local ROW_WIDTH = CONTENT_WIDTH - SCROLLBAR_WIDTH - SCROLLBAR_GAP
|
||||
local ROW_HEIGHT = 16
|
||||
|
||||
---@return number
|
||||
local function getCursorY()
|
||||
local _, y = GetCursorPosition()
|
||||
return y / UIParent:GetEffectiveScale()
|
||||
end
|
||||
|
||||
---@param locale GromissingcraftsLocale
|
||||
---@param vanillaFramePool VanillaFramePool
|
||||
---@return CraftsList
|
||||
function CraftsList:Create(locale, vanillaFramePool)
|
||||
local list = --[[---@type self]] {}
|
||||
setmetatable(list, {__index = CraftsList})
|
||||
|
||||
local frame = CreateFrame("Frame", nil, nil)
|
||||
frame:SetWidth(CONTENT_WIDTH)
|
||||
frame:SetFrameStrata("DIALOG")
|
||||
frame:EnableMouseWheel(true)
|
||||
frame:SetScript("OnMouseWheel", function()
|
||||
list:Scroll(-arg1)
|
||||
end)
|
||||
|
||||
local rows = CreateFrame("Frame", nil, frame)
|
||||
rows:SetWidth(ROW_WIDTH)
|
||||
rows:SetFrameStrata("DIALOG")
|
||||
rows:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
|
||||
|
||||
local upButton = CreateFrame("Button", nil, frame)
|
||||
upButton:SetWidth(SCROLLBAR_WIDTH)
|
||||
upButton:SetHeight(SCROLLBAR_WIDTH)
|
||||
upButton:SetFrameStrata("DIALOG")
|
||||
upButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
|
||||
local upTexture = upButton:CreateTexture()
|
||||
upTexture:SetTexture([[Interface\Buttons\UI-ScrollBar-ScrollUpButton-Up]])
|
||||
upTexture:SetAllPoints(upButton)
|
||||
upButton:SetNormalTexture(upTexture)
|
||||
local upHighlight = upButton:CreateTexture()
|
||||
upHighlight:SetTexture([[Interface\Buttons\UI-ScrollBar-ScrollUpButton-Highlight]])
|
||||
upHighlight:SetAllPoints(upButton)
|
||||
upHighlight:SetBlendMode("ADD")
|
||||
upButton:SetHighlightTexture(upHighlight)
|
||||
upButton:SetScript("OnClick", function() list:Scroll(-1) end)
|
||||
|
||||
local downButton = CreateFrame("Button", nil, frame)
|
||||
downButton:SetWidth(SCROLLBAR_WIDTH)
|
||||
downButton:SetHeight(SCROLLBAR_WIDTH)
|
||||
downButton:SetFrameStrata("DIALOG")
|
||||
downButton:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
|
||||
local downTexture = downButton:CreateTexture()
|
||||
downTexture:SetTexture([[Interface\Buttons\UI-ScrollBar-ScrollDownButton-Up]])
|
||||
downTexture:SetAllPoints(downButton)
|
||||
downButton:SetNormalTexture(downTexture)
|
||||
local downHighlight = downButton:CreateTexture()
|
||||
downHighlight:SetTexture([[Interface\Buttons\UI-ScrollBar-ScrollDownButton-Highlight]])
|
||||
downHighlight:SetAllPoints(downButton)
|
||||
downHighlight:SetBlendMode("ADD")
|
||||
downButton:SetHighlightTexture(downHighlight)
|
||||
downButton:SetScript("OnClick", function() list:Scroll(1) end)
|
||||
|
||||
local track = CreateFrame("Frame", nil, frame)
|
||||
track:SetWidth(SCROLLBAR_WIDTH)
|
||||
track:SetFrameStrata("DIALOG")
|
||||
track:SetPoint("TOP", upButton, "BOTTOM", 0, 0)
|
||||
track:SetPoint("BOTTOM", downButton, "TOP", 0, 0)
|
||||
local trackTexture = track:CreateTexture()
|
||||
trackTexture:SetTexture([[Interface\Buttons\UI-ScrollBar-Track]])
|
||||
trackTexture:SetAllPoints(track)
|
||||
|
||||
local thumb = CreateFrame("Button", nil, track)
|
||||
thumb:SetWidth(SCROLLBAR_WIDTH - 2)
|
||||
thumb:SetFrameStrata("DIALOG")
|
||||
thumb:EnableMouse(true)
|
||||
local thumbTexture = thumb:CreateTexture()
|
||||
thumbTexture:SetTexture([[Interface\Buttons\UI-ScrollBar-Knob]])
|
||||
thumbTexture:SetAllPoints(thumb)
|
||||
thumb:SetNormalTexture(thumbTexture)
|
||||
thumb:Hide()
|
||||
|
||||
list._frame = frame
|
||||
list._rows = rows
|
||||
list._track = track
|
||||
list._thumb = thumb
|
||||
list._locale = locale
|
||||
list._framePool = vanillaFramePool
|
||||
list._items = {}
|
||||
list._crafts = {}
|
||||
list._scrollOffset = 0
|
||||
list._visibleRowCount = 0
|
||||
|
||||
thumb:SetScript("OnMouseDown", function()
|
||||
thumb:SetScript("OnUpdate", function()
|
||||
if not IsMouseButtonDown("LeftButton") then
|
||||
thumb:SetScript("OnUpdate", nil)
|
||||
return
|
||||
end
|
||||
list:_DragThumbTo(getCursorY())
|
||||
end)
|
||||
end)
|
||||
thumb:SetScript("OnMouseUp", function()
|
||||
thumb:SetScript("OnUpdate", nil)
|
||||
end)
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
---@return Frame
|
||||
function CraftsList:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
---@param height number
|
||||
function CraftsList:SetHeight(height)
|
||||
self._frame:SetHeight(height)
|
||||
self._rows:SetHeight(height)
|
||||
self._visibleRowCount = math.max(1, math.floor(height / ROW_HEIGHT))
|
||||
self:_EnsureItemPool()
|
||||
self:_Render()
|
||||
end
|
||||
|
||||
function CraftsList:_EnsureItemPool()
|
||||
---@type Frame
|
||||
local anchor = self._rows
|
||||
for i = 1, self._visibleRowCount do
|
||||
if self._items[i] == nil then
|
||||
local item = CraftsListItem:Create(ROW_WIDTH, ROW_HEIGHT, self._locale, self._framePool)
|
||||
item:GetFrame():SetFrameStrata("DIALOG")
|
||||
item:Attach(self._rows, anchor, "TOPLEFT", i == 1 and "TOPLEFT" or "BOTTOMLEFT", 0, 0)
|
||||
self._items[i] = item
|
||||
end
|
||||
anchor = self._items[i]:GetFrame()
|
||||
end
|
||||
for i = getn(self._items), self._visibleRowCount + 1, -1 do
|
||||
local item = tremove(self._items, i)
|
||||
item:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
---@param delta number
|
||||
function CraftsList:Scroll(delta)
|
||||
self._scrollOffset = self._scrollOffset + delta
|
||||
self:_Render()
|
||||
end
|
||||
|
||||
---@param cursorY number
|
||||
function CraftsList:_DragThumbTo(cursorY)
|
||||
local maxOffset = getn(self._crafts) - self._visibleRowCount
|
||||
if maxOffset <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local trackTop = self._track:GetTop()
|
||||
local trackHeight = self._track:GetHeight()
|
||||
local thumbHeight = self._thumb:GetHeight()
|
||||
local travel = trackHeight - thumbHeight
|
||||
if travel <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local distanceFromTop = trackTop - cursorY - (thumbHeight / 2)
|
||||
local progress = distanceFromTop / travel
|
||||
if progress < 0 then
|
||||
progress = 0
|
||||
elseif progress > 1 then
|
||||
progress = 1
|
||||
end
|
||||
|
||||
self._scrollOffset = math.floor((progress * maxOffset) + 0.5)
|
||||
self:_Render()
|
||||
end
|
||||
|
||||
---@param a Craft
|
||||
---@param b Craft
|
||||
---@return boolean
|
||||
local function compareCrafts(a, b)
|
||||
if a.skillLevel ~= b.skillLevel then
|
||||
return a.skillLevel < b.skillLevel
|
||||
end
|
||||
return a.localizedName < b.localizedName
|
||||
end
|
||||
|
||||
---@param crafts Craft[]
|
||||
function CraftsList:PopulateInterface(crafts)
|
||||
table.sort(crafts, compareCrafts)
|
||||
self._crafts = crafts
|
||||
self._scrollOffset = 0
|
||||
self:_Render()
|
||||
end
|
||||
|
||||
function CraftsList:_Render()
|
||||
local craftCount = getn(self._crafts)
|
||||
local maxOffset = craftCount - self._visibleRowCount
|
||||
if maxOffset < 0 then
|
||||
maxOffset = 0
|
||||
end
|
||||
if self._scrollOffset > maxOffset then
|
||||
self._scrollOffset = maxOffset
|
||||
end
|
||||
if self._scrollOffset < 0 then
|
||||
self._scrollOffset = 0
|
||||
end
|
||||
|
||||
---@param clickedItem CraftsListItem
|
||||
local highlightItem = function(clickedItem)
|
||||
for _, otherItem in ipairs(self._items) do
|
||||
otherItem:SetHighlight(otherItem == clickedItem)
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, self._visibleRowCount do
|
||||
local item = self._items[i]
|
||||
if item ~= nil then
|
||||
local craft = self._crafts[self._scrollOffset + i]
|
||||
if craft ~= nil then
|
||||
item:SetHighlight(false)
|
||||
item:OnClick(highlightItem)
|
||||
item:PopulateInterface(craft)
|
||||
item:GetFrame():Show()
|
||||
else
|
||||
item:GetFrame():Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:_UpdateThumb(maxOffset, craftCount)
|
||||
end
|
||||
|
||||
---@param maxOffset number
|
||||
---@param craftCount number
|
||||
function CraftsList:_UpdateThumb(maxOffset, craftCount)
|
||||
if maxOffset <= 0 then
|
||||
self._thumb:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
local trackHeight = self._track:GetHeight()
|
||||
local thumbHeight = trackHeight * (self._visibleRowCount / craftCount)
|
||||
if thumbHeight < 10 then
|
||||
thumbHeight = 10
|
||||
end
|
||||
if thumbHeight > trackHeight then
|
||||
thumbHeight = trackHeight
|
||||
end
|
||||
|
||||
local travel = trackHeight - thumbHeight
|
||||
local progress = self._scrollOffset / maxOffset
|
||||
|
||||
self._thumb:SetHeight(thumbHeight)
|
||||
self._thumb:ClearAllPoints()
|
||||
self._thumb:SetPoint("TOP", self._track, "TOP", 0, -(progress * travel))
|
||||
self._thumb:Show()
|
||||
end
|
||||
@@ -0,0 +1,156 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class CraftsListItem
|
||||
---@field _locale GromissingcraftsLocale
|
||||
---@field _framePool VanillaFramePool
|
||||
---@field _button Button
|
||||
---@field _fontString FontString
|
||||
---@field _craft Craft
|
||||
CraftsListItem = {}
|
||||
|
||||
---@type table<CraftSource, string>
|
||||
local SOURCE_TO_TEXT = {}
|
||||
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@param locale GromissingcraftsLocale
|
||||
---@param framePool VanillaFramePool
|
||||
---@return self
|
||||
function CraftsListItem:Create(width, height, locale, framePool)
|
||||
local button = framePool:Acquire("Button")
|
||||
button:SetWidth(width)
|
||||
button:SetHeight(height)
|
||||
button:SetTextFontObject(GameFontNormal)
|
||||
button:SetHighlightFontObject(GameFontHighlight)
|
||||
button:SetText("")
|
||||
|
||||
local fontString = button:GetFontString()
|
||||
fontString:SetPoint("TOPLEFT", 0, 0)
|
||||
fontString:SetJustifyH("LEFT")
|
||||
|
||||
if next(SOURCE_TO_TEXT) == nil then
|
||||
SOURCE_TO_TEXT[CraftSource.Chest] = locale.craft_source_chest
|
||||
SOURCE_TO_TEXT[CraftSource.Craft] = locale.craft_source_craft
|
||||
SOURCE_TO_TEXT[CraftSource.Drop] = locale.craft_source_drop
|
||||
SOURCE_TO_TEXT[CraftSource.Fishing] = locale.craft_source_fishing
|
||||
SOURCE_TO_TEXT[CraftSource.Gift] = locale.craft_source_gift
|
||||
SOURCE_TO_TEXT[CraftSource.Pickpocketing] = locale.craft_source_pickpocketing
|
||||
SOURCE_TO_TEXT[CraftSource.Quest] = locale.craft_source_quest
|
||||
SOURCE_TO_TEXT[CraftSource.Trainer] = locale.craft_source_trainer
|
||||
SOURCE_TO_TEXT[CraftSource.Vendor] = locale.craft_source_vendor
|
||||
SOURCE_TO_TEXT[CraftSource.WorldObject] = locale.craft_source_world_object
|
||||
SOURCE_TO_TEXT[CraftSource.Unknown] = locale.craft_source_unknown
|
||||
end
|
||||
|
||||
local object = {}
|
||||
setmetatable(object, {__index = CraftsListItem})
|
||||
|
||||
local result = --[[---@type self]] object
|
||||
result._locale = locale
|
||||
result._framePool = framePool
|
||||
result._button = button
|
||||
result._fontString = fontString
|
||||
|
||||
button:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(button, "ANCHOR_TOPRIGHT")
|
||||
if result:_DrawTooltip(GameTooltip) then
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end)
|
||||
button:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function CraftsListItem:Destroy()
|
||||
if GameTooltip:IsOwned(self._button) then
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
self._framePool:Release(self._button)
|
||||
erase(self)
|
||||
end
|
||||
|
||||
---@param callback fun(item: CraftsListItem)
|
||||
function CraftsListItem:OnClick(callback)
|
||||
self._button:SetScript("OnClick", function() callback(self) end)
|
||||
end
|
||||
|
||||
---@return Frame
|
||||
function CraftsListItem:GetFrame()
|
||||
return self._button
|
||||
end
|
||||
|
||||
---@param parent Frame
|
||||
---@param anchor Frame
|
||||
---@param selfAnchorPoint WidgetAnchorPoint
|
||||
---@param foreignAnchorPoint WidgetAnchorPoint
|
||||
---@param x number
|
||||
---@param y number
|
||||
function CraftsListItem:Attach(parent, anchor, selfAnchorPoint, foreignAnchorPoint, x, y)
|
||||
self._button:SetParent(parent)
|
||||
self._button:ClearAllPoints()
|
||||
self._button:SetPoint(selfAnchorPoint, anchor, foreignAnchorPoint, x, y)
|
||||
self._button:Show()
|
||||
end
|
||||
|
||||
---@param craft Craft
|
||||
function CraftsListItem:PopulateInterface(craft)
|
||||
local color = craft.isAvailable and {r = 0.7, g = 0.9, b = 1.0} or {r = 1.0, g = 0.8, b = 0.9}
|
||||
local text = format("[%d] %s", craft.skillLevel, craft.localizedName)
|
||||
self._button:SetText(text)
|
||||
self._button:SetTextColor(color.r, color.g, color.b)
|
||||
self._fontString:SetText(text)
|
||||
self._craft = craft
|
||||
end
|
||||
|
||||
---@param enable boolean
|
||||
function CraftsListItem:SetHighlight(enable)
|
||||
if enable then
|
||||
self._button:LockHighlight()
|
||||
else
|
||||
self._button:UnlockHighlight()
|
||||
end
|
||||
end
|
||||
|
||||
---@param tooltip GameTooltip
|
||||
---@return boolean
|
||||
function CraftsListItem:_DrawTooltip(tooltip)
|
||||
if self._craft == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
if self._craft.recipeId ~= nil then
|
||||
tooltip:SetHyperlink(format("item:%d", self._craft.recipeId))
|
||||
elseif self._craft.resultId ~= nil then
|
||||
tooltip:SetHyperlink(format("item:%d", self._craft.resultId))
|
||||
else
|
||||
tooltip:SetHyperlink(GetSpellLink(self._craft.spellId))
|
||||
end
|
||||
tooltip:AddLine(" ")
|
||||
|
||||
local strings = {}
|
||||
for _, source in ipairs(self._craft.sources) do
|
||||
local name = SOURCE_TO_TEXT[source]
|
||||
if name ~= nil then
|
||||
tinsert(strings, name)
|
||||
end
|
||||
end
|
||||
|
||||
if getn(strings) == 0 then
|
||||
tinsert(strings, SOURCE_TO_TEXT[CraftSource.Unknown])
|
||||
end
|
||||
|
||||
table.sort(strings)
|
||||
|
||||
local count = getn(strings)
|
||||
local text = ""
|
||||
for i, s in ipairs(strings) do
|
||||
text = text .. s .. (i < count and ", " or "")
|
||||
end
|
||||
|
||||
tooltip:AddLine(format("%s: %s", self._locale.craft_tooltip_source, text))
|
||||
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class OpenButton
|
||||
---@field _frame CheckButton
|
||||
OpenButton = {}
|
||||
|
||||
---@param onClick fun():void
|
||||
---@param professionFrame ProfessionFrame
|
||||
---@param placementPolicy PlacementPolicy
|
||||
function OpenButton:Create(onClick, professionFrame, placementPolicy)
|
||||
local object = --[[---@type self]] {}
|
||||
setmetatable(object, {__index = OpenButton})
|
||||
|
||||
local button = CreateFrame("CheckButton")
|
||||
button:SetWidth(32)
|
||||
button:SetHeight(32)
|
||||
|
||||
local normalTexture = button:CreateTexture()
|
||||
normalTexture:SetTexture([[Interface\Icons\INV_Scroll_05]])
|
||||
normalTexture:SetAllPoints(button)
|
||||
button:SetNormalTexture(normalTexture)
|
||||
|
||||
local highlightTexture = button:CreateTexture()
|
||||
highlightTexture:SetTexture([[Interface\Buttons\ButtonHilight-Square]])
|
||||
highlightTexture:SetAllPoints(button)
|
||||
highlightTexture:SetBlendMode("ADD")
|
||||
button:SetHighlightTexture(highlightTexture)
|
||||
|
||||
local checkedTexture = button:CreateTexture()
|
||||
checkedTexture:SetTexture([[Interface\Buttons\CheckButtonHilight]])
|
||||
checkedTexture:SetAllPoints(button)
|
||||
checkedTexture:SetBlendMode("ADD")
|
||||
button:SetCheckedTexture(checkedTexture)
|
||||
|
||||
local anchor = placementPolicy:GetOpenButtonAnchor(professionFrame.type)
|
||||
button:SetParent(professionFrame.widget)
|
||||
button:SetPoint(anchor.framePoint, professionFrame.widget, anchor.selfPoint, anchor.selfCoords.x, anchor.selfCoords.y)
|
||||
|
||||
button:SetScript("OnClick", function()
|
||||
onClick()
|
||||
end)
|
||||
|
||||
button:Show()
|
||||
|
||||
object._frame = button
|
||||
return object
|
||||
end
|
||||
|
||||
function OpenButton:Destroy()
|
||||
clearFrame(self._frame)
|
||||
self._frame = nil
|
||||
erase(self)
|
||||
end
|
||||
|
||||
---@param checked boolean
|
||||
function OpenButton:SetChecked(checked)
|
||||
self._frame:SetChecked(checked)
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@shape Coords
|
||||
---@field x number
|
||||
---@field y number
|
||||
|
||||
---@shape Anchor
|
||||
---@field framePoint WidgetAnchorPoint
|
||||
---@field selfPoint WidgetAnchorPoint
|
||||
---@field selfCoords Coords
|
||||
|
||||
---@shape Geometry
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field offsetX number
|
||||
---@field offsetY number
|
||||
|
||||
---@class PlacementPolicy
|
||||
PlacementPolicy = {}
|
||||
|
||||
local FrameType = LibCraftingProfessionsConstants.FrameType
|
||||
|
||||
---@return boolean
|
||||
local function pfUI()
|
||||
return IsAddOnLoaded("pfUI") == 1
|
||||
end
|
||||
|
||||
---@param frameType LcpProfessionFrameType
|
||||
---@return boolean
|
||||
local function frameTypeSupportedByPfUI(frameType)
|
||||
return frameType == FrameType.VanillaCraftFrame or
|
||||
frameType == FrameType.VanillaTradeSkillFrame or
|
||||
frameType == FrameType.TurtleTradeSkillFrame
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
local function MTSL()
|
||||
return getglobal("MTSLUI_TOGGLE_BUTTON") ~= nil
|
||||
end
|
||||
|
||||
---@param frameType LcpProfessionFrameType
|
||||
---@return Anchor
|
||||
function PlacementPolicy:GetOpenButtonAnchor(frameType)
|
||||
---@type Anchor
|
||||
local anchor = {
|
||||
framePoint = "TOPRIGHT",
|
||||
selfPoint = "TOPRIGHT",
|
||||
selfCoords = {x = 0, y = 0},
|
||||
}
|
||||
|
||||
if pfUI() and frameTypeSupportedByPfUI(frameType) then
|
||||
if MTSL() then
|
||||
anchor.selfCoords = {x = -15, y = -70}
|
||||
else
|
||||
anchor.selfCoords = {x = -30, y = -1}
|
||||
end
|
||||
elseif frameType == FrameType.AdvancedTradeSkillWindow then
|
||||
anchor.selfCoords = {x = -54, y = -91}
|
||||
elseif frameType == FrameType.AdvancedTradeSkillWindow2 then
|
||||
anchor.selfCoords = {x = -48, y = -77}
|
||||
elseif frameType == FrameType.Artisan then
|
||||
anchor.selfCoords = {x = -44, y = -60}
|
||||
elseif frameType == FrameType.TurtleTradeSkillFrame then
|
||||
anchor.selfCoords = {x = -96, y = -61}
|
||||
elseif frameType == FrameType.VanillaTradeSkillFrame then
|
||||
if MTSL() then
|
||||
anchor.selfCoords = {x = -93, y = 20}
|
||||
else
|
||||
anchor.selfCoords = {x = -38, y = 20}
|
||||
end
|
||||
elseif frameType == FrameType.VanillaCraftFrame then
|
||||
anchor.selfCoords = {x = -44, y = -60}
|
||||
end
|
||||
|
||||
return anchor
|
||||
end
|
||||
|
||||
--[[
|
||||
offsetX/offsetY are relative to the profession frame's TOPRIGHT corner, meant
|
||||
to be used directly as a SetPoint offset anchored to that frame (not to
|
||||
UIParent). That makes the window a true child-of-position: WoW keeps it in
|
||||
sync automatically whenever the profession frame moves, with no polling or
|
||||
move hooks required.
|
||||
]]
|
||||
---@param frameType LcpProfessionFrameType
|
||||
---@return Geometry
|
||||
function PlacementPolicy:GetMainWindowGeometry(frameType)
|
||||
---@type Geometry
|
||||
local status
|
||||
|
||||
if pfUI() and frameTypeSupportedByPfUI(frameType) then
|
||||
status = {width = 384, height = 450, offsetX = 0, offsetY = 5}
|
||||
elseif frameType == FrameType.AdvancedTradeSkillWindow then
|
||||
status = {width = 384, height = 430, offsetX = -40, offsetY = -10}
|
||||
elseif frameType == FrameType.AdvancedTradeSkillWindow2 then
|
||||
status = {width = 384, height = 494, offsetX = -5, offsetY = -10}
|
||||
elseif frameType == FrameType.Artisan then
|
||||
status = {width = 384, height = 469, offsetX = -5, offsetY = -10}
|
||||
elseif frameType == FrameType.TurtleTradeSkillFrame then
|
||||
status = {width = 384, height = 430, offsetX = -87, offsetY = -10}
|
||||
else
|
||||
status = {width = 384, height = 430, offsetX = -40, offsetY = -10}
|
||||
end
|
||||
|
||||
return status
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
setfenv(1, Gromissingcrafts)
|
||||
|
||||
---@class Window
|
||||
---@field _frame Frame
|
||||
---@field _titleText FontString
|
||||
---@field _professionFrame ProfessionFrame
|
||||
---@field _closeButton Button
|
||||
---@field _placementPolicy PlacementPolicy
|
||||
---@field _onClose function
|
||||
Window = {}
|
||||
|
||||
local BACKDROP = {
|
||||
bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]],
|
||||
edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]],
|
||||
tile = true,
|
||||
tileSize = 32,
|
||||
edgeSize = 32,
|
||||
insets = {left = 11, right = 12, top = 12, bottom = 11},
|
||||
}
|
||||
|
||||
local TITLE_BAR_HEIGHT = 24
|
||||
local CONTENT_MARGIN = 16
|
||||
|
||||
---@param addonInfo AddonInfo
|
||||
---@param onClose fun():void
|
||||
---@param filtersPanel FiltersPanel
|
||||
---@param craftsList CraftsList
|
||||
---@param professionFrame ProfessionFrame
|
||||
---@param placementPolicy PlacementPolicy
|
||||
---@return self
|
||||
function Window:Create(addonInfo, onClose, filtersPanel, craftsList, professionFrame, placementPolicy)
|
||||
local window = --[[---@type self]] {}
|
||||
setmetatable(window, {__index = Window})
|
||||
|
||||
local frameStatus = placementPolicy:GetMainWindowGeometry(professionFrame.type)
|
||||
|
||||
local frame = CreateFrame("Frame", "GromissingcraftsWindow", UIParent)
|
||||
frame:SetBackdrop(BACKDROP)
|
||||
frame:SetBackdropColor(1, 1, 1, 1)
|
||||
-- Fixed high strata (not "whatever the profession frame uses") so this
|
||||
-- window and its widgets reliably win input priority over anything the
|
||||
-- profession frame or other addons draw underneath it.
|
||||
frame:SetFrameStrata("DIALOG")
|
||||
frame:SetToplevel(true)
|
||||
frame:SetWidth(frameStatus.width)
|
||||
frame:SetHeight(frameStatus.height)
|
||||
|
||||
-- Anchored directly to the profession frame (not to a computed UIParent
|
||||
-- offset) so it moves together with it automatically - no drag support
|
||||
-- of its own, it's not meant to be repositioned independently.
|
||||
frame:SetPoint("TOPLEFT", professionFrame.widget, "TOPRIGHT", frameStatus.offsetX, frameStatus.offsetY)
|
||||
|
||||
local titleText = frame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
titleText:SetPoint("TOP", frame, "TOP", 0, -14)
|
||||
titleText:SetText(format('%s v%s', addonInfo.name, addonInfo.version))
|
||||
|
||||
local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
|
||||
closeButton:SetFrameStrata("DIALOG")
|
||||
closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5)
|
||||
closeButton:SetScript("OnClick", function()
|
||||
window:Hide()
|
||||
end)
|
||||
|
||||
local filtersPanelFrame = filtersPanel:GetFrame()
|
||||
filtersPanelFrame:SetParent(frame)
|
||||
filtersPanelFrame:ClearAllPoints()
|
||||
filtersPanelFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", CONTENT_MARGIN, -(TITLE_BAR_HEIGHT + 8))
|
||||
|
||||
local craftsListFrame = craftsList:GetFrame()
|
||||
craftsListFrame:SetParent(frame)
|
||||
craftsListFrame:ClearAllPoints()
|
||||
craftsListFrame:SetPoint("TOPLEFT", filtersPanelFrame, "BOTTOMLEFT", 0, -5)
|
||||
|
||||
window._frame = frame
|
||||
window._titleText = titleText
|
||||
window._professionFrame = professionFrame
|
||||
window._closeButton = closeButton
|
||||
window._placementPolicy = placementPolicy
|
||||
window._craftsList = craftsList
|
||||
window._onClose = onClose
|
||||
|
||||
window:UpdateGeometry()
|
||||
|
||||
return window
|
||||
end
|
||||
|
||||
function Window:UpdateGeometry()
|
||||
local frameStatus = self._placementPolicy:GetMainWindowGeometry(self._professionFrame.type)
|
||||
self._frame:SetWidth(frameStatus.width)
|
||||
self._frame:SetHeight(frameStatus.height)
|
||||
self._frame:ClearAllPoints()
|
||||
self._frame:SetPoint("TOPLEFT", self._professionFrame.widget, "TOPRIGHT", frameStatus.offsetX, frameStatus.offsetY)
|
||||
self._frame:Show()
|
||||
|
||||
-- Recomputed on every geometry change, not just at creation: different
|
||||
-- profession frame types (and pfUI vs not) report different heights.
|
||||
self._craftsList:SetHeight(frameStatus.height - (TITLE_BAR_HEIGHT + 8) - FiltersPanel.HEIGHT - CONTENT_MARGIN)
|
||||
end
|
||||
|
||||
---@param professionFrame ProfessionFrame
|
||||
function Window:Show(professionFrame)
|
||||
self._professionFrame = professionFrame
|
||||
self._frame:Show()
|
||||
self:UpdateGeometry()
|
||||
end
|
||||
|
||||
function Window:Hide()
|
||||
self._frame:Hide()
|
||||
self._onClose()
|
||||
end
|
||||
Reference in New Issue
Block a user