From 92646b4d88215da557731a6d172bc6fad967d485 Mon Sep 17 00:00:00 2001 From: omicron Date: Thu, 2 Jul 2026 18:07:39 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + .stylua.toml | 1 + LICENSE.md | 19 +++++++ Makefile | 33 ++++++++++++ README.md | 22 ++++++++ src/LocationMount.toc | 6 +++ src/main.lua | 113 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 195 insertions(+) create mode 100644 .gitignore create mode 100644 .stylua.toml create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/LocationMount.toc create mode 100644 src/main.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..394e884 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1 @@ +indent_type = "Spaces" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2b6eca1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dbdacdb --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +.PHONY: all addon txz zip clean + +SRC_FILES := $(wildcard src/*) +ROOT_FILES := LICENSE.md README.md +SOURCES := $(SRC_FILES) $(ROOT_FILES) + +ADDON_NAME := LocationMount +BUILD_DIR := build +ADDON_DIR := $(BUILD_DIR)/$(ADDON_NAME) +BUILD_SOURCES := $(patsubst src/%,$(ADDON_DIR)/%,$(SRC_FILES)) \ + $(addprefix $(ADDON_DIR)/,$(ROOT_FILES)) + +$(ADDON_DIR)/%: src/% + mkdir -p $(dir $@) + cp $< $@ + +$(ADDON_DIR)/%: % + mkdir -p $(dir $@) + cp $< $@ + +addon: $(BUILD_SOURCES) + +txz: addon + tar -cJf $(BUILD_DIR)/$(ADDON_NAME).tar.xz -C $(BUILD_DIR) $(ADDON_NAME) + +zip: addon + cd $(BUILD_DIR) && 7z a $(ADDON_NAME).zip $(ADDON_NAME) + +all: txz zip + +clean: + rm -rf $(BUILD_DIR) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..dec0285 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# What + +LocationMount is a small addon that provides a macro command to automatically +select the correct mount based on your current location. It will prioritize +flying mounts over ground mounts and epic mounts over rare mounts. There is no +configuration. + +# How + +Install the addon and make a macro: +``` +/click LocationMount +``` +or if for some reason you want one that only picks the correct ground mount: +``` +/click LocationMountGround +``` + +# Why not just use a [flyable] macro? + +In TBC anniversary the flyable condition (and the related `IsFlyableArea()` +API) appears broken and returns true in many more zones than it should. diff --git a/src/LocationMount.toc b/src/LocationMount.toc new file mode 100644 index 0000000..43224ba --- /dev/null +++ b/src/LocationMount.toc @@ -0,0 +1,6 @@ +## Interface: 20504 +## Title: Location Mount +## Notes: Add a macro that uses the correct mount for the current location +## Version: 0.1.0 + +main.lua diff --git a/src/main.lua b/src/main.lua new file mode 100644 index 0000000..b3fc412 --- /dev/null +++ b/src/main.lua @@ -0,0 +1,113 @@ +local scanTooltip + +local FLYABLE_MAPS = { + [530] = true, -- Outland (TBC) +} + +-- zones that share a flyable instanceMapID but are not flyable +-- using UI map IDs as a proxy for zones +local NONFLYABLE_ZONES = { + [1941] = true, -- Eversong Woods + [1942] = true, -- Ghostlands + [1943] = true, -- Azuremyst Isle + [1947] = true, -- The Exodar + [1950] = true, -- Bloodmyst Isle + [1954] = true, -- Silvermoon City +} + +local RIDING_SKILLS = { [75] = false, [150] = false, [225] = true, [300] = true } + +local function IsFlyable() + local mapId = select(8, GetInstanceInfo()) + if not FLYABLE_MAPS[mapId] then + return false + end + local mapId = C_Map.GetBestMapForUnit("player") + return not NONFLYABLE_ZONES[mapId] +end + +local function IsFlyingMount(link) + scanTooltip:ClearLines() + scanTooltip:SetHyperlink(link) + for i = 1, scanTooltip:NumLines() do + local text = _G["LocationMountScanTooltipTextLeft" .. i]:GetText() + if text then + local skill = tonumber(string.match(text, "%((%d+)%)")) + + local flying = skill and RIDING_SKILLS[skill] + if flying ~= nil then + return flying + end + end + end + return false +end + +local function GetMountPriority(bag, slot) + local link = C_Container.GetContainerItemLink(bag, slot) + if not link then + return -1 + end + + local name, _, rarity, _, _, _, _, _, _, _, _, classID, subclassID = GetItemInfo(link) + if not name then + return -1 + end + + if classID ~= Enum.ItemClass.Miscellaneous or subclassID ~= Enum.ItemMiscellaneousSubclass.Mount then + return -1 + end + + local isFlying = IsFlyingMount(link) + local isEpic = rarity == Enum.ItemQuality.Epic + local priority = (isFlying and 2 or 0) + (isEpic and 1 or 0) + return priority +end + +local function FindBestMount(flyable) + local best, bestPriority = nil, -1 + local maxPriority = flyable and 3 or 1 + + for bag = 0, 4 do + for slot = 1, C_Container.GetContainerNumSlots(bag) do + local priority = GetMountPriority(bag, slot) + if priority > bestPriority and priority <= maxPriority then + bestPriority = priority + best = { bag = bag, slot = slot } + if bestPriority == maxPriority then + return best + end + end + end + end + return best +end + +local function MakeMountButton(name, forceGround) + local button = CreateFrame("Button", name, UIParent, "SecureActionButtonTemplate") + button:SetAttribute("useOnKeyDown", false) + button:RegisterForClicks("AnyDown") + button:SetScript("PreClick", function(self) + if InCombatLockdown() then + return + end + local flyable = not forceGround and IsFlyable() + local mount = FindBestMount(flyable) + if mount then + self:SetAttribute("item1", mount.bag .. " " .. mount.slot) + else + self:SetAttribute("item1", nil) + end + end) + button:SetAttribute("type1", "item") + return button +end + +local function main() + scanTooltip = CreateFrame("GameTooltip", "LocationMountScanTooltip", nil, "GameTooltipTemplate") + scanTooltip:SetOwner(UIParent, "ANCHOR_NONE") + MakeMountButton("LocationMount", false) + MakeMountButton("LocationMountGround", true) +end + +main()