From 722e5d24ab5c24d6cbc5b9a1a49700a356a137ba Mon Sep 17 00:00:00 2001 From: Ade <923+ade@noreply.octowow.st> Date: Thu, 8 Oct 2026 06:34:03 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AdePet.lua | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++++ AdePet.toc | 8 ++ README.md | 45 +++++++++ 3 files changed, 330 insertions(+) create mode 100644 AdePet.lua create mode 100644 AdePet.toc create mode 100644 README.md diff --git a/AdePet.lua b/AdePet.lua new file mode 100644 index 0000000..fd67418 --- /dev/null +++ b/AdePet.lua @@ -0,0 +1,277 @@ +-- AdePet.lua +-- Hunter-only addon for the 1.12 client. +-- Shows: Pet_Name: Loyalty_Level (Loyalty_Name) +-- and, under it, "Pet is hungry" when the pet's happiness drops below Happy (yellow/red). +-- Window hides itself at max loyalty (Level 6). +-- +-- Commands: +-- /hungry X set hunger check interval to X seconds (default 15) +-- /adepet lock | unlock | reset (alias: /ap) + +AdePetDB = AdePetDB or {} + +local LOYALTY_INTERVAL = 1.0 +local DEFAULT_HUNGRY_INTERVAL = 15 +local MAX_LOYALTY = 6 + +local LOYALTY_NAMES = { + [1] = "Rebellious", + [2] = "Unruly", + [3] = "Submissive", + [4] = "Dependable", + [5] = "Faithful", + [6] = "Best Friend", +} + +local isHunter = false +local isHungry = false +local hungerLevel = 3 -- 1 = unhappy (red), 2 = content (yellow), 3 = happy (green) + +local frame = CreateFrame("Frame", "AdePetFrame", UIParent) +frame:SetWidth(150) +frame:SetHeight(28) +frame:SetPoint("CENTER", UIParent, "CENTER", 0, 200) +frame:SetMovable(true) +frame:EnableMouse(true) +frame:RegisterForDrag("LeftButton") +frame:SetClampedToScreen(true) +frame:Hide() + +frame:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 16, edgeSize = 12, + insets = { left = 3, right = 3, top = 3, bottom = 3 }, +}) +frame:SetBackdropColor(0, 0, 0, 0.7) + +local text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal") +text:SetPoint("TOP", frame, "TOP", 0, -8) +text:SetJustifyH("CENTER") + +local hungryText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") +hungryText:SetPoint("TOP", text, "BOTTOM", 0, -3) +hungryText:SetJustifyH("CENTER") +hungryText:Hide() + +frame:SetScript("OnDragStart", function() + if not AdePetDB.locked then + this:StartMoving() + end +end) + +frame:SetScript("OnDragStop", function() + this:StopMovingOrSizing() + local point, _, relPoint, x, y = this:GetPoint() + AdePetDB.point = point + AdePetDB.relPoint = relPoint + AdePetDB.x = x + AdePetDB.y = y +end) + +local function GetHungryInterval() + return AdePetDB.hungryInterval or DEFAULT_HUNGRY_INTERVAL +end + +-- Fit the window to the visible text lines. +local function ResizeToText() + local w = text:GetStringWidth() + local h1 = text:GetHeight() + if not h1 or h1 < 1 then h1 = 12 end + local height = 8 + h1 + 8 + + if hungryText:IsShown() then + local w2 = hungryText:GetStringWidth() + if w2 > w then w = w2 end + local h2 = hungryText:GetHeight() + if not h2 or h2 < 1 then h2 = 10 end + height = height + h2 + 3 + end + + w = w + 24 + if w < 90 then w = 90 end + frame:SetWidth(w) + frame:SetHeight(height) +end + +-- Reads the pet's happiness. 1 = unhappy, 2 = content, 3 = happy. +-- Anything below "happy" is treated as hungry. +local function UpdateHunger() + if not isHunter or not UnitExists("pet") then + isHungry = false + return + end + local happiness = GetPetHappiness() + if type(happiness) ~= "number" then + happiness = tonumber(happiness) + end + if happiness and happiness < 3 then + isHungry = true + hungerLevel = happiness + else + isHungry = false + end +end + +-- Extract numeric loyalty level. Standard API returns a number; some servers +-- (e.g. OctoWoW) return text like "(Loyalty Level 1) Rebellious". +local function ParseLoyalty(raw) + if type(raw) == "number" then + return raw + elseif type(raw) == "string" then + local _, _, digits = string.find(raw, "(%d+)") + if digits then + return tonumber(digits) + end + -- fallback: match by rank name + local lower = string.lower(raw) + for level, name in pairs(LOYALTY_NAMES) do + if string.find(lower, string.lower(name), 1, true) then + return level + end + end + end + return nil +end + +local function UpdateDisplay() + if not isHunter then + frame:Hide() + return + end + + if not UnitExists("pet") then + text:SetText("No Pet") + hungryText:Hide() + ResizeToText() + frame:Show() + return + end + + local petName = UnitName("pet") or "Pet" + local loyaltyLevel = ParseLoyalty(GetPetLoyalty()) + + if loyaltyLevel and loyaltyLevel >= MAX_LOYALTY then + frame:Hide() + return + end + + if loyaltyLevel then + local loyaltyName = LOYALTY_NAMES[loyaltyLevel] or "Unknown" + text:SetText(petName .. ": " .. loyaltyLevel .. " (" .. loyaltyName .. ")") + else + text:SetText(petName) + end + + if isHungry then + hungryText:SetText("Pet is hungry") + if hungerLevel == 1 then + hungryText:SetTextColor(1, 0.2, 0.2) + else + hungryText:SetTextColor(1, 0.9, 0.1) + end + hungryText:Show() + else + hungryText:Hide() + end + + ResizeToText() + frame:Show() +end + +local function CheckHunger() + UpdateHunger() + UpdateDisplay() +end + +local loyaltyElapsed = 0 +local hungerElapsed = 0 +frame:SetScript("OnUpdate", function() + if not isHunter then + return + end + loyaltyElapsed = loyaltyElapsed + arg1 + hungerElapsed = hungerElapsed + arg1 + + if hungerElapsed >= GetHungryInterval() then + hungerElapsed = 0 + loyaltyElapsed = 0 + CheckHunger() + elseif loyaltyElapsed >= LOYALTY_INTERVAL then + loyaltyElapsed = 0 + UpdateDisplay() + end +end) + +frame:RegisterEvent("PLAYER_ENTERING_WORLD") +frame:RegisterEvent("UNIT_PET") +frame:RegisterEvent("PLAYER_REGEN_ENABLED") -- left combat + +frame:SetScript("OnEvent", function() + if event == "PLAYER_ENTERING_WORLD" then + local _, englishClass = UnitClass("player") + isHunter = (englishClass == "HUNTER") + + if not isHunter then + frame:Hide() + return + end + + if AdePetDB.point then + frame:ClearAllPoints() + frame:SetPoint(AdePetDB.point, UIParent, AdePetDB.relPoint, AdePetDB.x, AdePetDB.y) + end + end + + if not isHunter then + return + end + + if event == "UNIT_PET" and arg1 ~= "player" then + return + end + + -- Entering world, pet change, or leaving combat: check hunger right away. + hungerElapsed = 0 + CheckHunger() +end) + +SLASH_ADEPETHUNGRY1 = "/hungry" +SlashCmdList["ADEPETHUNGRY"] = function(msg) + if not isHunter then + DEFAULT_CHAT_FRAME:AddMessage("AdePet: this addon only works for Hunters.") + return + end + local seconds = tonumber(msg) + if not seconds or seconds < 1 then + DEFAULT_CHAT_FRAME:AddMessage("AdePet: hunger check every " .. GetHungryInterval() .. "s. Usage: /hungry X (X = seconds, minimum 1)") + return + end + AdePetDB.hungryInterval = seconds + hungerElapsed = 0 + DEFAULT_CHAT_FRAME:AddMessage("AdePet: hunger check interval set to " .. seconds .. "s.") +end + +SLASH_ADEPET1 = "/adepet" +SLASH_ADEPET2 = "/ap" +SlashCmdList["ADEPET"] = function(msg) + msg = string.lower(msg or "") + if not isHunter then + DEFAULT_CHAT_FRAME:AddMessage("AdePet: this addon only works for Hunters.") + return + end + if msg == "lock" then + AdePetDB.locked = true + DEFAULT_CHAT_FRAME:AddMessage("AdePet: window locked.") + elseif msg == "unlock" then + AdePetDB.locked = false + DEFAULT_CHAT_FRAME:AddMessage("AdePet: window unlocked (drag to move).") + elseif msg == "reset" then + AdePetDB.point = nil + frame:ClearAllPoints() + frame:SetPoint("CENTER", UIParent, "CENTER", 0, 200) + DEFAULT_CHAT_FRAME:AddMessage("AdePet: position reset.") + else + DEFAULT_CHAT_FRAME:AddMessage("AdePet commands: /adepet lock, unlock, reset | /hungry X") + end +end diff --git a/AdePet.toc b/AdePet.toc new file mode 100644 index 0000000..4c347cf --- /dev/null +++ b/AdePet.toc @@ -0,0 +1,8 @@ +## Interface: 11200 +## Title: AdePet +## Notes: Hunter pet loyalty window with hunger warning +## Author: Claude +## Version: 1.2 +## SavedVariablesPerCharacter: AdePetDB + +AdePet.lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f20bcb --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# AdePet + +Lightweight hunter pet addon for the World of Warcraft 1.12 client (tested target: OctoWoW). + +## Features + +- Small movable window: `Pet_Name: Loyalty_Level (Loyalty_Name)` +- Window size follows the text length +- "Pet is hungry" warning under the name when the pet is no longer Happy + (yellow = Content, red = Unhappy) +- Hunger is re-checked every 15 seconds (configurable), after combat, on pet + change and when feeding ends +- At max loyalty (Level 6) the window is hidden unless the pet is hungry +- Hunters only + +## Installation + +1. Download the latest `AdePet-v1.0.zip` from the Releases page. +2. Extract it so the folder `AdePet` sits directly in + `\Interface\AddOns\` (the path must be + `Interface\AddOns\AdePet\AdePet.toc`), remove -main from folder name. +3. Enable AdePet in the AddOns list on the character select screen. + + +## Commands + +| Command | Description | +| --- | --- | +| `/hungry X` | Set the hunger check interval to X seconds (minimum 1, default 15) | +| `/hungry` | Show the current interval | +| `/adepet lock` | Lock the window position | +| `/adepet unlock` | Unlock the window so it can be dragged | +| `/adepet reset` | Reset the window position | + +`/ap` works as a short alias for `/adepet`. Drag the window with the left mouse +button; the position and interval are saved per character. + +## Notes + +- Hunger detection reads the pet's happiness level (Happy / Content / Unhappy). + The 1.12 API does not expose a numeric satiety value, so there is no bar. +- Feeding is detected through the "Feed Pet Effect" buff. The check matches the + English buff name (this check will not work if using any other language). + +