├── db
├── refloot-epoch.lua
├── enUS
│ ├── professions-epoch.lua
│ └── objects-epoch.lua
├── areatrigger-epoch.lua
├── minimap-epoch.lua
└── quests-itemreq-epoch.lua
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── object-issue.yml
│ ├── item-issue.yml
│ ├── unit-issue.yml
│ └── quest-issue.yml
└── workflows
│ └── auto_release_on_db_change.yml
├── .gitattributes
├── init
├── enUS-epoch.xml
└── data-epoch.xml
├── pfQuest-epoch.toc
├── README.md
├── LICENSE
├── Contribute.md
├── Db.md
├── pfQuest-announce.lua
├── pfQuest-updater.lua
├── patchtable.lua
└── pfQuest-worldmap.lua
/db/refloot-epoch.lua:
--------------------------------------------------------------------------------
1 | pfDB["refloot"]["data-epoch"] = {
2 | }
3 |
--------------------------------------------------------------------------------
/db/enUS/professions-epoch.lua:
--------------------------------------------------------------------------------
1 | pfDB["professions"]["enUS-epoch"] = {
2 | }
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links: []
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/init/enUS-epoch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/init/data-epoch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pfQuest-epoch.toc:
--------------------------------------------------------------------------------
1 | ## Interface: 30300
2 | ## Title: |cff33ffccpf|cffffffffQuest |cffcccccc[Project Epoch DB]
3 | ## Author: Bennylavaa
4 | # Contributors: Exuria, Shagu, snifflewow, skmd0, trav346
5 | ## Notes: A Project Epoch DB extension for pfQuest
6 | ## Version: 2.22.2
7 | ## Dependencies: pfQuest-wotlk
8 | ## SavedVariables: pfQuest_epochcount, pfQuest_CompletedQuestData
9 |
10 | init\data-epoch.xml
11 | init\enUS-epoch.xml
12 |
13 | overwrites.lua
14 | patchtable.lua
15 | pfQuest-updater.lua
16 | pfQuest-announce.lua
17 | pfQuest-worldmap.lua
18 |
--------------------------------------------------------------------------------
/.github/workflows/auto_release_on_db_change.yml:
--------------------------------------------------------------------------------
1 | name: Release pfQuest-epoch on changes
2 | on:
3 | workflow_dispatch:
4 | permissions:
5 | contents: write
6 | actions: read
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@v4
13 |
14 | - name: Extract version from TOC file
15 | id: get_version
16 | run: |
17 | VERSION=$(grep "## Version:" pfQuest-epoch.toc | sed 's/## Version: //')
18 | echo "version=$VERSION" >> $GITHUB_OUTPUT
19 | echo "Found version: $VERSION"
20 |
21 | - name: Create pfQuest-epoch zip
22 | run: |
23 | mkdir pfQuest-epoch
24 | rsync -av --exclude='.git' --exclude='.github' --exclude='*.md' --exclude='LICENSE' --exclude='.gitattributes' --exclude='pfQuest-epoch' . pfQuest-epoch/
25 | zip -r pfQuest-epoch.zip pfQuest-epoch/
26 |
27 | - name: Create Versioned Release and Upload Asset
28 | uses: softprops/action-gh-release@v2
29 | with:
30 | tag_name: pfQuest-epoch-v${{ steps.get_version.outputs.version }}
31 | name: pfQuest-epoch v${{ steps.get_version.outputs.version }}
32 | files: pfQuest-epoch.zip
33 | make_latest: true
34 | env:
35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/object-issue.yml:
--------------------------------------------------------------------------------
1 | name: Object Issue
2 | description: Report an issue with a game object
3 | title: "[Object] "
4 | labels: ["object", "bug"]
5 | body:
6 | - type: input
7 | id: object_id
8 | attributes:
9 | label: Object ID (If known)
10 | description: The ID of the object (optional if unknown)
11 | placeholder: "e.g., 1234"
12 | validations:
13 | required: false
14 |
15 | - type: input
16 | id: object_name
17 | attributes:
18 | label: Object Name
19 | description: The name of the object
20 | placeholder: "e.g., Chest of Booty"
21 | validations:
22 | required: true
23 |
24 | - type: input
25 | id: object_zone
26 | attributes:
27 | label: Object Zone
28 | description: The zone where the object is located
29 | placeholder: "e.g., The Barrens"
30 | validations:
31 | required: true
32 |
33 | - type: input
34 | id: object_coords
35 | attributes:
36 | label: Object Coordinates (X, Y)
37 | description: The X and Y coordinates of the object
38 | placeholder: "e.g., 52.3, 41.7"
39 | validations:
40 | required: true
41 |
42 | - type: textarea
43 | id: issue_details
44 | attributes:
45 | label: Details of the Issue
46 | description: Please provide a detailed description of the issue
47 | placeholder: "Describe what's wrong with the object..."
48 | validations:
49 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/item-issue.yml:
--------------------------------------------------------------------------------
1 | name: Item Issue
2 | description: Report an issue with an item
3 | title: "[Item] "
4 | labels: ["item", "bug"]
5 | body:
6 | - type: input
7 | id: item_id
8 | attributes:
9 | label: Item ID
10 | description: The ID of the item
11 | placeholder: "e.g., 1234"
12 | validations:
13 | required: true
14 |
15 | - type: input
16 | id: item_name
17 | attributes:
18 | label: Item Name
19 | description: The name of the item
20 | placeholder: "e.g., Linen Cloth"
21 | validations:
22 | required: true
23 |
24 | - type: dropdown
25 | id: drops_from_type
26 | attributes:
27 | label: Drops From Type
28 | description: Type of entity that drops this item
29 | options:
30 | - Unit
31 | - Object
32 | - Nothing
33 | validations:
34 | required: true
35 |
36 | - type: input
37 | id: drops_from_id
38 | attributes:
39 | label: Drops From ID
40 | description: ID of the Unit or Object that drops this item
41 | placeholder: "e.g., 5678"
42 | validations:
43 | required: false
44 |
45 | - type: input
46 | id: drops_from_name
47 | attributes:
48 | label: Drops From Name
49 | description: Name of the Unit or Object that drops this item
50 | placeholder: "e.g., Hogger"
51 | validations:
52 | required: false
53 |
54 | - type: textarea
55 | id: issue_details
56 | attributes:
57 | label: Details of the Issue
58 | description: Please provide a detailed description of the issue
59 | placeholder: "Describe what's wrong with the item..."
60 | validations:
61 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/unit-issue.yml:
--------------------------------------------------------------------------------
1 | name: Unit Issue
2 | description: Report an issue with a unit (NPC/Mob)
3 | title: "[Unit] "
4 | labels: ["unit", "bug"]
5 | body:
6 | - type: input
7 | id: unit_id
8 | attributes:
9 | label: Unit ID
10 | description: The ID of the unit
11 | placeholder: "e.g., 1234"
12 | validations:
13 | required: true
14 |
15 | - type: input
16 | id: unit_name
17 | attributes:
18 | label: Unit Name
19 | description: The name of the unit
20 | placeholder: "e.g., Hogger"
21 | validations:
22 | required: true
23 |
24 | - type: dropdown
25 | id: unit_faction
26 | attributes:
27 | label: Unit Faction
28 | description: The faction of the unit
29 | options:
30 | - Horde
31 | - Alliance
32 | - Neutral
33 | validations:
34 | required: true
35 |
36 | - type: input
37 | id: unit_level
38 | attributes:
39 | label: Unit Level
40 | description: The level of the unit
41 | placeholder: "e.g., 10"
42 | validations:
43 | required: true
44 |
45 | - type: input
46 | id: unit_zone
47 | attributes:
48 | label: Unit Zone
49 | description: The zone where the unit is located
50 | placeholder: "e.g., Elwynn Forest"
51 | validations:
52 | required: true
53 |
54 | - type: input
55 | id: unit_coords
56 | attributes:
57 | label: Unit Coordinates (X, Y)
58 | description: The X and Y coordinates of the unit
59 | placeholder: "e.g., 45.2, 67.8"
60 | validations:
61 | required: true
62 |
63 | - type: textarea
64 | id: issue_details
65 | attributes:
66 | label: Details of the Issue
67 | description: Please provide a detailed description of the issue
68 | placeholder: "Describe what's wrong with the unit..."
69 | validations:
70 | required: true
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pfQuest [Project Epoch DB]
2 |
3 | **NOTE: I do not give permission for this addon to be hosted in any servers launchers of any kind unless directly pulled from my repo and you must contact me before doing so.**
4 |
5 | An extension for [pfQuest-wotlk](https://github.com/shagu/pfQuest) which adds support for [Project Epoch](https://www.project-epoch.net/).
6 | The latest version of [pfQuest-wotlk](https://github.com/shagu/pfQuest) is required and only enUS-Gameclients are supported at the time.
7 |
8 | ## Installation
9 | pfQuest-epoch is dependant on pfQuest to work.
10 |
11 | 1. Download the latest release **[pfQuest-wotlk](https://github.com/shagu/pfQuest/releases/latest/download/pfQuest-full-wotlk.zip)**
12 | 2. Unzip it and place the "pfQuest-wotlk" folder into Wow-Directory\Interface\AddOns
13 | 3. Download the latest release **[pfQuest-epoch](https://github.com/Bennylavaa/pfQuest-epoch/releases/latest/download/pfQuest-epoch.zip)**
14 | 4. Unzip it and place the "pfQuest-epoch" folder into Wow-Directory\Interface\AddOns
15 |
16 | ## Contribute
17 | To my knowledge there is no way to automate the data gathering so I urge
18 | as many as possible to read the contribution guide and help as this is not
19 | a one man job.
20 | [How to contribute](Contribute.md)
21 |
22 | ## Progress
23 |
24 | [Google Sheet](https://docs.google.com/spreadsheets/d/1uTlB9E-YUPJxO96Kn9RCpvx76kmhvHEs-i7XV8rPpW8/edit?usp=sharing)
25 |
26 | ## License 
27 | This project is licensed under a custom license that allows personal use and GitHub forks only. Redistribution or rehosting elsewhere is not permitted. See the [LICENSE](LICENSE) file for details.
28 |
29 | ## Special Thank You
30 | - Shagu: for making the base addon and creating pfQuest-turtle which this is based off of.
31 | - snifflewow: for the auto release workflow idea as well as the cleaned up readme documentation.
32 | - Exuria: for their countless hours spent contrubuting.
33 | - trav346: for the foundation of the overworld map markers.
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | # pfQuest-epoch License
2 |
3 | Copyright (c) 2021 Eric Mauser (Shagu)
4 | Copyright (c) 2021-2025 Bennylavaa
5 |
6 | Permission is hereby granted to use this software for personal use only, subject to the following conditions:
7 |
8 | ## PERMITTED USES:
9 | - Personal use and modification of the software
10 | - Creating forks on GitHub for development and contribution purposes
11 | - Contributing improvements back to the original repository via pull requests
12 | - Using the software for personal World of Warcraft gameplay
13 |
14 | ## PROHIBITED USES:
15 | - Redistributing, rehosting, or republishing this software on any platform other than GitHub forks
16 | - Including this software in game launchers, modpacks, or any distribution collections
17 | - Commercial use, sale, or monetization of this software in any form
18 | - Claiming this work as your own or removing attribution
19 | - Hosting modified versions outside of the GitHub fork system
20 |
21 | ## FORK REQUIREMENTS:
22 | Any forks of this repository must:
23 | - Clearly indicate in the repository name and description that it is derived from pfQuest-epoch
24 | - Retain this license file and all copyright notices
25 | - Include a prominent link back to the original repository (https://github.com/Bennylavaa/pfQuest-epoch)
26 | - Not misrepresent the fork as the original or official version
27 |
28 | ## DISCLAIMER:
29 | 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.
30 |
31 | ## ADDITIONAL NOTES:
32 | This license applies only to the pfQuest-epoch code and modifications. World of Warcraft and related assets remain the property of Blizzard Entertainment and are subject to their Terms of Service and End User License Agreement.
33 |
--------------------------------------------------------------------------------
/db/areatrigger-epoch.lua:
--------------------------------------------------------------------------------
1 | pfDB["areatrigger"]["data-epoch"] = {
2 | [2726] = {
3 | ["coords"] = {
4 | [1] = { 23.9, 68.4, 139 },
5 | },
6 | },
7 | [10000] = {
8 | ["coords"] = {
9 | [1] = { 55.6, 35.2, 12 },
10 | },
11 | },
12 | [4000000] = { --Revils Camp
13 | ["coords"] = {
14 | [1] = { 74.7, 62.8, 10 },
15 | },
16 | },
17 | [4000001] = { --Manor Mistmantle
18 | ["coords"] = {
19 | [1] = { 77.1, 35.8, 10 },
20 | },
21 | },
22 | [4000002] = { --Rolands Doom
23 | ["coords"] = {
24 | [1] = { 73.4, 84.3, 10 },
25 | },
26 | },
27 | [4000003] = { --Blazing Hills
28 | ["coords"] = {
29 | [1] = { 78.91, 74.50, 45 },
30 | },
31 | },
32 | [4000004] = { --Hillsbrad Foothills Central Tower
33 | ["coords"] = {
34 | [1] = { 55.60, 34.73, 267 },
35 | },
36 | },
37 | [4000005] = { --Durnhold Escape
38 | ["coords"] = {
39 | [1] = { 76.47, 46.64, 267 },
40 | },
41 | },
42 | [4000006] = { --Minkrik\'s Village
43 | ["coords"] = {
44 | [1] = { 61.5, 23.8, 17 },
45 | },
46 | },
47 | [4000007] = { --Blazing Hills
48 | ["coords"] = {
49 | [1] = { 82.8, 57.3, 45 },
50 | },
51 | },
52 | [4000008] = { --Stonetalon Caverns
53 | ["coords"] = {
54 | [1] = { 30.7, 48.3, 406 },
55 | },
56 | },
57 | [4000009] = { --Shadowforge Excavation
58 | ["coords"] = {
59 | [1] = { 81.0, 33.1, 3 },
60 | },
61 | },
62 | [4000010] = { --Centar Artifact
63 | ["coords"] = {
64 | [1] = { 42.15, 34.78, 400 },
65 | },
66 | },
67 | [4000011] = { --Aru-Tails Site One
68 | ["coords"] = {
69 | [1] = { 38.8, 87.2, 490 },
70 | },
71 | },
72 | [4000012] = { --Meeting Location
73 | ["coords"] = {
74 | [1] = { 67.1, 78.7, 85 },
75 | },
76 | },
77 | [4000013] = { --Odor\'s Source
78 | ["coords"] = {
79 | [1] = { 60.6, 15.1, 618 },
80 | },
81 | },
82 | [4000014] = { --Blackmaw Village Overlook
83 | ["coords"] = {
84 | [1] = { 52.4, 13.1, 16, 0 },
85 | },
86 | },
87 | [4000015] = { --Tul\'Mari\'s Tomb
88 | ["coords"] = {
89 | [1] = { 46.3, 90.2, 440, 0 },
90 | },
91 | },
92 | [4000016] = { --Northshore Mine
93 | ["coords"] = {
94 | [1] = { 22.4, 50.9, 85, 0 },
95 | },
96 | },
97 | [4000017] = { --Stillwater Pond
98 | ["coords"] = {
99 | [1] = { 49.75, 52.0, 85, 0 },
100 | },
101 | },
102 | }
103 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/quest-issue.yml:
--------------------------------------------------------------------------------
1 | name: Quest Issue
2 | description: Report an issue with a quest
3 | title: "[Quest] "
4 | labels: ["quest", "bug"]
5 | body:
6 | - type: input
7 | id: quest_id
8 | attributes:
9 | label: Quest ID
10 | description: The ID of the quest
11 | placeholder: "e.g., 1234"
12 | validations:
13 | required: true
14 |
15 | - type: input
16 | id: quest_name
17 | attributes:
18 | label: Quest Name
19 | description: The name of the quest
20 | placeholder: "e.g., The Missing Diplomat"
21 | validations:
22 | required: true
23 |
24 | - type: input
25 | id: starting_id
26 | attributes:
27 | label: Starting ID
28 | description: ID of the Unit, Object, or Item that starts the quest
29 | placeholder: "e.g., 5678"
30 | validations:
31 | required: true
32 |
33 | - type: input
34 | id: starting_name
35 | attributes:
36 | label: Starting Name
37 | description: Name of the Unit, Object, or Item that starts the quest
38 | placeholder: "e.g., Guard Thomas"
39 | validations:
40 | required: true
41 |
42 | - type: input
43 | id: ending_id
44 | attributes:
45 | label: Ending ID
46 | description: ID of the Unit, Object, or Item where the quest ends
47 | placeholder: "e.g., 9012"
48 | validations:
49 | required: true
50 |
51 | - type: input
52 | id: ending_name
53 | attributes:
54 | label: Ending Name
55 | description: Name of the Unit, Object, or Item where the quest ends
56 | placeholder: "e.g., Marshal Dughan"
57 | validations:
58 | required: true
59 |
60 | - type: dropdown
61 | id: objective_type
62 | attributes:
63 | label: Objective Type
64 | description: Type of objective for this quest
65 | options:
66 | - Item
67 | - Unit
68 | - Object
69 | - Nothing
70 | validations:
71 | required: true
72 |
73 | - type: input
74 | id: objective_id
75 | attributes:
76 | label: Objective ID
77 | description: ID of the objective (Item, Unit, or Object)
78 | placeholder: "e.g., 3456"
79 | validations:
80 | required: false
81 |
82 | - type: textarea
83 | id: issue_details
84 | attributes:
85 | label: Details of the Issue
86 | description: Please provide a detailed description of the issue
87 | placeholder: "Describe what's wrong with the quest..."
88 | validations:
89 | required: true
--------------------------------------------------------------------------------
/Contribute.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | - [Introduction](#introduction)
4 | - [Macros](#macros)
5 | - [Example](#example)
6 | - [Issue](#issue)
7 | - [Pull request](#pull-request)
8 |
9 |
10 | ---
11 |
12 | ## Introduction
13 |
14 | To help include or improve data in this project you can either open an issue
15 | or create a pull request.
16 |
17 | Gather as much data as you can about the thing you want to add.
18 | For quests this will be, starting entity(npc or object or item), ending entity
19 | objectives, english quest text (see macros below).
20 |
21 | [Zone IDs](https://github.com/Bennylavaa/wowchat-epoch/blob/main/src/main/resources/pre_cata_areas.csv)
22 |
23 | ## Macros
24 |
25 | Your Current Cords:
26 | ```lua
27 | /script SetMapToCurrentZone() local x,y=GetPlayerMapPosition("player") DEFAULT_CHAT_FRAME:AddMessage(format("%s, %s: %.1f, %.1f",GetZoneText(),GetSubZoneText(),x*100,y*100))
28 | ```
29 |
30 |
31 | Targeted Unit Information:
32 |
33 | ```lua
34 | /run local guid = UnitGUID("target"); local npcId = tonumber(string.sub(guid, 8, 12), 16); local npcName = UnitName("target"); print("NPC ID:", npcId, "NPC Name:", npcName)
35 | ```
36 |
37 | Selected QuestLog Data:
38 |
39 | ```lua
40 | /run local t, l, _, _, _, _, _, _, i = GetQuestLogTitle(GetQuestLogSelection()); print("\nID:"..i.."\nLevel:"..l.."\n[\"T\"] "..t.."\n[\"O\"] "..QuestInfoObjectivesText:GetText().."\n[\"D\"] "..QuestInfoDescriptionText:GetText())
41 | ```
42 |
43 | Hover Over Item ID:
44 | ```lua
45 | /run local _, link = GameTooltip:GetItem(); if link then local itemID = tonumber(link:match("item:(%d+):")); if itemID then print("Item ID:", itemID) end end
46 | ```
47 |
48 | Object ID:
49 | If you know of a way to extract this from the client make an issue
50 |
51 | ## Example
52 | Demo quest commit: [Do Slavers Keep Records?
53 | ](https://github.com/Bennylavaa/pfQuest-epoch/commit/39abc567413a0c004ea22ec38fed4eb2e486e9d6)
54 |
55 | ### Issue
56 | First search in issues to see if there already is one for whatever addition you
57 | want to report.
58 | Gather as much information you can about what you want to add.
59 | This can be npc names, items, quest objectives, coordinates, zone, and so on.
60 | You can use the macros from the section above to help.
61 | Make a issue over at [issues](https://github.com/Bennylavaa/pfQuest-epoch/issues)
62 |
63 | ### Pull request
64 | If you have the technical knowhow to edit and make a pull request then please
65 | checkout the [db spec](Db.md) to learn more about the structure of the addon.
66 |
67 | Note that not all id's are included in this project. Try to make sure the
68 | things you are adding like objects/items/npcs are not in the original pfquest adddon
69 | since they exist in vanilla.
70 |
--------------------------------------------------------------------------------
/db/minimap-epoch.lua:
--------------------------------------------------------------------------------
1 | pfDB["minimap-epoch"] = {
2 | [0] = { 36799.8, 24533.2 },
3 | [1] = { 4925.0, 3283.34 },
4 | [3] = { 2487.5, 1658.34 },
5 | [4] = { 3350.0, 2233.3 },
6 | [8] = { 2293.75, 1529.17 },
7 | [10] = { 2700.003, 1800.03 },
8 | [11] = { 4135.417, 2756.25 },
9 | [12] = { 3470.84, 2314.62 },
10 | [14] = { 5287.5, 3525.0 },
11 | [15] = { 5250.0, 3500.0 },
12 | [16] = { 5070.84, 3381.25 },
13 | [17] = { 10133.34, 6756.25 },
14 | [28] = { 4299.997, 2866.67 },
15 | [33] = { 6381.25, 4254.1 },
16 | [36] = { 2800.003, 1866.667 },
17 | [38] = { 2758.33, 1839.58 },
18 | [40] = { 3500.003, 2333.3 },
19 | [41] = { 2499.997, 1666.63 },
20 | [44] = { 2170.84, 1447.9 },
21 | [45] = { 3600.003, 2399.997 },
22 | [46] = { 2929.163, 1952.08 },
23 | [47] = { 3850.0, 2566.67 },
24 | [51] = { 2231.253, 1487.5 },
25 | [65] = { 5608.33, 3739.58 },
26 | [66] = { 4993.75, 3329.17 },
27 | [67] = { 7112.5, 4741.65 },
28 | [85] = { 4518.75, 3012.5 },
29 | [130] = { 4200.0, 2800.0 },
30 | [139] = { 4031.25, 2687.5 },
31 | [141] = { 5091.66, 3393.7 },
32 | [148] = { 6550.0, 4366.66 },
33 | [206] = { 0.0, 0.0 },
34 | [210] = { 6270.833, 4181.25 },
35 | [215] = { 5137.5, 3425.003 },
36 | [267] = { 3200.0, 2133.33 },
37 | [331] = { 5766.67, 3843.753 },
38 | [357] = { 6950.0, 4633.33 },
39 | [361] = { 5750.0, 3833.33 },
40 | [394] = { 5250.0, 3500.0 },
41 | [400] = { 4399.997, 2933.33 },
42 | [405] = { 4495.83, 2997.913 },
43 | [406] = { 4883.33, 3256.253 },
44 | [440] = { 6900.0, 4600.0 },
45 | [490] = { 3700.003, 2466.66 },
46 | [493] = { 2308.33, 1539.59 },
47 | [495] = { 6045.83, 4031.253 },
48 | [618] = { 7100.003, 4733.33 },
49 | [1196] = { 6550.0, 4366.67 },
50 | [1377] = { 3483.334, 2322.92 },
51 | [1497] = { 959.3754, 640.1 },
52 | [1519] = { 1737.5033, 1158.34 },
53 | [1537] = { 790.629, 527.61 },
54 | [1637] = { 1402.61, 935.42 },
55 | [1638] = { 1043.75, 695.83 },
56 | [1657] = { 1058.33, 705.71 },
57 | [2597] = { 4237.5, 2825.0 },
58 | [2817] = { 2722.92, 1814.58 },
59 | [3277] = { 1145.837, 764.58 },
60 | [3358] = { 1756.247, 1170.83 },
61 | [3456] = { 1856.25, 1237.5 },
62 | [3483] = { 5164.58, 3443.75 },
63 | [3518] = { 5524.97, 3683.3367 },
64 | [3519] = { 5400.0, 3600.0 },
65 | [3520] = { 5500.0, 3666.66 },
66 | [3521] = { 5027.08, 3352.09 },
67 | [3522] = { 5425.0, 3616.663 },
68 | [3523] = { 5574.9967, 3716.67 },
69 | [3537] = { 5764.58, 3843.75 },
70 | [3703] = { 1306.25, 870.84 },
71 | [3711] = { 4356.25, 2904.17 },
72 | [3820] = { 2270.837, 1514.58 },
73 | [4100] = { 1824.997, 1216.67 },
74 | [4196] = { 627.087, 418.75 },
75 | [4197] = { 2975.0, 1983.34 },
76 | [4228] = { 2600.0, 1733.333 },
77 | [4264] = { 3400.003, 2266.6667 },
78 | [4265] = { 0.0, 0.0 },
79 | [4272] = { 3400.0, 2266.6667 },
80 | [4273] = { 3287.5, 2191.67 },
81 | [4277] = { 1072.9133, 714.584 },
82 | [4298] = { 3162.5, 2108.333 },
83 | [4384] = { 1743.75, 1162.497 },
84 | [4395] = { 0.0, 0.0 },
85 | [4415] = { 383.333, 256.25 },
86 | [4416] = { 1143.753, 762.5 },
87 | [4493] = { 1162.4967, 775.0 },
88 | [4494] = { 972.917, 647.917 },
89 | [4500] = { 3400.003, 2266.6667 },
90 | [4603] = { 2600.0, 1733.33 },
91 | [4710] = { 2650.0, 1766.6633 },
92 | [4722] = { 2600.0, 1733.333 },
93 | [4723] = { 2600.0, 1733.333 },
94 | [4742] = { 3677.087, 2452.03 },
95 | [4809] = { 11400.0, 7600.0 },
96 | [4812] = { 12200.0, 8133.33 },
97 | [4813] = { 1533.333, 1022.917 },
98 | [4820] = { 13000.0, 8666.67 },
99 | [4987] = { 752.083, 502.09 },
100 | [5016] = { 1378.95, 911.12 },
101 | }
102 |
--------------------------------------------------------------------------------
/Db.md:
--------------------------------------------------------------------------------
1 | # Db Structure
2 |
3 | **Short explanation of each file:**
4 |
5 | - **fac** -- Faction by letter: A (Alliance), H (Horde), AH (both)
6 | - **U** -- Unit
7 | - **O** -- Object
8 | - **I** -- Item
9 | - **IR** -- Itemreq, see quests-itemreq-epoch
10 |
11 | ---
12 |
13 | ## `db/`
14 |
15 | ### `quests-epoch.lua`
16 | Contains quest information, including start and end points, level, and the next quest in a chain.
17 | Not all fields are mandatory.
18 |
19 | **Format:**
20 | ```lua
21 | [QUESTID#] = {
22 | ["start"] = {
23 | ["U"] = { UNITID# }, -- Quest giver unit ID
24 | },
25 | ["end"] = {
26 | ["U"] = { UNITID# }, -- Quest turn-in unit ID
27 | },
28 | ["lvl"] = quest_level, -- Recommended quest level
29 | ["min"] = min_level, -- Minimum level to accept
30 | ["next"] = next_quest_id, -- Next quest in the chain
31 | ["pre"] = prev_quest_id, -- Previous quest in the chain
32 | ["close"] = { conflicting_quest_ids }, -- Quests that cannot be taken together (e.g., profession specializations)
33 | ["skill"] = skill_id, -- Required profession/skill ID (e.g., 165 (Leatherworking))
34 | ["race"] = race_requirement, -- Race bitflag, advanced see pfQuest code https://github.com/shagu/pfQuest/blob/104f35678ca39ab1fb78b655f815cc7016f5e0c8/database.lua#L333
35 | ["class"] = class_requirement, -- see https://github.com/shagu/pfQuest/blob/104f35678ca39ab1fb78b655f815cc7016f5e0c8/database.lua#L351
36 | ["event"] = event_id, -- Event id
37 | ["obj"] = { -- Quest objectives
38 | ["I"] = { item_ids_to_collect }, -- Items to collect
39 | ["U"] = { unit_ids_to_kill }, -- Units to kill
40 | ["O"] = { object_ids }, -- Objects to interact with
41 | ["IR"] = { item_req_ids }, -- see quests-itemreq-epoch.lua
42 | },
43 | }
44 | ```
45 |
46 | **Example:**
47 | ```lua
48 | [27246] = {
49 | ["start"] = {
50 | ["U"] = { 14624 },
51 | },
52 | ["end"] = {
53 | ["U"] = { 46164 },
54 | },
55 | ["obj"] = {
56 | ["I"] = { 8165, 8203, 8204 },
57 | },
58 | ["lvl"] = 50,
59 | ["min"] = 50,
60 | ["next"] = 27242,
61 | },
62 | ```
63 |
64 | ---
65 |
66 | ### `units-epoch.lua`
67 | Contains unit information such as level, ID, coordinates, and faction.
68 | Both quest NPCs and mobs are in this file.
69 |
70 | **Format:**
71 | ```lua
72 | [UNITID#] = {
73 | ["coords"] = { -- List of spawn locations and respawn timer
74 | [1] = { x, y, zoneid, respawn },
75 | [2] = { x, y, zoneid, respawn },
76 | },
77 | ["fac"] = faction,
78 | ["lvl"] = level, -- can be ranges like 21-23
79 | ["rnk"] = rank -- 1 = elite?, 2 = rare elite?, no rank = normal mob
80 | },
81 | ```
82 |
83 | **Example:**
84 | ```lua
85 | [61100] = {
86 | ["coords"] = {
87 | [1] = { 96.5, 66.8, 440, 280 },
88 | [2] = { 52.7, 80.7, 5121, 280 },
89 | },
90 | ["fac"] = "AH",
91 | ["lvl"] = "55",
92 | },
93 | ```
94 |
95 | ---
96 |
97 | ### `items-epoch.lua`
98 | Contains item information, including which units (`"U"`) and objects (`"O"`) they drop from. An item can drop from multiple units or objects.
99 |
100 | **Format:**
101 | ```lua
102 | [ITEMID#] = {
103 | ["O"] = { -- object
104 | [OBJECTID#] = drop%number, -- first object it drops from
105 | [OBJECTID#] = drop%number, -- second object it drops from
106 | },
107 | ["U"] = { -- unit
108 | [UNITID#] = drop%number, -- first NPC it drops from
109 | [UNITID#] = drop%number, -- second NPC it drops from
110 | },
111 | ["V"] = { -- vendor
112 | [UNITID#] = 0,
113 | }
114 | },
115 | ```
116 |
117 | **Example:**
118 | ```lua
119 | [4606] = {
120 | ["O"] = {
121 | [153462] = 4.17,
122 | },
123 | ["U"] = {
124 | [3] = 4.73,
125 | [48] = 4.55,
126 | },
127 | },
128 | ```
129 |
130 | ---
131 |
132 | ### `areatrigger-epoch.lua`
133 | Contains area trigger locations for exploration quests.
134 |
135 | **Format:**
136 | ```lua
137 | [AREATRIGGERID#] = {
138 | ["coords"] = {
139 | [1] = { x, y, zoneid },
140 | },
141 | },
142 | ```
143 |
144 | **Example:**
145 | ```lua
146 | [1] = {
147 | ["coords"] = {
148 | [1] = { 35.8, 62.1, 11 },
149 | },
150 | },
151 | ```
152 |
153 | ---
154 |
155 | ### `objects-epoch.lua`
156 | Contains object information, such as coordinates and ID.
157 |
158 | **Format:**
159 | ```lua
160 | [OBJECTID#] = {
161 | ["coords"] = {
162 | [1] = { x, y, zoneid, respawn },
163 | },
164 | ["fac"] = faction_letters
165 | },
166 | ```
167 |
168 | **Example:**
169 | ```lua
170 | [34] = {
171 | ["coords"] = {
172 | [1] = { 40.6, 17, 40, 2700 },
173 | },
174 | },
175 | ```
176 |
177 | ---
178 |
179 | ### `meta-epoch.lua`
180 | Contains meta-relations, such as lists of game objects that are ores, unit IDs that are flight points, etc.
181 |
182 | **Format:**
183 | ```lua
184 | ["chests"] = {
185 | [-CHESTID#] = 0,
186 | },
187 | ["flight"] = {
188 | [UNITID#] = "FACTIONLETTER",
189 | [UNITID#] = "FACTIONLETTER",
190 | [UNITID#] = "FACTIONLETTER",
191 | },
192 | ["rares"] = {
193 | [UNITID#] = LEVEL,
194 | [UNITID#] = LEVEL,
195 | },
196 | ```
197 |
198 | **Example:**
199 | ```lua
200 | ["chests"] = {
201 | [-3000248] = 0,
202 | [-3000247] = 0,
203 | },
204 | ["flight"] = {
205 | [352] = "A",
206 | [1233] = "AH",
207 | [1387] = "H",
208 | },
209 | ["rares"] = {
210 | [61] = 11,
211 | [79] = 10,
212 | },
213 | ```
214 |
215 | ---
216 |
217 | ### `minimap-epoch.lua`
218 | Contains minimap scale factors for specific areas, which helps to correctly display dots on the minimap inside buildings.
219 |
220 | **Format:**
221 | ```lua
222 | [MAPID] = { xsize, ysize },
223 | ```
224 |
225 | **Example:**
226 | ```lua
227 | [25] = { 711.56, 468.68 },
228 | ```
229 |
230 | ---
231 |
232 | ### `quests-itemreq-epoch.lua`
233 | Contains a list of items required for a quest that are usable in the floating quest log UI.
234 |
235 | ### `refloot-epoch.lua`
236 | Contains item requirements for specific quests, such as listing all anvil objects for the "Broken Tools" quest.
237 |
238 | ### `zones-epoch.lua`
239 | Contains information about zones and their positions on maps, as well as the maps themselves.
240 | *(No format or example provided in the original text)*
241 |
242 | ---
243 |
244 | ## `enUS/` (Localization Folder)
245 |
246 | ### `items-epoch.lua`
247 | Contains item IDs and their corresponding names.
248 |
249 | **Format:**
250 | ```lua
251 | [ITEMID#] = ITEMNAME,
252 | ```
253 |
254 | ---
255 |
256 | ### `objects-epoch.lua`
257 | Contains object IDs and their corresponding names.
258 |
259 | **Format:**
260 | ```lua
261 | [OBJECTID#] = OBJECTNAME,
262 | ```
263 |
264 | ---
265 |
266 | ### `professions-epoch.lua`
267 | Contains IDs and names for custom professions only.
268 |
269 | **Format:**
270 | ```lua
271 | [PROFESSIONID#] = PROFESSIONNAME,
272 | ```
273 |
274 | ---
275 |
276 | ### `quests-epoch.lua`
277 | Contains quest IDs and their title, objective, and description.
278 |
279 | **Format:**
280 | ```lua
281 | [QUESTID#] = {
282 | ["T"] = quest_title
283 | ["O"] = quest_ojective,
284 | ["D"] = quest_description,
285 | },
286 | ```
287 |
288 | ---
289 |
290 | ### `units-epoch.lua`
291 | Contains unit IDs and their corresponding names.
292 |
293 | **Format:**
294 | ```lua
295 | [UNITID#] = unit_name,
296 | ```
297 |
298 | ---
299 |
300 | ### `zones-epoch.lua`
301 | Contains zone IDs and their corresponding names.
302 |
303 | **Format:**
304 | ```lua
305 | [ZONEID#] = zone_name,
306 | ```
307 |
--------------------------------------------------------------------------------
/pfQuest-announce.lua:
--------------------------------------------------------------------------------
1 | local questAnnounceFrame = CreateFrame("Frame", "pfQuestEpochAnnounce")
2 | questAnnounceFrame:RegisterEvent("UI_INFO_MESSAGE")
3 | questAnnounceFrame:SetScript("OnEvent", function(self, event, message)
4 | if event == "UI_INFO_MESSAGE" and message then
5 | pfQuestEpoch_OnQuestUpdate(message)
6 | end
7 | end)
8 |
9 | function pfQuestEpoch_OnQuestUpdate(message)
10 | if GetNumPartyMembers() == 0 then
11 | return
12 | end
13 |
14 | if not message or type(message) ~= "string" then
15 | return
16 | end
17 |
18 | local itemName, numItems, numNeeded = string.match(message, "(.*):%s*([-%d]+)%s*/%s*([-%d]+)%s*$")
19 |
20 | if itemName and numItems and numNeeded then
21 | local iNumItems = tonumber(numItems)
22 | local iNumNeeded = tonumber(numNeeded)
23 | local stillNeeded = iNumNeeded - iNumItems
24 | local questName = pfQuestEpoch_GetQuestNameForObjective(itemName)
25 | local outMessage
26 |
27 | if stillNeeded < 1 then
28 | if pfQuest_config["epochannounceFinished"] == "1" then
29 | if questName then
30 | outMessage = "Finished " .. questName .. "."
31 | else
32 | outMessage = "I have finished " .. itemName .. "."
33 | end
34 | end
35 | else
36 | if pfQuest_config["epochannounceRemaining"] == "1" then
37 | if questName then
38 | outMessage = "" .. itemName .. " for " .. questName .. " (" .. stillNeeded .. " left)"
39 | else
40 | outMessage = "" .. itemName .. " (" .. stillNeeded .. " left)"
41 | end
42 | end
43 | end
44 |
45 | if outMessage and outMessage ~= "" then
46 | SendChatMessage(outMessage, "PARTY")
47 | end
48 | end
49 | end
50 |
51 | function pfQuestEpoch_GetQuestNameForObjective(objectiveName)
52 | local numQuestLogEntries = GetNumQuestLogEntries()
53 |
54 | for i = 1, numQuestLogEntries do
55 | local questTitle, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete = GetQuestLogTitle(i)
56 |
57 | if not isHeader and questTitle then
58 | SelectQuestLogEntry(i)
59 | local numObjectives = GetNumQuestLeaderBoards()
60 |
61 | for j = 1, numObjectives do
62 | local description, type, finished = GetQuestLogLeaderBoard(j)
63 |
64 | if description then
65 | local objName = string.match(description, "(.*):%s*[-%d]+%s*/%s*[-%d]+%s*$")
66 |
67 | if objName and string.find(string.lower(objName), string.lower(objectiveName), 1, true) then
68 | return GetQuestLink(i)
69 | end
70 | end
71 | end
72 | end
73 | end
74 |
75 | return nil
76 | end
77 |
78 | local function ExtendPfQuestConfig()
79 | if not pfQuest_defconfig or not pfQuestConfig then
80 | return false
81 | end
82 |
83 | local foundHeader, foundFinished, foundRemaining = false, false, false
84 | for _, entry in pairs(pfQuest_defconfig) do
85 | if entry.text == "Announce" and entry.type == "header" then
86 | foundHeader = true
87 | elseif entry.config == "epochannounceFinished" then
88 | foundFinished = true
89 | elseif entry.config == "epochannounceRemaining" then
90 | foundRemaining = true
91 | end
92 | end
93 |
94 | if foundHeader and foundFinished and foundRemaining then
95 | return true
96 | end
97 |
98 | if not foundHeader then
99 | table.insert(pfQuest_defconfig, {
100 | text = "Announce",
101 | default = nil,
102 | type = "header"
103 | })
104 | end
105 |
106 | if not foundFinished then
107 | table.insert(pfQuest_defconfig, {
108 | text = "Announce Finished Quest Objectives",
109 | default = "0",
110 | type = "checkbox",
111 | config = "epochannounceFinished"
112 | })
113 | end
114 |
115 | if not foundRemaining then
116 | table.insert(pfQuest_defconfig, {
117 | text = "Announce Remaining Quest Objectives",
118 | default = "0",
119 | type = "checkbox",
120 | config = "epochannounceRemaining"
121 | })
122 | end
123 |
124 | if not pfQuest_config["epochannounceFinished"] then
125 | pfQuest_config["epochannounceFinished"] = "0"
126 | end
127 | if not pfQuest_config["epochannounceRemaining"] then
128 | pfQuest_config["epochannounceRemaining"] = "0"
129 | end
130 |
131 | if pfQuestConfig.CreateConfigEntries then
132 | for i = 1, 50 do
133 | local frame = getglobal("pfQuestConfig" .. i)
134 | if frame then
135 | frame:Hide()
136 | frame:SetParent(nil)
137 | else
138 | break
139 | end
140 | end
141 |
142 | pfQuestConfig.vpos = 40
143 |
144 | pfQuestConfig:CreateConfigEntries(pfQuest_defconfig)
145 | end
146 |
147 | return true
148 | end
149 |
150 | local function CheckAndHandleVersionUpdate()
151 | -- Only disable on version 2.22.1, one time only
152 | local currentVersion = GetAddOnMetadata("pfQuest-epoch", "Version") or "0.0.0"
153 |
154 | if currentVersion == "2.22.1" and not pfQuest_config["epochannounceForcedDisableOnce"] then
155 | pfQuest_config["epochannounceFinished"] = "0"
156 | pfQuest_config["epochannounceForcedDisableOnce"] = "1"
157 | return true
158 | end
159 |
160 | return false
161 | end
162 |
163 | local configExtenderFrame = CreateFrame("Frame")
164 | configExtenderFrame:RegisterEvent("ADDON_LOADED")
165 | configExtenderFrame:SetScript("OnEvent", function(self, event, addonName)
166 | if addonName == "pfQuest-epoch" then
167 | local timer = 0
168 | self:SetScript("OnUpdate", function()
169 | timer = timer + 1
170 | if timer > 10 then
171 | if ExtendPfQuestConfig() then
172 | local versionUpdated = CheckAndHandleVersionUpdate()
173 | if versionUpdated then
174 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest |cffcccccc[Epoch]|r: Updated - Finished quest announcements have been disabled. Re-enable manually if desired.")
175 | end
176 | self:SetScript("OnUpdate", nil)
177 | self:UnregisterAllEvents()
178 | elseif timer > 300 then
179 | self:SetScript("OnUpdate", nil)
180 | self:UnregisterAllEvents()
181 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest |cffcccccc[Epoch]|r: Config integration failed")
182 | end
183 | end
184 | end)
185 | end
186 | end)
187 |
188 | local function SetupAnnounceCommands()
189 | local currentHandler = SlashCmdList["PFDB"]
190 |
191 | SlashCmdList["PFDB"] = function(input, editbox)
192 | local commandlist = {}
193 | local command
194 |
195 | local compat = pfQuestCompat
196 | if compat and compat.gfind then
197 | for command in compat.gfind(input, "[^ ]+") do
198 | table.insert(commandlist, command)
199 | end
200 | else
201 | for word in string.gmatch(input, "[^%s]+") do
202 | table.insert(commandlist, word)
203 | end
204 | end
205 |
206 | local arg1 = commandlist[1] and string.lower(commandlist[1])
207 | local arg2 = commandlist[2] and string.lower(commandlist[2])
208 | local arg3 = commandlist[3] and string.lower(commandlist[3])
209 |
210 | if arg1 == "announce" then
211 | if arg2 == "finished" then
212 | if arg3 == "on" then
213 | pfQuest_config["epochannounceFinished"] = "1"
214 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest |cffcccccc[Epoch]|r: Finished quest announcements enabled")
215 | return
216 | elseif arg3 == "off" then
217 | pfQuest_config["epochannounceFinished"] = "0"
218 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest |cffcccccc[Epoch]|r: Finished quest announcements disabled")
219 | return
220 | end
221 | elseif arg2 == "remaining" then
222 | if arg3 == "on" then
223 | pfQuest_config["epochannounceRemaining"] = "1"
224 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest |cffcccccc[Epoch]|r: Remaining quest announcements enabled")
225 | return
226 | elseif arg3 == "off" then
227 | pfQuest_config["epochannounceRemaining"] = "0"
228 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest |cffcccccc[Epoch]|r: Remaining quest announcements disabled")
229 | return
230 | end
231 | end
232 | end
233 |
234 | if currentHandler then
235 | currentHandler(input, editbox)
236 | end
237 | end
238 | end
239 |
240 | local commandSetupFrame = CreateFrame("Frame")
241 | commandSetupFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
242 | commandSetupFrame:SetScript("OnEvent", function()
243 | local timer = 0
244 | this:SetScript("OnUpdate", function()
245 | timer = timer + 1
246 | if timer > 60 then
247 | SetupAnnounceCommands()
248 | this:SetScript("OnUpdate", nil)
249 | this:UnregisterAllEvents()
250 | end
251 | end)
252 | end)
--------------------------------------------------------------------------------
/db/quests-itemreq-epoch.lua:
--------------------------------------------------------------------------------
1 | pfDB["quests-itemreq"]["data-epoch"] = {
2 | [6436] = {
3 | [4663] = "7914",
4 | [4664] = "7914",
5 | [4665] = "7914",
6 | [4666] = "7914",
7 | [4667] = "7914",
8 | },
9 | [6635] = {
10 | [-100035] = "8202",
11 | [-63674] = "8202",
12 | [100028] = "8202",
13 | [101749] = "8202",
14 | [-5000041] = 0,
15 | },
16 | [7866] = {
17 | [2726] = 0,
18 | },
19 | [9466] = {
20 | [-144050] = "11757",
21 | },
22 | [9618] = {
23 | [5268] = 0,
24 | [5286] = 0,
25 | },
26 | [12815] = {
27 | [10902] = 0,
28 | [10903] = 0,
29 | [10904] = 0,
30 | [10905] = 0,
31 | },
32 | [10687] = {
33 | [-152598] = 0,
34 | },
35 | [10688] = {
36 | [-152604] = 0,
37 | },
38 | [10689] = {
39 | [-152605] = 0,
40 | },
41 | [10690] = {
42 | [-152606] = 0,
43 | },
44 | [11914] = {
45 | [7086] = 0,
46 | },
47 | [11948] = {
48 | [7092] = 0,
49 | },
50 | [11953] = {
51 | [6559] = 0,
52 | [6556] = 0,
53 | },
54 | [15766] = {
55 | [-177673] = 0,
56 | },
57 | [15826] = {
58 | [12296] = 0,
59 | },
60 | [60767] = {
61 | [-250073] = 0,
62 | },
63 | [62811] = {
64 | [-250365] = 0,
65 | },
66 | [62596] = {
67 | [45776] = 0,
68 | },
69 | [62590] = {
70 | [-250316] = 0,
71 | },
72 | [62605] = {
73 | [-250320] = 0,
74 | },
75 | [62531] = {
76 | [-250286] = 0,
77 | },
78 | [62498] = {
79 | [45694] = 0,
80 | },
81 | [62875] = {
82 | [45955] = 0,
83 | },
84 | [62885] = {
85 | [45969] = 0,
86 | },
87 | [62880] = {
88 | [-250604] = 0,
89 | [-250605] = 0,
90 | [-250606] = 0,
91 | },
92 | [62331] = {
93 | [45552] = 0,
94 | },
95 | [62333] = {
96 | [-250224] = 0,
97 | },
98 | [62335] = {
99 | [45557] = 0,
100 | },
101 | [62336] = {
102 | [45568] = 0,
103 | [45569] = 0,
104 | [45570] = 0,
105 | },
106 | [62655] = {
107 | [-250594] = 0,
108 | },
109 | [62667] = {
110 | [-250332] = 0,
111 | },
112 | [62482] = {
113 | [12047] = 0,
114 | [13076] = 0,
115 | },
116 | [62484] = {
117 | [-5000002] = 0,
118 | },
119 | [62488] = {
120 | [-250584] = 0,
121 | },
122 | [62487] = {
123 | [-250263] = 0,
124 | },
125 | [62625] = {
126 | [45801] = 0,
127 | },
128 | [62641] = {
129 | [-250330] = 0,
130 | },
131 | [63278] = {
132 | [-250473] = 0,
133 | },
134 | [63277] = {
135 | [-250471] = 0,
136 | },
137 | [63355] = {
138 | [-250499] = 0,
139 | },
140 | [63367] = {
141 | [46270] = 0,
142 | },
143 | [62783] = {
144 | [45875] = 0,
145 | [45876] = 0,
146 | [11198] = 0,
147 | [45878] = 0,
148 | [45881] = 0,
149 | [1776] = 0,
150 | [11874] = 0,
151 | },
152 | [63138] = {
153 | [3130] = 0,
154 | [3131] = 0,
155 | },
156 | [63052] = {
157 | [-5000003] = 0,
158 | },
159 | [63065] = {
160 | [7452] = 0,
161 | [7453] = 0,
162 | [7454] = 0,
163 | },
164 | [62403] = {
165 | [45610] = 0,
166 | },
167 | [63207] = {
168 | [-250491] = 0,
169 | },
170 | [62720] = {
171 | [-5000037] = 0,
172 | },
173 | [63044] = {
174 | [3240] = 0,
175 | [3239] = 0,
176 | },
177 | [63108] = {
178 | [46080] = 0,
179 | },
180 | [63109] = {
181 | [4664] = 0,
182 | [4663] = 0,
183 | [4666] = 0,
184 | [4665] = 0,
185 | },
186 | [62632] = {
187 | [-250594] = 0,
188 | },
189 | [62903] = {
190 | [-5000038] = 0,
191 | },
192 | [62938] = {
193 | [-250392] = 0,
194 | },
195 | [63163] = {
196 | [46119] = 0,
197 | },
198 | [62895] = {
199 | [12856] = 0,
200 | },
201 | [62896] = {
202 | [45987] = 0,
203 | },
204 | [60106] = {
205 | [46116] = 0,
206 | },
207 | [60090] = {
208 | [2657] = 0,
209 | },
210 | [60086] = {
211 | [5635] = 0,
212 | },
213 | [62760] = {
214 | [-250350] = 0,
215 | },
216 | [60094] = {
217 | [2505] = 0,
218 | },
219 | [63334] = {
220 | [46238] = 0,
221 | },
222 | [63335] = {
223 | [-250451] = 0,
224 | },
225 | [63338] = {
226 | [-250492] = 0,
227 | },
228 | [63311] = {
229 | [-250487] = 0,
230 | },
231 | [63344] = {
232 | [46249] = 0,
233 | },
234 | [63216] = {
235 | [-5000050] = 0,
236 | },
237 | [63288] = {
238 | [46208] = 0,
239 | },
240 | [63289] = {
241 | [5456] = 0,
242 | [5457] = 0,
243 | },
244 | [63281] = {
245 | [-250475] = 0,
246 | },
247 | [63296] = {
248 | [-250478] = 0,
249 | },
250 | [63340] = {
251 | [6517] = 0,
252 | [6527] = 0,
253 | [6518] = 0,
254 | [6519] = 0,
255 | },
256 | [63299] = {
257 | [46216] = 0,
258 | },
259 | [63307] = {
260 | [47001] = 0,
261 | },
262 | [63302] = {
263 | [46216] = 0,
264 | },
265 | [62456] = {
266 | [-250255] = 0,
267 | },
268 | [62458] = {
269 | [-250256] = 0,
270 | },
271 | [62460] = {
272 | [9691] = 0,
273 | [9695] = 0,
274 | [9698] = 0,
275 | },
276 | [62440] = {
277 | [-250246] = 0,
278 | },
279 | [63390] = {
280 | [46277] = 0,
281 | },
282 | [63388] = {
283 | [-181632] = 0,
284 | },
285 | [61939] = {
286 | [5287] = 0,
287 | [5272] = 0,
288 | [5308] = 0,
289 | },
290 | [63195] = {
291 | [5262] = 0,
292 | },
293 | [61945] = {
294 | [45212] = 0,
295 | [45213] = 0,
296 | },
297 | [63433] = {
298 | [7524] = 0,
299 | [7523] = 0,
300 | },
301 | [63432] = {
302 | [46295] = 0,
303 | },
304 | [63459] = {
305 | [7458] = 0,
306 | [7460] = 0,
307 | [7459] = 0,
308 | },
309 | [63450] = {
310 | [-250518] = 0,
311 | },
312 | [63452] = {
313 | [-250518] = 0,
314 | },
315 | [62829] = {
316 | [1806] = 0,
317 | [1808] = 0,
318 | },
319 | [62838] = {
320 | [-250371] = 0,
321 | },
322 | [62839] = {
323 | [-250371] = 0,
324 | },
325 | [62840] = {
326 | [-250371] = 0,
327 | },
328 | [63409] = {
329 | [6519] = 0,
330 | [6518] = 0,
331 | [6517] = 0,
332 | [6527] = 0,
333 | },
334 | [63408] = {
335 | [46282] = 0,
336 | [46281] = 0,
337 | },
338 | [63410] = {
339 | [6520] = 0,
340 | [6521] = 0,
341 | },
342 | [63412] = {
343 | [45762] = 0,
344 | [45761] = 0,
345 | },
346 | [63411] = {
347 | [6560] = 0,
348 | },
349 | [63414] = {
350 | [-250508] = 0,
351 | },
352 | [63420] = {
353 | [-250509] = 0,
354 | },
355 | [63421] = {
356 | [-250510] = 0,
357 | },
358 | [63422] = {
359 | [-250511] = 0,
360 | },
361 | [63423] = {
362 | [-250512] = 0,
363 | },
364 | [62577] = {
365 | [-5000005] = 0,
366 | [-5000006] = 0,
367 | [-5000007] = 0,
368 | },
369 | [62551] = {
370 | [-250299] = 0,
371 | },
372 | [62888] = {
373 | [-250384] = 0,
374 | },
375 | [62541] = {
376 | [-250295] = 0,
377 | },
378 | [62538] = {
379 | [-250292] = 0,
380 | },
381 | [62762] = {
382 | [1557] = 0,
383 | },
384 | [63155] = {
385 | [-250047] = 0,
386 | },
387 | [63316] = {
388 | [-250488] = 0,
389 | },
390 | [62252] = {
391 | [524] = 0,
392 | },
393 | [62253] = {
394 | [118] = 0,
395 | },
396 | [62254] = {
397 | [822] = 0,
398 | },
399 | [62271] = {
400 | [45501] = 0,
401 | },
402 | [62344] = {
403 | [-250227] = 0,
404 | },
405 | [62345] = {
406 | [-250227] = 0,
407 | },
408 | [62528] = {
409 | [5635] = 0,
410 | },
411 | [63241] = {
412 | [-250465] = 0,
413 | },
414 | [63237] = {
415 | [-250462] = 0,
416 | },
417 | [63427] = {
418 | [-250515] = 0,
419 | },
420 | [63481] = {
421 | [-250530] = 0,
422 | },
423 | [63483] = {
424 | [-250531] = 0,
425 | },
426 | [63457] = {
427 | [-250521] = 0,
428 | },
429 | [63458] = {
430 | [10619] = 0,
431 | },
432 | [62284] = {
433 | [-250210] = 0,
434 | },
435 | [62285] = {
436 | [-250211] = 0,
437 | },
438 | [62286] = {
439 | [-250212] = 0,
440 | },
441 | [63166] = {
442 | [-250443] = 0,
443 | },
444 | [63169] = {
445 | [-250443] = 0,
446 | },
447 | [63796] = {
448 | [-250541] = 0,
449 | },
450 | [63797] = {
451 | [-250545] = 0,
452 | },
453 | [63798] = {
454 | [-250547] = 0,
455 | },
456 | [63799] = {
457 | [-250549] = 0,
458 | },
459 | [63824] = {
460 | [46378] = 0,
461 | },
462 | [63810] = {
463 | [-176361] = 0,
464 | [-176392] = 0,
465 | [-176393] = 0,
466 | [-177289] = 0,
467 | },
468 | [63786] = {
469 | [8540] = 0,
470 | [8541] = 0,
471 | [8542] = 0,
472 | },
473 | [63787] = {
474 | [46340] = 0,
475 | },
476 | [63792] = {
477 | [-250538] = 0,
478 | },
479 | [63793] = {
480 | [-250536] = 0,
481 | },
482 | [62476] = {
483 | [-184164] = 0,
484 | },
485 | [62477] = {
486 | [-176694] = 0,
487 | },
488 | [62314] = {
489 | [45531] = 0,
490 | },
491 | [62822] = {
492 | [45908] = 0,
493 | },
494 | [65326] = {
495 | [11736] = 0,
496 | },
497 | }
498 |
--------------------------------------------------------------------------------
/pfQuest-updater.lua:
--------------------------------------------------------------------------------
1 | function hcstrsplit(delimiter, subject)
2 | if not subject then return nil end
3 | local delimiter, fields = delimiter or ":", {}
4 | local pattern = string.format("([^%s]+)", delimiter)
5 | string.gsub(subject, pattern, function(c) fields[table.getn(fields)+1] = c end)
6 | return unpack(fields)
7 | end
8 |
9 | function formatVersion(versionNum)
10 | local major = math.floor(versionNum / 10000)
11 | local minor = math.floor((versionNum % 10000) / 100)
12 | local fix = versionNum % 100
13 | return major .. "." .. minor .. "." .. fix
14 | end
15 |
16 | local major, minor, fix = hcstrsplit(".", tostring(GetAddOnMetadata("pfQuest-epoch", "Version")))
17 | fix = fix or 0
18 | local alreadyshown = false
19 | local localversion = tonumber(major*10000 + minor*100 + fix)
20 | local remoteversion = tonumber(gpiupdateavailable) or 0
21 | local loginchannels = { "BATTLEGROUND", "RAID", "GUILD", "PARTY" }
22 | local groupchannels = { "BATTLEGROUND", "RAID", "PARTY" }
23 | local partyVersions = {}
24 | local manualPings = {}
25 |
26 | local function StripRealmName(fullName)
27 | if fullName and fullName:find("-") then
28 | return fullName:match("^([^-]+)")
29 | end
30 | return fullName
31 | end
32 |
33 | local function UpdatePartyVersionDisplay()
34 | if UnitName("player") ~= "Bennylava" then
35 | return
36 | end
37 |
38 | for i = 1, GetNumPartyMembers() do
39 | local memberName = UnitName("party" .. i)
40 | local stripMemberName = StripRealmName(memberName)
41 | local version = partyVersions[memberName] or partyVersions[stripMemberName]
42 |
43 | if memberName and version then
44 | local frame = _G["ElvUF_PartyGroup1UnitButton" .. i]
45 | if not frame then
46 | frame = _G["PartyMemberFrame" .. i]
47 | end
48 |
49 | if frame then
50 | local labelName = "pfQuestVersionLabel" .. i
51 | local label = _G[labelName]
52 |
53 | local parent = frame.RaisedElementParent or frame
54 |
55 | if not label then
56 | label = parent:CreateFontString(labelName, "OVERLAY", "GameFontNormalSmall")
57 | label:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
58 | label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 15)
59 | end
60 |
61 | label:SetText("v" .. formatVersion(version))
62 | label:SetTextColor(0.4, 1, 1)
63 | label:Show()
64 | end
65 | end
66 | end
67 | end
68 |
69 | local function UpdateTargetVersionDisplay()
70 | if UnitName("player") ~= "Bennylava" then
71 | return
72 | end
73 |
74 | if not UnitExists("target") then
75 | local label = _G["pfQuestVersionLabelTarget"]
76 | if label then
77 | label:Hide()
78 | end
79 | return
80 | end
81 |
82 | local targetName = UnitName("target")
83 | if not targetName then return end
84 |
85 | local stripTargetName = StripRealmName(targetName)
86 | local version = partyVersions[targetName] or partyVersions[stripTargetName]
87 |
88 | if version then
89 | local frame = _G["ElvUF_Target"]
90 | if not frame then
91 | frame = _G["TargetFrame"]
92 | end
93 |
94 | if frame then
95 | local labelName = "pfQuestVersionLabelTarget"
96 | local label = _G[labelName]
97 |
98 | local parent = frame.RaisedElementParent or frame
99 |
100 | if not label then
101 | label = parent:CreateFontString(labelName, "OVERLAY", "GameFontNormalSmall")
102 | label:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
103 | label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 15)
104 | end
105 |
106 | label:SetText("v" .. formatVersion(version))
107 | label:SetTextColor(0.4, 1, 1)
108 | label:Show()
109 | end
110 | else
111 | local label = _G["pfQuestVersionLabelTarget"]
112 | if label then
113 | label:Hide()
114 | end
115 | end
116 | end
117 |
118 | gpiupdater = CreateFrame("Frame")
119 | gpiupdater:RegisterEvent("CHAT_MSG_ADDON")
120 | gpiupdater:RegisterEvent("PLAYER_ENTERING_WORLD")
121 | gpiupdater:RegisterEvent("PARTY_MEMBERS_CHANGED")
122 | gpiupdater:RegisterEvent("PLAYER_TARGET_CHANGED")
123 | gpiupdater:SetScript("OnEvent", function(_, event, ...)
124 | if event == "CHAT_MSG_ADDON" then
125 | local arg1, arg2, arg3, arg4 = ...
126 | if arg1 == "pfqe" then
127 | local v, remoteversion = hcstrsplit(":", arg2)
128 | remoteversion = tonumber(remoteversion)
129 | if v == "VERSION" and remoteversion then
130 | local strippedName = StripRealmName(arg4)
131 | partyVersions[strippedName] = remoteversion
132 | if remoteversion > localversion then
133 | gpiupdateavailable = remoteversion
134 | if not alreadyshown then
135 | local currentVer = formatVersion(localversion)
136 | local availableVer = formatVersion(remoteversion)
137 | print("|cff33ffccpfQuest |cffcccccc[Project Epoch DB]|r New version available!")
138 | print("Current: |cff66ccff" .. currentVer .. "|r -> Available: |cff66ccff" .. availableVer .. "|r")
139 | print("|cff66ccffhttps://github.com/Bennylavaa/pfQuest-epoch/releases|r")
140 | alreadyshown = true
141 | end
142 | end
143 | end
144 | if v == "PING?" then
145 | if arg3 == "WHISPER" then
146 | SendAddonMessage("pfqe", "PONG!:"..GetAddOnMetadata("pfQuest-epoch", "Version"), "WHISPER", arg4)
147 | else
148 | for _, chan in ipairs(loginchannels) do
149 | SendAddonMessage("pfqe", "PONG!:"..GetAddOnMetadata("pfQuest-epoch", "Version"), chan)
150 | end
151 | end
152 | end
153 | if v == "PONG!" then
154 | if UnitName("player") == "Bennylava" then
155 | local pongCmd, pongversion = hcstrsplit(":", arg2)
156 | local pmajor, pminor, pfix = hcstrsplit(".", tostring(pongversion))
157 | pfix = pfix or 0
158 | pongversion = tonumber(pmajor*10000 + pminor*100 + pfix)
159 | local strippedName = StripRealmName(arg4)
160 | partyVersions[strippedName] = pongversion
161 |
162 | if manualPings[strippedName] then
163 | print("|cffff8000"..arg4.."|r - |cff66ccffv"..formatVersion(pongversion).."|r")
164 | manualPings[strippedName] = nil
165 | end
166 |
167 | UpdatePartyVersionDisplay()
168 | UpdateTargetVersionDisplay()
169 | end
170 | end
171 | end
172 | elseif event == "PARTY_MEMBERS_CHANGED" then
173 | local groupsize = GetNumRaidMembers() > 0 and GetNumRaidMembers() or GetNumPartyMembers() > 0 and GetNumPartyMembers() or 0
174 | if (gpiupdater.group or 0) < groupsize then
175 | for _, chan in ipairs(groupchannels) do
176 | SendAddonMessage("pfqe", "VERSION:" .. localversion, chan)
177 | end
178 | end
179 | gpiupdater.group = groupsize
180 | UpdatePartyVersionDisplay()
181 | elseif event == "PLAYER_ENTERING_WORLD" then
182 | if not alreadyshown and localversion < remoteversion then
183 | local currentVer = formatVersion(localversion)
184 | local availableVer = formatVersion(remoteversion)
185 | print("|cff33ffccpfQuest |cffcccccc[Project Epoch DB]|r New version available!")
186 | print("Current: |cff66ccff" .. currentVer .. "|r -> Available: |cff66ccff" .. availableVer .. "|r")
187 | print("|cff66ccffhttps://github.com/Bennylavaa/pfQuest-epoch/releases|r")
188 | gpiupdateavailable = localversion
189 | alreadyshown = true
190 | end
191 | for _, chan in ipairs(loginchannels) do
192 | SendAddonMessage("pfqe", "VERSION:" .. localversion, chan)
193 | end
194 | elseif event == "PLAYER_TARGET_CHANGED" then
195 | if UnitName("player") == "Bennylava" then
196 | local targetName = UnitName("target")
197 | if targetName and UnitIsPlayer("target") then
198 | SendAddonMessage("pfqe", "PING?", "WHISPER", targetName)
199 | end
200 | end
201 | UpdateTargetVersionDisplay()
202 | end
203 | end)
204 |
205 | SLASH_PFQEPING1 = "/pfqe"
206 | SlashCmdList["PFQEPING"] = function(msg)
207 | if msg == "" then
208 | print("Usage: /pfqe PLAYERNAME")
209 | return
210 | end
211 | local strippedName = StripRealmName(msg)
212 | manualPings[strippedName] = true
213 | SendAddonMessage("pfqe", "PING?", "WHISPER", msg)
214 | end
--------------------------------------------------------------------------------
/db/enUS/objects-epoch.lua:
--------------------------------------------------------------------------------
1 | pfDB["objects"]["enUS-epoch"] = {
2 | [175802] = "Small Lockbox",
3 | [176495] = "Zeppelin (The Purple Princess)",
4 | [178824] = "Meeting Stone (Razorfen Downs)",
5 | [178825] = "Meeting Stone (Razorfen Kraul)",
6 | [178826] = "Meeting Stone (Dire Maul)",
7 | [178827] = "Meeting Stone (Maraudon)",
8 | [178828] = "Meeting Stone (Blackfathom Deeps)",
9 | [178829] = "Meeting Stone (Zul\'Farrak)",
10 | [178831] = "Meeting Stone (Strathholme)",
11 | [178832] = "Meeting Stone (Scholomance)",
12 | [178833] = "Meeting Stone (Uldaman)",
13 | [178834] = "Meeting Stone (The Deadmines)",
14 | [178844] = "Meeting Stone (Scarlet Monastery)",
15 | [178845] = "Meeting Stone (Shadowfang Keep)",
16 | [178884] = "Meeting Stone (Wailing Caverns)",
17 | [179554] = "Meeting Stone (The Temple of Atal\'Hakkar)",
18 | [179555] = "Meeting Stone (Gnomeregan)",
19 | [179584] = "Meeting Stone (Blackrock Depths)",
20 | [179585] = "Meeting Stone (Blackrock Spire)",
21 | [179595] = "Meeting Stone (The Stockade)",
22 | [179596] = "Meeting Stone (Ragefire Chasm)",
23 | [250000] = "Alteraci Shilling",
24 | [250001] = "Aqiri Ynusi",
25 | [250002] = "Atal\'ai Real",
26 | [250003] = "Azsharan Agorot",
27 | [250004] = "Azsharan Shekel",
28 | [250005] = "Kezan Penny",
29 | [250006] = "Kezan Dollar",
30 | [250007] = "Kezan Quarter",
31 | [250008] = "Dark Iron Drachma",
32 | [250009] = "Dark Iron Mina",
33 | [250010] = "Dark Iron Obol",
34 | [250011] = "Dark Iron Stater",
35 | [250012] = "Defias Ducat",
36 | [250013] = "Gnomish Guinea",
37 | [250014] = "Ironforge Florin",
38 | [250015] = "Ironforge Shilling",
39 | [250016] = "Kaldorei Lune",
40 | [250017] = "Kaldorei Sol",
41 | [250018] = "Kaldorei Star",
42 | [250019] = "Kul\'Tiran Crown",
43 | [250020] = "Kul\'Tiran Farthing",
44 | [250021] = "Legion Paisa",
45 | [250022] = "Legion Rupee",
46 | [250023] = "Lordaeron Crown",
47 | [250024] = "Lordaeron Farthing",
48 | [250025] = "Lordaeron Penny",
49 | [250026] = "Dalaran Shilling",
50 | [250027] = "Gilnean Crown",
51 | [250028] = "Naga Bigshell",
52 | [250029] = "Naga Smallshell",
53 | [250030] = "Pandaren Yuen",
54 | [250031] = "Stormwind Crown",
55 | [250032] = "Stormwind Shilling",
56 | [250033] = "Stormwind Farthing",
57 | [250034] = "Stormwind Sixpence",
58 | [250035] = "Stromgarde Shilling",
59 | [250036] = "A Coin of Unknown Origin",
60 | [250037] = "Venture Co. IOU",
61 | [250038] = "Venture Co. Voucher",
62 | [250039] = "Wildhammer Florin",
63 | [250040] = "Shadraspawn Sack",
64 | [250041] = "Gryphon Nest",
65 | [250042] = "Waterlogged Chest",
66 | [250043] = "Falstad Wildhammer",
67 | [250044] = "Royal Bite Reed",
68 | [250045] = "Turtle \"Nugget\"",
69 | [250046] = "Ubo\'s Chest",
70 | [250047] = "Rirrek\'s Effigy",
71 | [250048] = "Wanted: Foulcrest",
72 | [250049] = "Wanted Poster", -- Badlands
73 | [250050] = "Wildhammer Bones",
74 | [250051] = "Job Opening: Captain of the Guard",
75 | [250052] = "Howin\'s Fishing Chair",
76 | [250053] = "Howin\'s Tackle Box",
77 | [250054] = "Driftwood",
78 | [250055] = "Waterlogged Sword",
79 | [250056] = "Relic of the Past",
80 | [250057] = "Smokywood Pastures Shipping Crate",
81 | [250058] = "Mecha-Chicken Parts",
82 | [250059] = "Cauldron",
83 | [250060] = "Prism Prison",
84 | [250061] = "Crystal Lock",
85 | [250062] = "Crystal Lock",
86 | [250063] = "Crystal Lock",
87 | [250064] = "Crystal Lock",
88 | [250065] = "Milhouse\'s Belongings",
89 | [250066] = "Ghost Gate",
90 | [250067] = "Cache of the Warchief",
91 | [250068] = "Solid Chest",
92 | [250069] = "Balefire Draught",
93 | [250070] = "Crate of Felstone Grog",
94 | [250071] = "Felstone Grog",
95 | [250072] = "Kuzhir Atramentum",
96 | [250073] = "High Altar of Felstone Fortress",
97 | [250074] = "Felstone - Fel Mark",
98 | [250075] = "Highborne Relic Chest",
99 | [250076] = "Starfall Crystal",
100 | [250077] = "Eleanor\'s Crate",
101 | [250078] = "Starlight Shrine",
102 | [250079] = "Arcanaeum Mooncrystal Altar",
103 | [250080] = "Immaculate Scroll",
104 | [250081] = "Broodmother\'s Lair",
105 | [250082] = "Rune of Warding",
106 | [250083] = "Small Fleshwerks Fire",
107 | [250084] = "Ghost Gate",
108 | [250085] = "Arena Spoils",
109 | [250086] = "Anvilrage Mole Machine",
110 | [250087] = "Anvilrage Mole Machine",
111 | [250088] = "Doodad_BOSSGATE02",
112 | [250089] = "Defias Gunpowder",
113 | [250090] = "Defias Cannon",
114 | [250091] = "Factory Door",
115 | [250092] = "Mast Room Door",
116 | [250093] = "Foundry Door",
117 | [250094] = "Iron Clad Door",
118 | [250095] = "Vacant Minecart",
119 | [250096] = "Excavated Artifact",
120 | [250097] = "Crystal Miner",
121 | [250098] = "Crystal Miner",
122 | [250099] = "Crystal Miner",
123 | [250100] = "Crystal Miner",
124 | [250101] = "Crystal Miner",
125 | [250102] = "Crystal Miner",
126 | [250103] = "Warning!",
127 | [250104] = "No blasting!",
128 | [250105] = "Turn away!",
129 | [250106] = "Do not wake the beast!",
130 | [250107] = "Eye\'s closed!",
131 | [250108] = "Warning!!!",
132 | [250109] = "Danger!",
133 | [250110] = "Run!!",
134 | [250111] = "I\'m not joking!",
135 | [250112] = "Don\'t be a fool!",
136 | [250113] = "No! No! No!",
137 | [250114] = "DANGER!",
138 | [250115] = "No Overtime!",
139 | [250116] = "STOP!",
140 | [250117] = "NO BLAST MINING!",
141 | [250118] = "Stop!",
142 | [250119] = "Do not enter!",
143 | [250120] = "Mine closed!",
144 | [250121] = "Silithid Metal Alloy",
145 | [250122] = "Wetland Hemp",
146 | [250123] = "Garr\'s Core",
147 | [250124] = "Spiked Earth Pillar",
148 | [250125] = "Lava Bubbles",
149 | [250126] = "Active Rune of Warding",
150 | [250127] = "Molten Bridge Lava Flow",
151 | [250128] = "Onyxia\'s Gate",
152 | [250129] = "Onyxia Room Encloser",
153 | [250130] = "Onyxia Egg",
154 | [250131] = "Frozen Onyxia Egg",
155 | [250132] = "Portal Visual",
156 | [250133] = "Blood Filled Orb",
157 | [250134] = "The Pervasive Argument for Laws",
158 | [250135] = "Is Eating Tauren Wrong: A Case Study",
159 | [250136] = "The Troll Wars & The Troll Warcrimes",
160 | [250137] = "Crate of Blessed Ingots",
161 | [250138] = "Sothos and Jarien\'s Heirlooms",
162 | [250139] = "Atal\'ai Statue",
163 | [250140] = "Atal\'ai Statue",
164 | [250141] = "Atal\'ai Statue",
165 | [250142] = "Atal\'ai Statue",
166 | [250143] = "Atal\'ai Statue",
167 | [250144] = "Atal\'ai Statue",
168 | [250145] = "Pedestal of Blood",
169 | [250146] = "The Tablet of Alani",
170 | [250147] = "The Tablet of Jakari",
171 | [250148] = "Royal Troll Mummy",
172 | [250149] = "Falrin Forcefield",
173 | [250150] = "A History of Baradin Hold",
174 | [250151] = "Captain\'s Log",
175 | [250152] = "Origin of the Kirin Tor",
176 | [250153] = "The Eternal Woods Manuscript",
177 | [250154] = "Reflections on Celestial Immortality",
178 | [250155] = "Solarsong: A Novel",
179 | [250156] = "A Short History of the High Elf Migration",
180 | [250157] = "Hidden Compartment",
181 | [250158] = "An Undisturbed Pile of Dirt",
182 | [250159] = "An Undisturbed Pile of Dirt",
183 | [250160] = "Spectral Treasure Chest",
184 | [250161] = "Jailor\'s Journal",
185 | [250162] = "Special Brew",
186 | [250163] = "Arcanite Lode",
187 | [250164] = "Ooze Covered Arcanite Lode",
188 | [250165] = "Call to Skirmish", -- Horde
189 | [250166] = "Call to Skirmish", -- Alliance
190 | [250167] = "Call to Skirmish: Stonetalon Mountains",
191 | [250168] = "Call to Skirmish: Ashenvale",
192 | [250169] = "Call to Skirmish: Hillsbrad Foothills",
193 | [250170] = "Call to Skirmish: Thousand Needles",
194 | [250171] = "Call to Skirmish: Alterac Mountains",
195 | [250172] = "Call to Skirmish: Desolace",
196 | [250173] = "Call to Skirmish: Arathi Highlands",
197 | [250174] = "Call to Skirmish: Swamp of Sorrows",
198 | [250175] = "Call to Skirmish: Badlands",
199 | [250176] = "Call to Skirmish: The Hinterlands",
200 | [250177] = "Call to Skirmish: Stranglethorn Vale",
201 | [250178] = "Call to Skirmish: Tanaris",
202 | [250179] = "Call to Skirmish: Felwood",
203 | [250180] = "Call to Skirmish: Azshara",
204 | [250181] = "Call to Skirmish: Un\'Goro Crater",
205 | [250182] = "Call to Skirmish: Searing Gorge",
206 | [250183] = "Call to Skirmish: Western Plaguelands",
207 | [250184] = "Call to Skirmish: Burning Steppes",
208 | [250185] = "Call to Skirmish: Eastern Plaguelands",
209 | [250186] = "Call to Skirmish: Silithus",
210 | [250187] = "Call to Skirmish: Feralas",
211 | [250188] = "Call to Skirmish: Winterspring",
212 | [250189] = "Call to Skirmish: Dustwallow Marsh",
213 | [250190] = "Call to Skirmish: Blasted Lands",
214 | [250191] = "Aid Station Banner",
215 | [250192] = "Aid Station Banner",
216 | [250193] = "Aid Station Banner",
217 | [250194] = "Aid Station Banner",
218 | [250195] = "Aid Station Banner",
219 | [250196] = "Aid Station Banner",
220 | [250197] = "Aid Station Banner",
221 | [250198] = "Alliance Banner",
222 | [250199] = "Contested Banner",
223 | [250200] = "Flag Pole",
224 | [250201] = "Horde Banner",
225 | [250202] = "Alliance Banner",
226 | [250203] = "Neutral Banner",
227 | [250204] = "Dark Portal Fragment",
228 | [250205] = "the basement of Foothold Citadel",
229 | [250206] = "the southern orc burrow outside of Stonard",
230 | [250207] = "Anvil",
231 | [250208] = "Caer Darrow",
232 | [250209] = "Brazier of the Dormant Flame",
233 | [250210] = "Natural Spring Fissure",
234 | [250211] = "Stonetalon Spring Fissure",
235 | [250212] = "Raven Hill Well",
236 | [250213] = "Brazier of Springfont",
237 | [250214] = "Gravestone",
238 | [250215] = "Frost Thistle",
239 | [250216] = "Magically-Sealed Strongbox",
240 | [250217] = "A Weathered Headstone",
241 | [250218] = "Uplands Intelligence Drop",
242 | [250219] = "Dandred\'s Fold Intelligence Drop",
243 | [250220] = "Teleportation Crystal",
244 | [250221] = "Barony Mordis Chest",
245 | [250222] = "Apple Bucket",
246 | [250223] = "Juicy Watermelon",
247 | [250224] = "Steam Vent",
248 | [250225] = "Shadowberry Bush",
249 | [250226] = "Oversized Shadowberry Bush",
250 | [250227] = "Fireplume\'s Roost",
251 | [250228] = "Strong Smelling Bait",
252 | [250229] = "Falstad Wildhammer",
253 | [250230] = "Battered Journal",
254 | [250231] = "Sunken Shaman Shrine",
255 | [250232] = "Shadowforge Treasure",
256 | [250233] = "Case of Mithril",
257 | [250234] = "Crate of Indurium Ore",
258 | [250235] = "Indurium Matrix",
259 | [250236] = "Wanted Poster", -- Darkshore
260 | [250237] = "Submerged Necklace",
261 | [250238] = "Lava-Scarred Chest",
262 | [250239] = "Mound of Clay",
263 | [250240] = "Ore Crate",
264 | [250241] = "Gold Ore Crate",
265 | [250242] = "Sunken Treasure",
266 | [250243] = "WANTED!",
267 | [250244] = "WANTED!",
268 | [250245] = "Izalnir\'s Belongings",
269 | [250246] = "Dreadmaul Peak Altar",
270 | [250247] = "Frozen Rookery Egg",
271 | [250248] = "Abandoned Herb Pouch",
272 | [250249] = "Torn Shirt",
273 | [250250] = "Subtly Writhing Sack",
274 | [250251] = "Woven Basket",
275 | [250252] = "Ornate Wardrobe",
276 | [250253] = "Metal Box",
277 | [250254] = "Knapsack",
278 | [250255] = "Lava Pool",
279 | [250256] = "Altar of Storms",
280 | [250257] = "Karazhan Front Door",
281 | [250258] = "Karazhan Valuables",
282 | [250259] = "Karazhan Valuables",
283 | [250260] = "Karazhan Valuables",
284 | [250261] = "Karazhan Valuables",
285 | [250262] = "Old Cabinet",
286 | [250263] = "Frostmane Armaments",
287 | [250264] = "Frostmane Armaments",
288 | [250265] = "the deepest part of Ironforge Airfield\'s lake",
289 | [250266] = "Bucket of Tools",
290 | [250267] = "Sudden Fire",
291 | [250268] = "Drum of Lubricant",
292 | [250269] = "Metal Plating",
293 | [250270] = "Trash Part",
294 | [250271] = "Trash Part",
295 | [250272] = "Trash Part",
296 | [250273] = "Trash Part",
297 | [250274] = "Cage",
298 | [250275] = "Frostmane Cage",
299 | [250276] = "Anvil",
300 | [250277] = "Amberstill Ranch Dummy",
301 | [250278] = "Kinetic Ore",
302 | [250279] = "Lost Tablet",
303 | [250280] = "Blood Petal Bush",
304 | [250281] = "Bloody Note",
305 | [250282] = "Pile of Dust",
306 | [250283] = "Revil\'s Notes",
307 | [250284] = "Lost Shipment",
308 | [250285] = "Brightwood Bloom",
309 | [250286] = "Twilight Grove Moonwell",
310 | [250287] = "Sarae\'s Experiment",
311 | [250288] = "Sarae\'s Experiment",
312 | [250289] = "Nelle\'s Diary",
313 | [250290] = "Leftover Bread",
314 | [250291] = "Cask of Brightwood White",
315 | [250292] = "Darkshire Hay Field",
316 | [250293] = "Picnic Basket",
317 | [250294] = "Cleansing Clam",
318 | [250295] = "Faye Underhills Home",
319 | [250296] = "Jacks Letter",
320 | [250297] = "Wanted: Plagued Shambler",
321 | [250298] = "Comfortably Large Crate",
322 | [250299] = "Blackrock Mountain",
323 | [250300] = "Cenarion Ritual Altar",
324 | [250301] = "Old Lantern",
325 | [250302] = "Empty Jug",
326 | [250303] = "Old Bottle",
327 | [250304] = "Logging Equipment",
328 | [250305] = "Old Book",
329 | [250306] = "Old Bucket",
330 | [250307] = "Discarded Doll",
331 | [250308] = "Old Clothes",
332 | [250309] = "Discarded Spool",
333 | [250310] = "A Dusty Letter",
334 | [250311] = "A Dirty Letter",
335 | [250312] = "A Torn Letter",
336 | [250313] = "Box of Salvaged Goods",
337 | [250314] = "Egg Sac",
338 | [250315] = "Scourge Cauldron Focus",
339 | [250316] = "Tower of Azora Well Dummy",
340 | [250317] = "Mining Equipment",
341 | [250318] = "Soaked Barrel",
342 | [250319] = "Northshire Reed",
343 | [250320] = "Northshire Falls Dummy",
344 | [250321] = "Alteraci Legion Banner of Honor", -- Alterac Mountains - Alterac
345 | [250322] = "Alteraci Legion Banner of Honor", -- Alterac Mountains - Dalaran
346 | [250323] = "Alteraci Legion Banner of Honor", -- Arathi Highlands
347 | [250324] = "Wanted: Big Blue",
348 | [250325] = "Syndicate Food Supplies Dummy",
349 | [250326] = "Apple",
350 | [250327] = "Apple Cider Barrel",
351 | [250328] = "Apple Press",
352 | [250329] = "Crates of Armaments",
353 | [250330] = "Ghost-Touched Ore",
354 | [250331] = "WANTED", -- Crossroads
355 | [250332] = "Thelsamar Graveyard Dummy",
356 | [250333] = "Lost Artifact",
357 | [250334] = "Loch Weed",
358 | [250335] = "WANTED", -- Hillsbrad Foothills
359 | [250336] = "Crate of Stolen Horde Supplies",
360 | [250337] = "Ritual Altar",
361 | [250338] = "Crate of Artifacts",
362 | [250339] = "Makeshift Ambermill Cage",
363 | [250340] = "Historian\'s Bookcase",
364 | [250341] = "Looted Book",
365 | [250342] = "Looted Perfume",
366 | [250343] = "Looted Lantern",
367 | [250344] = "Looted Jewelry",
368 | [250345] = "Bloodscalp Ritual Altar",
369 | [250346] = "Blood Brazier",
370 | [250347] = "Waterlogged Weapons Cache",
371 | [250348] = "Sidis Faintsnipe Dummy",
372 | [250349] = "Zul\'Kunda Cage",
373 | [250350] = "Zul\'Kunda Cage",
374 | [250351] = "Failed Experiment",
375 | [250352] = "The Tablet of Zuuldaia",
376 | [250353] = "Conspicuous Sand Pile",
377 | [250354] = "Conspicuous Sand Pile",
378 | [250355] = "Conspicuous Sand Pile",
379 | [250356] = "Tulip\'s Final Entry",
380 | [250357] = "Ocniir\'s Chest",
381 | [250358] = "Fire Snap Chili",
382 | [250359] = "Swamp Reed",
383 | [250360] = "WANTED", -- Loch Modan
384 | [250361] = "WANTED", -- Swamp of Sorrows
385 | [250362] = "Box of Collected Relics",
386 | [250363] = "Case of Ore",
387 | [250364] = "Zapetta\'s Stash",
388 | [250365] = "Joseph\'s Grave Dummy",
389 | [250366] = "Winterspring Temporal Disturbance",
390 | [250367] = "Pile of Correspondence",
391 | [250368] = "Old Woodpile",
392 | [250369] = "Pure Elwynn Soil Sample",
393 | [250370] = "Anvil",
394 | [250371] = "Prepared Soil",
395 | [250372] = "Barov Sepulcher Plaque",
396 | [250373] = "Scarlet Outpost",
397 | [250374] = "Prank Bag", --Bullwork
398 | [250375] = "Prank Bag", --Chillind Camp
399 | [250376] = "Prank Bag", --Uther\'s Tomb
400 | [250377] = "WANTED", -- Feralas
401 | [250378] = "Dragonmaw Weapon Rack",
402 | [250379] = "Box of Assorted Parts",
403 | [250380] = "Highlands Corn",
404 | [250381] = "Conspicuous Pile Of Dirt",
405 | [250382] = "Ameth\'Aran Focus",
406 | [250383] = "Ameth\'Aran Focus Ward",
407 | [250384] = "Ruins of Stardust Dummy",
408 | [250385] = "Sturdy Reed",
409 | [250386] = "Tremormatic MK I",
410 | [250387] = "Tremormatic MK II",
411 | [250388] = "New Demon Seed",
412 | [250389] = "",
413 | [250390] = "",
414 | [250391] = "Valormok Mine Vein",
415 | [250392] = "Lake Mennar",
416 | [250393] = "West Legashi Effigy",
417 | [250394] = "East Legashi Effigy",
418 | [250395] = "South Legashi Effigy",
419 | [250396] = "Thunderhead Hippogryph Feather",
420 | [250397] = "Naga Shrine",
421 | [250398] = "Watery Highborne Relic",
422 | [250399] = "Shipwrecked Part",
423 | [250400] = "Wreck of the Spirit of Gnomeregan",
424 | [250401] = "Wreck of the Pine Duck",
425 | [250402] = "Wreck of the Gnomeforce One",
426 | [250403] = "Wreck of the Microtanic",
427 | [250404] = "Shipwrecked Supplies",
428 | [250405] = "Whitetide Lotus",
429 | [250406] = "Deviate Feast",
430 | [250407] = "Medical Supply Crate",
431 | [250408] = "Wobble Hollow Wanted Poster",
432 | [250409] = "Wobble Hollow Wanted Poster",
433 | [250410] = "Cage",
434 | [250411] = "Anvil",
435 | [250412] = "The Beast\'s Room",
436 | [250413] = "Smokywood Lockbox",
437 | [250414] = "Smokywood Lockbox",
438 | [250415] = "Quilboar Dagger",
439 | [250416] = "Looted Lorespeaker Crate",
440 | [250417] = "WANTED", -- Durotar
441 | [250418] = "Torn Travel Pamphlet",
442 | [250419] = "Clue 1",
443 | [250420] = "Clue 2",
444 | [250421] = "Clue 3",
445 | [250422] = "Defias Strongbox",
446 | [250423] = "Bottle of Raven\'s Dark Ale",
447 | [250424] = "Everit\'s Canteen",
448 | [250425] = "Everit\'s Notebook",
449 | [250426] = "Everit\'s Lantern",
450 | [250427] = "Everit\'s Laundry",
451 | [250428] = "Sunken Chest",
452 | [250429] = "Sunken Treasure",
453 | [250430] = "Magical Barrier",
454 | [250431] = "Ceremonial Bonfire",
455 | [250432] = "Grimtotem Treasured Possession",
456 | [250433] = "Fossilised Gallbladder",
457 | [250434] = "Wanted: Keiko",
458 | [250435] = "Wanted: Wilfiz Silverbit",
459 | [250436] = "Thistleweed",
460 | [250437] = "WANTED", -- Westfall
461 | [250438] = "Cage",
462 | [250439] = "Wanted: Dragon Killers",
463 | [250440] = "Wanted: King Krool",
464 | [250441] = "Wanted: King Krool",
465 | [250442] = "Zeppelin Cargo",
466 | [250443] = "Bloodvenom Falls Dock",
467 | [250444] = "Memento of Kil\'jaeden",
468 | [250445] = "Memento of Sargeras",
469 | [250446] = "Memento of Kil\'Jaeden",
470 | [250447] = "Memento of Tichondrius",
471 | [250448] = "Ogre Corpse",
472 | [250449] = "WANTED", -- Swamp of Sorrows
473 | [250450] = "Palemane Cage",
474 | [250451] = "Oil Spill",
475 | [250452] = "Inn Entrance",
476 | [250453] = "Calf Bait",
477 | [250454] = "Wanted: Jasone",
478 | [250455] = "Ancient Reports",
479 | [250456] = "Silithus Crystal Pile",
480 | [250457] = "Silithus Wasp Swarm",
481 | [250458] = "Lower Twilight Tablet",
482 | [250459] = "Upper Twilight Tablet",
483 | [250460] = "Silithus Crystal Fragment",
484 | [250461] = "Cult Plans",
485 | [250462] = "Pile of Meat",
486 | [250463] = "Pile of Meat",
487 | [250464] = "Anvil",
488 | [250465] = "Heart of Hive\'Regal",
489 | [250466] = "Valor\'s Rest Supplies",
490 | [250467] = "Palladium Ore",
491 | [250468] = "Coprolite Node",
492 | [250469] = "Tremormatic MK II",
493 | [250470] = "Water Vine",
494 | [250471] = "Venture Co. Supplies",
495 | [250472] = "Venture Co. Supplies",
496 | [250473] = "Kaela\'s Campfire Dummy",
497 | [250474] = "Alter of Gral",
498 | [250475] = "Altar of Gral",
499 | [250476] = "Gral Offering",
500 | [250477] = "Sandshore Seagrass",
501 | [250478] = "Thistleshrub Valley Moonwell",
502 | [250479] = "Roknar",
503 | [250480] = "Empowered Kezan\'s Premium Refrigerator",
504 | [250481] = "Ancient Troll Pot",
505 | [250482] = "Ancient Troll Pot",
506 | [250483] = "Ancient Troll Pot",
507 | [250484] = "Gnome Racer Parts",
508 | [250485] = "Gnome Racer Parts",
509 | [250486] = "Gnome Racer Parts",
510 | [250487] = "Oil Platform Base",
511 | [250488] = "Narain Southfancy\'s House",
512 | [250489] = "Rocket Detonator",
513 | [250490] = "Ancient Chest",
514 | [250491] = "Oil Spill",
515 | [250492] = "Slightly-Open Oil Barrel",
516 | [250493] = "Timed Explosive",
517 | [250494] = "Filter Parts",
518 | [250495] = "Filter Parts",
519 | [250496] = "Zeppelin Cargo",
520 | [250497] = "Wanted Poster", -- Tanaris
521 | [250498] = "Bloodfeather Egg",
522 | [250499] = "Dolanaar Moonwell Dummy",
523 | [250500] = "Lily Whip",
524 | [250501] = "Ancient Tauren Totem",
525 | [250502] = "Weaveleaf",
526 | [250503] = "Thalanaar Moonwell",
527 | [250504] = "Weathered Totem",
528 | [250505] = "Carrion Vulture Egg",
529 | [250506] = "Golakka Hot Springs",
530 | [250507] = "Aru-Talis Artifact",
531 | [250508] = "Area of Interest Focus",
532 | [250509] = "Aru-Talis Site One",
533 | [250510] = "Aru-Talis Site Two",
534 | [250511] = "Aru-Talis Site Three",
535 | [250512] = "Aru-Talis Site Four",
536 | [250513] = "Fine River Sand",
537 | [250514] = "Anvil",
538 | [250515] = "Southern Pylon",
539 | [250516] = "How to Train Your Dinosaur",
540 | [250517] = "Anvil",
541 | [250518] = "Heap of Flesh",
542 | [250519] = "Heap of Flesh",
543 | [250520] = "A Treatise on Wintersaber Training",
544 | [250521] = "Frostfire Hot Springs",
545 | [250522] = "Icesap",
546 | [250523] = "Frost Crystal",
547 | [250524] = "Arcanaeum Door",
548 | [250525] = "Arcanaeum Door Rune",
549 | [250526] = "Arcanaeum Vault",
550 | [250527] = "Quel\'dorei Tablet",
551 | [250528] = "Nightmare Seed",
552 | [250529] = "Anvil",
553 | [250530] = "Malvor\'s Moonwell",
554 | [250531] = "Soft Soil",
555 | [250532] = "Snow Cherry Bush",
556 | [250533] = "Heart of An\'she",
557 | [250534] = "Heart of Mu\'sha",
558 | [250535] = "Anvil",
559 | [250536] = "Earthmother\'s Shrine", -- The Barrens
560 | [250537] = "Anvil",
561 | [250538] = "Earthmother\'s Shrine", -- Stonetalon Mountains
562 | [250539] = "Anvil",
563 | [250540] = "Bones of the Earthmother",
564 | [250541] = "Monument Rock",
565 | [250542] = "Anvil",
566 | [250543] = "Resting Place of Ancestors",
567 | [250544] = "Anvil",
568 | [250545] = "Resting Place of Ancestors", -- Dun Morogh
569 | [250546] = "Anvil",
570 | [250547] = "Resting Place of Ancestors", -- Ashenvale
571 | [250548] = "Anvil",
572 | [250549] = "Resting Place of Ancestors", -- Duskwood
573 | [250550] = "Anvil",
574 | [250551] = "Resting Place of Ancestors",
575 | [250552] = "Anvil",
576 | [250553] = "Resting Place of Ancestors",
577 | [250554] = "Anvil",
578 | [250555] = "Resting Place of Ancestors",
579 | [250556] = "Jug of Healing Water",
580 | [250557] = "Anvil",
581 | [250558] = "Durnholde Keep Gallows",
582 | [250559] = "Anvil",
583 | [250560] = "Plaguelands Cauldrons Dummy",
584 | [250561] = "Plagued Weed",
585 | [250562] = "Firebrew\'s Keg",
586 | [250563] = "Wiggi\'s Terminal",
587 | [250564] = "",
588 | [250565] = "Frozen Prey",
589 | [250566] = "Gnomish Components",
590 | [250567] = "Anvil",
591 | [250568] = "Shatterspear Axe Throwing Tournament",
592 | [250569] = "Cache of Courage",
593 | [250570] = "Anvil",
594 | [250571] = "Shatterspear Cave",
595 | [250572] = "Nitrous Power-Up",
596 | [250573] = "Red Rocket Power-Up",
597 | [250574] = "Green Rocket Power-Up",
598 | [250575] = "Jump Power-Up",
599 | [250576] = "Tar Puddle Power-Up",
600 | [250577] = "Cage",
601 | [250578] = "Summoning Crystal",
602 | [250579] = "Blue Sigil",
603 | [250580] = "Ice Block",
604 | [250581] = "Crescent Vent",
605 | [250582] = "Uldaman",
606 | [250583] = "Mailbox",
607 | [250584] = "Bonfire",
608 | [250585] = "Wildhammer Clan Headquarters",
609 | [250586] = "Ironforge Airfield Guard Post",
610 | [250587] = "Corrupted Dirt",
611 | [250588] = "Sarae\'s Experiment",
612 | [250589] = "The Bearded Barmaid",
613 | [250590] = "The Citrine Eagle",
614 | [250591] = "Durnholde Armaments",
615 | [250592] = "Tog\'thar Shackles",
616 | [250593] = "Vernado Shackles",
617 | [250594] = "Syndicate Food Supplies",
618 | [250595] = "Bhondur\'s Bones",
619 | [250596] = "The Sleeper\'s Bed",
620 | [250597] = "The Sleeper\'s Bed",
621 | [250598] = "The Sleeper\'s Bed",
622 | [250599] = "Deathguard Chunks",
623 | [250600] = "Joseph\'s Watch",
624 | [250601] = "Psathshroom",
625 | [250602] = "Rusty Metal Doors",
626 | [250603] = "Sarcophagus of an Unknown Human",
627 | [250604] = "Dry Hay Bail",
628 | [250605] = "Dry Hay Bail",
629 | [250606] = "Dry Hay Bail",
630 | [250607] = "Grim Batol Instance Portal",
631 | [250608] = "Box of Treasure",
632 | [250609] = "Crate of Assorted Armor",
633 | [250610] = "Shrine of Sha\'gri",
634 | [250611] = "Venture Co. Debris",
635 | [250612] = "Venture Co. Debris",
636 | [250613] = "Venture Co. Debris",
637 | [250614] = "Blacksmith",
638 | [250615] = "Engineering",
639 | [250616] = "Mining",
640 | [250617] = "Invigilator Watchtower Transporter",
641 | [250618] = "Invigilator Watchtower Transporter",
642 | [250619] = "Invigilator Watchtower Transporter",
643 | [250620] = "Invigilator Watchtower Transporter",
644 | [250621] = "Parachute Crate",
645 | [250622] = "Ancient Smithing Tome",
646 | [250623] = "Bubbly Fissure",
647 | [250624] = "Highlands Nook",
648 | [250625] = "Crambers Ranch",
649 | [250626] = "Burndural",
650 | [250627] = "Dun Guldar",
651 | [250628] = "Zeppelin Fire",
652 | [250629] = "Zeppelin Bonfire",
653 | [250630] = "",
654 | [250631] = "",
655 | [250632] = "Meeting Stone",
656 | [250633] = "Meeting Stone (Baradin Hold)",
657 | [250634] = "Meeting Stone (Glittermurk Mines)",
658 | [250635] = "Meeting Stone (Uldum)",
659 | [250636] = "Ssaggh ez oou Uul\'gwa Ryiu",
660 | [250637] = "Ssaggh ez oou Par\'okoth Vwyq",
661 | [250638] = "Ssagh ez oou Yyg\'far Vwah",
662 | [250639] = "Ssaggh ez oou Lwhuk Ak\'agthshi",
663 | [250640] = "Westfall",
664 | [250641] = "Northshire Abbey",
665 | [250642] = "Stormwind City",
666 | [250643] = "Northshire Abbey",
667 | [250644] = "Redridge",
668 | [250645] = "Goldshire",
669 | [250646] = "Southshore Sentinel",
670 | [250647] = "Southshore Sentinel",
671 | [250648] = "Karazhan Alchemy Tome",
672 | [250649] = "How to Cook Forty Humans",
673 | [250650] = "Unscrying Glyph",
674 | [250651] = "Tackle Box",
675 | [250652] = "Mycobloom",
676 | [250653] = "Black Forge Lava",
677 | [250654] = "Seething Ore",
678 | [250655] = "Buzzing Ore",
679 | [250656] = "Grundel\'s Bracers",
680 | [250657] = "Grundel\'s Chestpiece",
681 | [250658] = "Grundel\'s Hammer",
682 | [250659] = "Felix\'s Box",
683 | [250660] = "Felix\'s Chest",
684 | [250661] = "Felix\'s Bucket of Bolts",
685 | [250662] = "Relic of the Past",
686 | [250663] = "Sun-Ripened Banana",
687 | [250664] = "Tropical Seashell",
688 | [250665] = "Palm Frond Scroll",
689 | [250666] = "First Tablet of Bwonsamdi",
690 | [250667] = "Second Tablet of Bwonsamdi",
691 | [250668] = "Darkspear Isle Guardpost",
692 | [250669] = "Empty Signpost",
693 | [250670] = "Sen\'jin Village",
694 | [250671] = "Empty Signpost",
695 | [250672] = "Darkspear Isle",
696 | [250673] = "Sen\'jin Village",
697 | [250674] = "Gnomish Emergency Assembly Route",
698 | [250675] = "Banner of Heritage",
699 | [250676] = "Back Door",
700 | [250677] = "Springsocket Guard Post",
701 | [250678] = "Thorium Brotherhood Guard Post",
702 | [250679] = "Volchan\'s Heart",
703 | [250680] = "Abandoned Lockbox",
704 | -- Placeholder IDs
705 | [4000077] = "Memento of Archimonde",
706 | [5000001] = "Mana Berry Bush",
707 | [5000002] = "Ironforge Airfield Lake",
708 | [5000003] = "Corehound Manure",
709 | [5000004] = "Sander\'s Chest",
710 | [5000005] = "North Plague Cauldron",
711 | [5000006] = "South Plague Cauldron",
712 | [5000007] = "South West Plague Cauldron",
713 | [5000008] = "Noxious Glade Cauldron",
714 | [5000009] = "Fungal Vale Cauldron",
715 | [5000010] = "Strange Pylon Tanaris",
716 | [5000011] = "Strange Pylon Silithus",
717 | [5000012] = "Valley of Honor Pond",
718 | [5000013] = "Lake Elune\'ara",
719 | [5000014] = "Deadwind Pass River",
720 | [5000015] = "Winterspring Waters",
721 | [5000016] = "Blackwood Lake",
722 | [5000017] = "Thoridal River",
723 | [5000018] = "Blackrock Magma",
724 | [5000019] = "Un\'goro Waters",
725 | [5000020] = "Blasted Lands Coast",
726 | [5000021] = "Azshara Coast",
727 | [5000022] = "The Slag Pit",
728 | [5000023] = "Sandsorrow Pond",
729 | [5000024] = "Quel\'Danil Pond",
730 | [5000025] = "The Forgotten Coast",
731 | [5000026] = "Swamp Water",
732 | [5000027] = "Springsocket Pond",
733 | [5000028] = "Dustwallow Bay",
734 | [5000029] = "Lake Nazferiti",
735 | [5000030] = "The Veiled Sea",
736 | [5000031] = "Arathi Waters",
737 | [5000032] = "Darkcloud Pinnacle Pond",
738 | [5000033] = "Menethil Bay",
739 | [5000034] = "Western Strand",
740 | [5000035] = "Ashenvale Lake",
741 | [5000036] = "Mirkfallon Lake",
742 | [5000037] = "The Sepulcher",
743 | [5000038] = "Splintertree Mine",
744 | [5000039] = "Crystal Cave",
745 | [5000040] = "Fire Plume Ridge",
746 | [5000041] = "Shaman Shrine", -- Darkspear Isle
747 | [5000050] = "Bloodhoof Village Inn Entrance",
748 | [5000051] = "Call to Skirmish: Hillsbrad Foothills",
749 | [5000052] = "Call to Skirmish: Stonetalon Mountains",
750 | [5000053] = "Call to Skirmish: Ashenvale",
751 | [5000054] = "Call to Skirmish: Thousand Needles",
752 | [5000055] = "Call to Skirmish: Alterac Mountains",
753 | [5000056] = "Call to Skirmish: Desolace",
754 | [5000057] = "Call to Skirmish: Arathi Highlands",
755 | [5000058] = "Call to Skirmish: Swamp of Sorrows",
756 | [5000059] = "Call to Skirmish: Badlands",
757 | [5000060] = "Call to Skirmish: The Hinterlands",
758 | [5000061] = "Call to Skirmish: Stranglethorn Vale",
759 | [5000062] = "Call to Skirmish: Tanaris",
760 | [5000063] = "Call to Skirmish: Felwood",
761 | [5000064] = "Call to Skirmish: Azshara",
762 | [5000065] = "Call to Skirmish: Un\'Goro Crater",
763 | [5000066] = "Call to Skirmish: Searing Gorge",
764 | [5000067] = "Call to Skirmish: Western Plaguelands",
765 | [5000068] = "Call to Skirmish: Burning Steppes",
766 | [5000069] = "Call to Skirmish: Eastern Plaguelands",
767 | [5000070] = "Call to Skirmish: Feralas",
768 | [5000071] = "Call to Skirmish: Winterspring",
769 | [5000072] = "Call to Skirmish: Dustwallow Marsh",
770 | [5000073] = "Call to Skirmish: Blasted Lands",
771 | [5000074] = "Call to Skirmish: Silithus",
772 | }
--------------------------------------------------------------------------------
/patchtable.lua:
--------------------------------------------------------------------------------
1 | local loc = GetLocale()
2 | local dbs = { "items", "quests", "quests-itemreq", "objects", "units", "zones", "professions", "areatrigger", "refloot" }
3 | local noloc = { "items", "quests", "objects", "units" }
4 |
5 | -- Patch databases to merge ProjectEpoch data
6 | local function patchtable(base, diff)
7 | for k, v in pairs(diff) do
8 | if type(v) == "string" and v == "_" then
9 | base[k] = nil
10 | else
11 | base[k] = v
12 | end
13 | end
14 | end
15 |
16 | local loc_core, loc_update
17 | for _, db in pairs(dbs) do
18 | if pfDB[db]["data-epoch"] then
19 | patchtable(pfDB[db]["data"], pfDB[db]["data-epoch"])
20 | end
21 |
22 | for loc, _ in pairs(pfDB.locales) do
23 | if pfDB[db][loc] and pfDB[db][loc.."-epoch"] then
24 | loc_update = pfDB[db][loc.."-epoch"] or pfDB[db]["enUS-epoch"]
25 | patchtable(pfDB[db][loc], loc_update)
26 | end
27 | end
28 | end
29 |
30 | loc_core = pfDB["professions"][loc] or pfDB["professions"]["enUS"]
31 | loc_update = pfDB["professions"][loc.."-epoch"] or pfDB["professions"]["enUS-epoch"]
32 | if loc_update then patchtable(loc_core, loc_update) end
33 |
34 | if pfDB["minimap-epoch"] then patchtable(pfDB["minimap"], pfDB["minimap-epoch"]) end
35 | if pfDB["meta-epoch"] then patchtable(pfDB["meta"], pfDB["meta-epoch"]) end
36 |
37 | -- Update bitmasks to include custom races if needed
38 | -- if pfDB.bitraces then
39 | -- pfDB.bitraces[256] = "Goblin"
40 | -- pfDB.bitraces[512] = "BloodElf"
41 | -- end
42 |
43 | -- Use wowhead database url for now
44 | pfQuest.dburl = "https://epochhead.com/?quest="
45 |
46 | -- Disable Minimap in custom dungeon maps
47 | function pfMap:HasMinimap(map_id)
48 | -- disable dungeon minimap
49 | local has_minimap = not IsInInstance()
50 |
51 | -- enable dungeon minimap if continent is less then 3 (e.g AV)
52 | if IsInInstance() and GetCurrentMapContinent() < 3 then
53 | has_minimap = true
54 | end
55 |
56 | return has_minimap
57 | end
58 |
59 | -- Reload all pfQuest internal database shortcuts
60 | pfDatabase:Reload()
61 |
62 | -- Automatically clear quest cache if new quests have been found
63 | local updatecheck = CreateFrame("Frame")
64 | updatecheck:RegisterEvent("PLAYER_ENTERING_WORLD")
65 | updatecheck:SetScript("OnEvent", function()
66 | if pfDB["quests"]["data-epoch"] then
67 | -- count all known epoch quests
68 | local count = 0
69 | for k, v in pairs(pfDB["quests"]["data-epoch"]) do
70 | count = count + 1
71 | end
72 |
73 | pfQuest:Debug("Project Epoch loaded with |cff33ffcc" .. count .. "|r quests.")
74 |
75 | -- check if the last count differs to the current amount of quests
76 | if not pfQuest_epochcount or pfQuest_epochcount ~= count then
77 | -- remove quest cache to force reinitialisation of all quests.
78 | pfQuest:Debug("New quests found. Reloading |cff33ffccCache|r")
79 | pfQuest_questcache = {}
80 | end
81 |
82 | -- write current count to the saved variable
83 | pfQuest_epochcount = count
84 | end
85 | end)
86 |
87 | local originalSlashHandler = SlashCmdList["PFDB"]
88 |
89 | SlashCmdList["PFDB"] = originalSlashHandler
90 |
91 | function pfDatabase:QueryServer()
92 | if not QueryQuestsCompleted then
93 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Option is not available on your server.")
94 | return
95 | end
96 | QueryQuestsCompleted()
97 | local frame = CreateFrame("Frame")
98 | frame:RegisterEvent("QUEST_QUERY_COMPLETE")
99 | local function OnQuestQueryComplete()
100 | frame:UnregisterEvent("QUEST_QUERY_COMPLETE")
101 | local completedQuests = GetQuestsCompleted()
102 | if type(completedQuests) == "table" then
103 | local count = 0
104 | local closed = 0
105 |
106 | -- First pass: mark all completed quests
107 | for questID, _ in pairs(completedQuests) do
108 | pfQuest_history[questID] = { time(), UnitLevel("player") }
109 | count = count + 1
110 | end
111 |
112 | -- Second pass: auto-close mutually exclusive quests
113 | for questID, _ in pairs(pfQuest_history) do
114 | local questData = pfDB["quests"]["data"][questID]
115 | if questData and questData["close"] then
116 | for _, closedQuestID in pairs(questData["close"]) do
117 | if not pfQuest_history[closedQuestID] then
118 | pfQuest_history[closedQuestID] = { time(), UnitLevel("player") }
119 | closed = closed + 1
120 | end
121 | end
122 | end
123 | end
124 |
125 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Found " .. count .. " completed quests.")
126 | if closed > 0 then
127 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Auto-closed " .. closed .. " mutually exclusive quests.")
128 | end
129 | pfQuest:ResetAll()
130 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Query Complete. Please /reload to save the changes.")
131 | elseif completedQuests == nil then
132 | print("Error: GetQuestsCompleted() returned nil.")
133 | else
134 | print("Error: GetQuestsCompleted() did not return a valid table. Value: ", completedQuests)
135 | end
136 | end
137 | frame:SetScript("OnEvent", OnQuestQueryComplete)
138 | end
139 |
140 | function pfDatabase:PrintQuestData()
141 | local completedQuests = GetQuestsCompleted()
142 | if completedQuests then
143 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Raw quest data:")
144 | for questID, data in pairs(completedQuests) do
145 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuestID: " .. questID .. " = " .. tostring(data))
146 | end
147 | else
148 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: No quest data available. Try running QueryQuestsCompleted() first.")
149 | end
150 | end
151 |
152 | pfQuest_CompletedQuestData = pfQuest_CompletedQuestData or {}
153 |
154 | function pfDatabase:SaveCompletedQuests()
155 | if not QueryQuestsCompleted then
156 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Option is not available on your server.")
157 | return
158 | end
159 |
160 | QueryQuestsCompleted()
161 | local frame = CreateFrame("Frame")
162 | frame:RegisterEvent("QUEST_QUERY_COMPLETE")
163 |
164 | local function OnQuestQueryComplete()
165 | frame:UnregisterEvent("QUEST_QUERY_COMPLETE")
166 |
167 | local completedQuests = GetQuestsCompleted()
168 | if type(completedQuests) == "table" then
169 | pfQuest_CompletedQuestData = {
170 | data = completedQuests,
171 | timestamp = time(),
172 | characterName = UnitName("player"),
173 | realm = GetRealmName()
174 | }
175 |
176 | local count = 0
177 | for questID, _ in pairs(completedQuests) do
178 | count = count + 1
179 | end
180 |
181 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Saved " .. count .. " completed quests to SavedVariables.")
182 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Data will persist between sessions.")
183 | else
184 | DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpf|cffffffffQuest: Error - Could not retrieve quest data.")
185 | end
186 | end
187 |
188 | frame:SetScript("OnEvent", OnQuestQueryComplete)
189 | end
190 |
191 | -- Function to calculate gray level (no XP level) based on WoW's system
192 | local function GetGrayLevel(charLevel)
193 | if charLevel <= 5 then
194 | return 0 -- all mobs give XP
195 | elseif charLevel <= 49 then
196 | return charLevel - math.floor(charLevel / 10) - 5
197 | elseif charLevel == 50 then
198 | return 40 -- charLevel - 10
199 | elseif charLevel <= 59 then
200 | return charLevel - math.floor(charLevel / 5) - 1
201 | else -- level 60-70
202 | return charLevel - 9
203 | end
204 | end
205 |
206 | function pfDatabase:QuestFilter(id, plevel, pclass, prace)
207 | -- hide active quest
208 | if pfQuest.questlog[id] then return end
209 | -- hide completed quests
210 | if pfQuest_history[id] then return end
211 |
212 | -- hide broken quests without names
213 | if not pfDB.quests.loc[id] or not pfDB.quests.loc[id].T then return end
214 |
215 | local quest = pfDB["quests"]["data"][id]
216 | if not quest then return end
217 |
218 | -- hide missing pre-quests
219 | if quest["pre"] then
220 | -- check all pre-quests for one to be completed
221 | local one_complete = nil
222 | for _, prequest in pairs(quest["pre"]) do
223 | if pfQuest_history[prequest] then
224 | one_complete = true
225 | end
226 | end
227 | -- hide if none of the pre-quests has been completed
228 | if not one_complete then return end
229 | end
230 |
231 | -- hide non-available quests for your race
232 | if quest["race"] and not ( bit.band(quest["race"], prace) == prace ) then return end
233 | -- hide non-available quests for your class
234 | if quest["class"] and not ( bit.band(quest["class"], pclass) == pclass ) then return end
235 | -- hide non-available quests for your profession
236 | if quest["skill"] then
237 | local playerSkillLevel = pfDatabase:GetPlayerSkill(quest["skill"])
238 | if not playerSkillLevel or quest["skillmin"] and playerSkillLevel < quest["skillmin"] then return end
239 | end
240 | -- hide lowlevel quests using WoW's gray level system
241 | if quest["lvl"] and quest["lvl"] <= GetGrayLevel(plevel) and pfQuest_config["showlowlevel"] == "0" then return end
242 | -- hide highlevel quests (or show those that are 3 levels above)
243 | if quest["min"] and quest["min"] > plevel + ( pfQuest_config["showhighlevel"] == "1" and 3 or 0 ) then return end
244 | -- hide event quests
245 | if quest["event"] and pfQuest_config["showfestival"] == "0" then return end
246 |
247 | -- hide PvP quests
248 | if pfQuest_config["epochHidePvPQuests"] == "1" then
249 | local title = pfDB.quests.loc[id].T
250 | if title and not string.find(title, "Alteraci Shilling") and (
251 | string.find(title, "Warsong") or
252 | string.find(title, "Arathi") or
253 | string.find(title, "Alterac") or
254 | string.find(title, "Battleground") or
255 | string.find(title, "Call to Skirmish")
256 | ) then
257 | return
258 | end
259 | end
260 |
261 | -- hide Commission quests
262 | if pfQuest_config["epochHideCommissionQuests"] == "1" then
263 | local title = pfDB.quests.loc[id].T
264 | if title and string.find(title, "Commission for") then
265 | return
266 | end
267 | end
268 |
269 | -- hide chicken quests
270 | if pfQuest_config["epochHideChickenQuests"] == "1" then
271 | local title = pfDB.quests.loc[id].T
272 | if title and string.find(title, "CLUCK!") then
273 | return
274 | end
275 | end
276 |
277 | -- hide Felwood flowers
278 | if pfQuest_config["epochHideFelwoodFlowers"] == "1" then
279 | local title = pfDB.quests.loc[id].T
280 | if title and (
281 | title == "Corrupted Windblossom" or
282 | title == "Corrupted Whipper Root" or
283 | title == "Corrupted Songflower" or
284 | title == "Corrupted Night Dragon"
285 | ) then
286 | return
287 | end
288 | end
289 |
290 | return true
291 | end
292 |
293 | -- fix wotlk linking from db menu
294 | pfQuestCompat.InsertQuestLink = function(questid, name)
295 | local questid = questid or 0
296 | local fallback = name or UNKNOWN
297 | local level = pfDB["quests"]["data"][questid] and pfDB["quests"]["data"][questid]["lvl"] or 0
298 | local name = pfDB["quests"]["loc"][questid] and pfDB["quests"]["loc"][questid]["T"] or fallback
299 | local hex = pfUI.api.rgbhex(pfQuestCompat.GetDifficultyColor(level))
300 |
301 | -- Use the correct editbox for 3.3.5
302 | local editBox = ChatFrame1EditBox or ChatFrameEditBox
303 |
304 | if editBox then
305 | editBox:Show()
306 | if pfQuest_config["questlinks"] == "1" then
307 | -- seems server blocks the other method so I used this
308 | editBox:Insert("\124cffffff00\124Hquest:" .. questid .. ":" .. level .. "\124h[" .. name .. "]\124h\124r")
309 | else
310 | editBox:Insert("[" .. name .. "]")
311 | end
312 | end
313 | end
314 |
315 | local corpseMessage = ""
316 | local wasDeadLastFrame = false
317 |
318 | pfQuest.route.arrow:SetScript("OnUpdate", function()
319 | if not this.parent then return end
320 |
321 | local isCurrentlyDead = UnitIsDead("player") or UnitIsGhost("player")
322 |
323 | if isCurrentlyDead then
324 | if not wasDeadLastFrame then
325 | local corpseMessages = {
326 | "Skill Issue, have fun running back",
327 | "Git Gud Scrub",
328 | "You Died LOL",
329 | "Walk of Shame Initiated",
330 | "Corpse Run Express",
331 | "Better Luck Next Time",
332 | "RIP Your Repair Bill",
333 | "Death Tax Collector Awaits",
334 | "Your Body is Over There Dummy",
335 | "Congratulations, You're Dead",
336 | "Achievement Unlocked: Floor Tank",
337 | "Press F to Pay Respects",
338 | "This is Why We Can't Have Nice Things",
339 | "Maybe Try Reading the Tactics Next Time",
340 | "Outstanding Move, Chief",
341 | "Welcome to the Spirit World",
342 | "Ghost Mode: ACTIVATED",
343 | "That Went Well",
344 | "Professional Grave Digger",
345 | "Another Happy Landing",
346 | "Task Failed Successfully",
347 | "Speedrun: Any% Death Category",
348 | "You've Been Disconnected from Life",
349 | "Error 404: HP Not Found",
350 | "Critical Hit: Your Pride",
351 | "Respawn Timer: Your Dignity",
352 | "New Personal Best: Worst Decision",
353 | "Plot Twist: You're the Bad Guy",
354 | "Congratulations, You Played Yourself",
355 | "Tutorial Complete: How to Die",
356 | "Achievement: First Time?",
357 | "Pro Tip: Don't Die Next Time",
358 | "Your Performance Review: Needs Improvement",
359 | "Status Update: Currently Deceased",
360 | "That's a Bold Strategy Cotton",
361 | "The Afterlife Called, They're Expecting You",
362 | "Death Certificate: Cause of Death - Bad Decision"
363 | }
364 | corpseMessage = corpseMessages[math.random(1, #corpseMessages)]
365 | wasDeadLastFrame = true
366 | end
367 |
368 | local cx, cy = GetCorpseMapPosition()
369 | -- corpse coords are 0–1; ignore if invalid (0,0)
370 | if cx and cy and (cx > 0 or cy > 0) then
371 | local xplayer, yplayer = GetPlayerMapPosition("player")
372 | local dx = (cx - xplayer) * 100 * 1.5
373 | local dy = (cy - yplayer) * 100
374 |
375 | -- Calculate distance to corpse
376 | local corpseDistance = ceil(math.sqrt(dx*dx + dy*dy)*100)/100
377 |
378 | local dir = atan2(dx, -dy)
379 | dir = dir > 0 and (2*math.pi) - dir or -dir
380 | if dir < 0 then dir = dir + 360 end
381 | local angle = math.rad(dir) - pfQuestCompat.GetPlayerFacing()
382 |
383 | -- rotate the arrow model to point at corpse
384 | local cell = modulo(floor(angle / (2*math.pi) * 108 + .5), 108)
385 | local col = modulo(cell, 9)
386 | local row = floor(cell / 9)
387 | this.model:SetTexCoord(
388 | (col * 56)/512, ((col+1)*56)/512,
389 | (row * 42)/512, ((row+1)*42)/512
390 | )
391 |
392 | this.title:SetText("Corpse")
393 | this.description:SetText("|cffff0000" .. corpseMessage .. "|r")
394 | this.distance:SetText("|cffaaaaaa" .. (pfQuest_Loc["Distance"] or "Distance") .. ": " .. string.format("%.1f", corpseDistance))
395 |
396 | this:Show()
397 | return
398 | end
399 | else
400 |
401 | if wasDeadLastFrame then
402 | wasDeadLastFrame = false
403 | corpseMessage = ""
404 | end
405 | end
406 |
407 | xplayer, yplayer = GetPlayerMapPosition("player")
408 | wrongmap = xplayer == 0 and yplayer == 0 and true or nil
409 | target = this.parent.coords and this.parent.coords[1] and this.parent.coords[1][4] and this.parent.coords[1] or nil
410 |
411 | if not target or wrongmap or pfQuest_config["arrow"] == "0" then
412 | if invalid and invalid < GetTime() then
413 | this:Hide()
414 | elseif not invalid then
415 | invalid = GetTime() + 1
416 | end
417 |
418 | return
419 | else
420 | invalid = nil
421 | end
422 |
423 | xDelta = (target[1] - xplayer*100)*1.5
424 | yDelta = (target[2] - yplayer*100)
425 | dir = atan2(xDelta, -(yDelta))
426 | dir = dir > 0 and (math.pi*2) - dir or -dir
427 | if dir < 0 then dir = dir + 360 end
428 | angle = math.rad(dir)
429 |
430 | player = pfQuestCompat.GetPlayerFacing()
431 | angle = angle - player
432 | perc = math.abs(((math.pi - math.abs(angle)) / math.pi))
433 | r, g, b = pfUI.api.GetColorGradient(floor(perc*100)/100)
434 | cell = modulo(floor(angle / (math.pi*2) * 108 + 0.5), 108)
435 | column = modulo(cell, 9)
436 | row = floor(cell / 9)
437 | xstart = (column * 56) / 512
438 | ystart = (row * 42) / 512
439 | xend = ((column + 1) * 56) / 512
440 | yend = ((row + 1) * 42) / 512
441 |
442 | area = target[3].priority and target[3].priority or 1
443 | area = max(1, area)
444 | area = min(20, area)
445 | area = (area / 10) + 1
446 |
447 | alpha = target[4] - area
448 | alpha = alpha > 1 and 1 or alpha
449 | alpha = alpha < .5 and .5 or alpha
450 |
451 | texalpha = (1 - alpha) * 2
452 | texalpha = texalpha > 1 and 1 or texalpha
453 | texalpha = texalpha < 0 and 0 or texalpha
454 |
455 | r, g, b = r + texalpha, g + texalpha, b + texalpha
456 |
457 | this.model:SetTexCoord(xstart,xend,ystart,yend)
458 | this.model:SetVertexColor(r,g,b)
459 |
460 | if target ~= lasttarget then
461 | color = defcolor
462 | if tonumber(target[3]["qlvl"]) then
463 | color = pfMap:HexDifficultyColor(tonumber(target[3]["qlvl"]))
464 | end
465 |
466 | if target[3].texture then
467 | this.texture:SetTexture(target[3].texture)
468 |
469 | if target[3].vertex and ( target[3].vertex[1] > 0
470 | or target[3].vertex[2] > 0
471 | or target[3].vertex[3] > 0 )
472 | then
473 | this.texture:SetVertexColor(unpack(target[3].vertex))
474 | else
475 | this.texture:SetVertexColor(1,1,1,1)
476 | end
477 | else
478 | this.texture:SetTexture(pfQuestConfig.path.."\\img\\node")
479 | this.texture:SetVertexColor(pfMap.str2rgb(target[3].title))
480 | end
481 |
482 | local level = target[3].qlvl and "[" .. target[3].qlvl .. "] " or ""
483 | this.title:SetText(color..level..target[3].title.."|r")
484 | local desc = target[3].description or ""
485 | if not pfUI or not pfUI.uf then
486 | this.description:SetTextColor(1,.9,.7,1)
487 | desc = string.gsub(desc, "ff33ffcc", "ffffffff")
488 | end
489 | this.description:SetText(desc.."|r.")
490 | end
491 |
492 | local distance = floor(target[4]*10)/10
493 | if distance ~= this.distance.number then
494 | this.distance:SetText("|cffaaaaaa" .. pfQuest_Loc["Distance"] .. ": "..string.format("%.1f", distance))
495 | this.distance.number = distance
496 | end
497 |
498 | this.texture:SetAlpha(texalpha)
499 | this.model:SetAlpha(alpha)
500 | end)
501 |
502 | -- Item Drop System
503 | local originalSearchQuestID = pfDatabase.SearchQuestID
504 | pfDatabase.SearchQuestID = function(self, id, meta, maps)
505 |
506 | maps = originalSearchQuestID(self, id, meta, maps)
507 |
508 | local quests = pfDB["quests"]["data"]
509 | local items = pfDB["items"]["data"]
510 | local units = pfDB["units"]["data"]
511 | local objects = pfDB["objects"]["data"]
512 | local refloot = pfDB["refloot"]["data"]
513 |
514 | if not quests[id] then return maps end
515 |
516 | if pfQuest_config["epochHideItemDrops"] == "1" then
517 | return maps
518 | end
519 |
520 | if pfQuest_config["currentquestgivers"] == "1" then
521 | if quests[id]["start"] and not meta["qlogid"] then
522 | if quests[id]["start"]["I"] then
523 | for _, item in pairs(quests[id]["start"]["I"]) do
524 | if items[item] then
525 | local drop_sources = {}
526 | local sources_with_levels = {}
527 |
528 | if items[item]["U"] then
529 | for unit, chance in pairs(items[item]["U"]) do
530 | local unit_name = pfDB["units"]["loc"][unit]
531 | if unit_name and not drop_sources[unit_name] then
532 | drop_sources[unit_name] = true
533 | local unit_level = units[unit] and units[unit]["lvl"] or "?"
534 | sources_with_levels[unit_name] = {level = unit_level, chance = chance}
535 | end
536 | end
537 | end
538 |
539 | if items[item]["O"] then
540 | for object, chance in pairs(items[item]["O"]) do
541 | local obj_name = pfDB["objects"]["loc"][object]
542 | if obj_name and not drop_sources[obj_name] then
543 | drop_sources[obj_name] = true
544 | sources_with_levels[obj_name] = {level = "Object", chance = chance}
545 | end
546 | end
547 | end
548 |
549 | if items[item]["R"] then
550 | for ref, chance in pairs(items[item]["R"]) do
551 | if refloot[ref] then
552 | if refloot[ref]["U"] then
553 | for unit in pairs(refloot[ref]["U"]) do
554 | local unit_name = pfDB["units"]["loc"][unit]
555 | if unit_name and not drop_sources[unit_name] then
556 | drop_sources[unit_name] = true
557 | local unit_level = units[unit] and units[unit]["lvl"] or "?"
558 | sources_with_levels[unit_name] = {level = unit_level, chance = chance}
559 | end
560 | end
561 | end
562 |
563 | if refloot[ref]["O"] then
564 | for object in pairs(refloot[ref]["O"]) do
565 | local obj_name = pfDB["objects"]["loc"][object]
566 | if obj_name and not drop_sources[obj_name] then
567 | drop_sources[obj_name] = true
568 | sources_with_levels[obj_name] = {level = "Object", chance = chance}
569 | end
570 | end
571 | end
572 | end
573 | end
574 | end
575 |
576 | local sources_text = ""
577 | local source_count = 0
578 | local display_count = 0
579 | for source_name in pairs(drop_sources) do
580 | source_count = source_count + 1
581 | end
582 |
583 | for source_name in pairs(drop_sources) do
584 | display_count = display_count + 1
585 | if display_count > 1 then sources_text = sources_text .. ", " end
586 | sources_text = sources_text .. source_name
587 | if display_count >= 3 then
588 | if source_count > 3 then
589 | sources_text = sources_text .. " (+" .. (source_count - 3) .. " more)"
590 | end
591 | break
592 | end
593 | end
594 |
595 | local best_source = nil
596 | local best_count = 0
597 |
598 | if items[item]["U"] then
599 | for unit, chance in pairs(items[item]["U"]) do
600 | if units[unit] and units[unit]["coords"] then
601 | local count = table.getn(units[unit]["coords"])
602 | if count > best_count then
603 | best_count = count
604 | best_source = {type = "unit", id = unit}
605 | end
606 | end
607 | end
608 | end
609 |
610 | if items[item]["O"] then
611 | for object, chance in pairs(items[item]["O"]) do
612 | if objects[object] and objects[object]["coords"] then
613 | local count = table.getn(objects[object]["coords"])
614 | if count > best_count then
615 | best_count = count
616 | best_source = {type = "object", id = object}
617 | end
618 | end
619 | end
620 | end
621 |
622 | if items[item]["R"] then
623 | for ref, chance in pairs(items[item]["R"]) do
624 | if refloot[ref] then
625 | if refloot[ref]["U"] then
626 | for unit in pairs(refloot[ref]["U"]) do
627 | if units[unit] and units[unit]["coords"] then
628 | local count = table.getn(units[unit]["coords"])
629 | if count > best_count then
630 | best_count = count
631 | best_source = {type = "unit", id = unit}
632 | end
633 | end
634 | end
635 | end
636 |
637 | if refloot[ref]["O"] then
638 | for object in pairs(refloot[ref]["O"]) do
639 | if objects[object] and objects[object]["coords"] then
640 | local count = table.getn(objects[object]["coords"])
641 | if count > best_count then
642 | best_count = count
643 | best_source = {type = "object", id = object}
644 | end
645 | end
646 | end
647 | end
648 | end
649 | end
650 | end
651 |
652 | if best_source then
653 | local coords_table = best_source.type == "unit" and units[best_source.id]["coords"] or objects[best_source.id]["coords"]
654 |
655 | local zones_coords = {}
656 | for _, data in pairs(coords_table) do
657 | local x, y, zone = unpack(data)
658 | if zone > 0 and not zones_coords[zone] then
659 | zones_coords[zone] = {x, y}
660 | end
661 | end
662 |
663 | for zone, coords in pairs(zones_coords) do
664 | local item_meta = {}
665 | for k, v in pairs(meta or {}) do item_meta[k] = v end
666 |
667 | item_meta["QTYPE"] = "ITEM_START"
668 | item_meta["layer"] = 4
669 | item_meta["texture"] = pfQuestConfig.path.."\\img\\available"
670 | --item_meta["vertex"] = { 0.7, 0.4, 1 }
671 | local plevel = UnitLevel("player")
672 | if quests[id]["min"] and quests[id]["min"] > plevel then
673 | item_meta["vertex"] = { 1, .6, .6 }
674 | item_meta["layer"] = 2
675 | elseif quests[id]["lvl"] and quests[id]["lvl"] <= GetGrayLevel(plevel) then
676 | item_meta["vertex"] = { 1, 1, 1 }
677 | item_meta["layer"] = 2
678 | elseif quests[id]["event"] then
679 | item_meta["vertex"] = { .2, .8, 1 }
680 | item_meta["layer"] = 2
681 | end
682 |
683 | item_meta["spawn"] = pfDB["items"]["loc"][item] or UNKNOWN
684 | item_meta["spawnid"] = item
685 | item_meta["item"] = pfDB["items"]["loc"][item]
686 | item_meta["dropsources"] = sources_text
687 | item_meta["dropsources_levels"] = sources_with_levels
688 | item_meta["title"] = item_meta["quest"] or item_meta["spawn"]
689 | item_meta["zone"] = zone
690 | item_meta["x"] = coords[1]
691 | item_meta["y"] = coords[2]
692 | item_meta["level"] = pfQuest_Loc["N/A"]
693 | item_meta["spawntype"] = pfQuest_Loc["Item Drop"]
694 | item_meta["respawn"] = pfQuest_Loc["N/A"]
695 | item_meta["description"] = pfDatabase:BuildQuestDescription(item_meta)
696 |
697 | maps = maps or {}
698 | maps[zone] = maps[zone] and maps[zone] + 0 or 0
699 | pfMap:AddNode(item_meta)
700 | end
701 | end
702 | end
703 | end
704 | end
705 | end
706 | end
707 |
708 | return maps
709 | end
710 |
711 | local originalSearchQuests = pfDatabase.SearchQuests
712 | pfDatabase.SearchQuests = function(self, meta, maps)
713 | maps = originalSearchQuests(self, meta, maps)
714 |
715 | local quests = pfDB["quests"]["data"]
716 | local items = pfDB["items"]["data"]
717 | local units = pfDB["units"]["data"]
718 | local objects = pfDB["objects"]["data"]
719 | local refloot = pfDB["refloot"]["data"]
720 |
721 | if pfQuest_config["epochHideItemDrops"] == "1" then
722 | return maps
723 | end
724 |
725 | local plevel = UnitLevel("player")
726 | local pfaction = UnitFactionGroup("player")
727 | pfaction = pfaction == "Horde" and "H" or pfaction == "Alliance" and "A" or "GM"
728 |
729 | local _, race = UnitRace("player")
730 | local prace = pfDatabase:GetBitByRace(race)
731 | local _, class = UnitClass("player")
732 | local pclass = pfDatabase:GetBitByClass(class)
733 |
734 | for id in pairs(quests) do
735 | if pfDatabase:QuestFilter(id, plevel, pclass, prace) then
736 | -- Additional faction check for quest enders
737 | local validFaction = true
738 | if quests[id]["end"] and quests[id]["end"]["U"] then
739 | validFaction = false
740 | for _, unit in pairs(quests[id]["end"]["U"]) do
741 | if pfDatabase:IsFriendly(unit) then
742 | validFaction = true
743 | break
744 | end
745 | end
746 | end
747 |
748 | if validFaction and quests[id]["start"] and quests[id]["start"]["I"] then
749 | for _, item in pairs(quests[id]["start"]["I"]) do
750 | if items[item] then
751 | local drop_sources = {}
752 | local sources_with_levels = {}
753 |
754 | if items[item]["U"] then
755 | for unit, chance in pairs(items[item]["U"]) do
756 | local unit_name = pfDB["units"]["loc"][unit]
757 | if unit_name and not drop_sources[unit_name] then
758 | drop_sources[unit_name] = true
759 | local unit_level = units[unit] and units[unit]["lvl"] or "?"
760 | sources_with_levels[unit_name] = {level = unit_level, chance = chance}
761 | end
762 | end
763 | end
764 |
765 | if items[item]["O"] then
766 | for object, chance in pairs(items[item]["O"]) do
767 | local obj_name = pfDB["objects"]["loc"][object]
768 | if obj_name and not drop_sources[obj_name] then
769 | drop_sources[obj_name] = true
770 | sources_with_levels[obj_name] = {level = "Object", chance = chance}
771 | end
772 | end
773 | end
774 |
775 | if items[item]["R"] then
776 | for ref, chance in pairs(items[item]["R"]) do
777 | if refloot[ref] then
778 | if refloot[ref]["U"] then
779 | for unit in pairs(refloot[ref]["U"]) do
780 | local unit_name = pfDB["units"]["loc"][unit]
781 | if unit_name and not drop_sources[unit_name] then
782 | drop_sources[unit_name] = true
783 | local unit_level = units[unit] and units[unit]["lvl"] or "?"
784 | sources_with_levels[unit_name] = {level = unit_level, chance = chance}
785 | end
786 | end
787 | end
788 |
789 | if refloot[ref]["O"] then
790 | for object in pairs(refloot[ref]["O"]) do
791 | local obj_name = pfDB["objects"]["loc"][object]
792 | if obj_name and not drop_sources[obj_name] then
793 | drop_sources[obj_name] = true
794 | sources_with_levels[obj_name] = {level = "Object", chance = chance}
795 | end
796 | end
797 | end
798 | end
799 | end
800 | end
801 |
802 | local sources_text = ""
803 | local source_count = 0
804 | local display_count = 0
805 | for source_name in pairs(drop_sources) do
806 | source_count = source_count + 1
807 | end
808 |
809 | for source_name in pairs(drop_sources) do
810 | display_count = display_count + 1
811 | if display_count > 1 then sources_text = sources_text .. ", " end
812 | sources_text = sources_text .. source_name
813 | if display_count >= 3 then
814 | if source_count > 3 then
815 | sources_text = sources_text .. " (+" .. (source_count - 3) .. " more)"
816 | end
817 | break
818 | end
819 | end
820 |
821 | local best_source = nil
822 | local best_count = 0
823 |
824 | if items[item]["U"] then
825 | for unit, chance in pairs(items[item]["U"]) do
826 | if units[unit] and units[unit]["coords"] then
827 | local count = table.getn(units[unit]["coords"])
828 | if count > best_count then
829 | best_count = count
830 | best_source = {type = "unit", id = unit}
831 | end
832 | end
833 | end
834 | end
835 |
836 | if items[item]["O"] then
837 | for object, chance in pairs(items[item]["O"]) do
838 | if objects[object] and objects[object]["coords"] then
839 | local count = table.getn(objects[object]["coords"])
840 | if count > best_count then
841 | best_count = count
842 | best_source = {type = "object", id = object}
843 | end
844 | end
845 | end
846 | end
847 |
848 | if items[item]["R"] then
849 | for ref, chance in pairs(items[item]["R"]) do
850 | if refloot[ref] then
851 | if refloot[ref]["U"] then
852 | for unit in pairs(refloot[ref]["U"]) do
853 | if units[unit] and units[unit]["coords"] then
854 | local count = table.getn(units[unit]["coords"])
855 | if count > best_count then
856 | best_count = count
857 | best_source = {type = "unit", id = unit}
858 | end
859 | end
860 | end
861 | end
862 |
863 | if refloot[ref]["O"] then
864 | for object in pairs(refloot[ref]["O"]) do
865 | if objects[object] and objects[object]["coords"] then
866 | local count = table.getn(objects[object]["coords"])
867 | if count > best_count then
868 | best_count = count
869 | best_source = {type = "object", id = object}
870 | end
871 | end
872 | end
873 | end
874 | end
875 | end
876 | end
877 |
878 | if best_source then
879 | local coords_table = best_source.type == "unit" and units[best_source.id]["coords"] or objects[best_source.id]["coords"]
880 |
881 | local zones_coords = {}
882 | for _, data in pairs(coords_table) do
883 | local x, y, zone = unpack(data)
884 | if zone > 0 and not zones_coords[zone] then
885 | zones_coords[zone] = {x, y}
886 | end
887 | end
888 |
889 | for zone, coords in pairs(zones_coords) do
890 | local item_meta = {}
891 | for k, v in pairs(meta or {}) do item_meta[k] = v end
892 |
893 | item_meta["quest"] = pfDB["quests"]["loc"][id] and pfDB["quests"]["loc"][id].T or UNKNOWN
894 | item_meta["questid"] = id
895 | item_meta["QTYPE"] = "ITEM_START"
896 | item_meta["layer"] = 3
897 | item_meta["texture"] = pfQuestConfig.path.."\\img\\available"
898 | --item_meta["vertex"] = { 0.7, 0.4, 1 }
899 | item_meta["spawn"] = pfDB["items"]["loc"][item] or UNKNOWN
900 | item_meta["spawnid"] = item
901 | item_meta["item"] = pfDB["items"]["loc"][item]
902 | item_meta["dropsources"] = sources_text
903 | item_meta["dropsources_levels"] = sources_with_levels
904 | item_meta["title"] = item_meta["quest"]
905 | item_meta["zone"] = zone
906 | item_meta["x"] = coords[1]
907 | item_meta["y"] = coords[2]
908 | item_meta["level"] = pfQuest_Loc["N/A"]
909 | item_meta["spawntype"] = pfQuest_Loc["Item Drop"]
910 | item_meta["respawn"] = pfQuest_Loc["N/A"]
911 | item_meta["qlvl"] = quests[id]["lvl"]
912 | item_meta["qmin"] = quests[id]["min"]
913 |
914 | if quests[id]["min"] and quests[id]["min"] > plevel then
915 | item_meta["vertex"] = { 1, .6, .6 }
916 | item_meta["layer"] = 2
917 | elseif quests[id]["lvl"] and quests[id]["lvl"] <= GetGrayLevel(plevel) then
918 | item_meta["vertex"] = { 1, 1, 1 }
919 | item_meta["layer"] = 2
920 | elseif quests[id]["event"] then
921 | item_meta["vertex"] = { .2, .8, 1 }
922 | item_meta["layer"] = 2
923 | end
924 |
925 | item_meta["description"] = pfDatabase:BuildQuestDescription(item_meta)
926 |
927 | maps = maps or {}
928 | maps[zone] = maps[zone] and maps[zone] + 1 or 1
929 | pfMap:AddNode(item_meta)
930 | end
931 | end
932 | end
933 | end
934 | end
935 | end
936 | end
937 |
938 | return maps
939 | end
940 |
941 | local originalNodeEnter = pfMap.NodeEnter
942 | pfMap.NodeEnter = function()
943 | if not this or not this.node then
944 | if originalNodeEnter then originalNodeEnter() end
945 | return
946 | end
947 |
948 | local hasItemStart = false
949 | local itemStartMeta = nil
950 | for title, meta in pairs(this.node) do
951 | if meta.QTYPE == "ITEM_START" and meta.dropsources_levels then
952 | hasItemStart = true
953 | itemStartMeta = meta
954 | break
955 | end
956 | end
957 |
958 | if hasItemStart and itemStartMeta then
959 | if pfQuestCompat and pfQuestCompat.client and pfQuestCompat.client >= 30300 then
960 | WorldMapPOIFrame.allowBlobTooltip = false
961 | end
962 |
963 | local tooltip = this:GetParent() == WorldMapButton and WorldMapTooltip or GameTooltip
964 | tooltip:SetOwner(this, "ANCHOR_LEFT")
965 | this.spawn = this.spawn or UNKNOWN
966 | tooltip:SetText(this.spawn..(pfQuest_config.showids == "1" and " |cffcccccc("..this.spawnid..")|r" or ""), .3, 1, .8)
967 | tooltip:AddDoubleLine(pfQuest_Loc["Type"] .. ":", (this.spawntype or UNKNOWN), .8,.8,.8, 1,1,1)
968 |
969 | if itemStartMeta.dropsources_levels then
970 | tooltip:AddLine(" ")
971 | tooltip:AddLine("Drops from:", .8,.8,.8)
972 |
973 | local sorted_sources = {}
974 | for source_name, data in pairs(itemStartMeta.dropsources_levels) do
975 | table.insert(sorted_sources, {name = source_name, level = data.level, chance = data.chance or 0})
976 | end
977 |
978 | table.sort(sorted_sources, function(a, b)
979 | return a.chance > b.chance
980 | end)
981 |
982 | local count = 0
983 | for _, source in ipairs(sorted_sources) do
984 | count = count + 1
985 | if count <= 5 then
986 | tooltip:AddLine(" " .. source.name .. " (" .. source.level .. ") - " .. source.chance .. "%", 1,1,1)
987 | end
988 | end
989 |
990 | if table.getn(sorted_sources) > 5 then
991 | tooltip:AddLine(" (+" .. (table.getn(sorted_sources) - 5) .. " more)", .7,.7,.7)
992 | end
993 | end
994 |
995 | tooltip:AddLine(" ")
996 |
997 | for title, meta in pairs(this.node) do
998 | pfMap:ShowTooltip(meta, tooltip)
999 | end
1000 |
1001 | if pfQuest_config["tooltiphelp"] == "1" then
1002 | local text = pfQuest_Loc["Use -Click To Mark Quest As Done"]
1003 | tooltip:AddLine(text, .6, .6, .6)
1004 | tooltip:Show()
1005 | end
1006 |
1007 | pfMap.highlight = pfQuest_config["mouseover"] == "1" and this.title
1008 | else
1009 | if originalNodeEnter then
1010 | originalNodeEnter()
1011 | end
1012 | end
1013 | end
1014 |
1015 | -- Yoinked from https://github.com/shagu/pfQuest/pull/301 props to BlacRyu
1016 | local original_ResizeNode = pfMap.ResizeNode
1017 |
1018 | -- Make mainmap_inversescale global so we can control it across files
1019 | mainmap_inversescale = 1.0
1020 |
1021 | -- Override ResizeNode to use our global mainmap_inversescale
1022 | function pfMap:ResizeNode(frame, obj)
1023 | local highlight = frame.texture and pfMap.highlightdb[frame][pfMap.highlight] and true or nil
1024 | local target = frame.texture and pfQuest.route and pfQuest.route.IsTarget(frame) or nil
1025 |
1026 | -- set default sizes for different node types
1027 | frame.defsize = (frame.cluster or frame.layer == 4) and 18 or 14
1028 | -- Adjust node size if main map is zoomed in/out
1029 | if (obj ~= "minimap") then
1030 | if (frame.title and pfQuest.icons[frame.title]) or frame.icon then
1031 | -- Adjust for icons being 1 unit smaller than their parent frame
1032 | -- Looks better to keep the icon size constant even if the frame grows a bit.
1033 | frame.defsize = (frame.defsize - 2) * (mainmap_inversescale) + 2
1034 | else
1035 | frame.defsize = frame.defsize * mainmap_inversescale
1036 | end
1037 | end
1038 |
1039 | -- make the current route target visible
1040 | if target then frame.hl:Show() else frame.hl:Hide() end
1041 |
1042 | -- reset frame size except for highlights
1043 | if not highlight then
1044 | frame:SetWidth(frame.defsize)
1045 | frame:SetHeight(frame.defsize)
1046 | end
1047 | end
1048 |
1049 | -- Define ResizeNodes function (doesn't exist in base pfQuest)
1050 | function pfMap:ResizeNodes()
1051 | local i = 1
1052 | if pfMap.pins then
1053 | while pfMap.pins[i] and pfMap.pins[i]:IsShown() do
1054 | pfMap:ResizeNode(pfMap.pins[i])
1055 | i = i + 1
1056 | end
1057 | end
1058 | end
1059 |
1060 | -- Handle map scale changes
1061 | function pfMap:OnMapScaleChanged(frame, scale, hookedfunction)
1062 | hookedfunction(frame, scale)
1063 |
1064 | local new_inversescale = 1.0 / WorldMapButton:GetEffectiveScale()
1065 | if (mainmap_inversescale ~= new_inversescale) then
1066 | mainmap_inversescale = new_inversescale
1067 | pfMap:ResizeNodes()
1068 | end
1069 | end
1070 |
1071 | -- Listen for WorldMapFrame scale changes
1072 | local pfHookWorldMapFrame_SetScale = WorldMapFrame.SetScale
1073 | WorldMapFrame.SetScale = function(frame, scale) pfMap:OnMapScaleChanged(frame, scale, pfHookWorldMapFrame_SetScale) end
1074 |
1075 | -- Listen for WorldMapDetailFrame scale changes
1076 | local pfHookWorldMapDetailFrame_SetScale = WorldMapDetailFrame.SetScale
1077 | WorldMapDetailFrame.SetScale = function(frame, scale) pfMap:OnMapScaleChanged(frame, scale, pfHookWorldMapDetailFrame_SetScale) end
1078 |
1079 | -- Listen for WorldMapButton scale changes
1080 | local pfHookWorldMapButton_SetScale = WorldMapButton.SetScale
1081 | WorldMapButton.SetScale = function(frame, scale) pfMap:OnMapScaleChanged(frame, scale, pfHookWorldMapButton_SetScale) end
1082 |
1083 | function pfDatabase:BuildQuestDescription(meta)
1084 | if not meta.title or not meta.quest or not meta.QTYPE then return meta.description end
1085 |
1086 | if meta.QTYPE == "NPC_START" then
1087 | return string.format(pfQuest_Loc["Speak with |cff33ffcc%s|r to obtain |cffffcc00[!]|cff33ffcc %s|r"], (meta.spawn or UNKNOWN), (meta.quest or UNKNOWN))
1088 | elseif meta.QTYPE == "OBJECT_START" then
1089 | return string.format(pfQuest_Loc["Interact with |cff33ffcc%s|r to obtain |cff66ff66[!]|cff33ffcc %s|r"], (meta.spawn or UNKNOWN), (meta.quest or UNKNOWN))
1090 | elseif meta.QTYPE == "ITEM_START" then
1091 | if meta.dropsources and meta.dropsources ~= "" then
1092 | return string.format(pfQuest_Loc["Loot |cff33ffcc[%s]|r from |cff33ffcc%s|r to obtain |cff66ff66[!]|cff33ffcc %s|r"], (meta.spawn or UNKNOWN), meta.dropsources, (meta.quest or UNKNOWN))
1093 | else
1094 | return string.format(pfQuest_Loc["Loot |cff33ffcc[%s]|r to obtain |cff66ff66[!]|cff33ffcc %s|r"], (meta.spawn or UNKNOWN), (meta.quest or UNKNOWN))
1095 | end
1096 | elseif meta.QTYPE == "NPC_END" then
1097 | return string.format(pfQuest_Loc["Speak with |cff33ffcc%s|r to complete |cffffcc00[?]|cff33ffcc %s|r"], (meta.spawn or UNKNOWN), (meta.quest or UNKNOWN))
1098 | elseif meta.QTYPE == "OBJECT_END" then
1099 | return string.format(pfQuest_Loc["Interact with |cff33ffcc%s|r to complete |cffffcc00[?]|cff33ffcc %s|r"], (meta.spawn or UNKNOWN), (meta.quest or UNKNOWN))
1100 | elseif meta.QTYPE == "UNIT_OBJECTIVE" then
1101 | if pfDatabase:IsFriendly(meta.spawnid) then
1102 | return string.format(pfQuest_Loc["Talk to |cff33ffcc%s|r"], (meta.spawn or UNKNOWN))
1103 | else
1104 | return string.format(pfQuest_Loc["Kill |cff33ffcc%s|r"], (meta.spawn or UNKNOWN))
1105 | end
1106 | elseif meta.QTYPE == "UNIT_OBJECTIVE_ITEMREQ" then
1107 | return string.format(pfQuest_Loc["Use |cff33ffcc%s|r on |cff33ffcc%s|r"], (meta.itemreq or UNKNOWN), (meta.spawn or UNKNOWN))
1108 | elseif meta.QTYPE == "OBJECT_OBJECTIVE" then
1109 | return string.format(pfQuest_Loc["Interact with |cff33ffcc%s|r"], (meta.spawn or UNKNOWN))
1110 | elseif meta.QTYPE == "OBJECT_OBJECTIVE_ITEMREQ" then
1111 | return string.format(pfQuest_Loc["Use |cff33ffcc%s|r at |cff33ffcc%s|r"], (meta.itemreq or UNKNOWN), (meta.spawn or UNKNOWN))
1112 | elseif meta.QTYPE == "ITEM_OBJECTIVE_LOOT" then
1113 | return string.format(pfQuest_Loc["Loot |cff33ffcc[%s]|r from |cff33ffcc%s|r"], (meta.item or UNKNOWN), (meta.spawn or UNKNOWN))
1114 | elseif meta.QTYPE == "ITEM_OBJECTIVE_USE" then
1115 | return string.format(pfQuest_Loc["Loot and/or Use |cff33ffcc[%s]|r from |cff33ffcc%s|r"], (meta.item or UNKNOWN), (meta.spawn or UNKNOWN))
1116 | elseif meta.QTYPE == "AREATRIGGER_OBJECTIVE" then
1117 | return string.format(pfQuest_Loc["Explore |cff33ffcc%s|r"], (meta.spawn or UNKNOWN))
1118 | elseif meta.QTYPE == "ZONE_OBJECTIVE" then
1119 | return string.format(pfQuest_Loc["Use Quest Item at |cff33ffcc%s|r"], (meta.spawn or UNKNOWN))
1120 | end
1121 | end
--------------------------------------------------------------------------------
/pfQuest-worldmap.lua:
--------------------------------------------------------------------------------
1 | local original_UpdateNodes = pfMap.UpdateNodes
2 | local continentPins = {}
3 | local maxContinentPins = 2000 -- guessing here but more is better if possible
4 |
5 | -- ============================================================================
6 | -- WorldMapArea.dbc to mapData Conversion Formula
7 | -- ============================================================================
8 | --
9 | -- WorldMapArea Format: LocLeft, LocRight, LocTop, LocBottom
10 | -- mapData Format: {width, height, left, top}
11 | --
12 | -- Conversion: || means absolute value
13 | -- width = |LocRight - LocLeft|
14 | -- height = |LocTop - LocBottom|
15 | -- left = LocLeft
16 | -- top = LocTop
17 | --
18 | -- Example:
19 | -- MPQ: "2938.36","1880.03","10238.3","9532.59"
20 | -- Result: {1058.33, 705.71, 2938.36, 10238.3}
21 | -- ============================================================================
22 |
23 | local mapData = {
24 | -- Eastern Kingdoms zones (instance 0)
25 | -- Format: {width, height, left, top} in world coordinates
26 | [1429] = {3470.84, 2314.62, 1535.42, -7939.58}, -- Elwynn Forest
27 | [1436] = {3500.00, 2333.3, 3016.67, -9400}, -- Westfall
28 | [1433] = {2170.84, 1447.9, -1570.83, -8575}, -- Redridge Mountains
29 | [1431] = {2700.00, 1800.03, 833.333, -9716.67}, -- Duskwood
30 | [1434] = {6381.25, 4254.1, 2220.83, -11168.8}, -- Stranglethorn Vale
31 | [1453] = {1737.50, 1158.34, 1722.92, -7995.83}, -- Stormwind City
32 | [1426] = {4925.00, 3283.34, 1802.08, -3877.08}, -- Dun Morogh
33 | [1455] = {790.63, 527.61, -713.591, -4569.24}, -- Ironforge
34 | [1432] = {2758.33, 1839.58, -1993.75, -4487.5}, -- Loch Modan
35 | [1437] = {4135.42, 2756.25, -389.583, -2147.92}, -- Wetlands
36 | [1424] = {3200.00, 2133.33, 1066.67, 400}, -- Hillsbrad Foothills
37 | [1416] = {2800.00, 1866.667, 783.333, 1500}, -- Alterac Mountains
38 | [1417] = {3600.00, 2400.00, -866.667, -133.333}, -- Arathi Highlands
39 | [1425] = {3850.00, 2566.67, -1575, 1466.67}, -- The Hinterlands
40 | [1420] = {4518.75, 3012.5, 3033.33, 3837.5}, -- Tirisfal Glades
41 | [1421] = {4200.00, 2800.00, 3450, 1666.67}, -- Silverpine Forest
42 | [1458] = {959.38, 640.1, 873.193, 1877.94}, -- Undercity
43 | [1422] = {4300.00, 2866.67, 416.667, 3366.67}, -- Western Plaguelands
44 | [1423] = {4031.25, 2687.5, -2287.5, 3704.17}, -- Eastern Plaguelands
45 | [1418] = {2487.50, 1658.34, -2079.17, -5889.58}, -- Badlands
46 | [1427] = {2231.253, 1487.5, -322.917, -6100}, -- Searing Gorge
47 | [1428] = {2929.163, 1952.08, -266.667, -7031.25}, -- Burning Steppes
48 | [1435] = {2293.75, 1529.17, -2222.92, -9620.83}, -- Swamp of Sorrows
49 | [1419] = {3350.00, 2233.30, -1241.67, -10566.7}, -- Blasted Lands
50 | [1430] = {2500.00, 1666.63, -833.333, -9866.67}, -- Deadwind Pass
51 | -- Kalimdor zones (instance 1)
52 | -- Format: {width, height, left, top} in world coordinates
53 | [1438] = {5091.66, 3393.7, 3814.58, 11831.2}, -- Teldrassil (zone 141)
54 | [1457] = {1058.33, 705.71, 2938.36, 10238.3}, -- Darnassus (zone 1657)
55 | [1439] = {6550.00, 4366.66, 2941.67, 8333.33}, -- Darkshore (zone 148)
56 | [1440] = {5766.67, 3843.75, 1700, 4672.92}, -- Ashenvale (zone 331)
57 | [1442] = {4883.33, 3256.25, 3245.83, 2916.67}, -- Stonetalon Mountains (zone 406)
58 | [1413] = {10133.34, 6756.25, 2622.92, 1612.5}, -- The Barrens (zone 17)
59 | [1411] = {5287.5, 3525, -1962.5, 1808.33}, -- Durotar (zone 14)
60 | [1454] = {1402.61, 935.42, -3680.6, 2273.88}, -- Orgrimmar (zone 1637)
61 | [1412] = {5137.5, 3425.00, 2047.92, -272.917}, -- Mulgore (zone 215)
62 | [1456] = {1043.75, 695.83, 516.667, -850}, -- Thunder Bluff (zone 1638)
63 | [1443] = {4495.83, 2997.91, 4233.33, 452.083}, -- Desolace (zone 405)
64 | [1444] = {6950.00, 4633.33, 5441.67, -2366.67}, -- Feralas (zone 357)
65 | [1441] = {4400.00, 2933.33, -433.333, -3966.67}, -- Thousand Needles (zone 400)
66 | [1446] = {6900.00, 4600.00, -218.75, -5875}, -- Tanaris (zone 440)
67 | [1449] = {3700.00, 2466.66, 533.333, -5966.67}, -- Un'Goro Crater (zone 490)
68 | [1451] = {3483.33, 2322.92, 2537.5, -5958.33}, -- Silithus (zone 1377)
69 | [1445] = {5250.00, 3500.00, -975, -2033.33}, -- Dustwallow Marsh (zone 15)
70 | [1452] = {7100.00, 4733.33, -316.667, 8533.33}, -- Winterspring (zone 618)
71 | [1447] = {5070.84, 3381.25, -3277.08, 5341.67}, -- Azshara (zone 16)
72 | [1448] = {5750.00, 3833.33, 1641.67, 7133.33}, -- Felwood (zone 361)
73 | [1450] = {2308.33, 1539.59, -1381.25, 8491.67}, -- Moonglade (zone 493)
74 | -- Eastern Kingdoms
75 | [1415] = {40741.18, 27149.69, 18171.97, 11176.34}, -- Eastern Kingdoms continent
76 | -- Kalimdor continent
77 | [1414] = {36799.81, 24533.20, 17066.60, 12799.90} -- Kalimdor continent
78 | }
79 |
80 | local zoneToUiMapID = {
81 | -- Eastern Kingdoms
82 | [12] = 1429,
83 | [40] = 1436,
84 | [44] = 1433,
85 | [10] = 1431,
86 | [33] = 1434,
87 | [1519] = 1453,
88 | [1] = 1426,
89 | [1537] = 1455,
90 | [38] = 1432,
91 | [11] = 1437,
92 | [267] = 1424,
93 | [36] = 1416,
94 | [45] = 1417,
95 | [47] = 1425,
96 | [85] = 1420,
97 | [130] = 1421,
98 | [1497] = 1458,
99 | [28] = 1422,
100 | [139] = 1423,
101 | [3] = 1418,
102 | [51] = 1427,
103 | [46] = 1428,
104 | [8] = 1435,
105 | [4] = 1419,
106 | [41] = 1430,
107 | -- Kalimdor
108 | [141] = 1438,
109 | [1657] = 1457,
110 | [148] = 1439,
111 | [331] = 1440,
112 | [406] = 1442,
113 | [17] = 1413,
114 | [14] = 1411,
115 | [1637] = 1454,
116 | [215] = 1412,
117 | [1638] = 1456,
118 | [405] = 1443,
119 | [357] = 1444,
120 | [400] = 1441,
121 | [440] = 1446,
122 | [490] = 1449,
123 | [1377] = 1451,
124 | [15] = 1445,
125 | [618] = 1452,
126 | [16] = 1447,
127 | [361] = 1448,
128 | [493] = 1450
129 | }
130 |
131 | -- Get zone data from either main or epoch databases
132 | local function GetZoneData(zoneID)
133 | local zoneData = pfDB and pfDB["zones"] and pfDB["zones"]["data"] and pfDB["zones"]["data"][zoneID]
134 | if not zoneData then
135 | zoneData = pfDB and pfDB["zones"] and pfDB["zones"]["data-epoch"] and pfDB["zones"]["data-epoch"][zoneID]
136 | end
137 | return zoneData
138 | end
139 |
140 | -- Continent assignments
141 | local zoneContinent = {
142 | -- Eastern Kingdoms = 2 (continent 0 in game)
143 | [1] = 2,
144 | [3] = 2,
145 | [4] = 2,
146 | [8] = 2,
147 | [10] = 2,
148 | [11] = 2,
149 | [12] = 2,
150 | [28] = 2,
151 | [33] = 2,
152 | [36] = 2,
153 | [38] = 2,
154 | [40] = 2,
155 | [41] = 2,
156 | [44] = 2,
157 | [45] = 2,
158 | [46] = 2,
159 | [47] = 2,
160 | [51] = 2,
161 | [85] = 2,
162 | [130] = 2,
163 | [139] = 2,
164 | [267] = 2,
165 | [1497] = 2,
166 | [1519] = 2,
167 | [1537] = 2,
168 | -- Kalimdor = 1 (continent 1 in game)
169 | [14] = 1,
170 | [15] = 1,
171 | [16] = 1,
172 | [17] = 1,
173 | [141] = 1,
174 | [148] = 1,
175 | [215] = 1,
176 | [331] = 1,
177 | [357] = 1,
178 | [361] = 1,
179 | [400] = 1,
180 | [405] = 1,
181 | [406] = 1,
182 | [440] = 1,
183 | [490] = 1,
184 | [493] = 1,
185 | [618] = 1,
186 | [1377] = 1,
187 | [1637] = 1,
188 | [1638] = 1,
189 | [1657] = 1
190 | }
191 |
192 | local function GetZoneGroup(zoneID)
193 | -- Group related zones together to prevent duplicate NPCs
194 | local zoneGroups = {
195 | teldrassil = {141, 1657}, -- Teldrassil + Darnassus
196 | stormwind = {12, 1519}, -- Elwynn + Stormwind City
197 | ironforge = {1, 1537}, -- Dun Morogh + Ironforge
198 | orgrimmar = {14, 1637}, -- Durotar + Orgrimmar
199 | thunderbluff = {215, 1638}, -- Mulgore + Thunder Bluff
200 | undercity = {85, 1497} -- Tirisfal + Undercity
201 | }
202 |
203 | for group, zones in pairs(zoneGroups) do
204 | for _, zID in pairs(zones) do
205 | if zID == zoneID then
206 | return group
207 | end
208 | end
209 | end
210 | return zoneID
211 | end
212 |
213 | local function GetZoneContinent(zoneID)
214 | if zoneContinent[zoneID] then
215 | return zoneContinent[zoneID]
216 | end
217 |
218 | local zoneData = GetZoneData(zoneID)
219 | if zoneData and zoneData[1] then
220 | local continent = zoneData[1]
221 | if continent == 0 then
222 | continent = 2
223 | elseif continent == 1 then
224 | continent = 1
225 | else
226 | return nil
227 | end
228 |
229 | zoneContinent[zoneID] = continent
230 | return continent
231 | end
232 |
233 | return nil
234 | end
235 |
236 | local function ZoneToWorld(x, y, zoneID)
237 | local uiMapID = zoneToUiMapID[zoneID]
238 | if not uiMapID then
239 | return nil, nil
240 | end
241 | local data = mapData[uiMapID]
242 | if not data then
243 | return nil, nil
244 | end
245 |
246 | local worldX = data[3] - data[1] * (x / 100)
247 | local worldY = data[4] - data[2] * (y / 100)
248 |
249 | return worldX, worldY
250 | end
251 |
252 | local function WorldToContinent(worldX, worldY, continent)
253 | local contData = mapData[continent == 1 and 1414 or 1415]
254 | if not contData then
255 | return nil, nil
256 | end
257 |
258 | local x = (contData[3] - worldX) / contData[1]
259 | local y = (contData[4] - worldY) / contData[2]
260 |
261 | return x, y
262 | end
263 |
264 | local function NodeAnimate(self, max)
265 | return
266 | end
267 |
268 | local inverseMapScale = 1.0
269 | local function ResizeContinentNode(frame)
270 | if not frame.icon then
271 | -- Use config value for regular nodes, fallback to 12 if not set
272 | frame.defsize = tonumber(pfQuest_config["continentNodeSize"]) or 12
273 | frame.defsize = frame.defsize * inverseMapScale
274 | else
275 | -- Use config value for utility NPCs, fallback to 14 if not set
276 | frame.defsize = tonumber(pfQuest_config["continentUtilityNodeSize"]) or 14
277 | -- Compensate for icon's 1 pixel padding so it doesn't shrink down to nothing
278 | frame.defsize = (frame.defsize - 2) * inverseMapScale + 2
279 | end
280 | frame:SetWidth(frame.defsize)
281 | frame:SetHeight(frame.defsize)
282 | frame.hl:SetWidth(frame.defsize)
283 | frame.hl:SetHeight(frame.defsize)
284 | end
285 |
286 | local function ResizeContinentNodes()
287 | local i = 1
288 | if continentPins then
289 | while continentPins[i] and continentPins[i]:IsShown() do
290 | ResizeContinentNode(continentPins[i])
291 | i = i + 1
292 | end
293 | end
294 | end
295 |
296 | -- Resize icons on map zoom change
297 | local function OnMapScaleChanged(frame, scale, originalfunction)
298 | originalfunction(frame, scale)
299 |
300 | local newInverseScale = 1.0 / WorldMapButton:GetEffectiveScale()
301 | if (inverseMapScale ~= newInverseScale) then
302 | inverseMapScale = newInverseScale
303 | ResizeContinentNodes()
304 | end
305 | end
306 | -- Listen for WorldMapFrame scale changes
307 | local originalWorldMapFrame_SetScale = WorldMapFrame.SetScale
308 | WorldMapFrame.SetScale = function(frame, scale)
309 | OnMapScaleChanged(frame, scale, originalWorldMapFrame_SetScale)
310 | end
311 | -- Listen for WorldMapDetailFrame scale changes
312 | local originalWorldMapDetailFrame_SetScale = WorldMapDetailFrame.SetScale
313 | WorldMapDetailFrame.SetScale = function(frame, scale)
314 | OnMapScaleChanged(frame, scale, originalWorldMapDetailFrame_SetScale)
315 | end
316 | -- Listen for WorldMapButton scale changes
317 | local originalWorldMapButton_SetScale = WorldMapButton.SetScale
318 | WorldMapButton.SetScale = function(frame, scale)
319 | OnMapScaleChanged(frame, scale, originalWorldMapButton_SetScale)
320 | end
321 |
322 | local function CreateContinentPin(index)
323 | if not continentPins[index] then
324 | local pin = CreateFrame("Button", "pfQuestContinentPin" .. index, WorldMapButton)
325 | pin:SetFrameLevel(WorldMapButton:GetFrameLevel() + 10)
326 | pin:SetFrameStrata("DIALOG")
327 |
328 | pin.tex = pin:CreateTexture(nil, "BACKGROUND")
329 | pin.tex:SetAllPoints(pin)
330 |
331 | pin.pic = pin:CreateTexture(nil, "BORDER")
332 | pin.pic:SetPoint("TOPLEFT", pin, "TOPLEFT", 1, -1)
333 | pin.pic:SetPoint("BOTTOMRIGHT", pin, "BOTTOMRIGHT", -1, 1)
334 |
335 | pin.hl = pin:CreateTexture(nil, "OVERLAY")
336 | pin.hl:SetTexture(pfQuestConfig.path .. "\\img\\track")
337 | pin.hl:SetPoint("TOPLEFT", pin, "TOPLEFT", -5, 5)
338 | pin.hl:Hide()
339 |
340 | pin.defalpha = 1
341 | pin.Animate = NodeAnimate
342 | pin.dt = 0
343 |
344 | if pfQuest_config["continentClickThrough"] == "1" then
345 | pin.tooltipTimer = 0
346 | pin.wasMouseOver = false
347 |
348 | local function CheckTooltip(self, elapsed)
349 | if not self:IsVisible() then return end
350 |
351 | local x, y = GetCursorPosition()
352 | local scale = self:GetEffectiveScale()
353 | x = x / scale
354 | y = y / scale
355 |
356 | local left = self:GetLeft()
357 | local right = self:GetRight()
358 | local top = self:GetTop()
359 | local bottom = self:GetBottom()
360 |
361 | local isMouseOver = false
362 | if left and right and top and bottom then
363 | isMouseOver = (x >= left and x <= right and y >= bottom and y <= top)
364 | end
365 |
366 | if isMouseOver and not self.wasMouseOver then
367 | if self.node then
368 | pfMap.NodeEnter(self)
369 | end
370 | self.wasMouseOver = true
371 | elseif not isMouseOver and self.wasMouseOver then
372 | self.pulse = 1
373 | self.mod = 1
374 | self:SetWidth(self.defsize)
375 | self:SetHeight(self.defsize)
376 | pfMap.NodeLeave(self)
377 | self.wasMouseOver = false
378 | end
379 | end
380 |
381 | pin:SetScript("OnUpdate", function(self, elapsed)
382 | if IsControlKeyDown() then
383 | -- Enable full mouse interaction when Ctrl is held
384 | if not self.mouseEnabled then
385 | self:EnableMouse(true)
386 | self:RegisterForClicks("LeftButtonUp", "RightButtonUp")
387 | self.mouseEnabled = true
388 | end
389 | else
390 | -- Disable mouse interaction when Ctrl is not held, but keep tooltips
391 | if self.mouseEnabled ~= false then
392 | self:EnableMouse(false)
393 | self:RegisterForClicks()
394 | self.mouseEnabled = false
395 | end
396 | end
397 |
398 | CheckTooltip(self, elapsed)
399 | end)
400 |
401 | pin:SetScript(
402 | "OnClick",
403 | function(self, button)
404 | if IsControlKeyDown() and self.node then
405 | if pfMap.NodeClick then
406 | pfMap.NodeClick(self, button)
407 | end
408 | end
409 | end
410 | )
411 | else
412 | pin:EnableMouse(true)
413 | pin:RegisterForClicks("LeftButtonUp", "RightButtonUp")
414 |
415 | pin:SetScript(
416 | "OnEnter",
417 | function(self)
418 | if self.node then
419 | pfMap.NodeEnter(self)
420 | end
421 | end
422 | )
423 |
424 | pin:SetScript(
425 | "OnLeave",
426 | function(self)
427 | self.pulse = 1
428 | self.mod = 1
429 | self:SetWidth(self.defsize)
430 | self:SetHeight(self.defsize)
431 | pfMap.NodeLeave(self)
432 | end
433 | )
434 |
435 | pin:SetScript(
436 | "OnClick",
437 | function(self, button)
438 | if self.node then
439 | if pfMap.NodeClick then
440 | pfMap.NodeClick(self, button)
441 | end
442 | end
443 | end
444 | )
445 | end
446 |
447 | continentPins[index] = pin
448 | end
449 | return continentPins[index]
450 | end
451 |
452 | function pfMap:UpdateNodes()
453 | local continent = GetCurrentMapContinent()
454 | local zone = GetCurrentMapZone()
455 |
456 | original_UpdateNodes(self)
457 |
458 | local mapName = GetMapInfo()
459 | local isContinent =
460 | (mapName == "Kalimdor" and zone == 0) or (mapName == "Azeroth" and zone == 0) or
461 | (mapName == nil and continent == 0 and zone == 0)
462 |
463 | -- Function to calculate gray level (no XP level) based on WoW's system
464 | local function GetGrayLevel(charLevel)
465 | if charLevel <= 5 then
466 | return 0 -- all mobs give XP
467 | elseif charLevel <= 49 then
468 | return charLevel - math.floor(charLevel / 10) - 5
469 | elseif charLevel == 50 then
470 | return 40 -- charLevel - 10
471 | elseif charLevel <= 59 then
472 | return charLevel - math.floor(charLevel / 5) - 1
473 | else -- level 60-70
474 | return charLevel - 9
475 | end
476 | end
477 |
478 | for i = 1, maxContinentPins do
479 | if continentPins[i] then
480 | continentPins[i]:Hide()
481 | continentPins[i].node = nil
482 | continentPins[i].sourceContinent = nil
483 | end
484 | end
485 |
486 | if pfQuest_config["epochContinentPins"] == "0" then
487 | return
488 | end
489 |
490 | if zone > 0 and not isContinent then
491 | return
492 | end
493 |
494 | for _, pin in pairs(pfMap.pins) do
495 | if pin then
496 | pin:Hide()
497 | end
498 | end
499 |
500 | if continent == 0 then
501 | local pinCount = 0
502 | local playerLevel = UnitLevel("player")
503 | local processedZones = {}
504 | local processedQuests = {}
505 |
506 | for targetContinent = 1, 2 do
507 | for addon, addonData in pairs(pfMap.nodes) do
508 | for zID, zoneNodes in pairs(addonData) do
509 | local zoneCont = GetZoneContinent(zID)
510 |
511 | if zoneCont == targetContinent then
512 | processedZones[zID] = true
513 | local uiMapID = zoneToUiMapID[zID]
514 | if uiMapID and mapData[uiMapID] then
515 | for coords, node in pairs(zoneNodes) do
516 | local skipNode = false
517 | local questKey = nil
518 |
519 | for title, data in pairs(node) do
520 | local needsDeduplication = false
521 | local isUtilityNPC = false
522 |
523 | if data.addon == "PFDB" then
524 | local utilityTypes = {
525 | "flight",
526 | "auctioneer",
527 | "banker",
528 | "battlemaster",
529 | "innkeeper",
530 | "mailbox",
531 | "stablemaster",
532 | "spirithealer",
533 | "meetingstone"
534 | }
535 |
536 | for _, utilityType in pairs(utilityTypes) do
537 | if pfDB["meta-epoch"] and pfDB["meta-epoch"][utilityType] then
538 | for objectId, faction in pairs(pfDB["meta-epoch"][utilityType]) do
539 | if data.id and tonumber(data.id) == objectId then
540 | isUtilityNPC = true
541 | break
542 | end
543 | end
544 | if isUtilityNPC then
545 | break
546 | end
547 | end
548 | end
549 |
550 | -- Block unwanted PFDB trackables (herbs, mines, chests, etc.)
551 | if not isUtilityNPC then
552 | local blockedTypes = {"herbs", "mines", "chests", "fish", "rares"}
553 | for _, blockedType in pairs(blockedTypes) do
554 | if pfDB["meta-epoch"] and pfDB["meta-epoch"][blockedType] then
555 | for objectId, faction in pairs(pfDB["meta-epoch"][blockedType]) do
556 | if data.id and tonumber(data.id) == objectId then
557 | skipNode = true
558 | break
559 | end
560 | end
561 | if skipNode then
562 | break
563 | end
564 | end
565 | end
566 | end
567 | elseif data.addon and string.find(data.addon, "TRACK_") then
568 | local allowedTracks = {
569 | "TRACK_FLIGHT",
570 | "TRACK_AUCTIONEER",
571 | "TRACK_BANKER",
572 | "TRACK_BATTLEMASTER",
573 | "TRACK_INNKEEPER",
574 | "TRACK_MAILBOX",
575 | "TRACK_STABLEMASTER",
576 | "TRACK_SPIRITHEALER",
577 | "TRACK_MEETINGSTONE"
578 | }
579 |
580 | local isAllowed = false
581 | for _, track in pairs(allowedTracks) do
582 | if string.find(data.addon, track) then
583 | isAllowed = true
584 | break
585 | end
586 | end
587 |
588 | if isAllowed then
589 | isUtilityNPC = true
590 | else
591 | skipNode = true
592 | end
593 | end
594 |
595 | if
596 | (zID == 141 or zID == 1657) or (zID == 12 or zID == 1519) or
597 | (zID == 1 or zID == 1537) or
598 | (zID == 14 or zID == 1637) or
599 | (zID == 215 or zID == 1638) or
600 | (zID == 85 or zID == 1497)
601 | then
602 | needsDeduplication = true
603 |
604 | if zID == 141 or zID == 1657 then
605 | questKey = title .. "_teldrassil"
606 | elseif zID == 12 or zID == 1519 then
607 | questKey = title .. "_stormwind"
608 | elseif zID == 1 or zID == 1537 then
609 | questKey = title .. "_ironforge"
610 | elseif zID == 14 or zID == 1637 then
611 | questKey = title .. "_orgrimmar"
612 | elseif zID == 215 or zID == 1638 then
613 | questKey = title .. "_thunderbluff"
614 | elseif zID == 85 or zID == 1497 then
615 | questKey = title .. "_undercity"
616 | end
617 | end
618 |
619 | if needsDeduplication and questKey and processedQuests[questKey] then
620 | skipNode = true
621 | break
622 | end
623 |
624 | local questLevel = tonumber(data.qlvl) or tonumber(data.lvl) or 0
625 | local minLevel = tonumber(data.min) or 0
626 |
627 | if not isUtilityNPC then
628 | if pfQuest_config["showlowlevel"] == "0" then
629 | if questLevel > 0 and questLevel <= GetGrayLevel(playerLevel) then
630 | if not (data.texture and string.find(data.texture, "complete")) then
631 | skipNode = true
632 | break
633 | end
634 | end
635 | end
636 | end
637 |
638 | -- Skip quests that are way too high level (red quests) - only if high level display is disabled
639 | if not isUtilityNPC then
640 | if minLevel > playerLevel + (pfQuest_config["showhighlevel"] == "1" and 3 or 0) then
641 | if not (data.texture and string.find(data.texture, "complete")) then
642 | skipNode = true
643 | break
644 | end
645 | end
646 | end
647 |
648 | -- Special filter for quests with suspiciously low min level - only if low level display is disabled
649 | if not isUtilityNPC then
650 | if pfQuest_config["showlowlevel"] == "0" then
651 | if minLevel <= 1 and questLevel <= GetGrayLevel(playerLevel) then
652 | if not (data.texture and string.find(data.texture, "complete")) then
653 | skipNode = true
654 | break
655 | end
656 | end
657 | end
658 | end
659 |
660 | if needsDeduplication and questKey and not skipNode then
661 | processedQuests[questKey] = true
662 | end
663 | end
664 |
665 | if not skipNode then
666 | local _, _, strx, stry = strfind(coords, "(.*)|(.*)")
667 | local zoneX = tonumber(strx)
668 | local zoneY = tonumber(stry)
669 |
670 | if zoneX and zoneY then
671 | local worldX, worldY = ZoneToWorld(zoneX, zoneY, zID)
672 |
673 | if worldX and worldY then
674 | local contX, contY = WorldToContinent(worldX, worldY, targetContinent)
675 |
676 | if
677 | contX and contY and contX >= 0 and contX <= 1 and contY >= 0 and
678 | contY <= 1
679 | then
680 | local worldMapX, worldMapY
681 |
682 | if targetContinent == 1 then
683 | worldMapX = contX * 0.90 - 0.22
684 | worldMapY = contY * 0.85 + 0.05
685 | else
686 | worldMapX = 0.33 + (contX * 0.90)
687 | worldMapY = contY * 0.90 - 0.04
688 | end
689 |
690 | if
691 | worldMapX >= 0 and worldMapX <= 1 and worldMapY >= 0 and
692 | worldMapY <= 1
693 | then
694 | pinCount = pinCount + 1
695 | if pinCount > maxContinentPins then
696 | break
697 | end
698 |
699 | local pin = CreateContinentPin(pinCount)
700 | pin.node = node
701 | pin.sourceContinent = targetContinent
702 |
703 | pfMap:UpdateNode(pin, node, nil, nil, nil)
704 |
705 | ResizeContinentNode(pin)
706 |
707 | pin:ClearAllPoints()
708 | pin:SetPoint(
709 | "CENTER",
710 | WorldMapButton,
711 | "TOPLEFT",
712 | worldMapX * WorldMapButton:GetWidth(),
713 | -worldMapY * WorldMapButton:GetHeight()
714 | )
715 | pin:Show()
716 | end
717 | end
718 | end
719 | end
720 | end
721 | end
722 | if pinCount >= maxContinentPins then
723 | break
724 | end
725 | end
726 | end
727 | end
728 | if pinCount >= maxContinentPins then
729 | break
730 | end
731 | end
732 | if pinCount >= maxContinentPins then
733 | break
734 | end
735 | end
736 |
737 | for i = pinCount + 1, maxContinentPins do
738 | if continentPins[i] then
739 | continentPins[i]:Hide()
740 | end
741 | end
742 | return
743 | end
744 |
745 | if continent > 2 or continent < 1 then
746 | return
747 | end
748 |
749 | local pinCount = 0
750 | local playerLevel = UnitLevel("player")
751 | local processedZones = {}
752 | local processedQuests = {}
753 |
754 | local function GetGrayLevel(charLevel)
755 | if charLevel <= 5 then
756 | return 0
757 | elseif charLevel <= 49 then
758 | return charLevel - math.floor(charLevel / 10) - 5
759 | elseif charLevel == 50 then
760 | return 40
761 | elseif charLevel <= 59 then
762 | return charLevel - math.floor(charLevel / 5) - 1
763 | else
764 | return charLevel - 9
765 | end
766 | end
767 |
768 | for addon, addonData in pairs(pfMap.nodes) do
769 | for zID, zoneNodes in pairs(addonData) do
770 | local zoneCont = GetZoneContinent(zID)
771 | if not zoneCont or zoneCont ~= continent then
772 | else
773 | processedZones[zID] = true
774 | local uiMapID = zoneToUiMapID[zID]
775 | if uiMapID and mapData[uiMapID] then
776 | for coords, node in pairs(zoneNodes) do
777 | local skipNode = false
778 | local questKey = nil
779 |
780 | for title, data in pairs(node) do
781 | local needsDeduplication = false
782 | local isUtilityNPC = false
783 |
784 | if data.addon == "PFDB" then
785 | local utilityTypes = {
786 | "flight",
787 | "auctioneer",
788 | "banker",
789 | "battlemaster",
790 | "innkeeper",
791 | "mailbox",
792 | "stablemaster",
793 | "spirithealer",
794 | "meetingstone"
795 | }
796 |
797 | for _, utilityType in pairs(utilityTypes) do
798 | if pfDB["meta-epoch"] and pfDB["meta-epoch"][utilityType] then
799 | for objectId, faction in pairs(pfDB["meta-epoch"][utilityType]) do
800 | if data.id and tonumber(data.id) == objectId then
801 | isUtilityNPC = true
802 | break
803 | end
804 | end
805 | if isUtilityNPC then
806 | break
807 | end
808 | end
809 | end
810 |
811 | -- Block unwanted PFDB trackables (herbs, mines, chests, etc.)
812 | if not isUtilityNPC then
813 | local blockedTypes = {"herbs", "mines", "chests", "fish", "rares"}
814 | for _, blockedType in pairs(blockedTypes) do
815 | if pfDB["meta-epoch"] and pfDB["meta-epoch"][blockedType] then
816 | for objectId, faction in pairs(pfDB["meta-epoch"][blockedType]) do
817 | if data.id and tonumber(data.id) == objectId then
818 | skipNode = true
819 | break
820 | end
821 | end
822 | if skipNode then
823 | break
824 | end
825 | end
826 | end
827 | end
828 | elseif data.addon and string.find(data.addon, "TRACK_") then
829 | local allowedTracks = {
830 | "TRACK_FLIGHT",
831 | "TRACK_AUCTIONEER",
832 | "TRACK_BANKER",
833 | "TRACK_BATTLEMASTER",
834 | "TRACK_INNKEEPER",
835 | "TRACK_MAILBOX",
836 | "TRACK_STABLEMASTER",
837 | "TRACK_SPIRITHEALER",
838 | "TRACK_MEETINGSTONE"
839 | }
840 |
841 | local isAllowed = false
842 | for _, track in pairs(allowedTracks) do
843 | if string.find(data.addon, track) then
844 | isAllowed = true
845 | break
846 | end
847 | end
848 |
849 | if isAllowed then
850 | isUtilityNPC = true
851 | else
852 | skipNode = true
853 | end
854 | end
855 |
856 | -- Only apply deduplication to specific problem zones
857 | if
858 | (zID == 141 or zID == 1657) or -- Teldrassil/Darnassus
859 | (zID == 12 or zID == 1519) or -- Elwynn/Stormwind
860 | (zID == 1 or zID == 1537) or -- Dun Morogh/Ironforge
861 | (zID == 14 or zID == 1637) or -- Durotar/Orgrimmar
862 | (zID == 215 or zID == 1638) or -- Mulgore/Thunder Bluff
863 | (zID == 85 or zID == 1497) -- Tirisfal/Undercity
864 | then
865 | needsDeduplication = true
866 |
867 | -- Create zone-pair specific keys
868 | if zID == 141 or zID == 1657 then
869 | questKey = title .. "_teldrassil"
870 | elseif zID == 12 or zID == 1519 then
871 | questKey = title .. "_stormwind"
872 | elseif zID == 1 or zID == 1537 then
873 | questKey = title .. "_ironforge"
874 | elseif zID == 14 or zID == 1637 then
875 | questKey = title .. "_orgrimmar"
876 | elseif zID == 215 or zID == 1638 then
877 | questKey = title .. "_thunderbluff"
878 | elseif zID == 85 or zID == 1497 then
879 | questKey = title .. "_undercity"
880 | end
881 | end
882 |
883 | -- Only check for duplicates in problem zones
884 | if needsDeduplication and questKey and processedQuests[questKey] then
885 | skipNode = true
886 | break
887 | end
888 |
889 | local questLevel = tonumber(data.qlvl) or tonumber(data.lvl) or 0
890 | local minLevel = tonumber(data.min) or 0
891 |
892 | if not isUtilityNPC then
893 | if pfQuest_config["showlowlevel"] == "0" then
894 | if questLevel > 0 and questLevel <= GetGrayLevel(playerLevel) then
895 | if not (data.texture and string.find(data.texture, "complete")) then
896 | skipNode = true
897 | break
898 | end
899 | end
900 | end
901 | end
902 |
903 | -- Skip quests that are way too high level (red quests) - only if high level display is disabled
904 | if not isUtilityNPC then
905 | if minLevel > playerLevel + (pfQuest_config["showhighlevel"] == "1" and 3 or 0) then
906 | if not (data.texture and string.find(data.texture, "complete")) then
907 | skipNode = true
908 | break
909 | end
910 | end
911 | end
912 |
913 | -- Special filter for quests with suspiciously low min level - only if low level display is disabled
914 | if not isUtilityNPC then
915 | if pfQuest_config["showlowlevel"] == "0" then
916 | if minLevel <= 1 and questLevel <= GetGrayLevel(playerLevel) then
917 | if not (data.texture and string.find(data.texture, "complete")) then
918 | skipNode = true
919 | break
920 | end
921 | end
922 | end
923 | end
924 |
925 | if needsDeduplication and questKey and not skipNode then
926 | processedQuests[questKey] = true
927 | end
928 | end
929 |
930 | if not skipNode then
931 | local _, _, strx, stry = strfind(coords, "(.*)|(.*)")
932 | local zoneX = tonumber(strx)
933 | local zoneY = tonumber(stry)
934 |
935 | if zoneX and zoneY then
936 | local worldX, worldY = ZoneToWorld(zoneX, zoneY, zID)
937 | if worldX and worldY then
938 | local contX, contY = WorldToContinent(worldX, worldY, continent)
939 | if contX and contY and contX >= 0 and contX <= 1 and contY >= 0 and contY <= 1 then
940 | pinCount = pinCount + 1
941 | if pinCount > maxContinentPins then
942 | break
943 | end
944 |
945 | local pin = CreateContinentPin(pinCount)
946 | pin.node = node
947 | pin.sourceContinent = continent
948 |
949 | pfMap:UpdateNode(pin, node, nil, nil, nil)
950 |
951 | ResizeContinentNode(pin)
952 |
953 | pin:ClearAllPoints()
954 | pin:SetPoint(
955 | "CENTER",
956 | WorldMapButton,
957 | "TOPLEFT",
958 | contX * WorldMapButton:GetWidth(),
959 | -contY * WorldMapButton:GetHeight()
960 | )
961 | pin:Show()
962 | end
963 | end
964 | end
965 | end
966 | end
967 | if pinCount >= maxContinentPins then
968 | break
969 | end
970 | end
971 | end
972 | end
973 | if pinCount >= maxContinentPins then
974 | break
975 | end
976 | end
977 |
978 | for i = pinCount + 1, maxContinentPins do
979 | if continentPins[i] then
980 | continentPins[i]:Hide()
981 | end
982 | end
983 | end
984 |
985 | local originalWorldMapButton_OnUpdate = WorldMapButton:GetScript("OnUpdate")
986 | WorldMapButton:SetScript(
987 | "OnUpdate",
988 | function(self, elapsed)
989 | if originalWorldMapButton_OnUpdate then
990 | originalWorldMapButton_OnUpdate(self, elapsed)
991 | end
992 |
993 | local currentContinent = GetCurrentMapContinent()
994 | local currentZone = GetCurrentMapZone()
995 |
996 | if self.lastContinent ~= currentContinent or self.lastZone ~= currentZone then
997 | self.lastContinent = currentContinent
998 | self.lastZone = currentZone
999 |
1000 | if currentZone == 0 then
1001 | pfMap:UpdateNodes()
1002 | end
1003 | end
1004 | end
1005 | )
1006 |
1007 | local function ExtendPfQuestConfig()
1008 | table.insert(
1009 | pfQuest_defconfig,
1010 | {
1011 | text = "|cff33ffccContinent Map|r",
1012 | type = "header"
1013 | }
1014 | )
1015 | table.insert(
1016 | pfQuest_defconfig,
1017 | {
1018 | text = "Display Continent Pins",
1019 | default = "1",
1020 | type = "checkbox",
1021 | config = "epochContinentPins"
1022 | }
1023 | )
1024 | table.insert(
1025 | pfQuest_defconfig,
1026 | {
1027 | text = "Require Ctrl+Click for Pin Interaction",
1028 | default = "0",
1029 | type = "checkbox",
1030 | config = "continentClickThrough"
1031 | }
1032 | )
1033 | table.insert(
1034 | pfQuest_defconfig,
1035 | {
1036 | text = "Continent Node Size",
1037 | default = "12",
1038 | type = "text",
1039 | config = "continentNodeSize"
1040 | }
1041 | )
1042 | table.insert(
1043 | pfQuest_defconfig,
1044 | {
1045 | text = "Continent Utility Node Size",
1046 | default = "14",
1047 | type = "text",
1048 | config = "continentUtilityNodeSize"
1049 | }
1050 | )
1051 | table.insert(
1052 | pfQuest_defconfig,
1053 | {
1054 | text = "Hide Chicken Quests (CLUCK!)",
1055 | default = "1",
1056 | type = "checkbox",
1057 | config = "epochHideChickenQuests"
1058 | }
1059 | )
1060 | table.insert(
1061 | pfQuest_defconfig,
1062 | {
1063 | text = "Hide Felwood Corrupted Flowers",
1064 | default = "1",
1065 | type = "checkbox",
1066 | config = "epochHideFelwoodFlowers"
1067 | }
1068 | )
1069 | table.insert(
1070 | pfQuest_defconfig,
1071 | {
1072 | text = "Hide PvP/Battleground Quests",
1073 | default = "1",
1074 | type = "checkbox",
1075 | config = "epochHidePvPQuests"
1076 | }
1077 | )
1078 | table.insert(
1079 | pfQuest_defconfig,
1080 | {
1081 | text = "Hide Commission Quests",
1082 | default = "0",
1083 | type = "checkbox",
1084 | config = "epochHideCommissionQuests"
1085 | }
1086 | )
1087 | table.insert(
1088 | pfQuest_defconfig,
1089 | {
1090 | text = "Hide Item Drop Quest Starters",
1091 | default = "0",
1092 | type = "checkbox",
1093 | config = "epochHideItemDrops"
1094 | }
1095 | )
1096 |
1097 | -- Initialize the config values with defaults
1098 | pfQuest_config["epochContinentPins"] = pfQuest_config["epochContinentPins"] or "1"
1099 | pfQuest_config["continentClickThrough"] = pfQuest_config["continentClickThrough"] or "0"
1100 | pfQuest_config["continentNodeSize"] = pfQuest_config["continentNodeSize"] or "12"
1101 | pfQuest_config["continentUtilityNodeSize"] = pfQuest_config["continentUtilityNodeSize"] or "14"
1102 | pfQuest_config["epochHideChickenQuests"] = pfQuest_config["epochHideChickenQuests"] or "1"
1103 | pfQuest_config["epochHideFelwoodFlowers"] = pfQuest_config["epochHideFelwoodFlowers"] or "1"
1104 | pfQuest_config["epochHidePvPQuests"] = pfQuest_config["epochHidePvPQuests"] or "1"
1105 | pfQuest_config["epochHideCommissionQuests"] = pfQuest_config["epochHideCommissionQuests"] or "0"
1106 | pfQuest_config["epochHideItemDrops"] = pfQuest_config["epochHideItemDrops"] or "0"
1107 | end
1108 |
1109 | local f = CreateFrame("Frame")
1110 | f:RegisterEvent("VARIABLES_LOADED")
1111 | f:SetScript(
1112 | "OnEvent",
1113 | function()
1114 | ExtendPfQuestConfig()
1115 | end
1116 | )
1117 |
--------------------------------------------------------------------------------