-- 1. Persistent Data & Session Initialization FarmTrackerDB = FarmTrackerDB or { items = {2840, 2841, 2770}, locked = false, goal = 0, pos = nil, scale = 1.0 } local sessionStartCounts = {} local startTime = GetTime() local isInitialized = false local hoverButtons = {} local goalReachedSoundPlayed = false -- 2. Create the Main Tracker Frame local f = CreateFrame("Frame", "FarmTrackerFrame", UIParent, "BackdropTemplate") f:SetSize(280, 100) f:SetFrameStrata("MEDIUM") f:SetMovable(true) f:EnableMouse(true) f:RegisterForDrag("LeftButton") local function SavePosition(self) local point, _, relativePoint, xOfs, yOfs = self:GetPoint() FarmTrackerDB.pos = { point = point, relativePoint = relativePoint, x = xOfs, y = yOfs } end f:SetScript("OnDragStart", f.StartMoving) f:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() SavePosition(self) end) f:SetBackdrop({ bgFile = "Interface/Tooltips/UI-Tooltip-Background", edgeFile = "Interface/Buttons/WHITE8x8", tile = true, tileSize = 16, edgeSize = 1, insets = { left = 2, right = 2, top = 2, bottom = 2 } }) f:SetBackdropColor(0.20,0.20,0.20,1); f.title = f:CreateFontString(nil, "OVERLAY") f.title:SetFont("Interface\\Addons\\MxW\\Media\\Font\\Consola.ttf", 14); f.title:SetPoint("TOP", f, "TOP", 0, -10) f.title:SetTextColor(1.00,0.49,0.04); f.title:SetText("MxW.Farm") f.statsText = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") f.statsText:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 12, 12) f.statsText:SetJustifyH("LEFT") f.text = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") f.text:SetPoint("BOTTOMLEFT", f.statsText, "TOPLEFT", 0, 10) f.text:SetJustifyH("LEFT") -- Buttons: Reset & Manager Toggle f.resetBtn = CreateFrame("Button", nil, f) f.resetBtn:SetSize(18, 18) f.resetBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 10) f.resetBtn:SetNormalTexture("Interface\\Buttons\\UI-RefreshButton") f.resetBtn:SetScript("OnClick", function() for _, id in ipairs(FarmTrackerDB.items) do sessionStartCounts[id] = GetItemCount(id, true) end startTime = GetTime() goalReachedSoundPlayed = false RefreshDisplay() print("|cFF00FF00MxW.Farm:|r Session reset for " .. UnitName("player")) end) f.configBtn = CreateFrame("Button", nil, f) f.configBtn:SetSize(18, 18) f.configBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -8, -8) f.configBtn:SetNormalTexture("Interface\\Buttons\\UI-OptionsButton") f.configBtn:SetScript("OnClick", function() if FarmTrackerManager:IsShown() then FarmTrackerManager:Hide() else FarmTrackerManager:Show() end end) -- 3. The SCROLLABLE Management UI (Drag & Drop) local m = CreateFrame("Frame", "FarmTrackerManager", UIParent, "BackdropTemplate") m:SetSize(320, 420) m:SetPoint("CENTER") m:SetFrameStrata("HIGH") m:SetMovable(true) m:EnableMouse(true) m:RegisterForDrag("LeftButton") m:SetScript("OnDragStart", m.StartMoving) m:SetScript("OnDragStop", m.StopMovingOrSizing) m:SetBackdrop(f:GetBackdrop()) m:SetBackdropColor(0, 0, 0, 1) m:Hide() local function AddItemToList(id) if not id then return end local found = false for _, existing in ipairs(FarmTrackerDB.items) do if existing == id then found = true end end if not found then table.insert(FarmTrackerDB.items, id) sessionStartCounts[id] = GetItemCount(id, true) C_Item.RequestLoadItemDataByID(id) UpdateManagerList() RefreshDisplay() end end m:SetScript("OnReceiveDrag", function() local infoType, itemID = GetCursorInfo() if infoType == "item" then AddItemToList(itemID) ClearCursor() end end) m:SetScript("OnUpdate", function(self) if GetCursorInfo() == "item" and MouseIsOver(self) then self:SetBackdropBorderColor(0, 1, 0, 1) else self:SetBackdropBorderColor(1, 1, 1, 1) end end) m.title = m:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") m.title:SetPoint("TOPLEFT", m, "TOPLEFT", 15, -15) m.title:SetText("Manage Materials") m.desc = m:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall") m.desc:SetPoint("TOPLEFT", m.title, "BOTTOMLEFT", 0, -2) m.desc:SetText("Drag & drop items here to add them") m.close = CreateFrame("Button", nil, m, "UIPanelCloseButton") m.close:SetPoint("TOPRIGHT", m, "TOPRIGHT", -5, -5) local scrollFrame = CreateFrame("ScrollFrame", "FTManagerScrollFrame", m, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", m, "TOPLEFT", 10, -60) scrollFrame:SetPoint("BOTTOMRIGHT", m, "BOTTOMRIGHT", -30, 60) local scrollChild = CreateFrame("Frame") scrollChild:SetSize(270, 1) scrollFrame:SetScrollChild(scrollChild) local itemRows = {} function UpdateManagerList() for _, row in ipairs(itemRows) do row:Hide() end for i, id in ipairs(FarmTrackerDB.items) do if not itemRows[i] then local row = CreateFrame("Frame", nil, scrollChild) row:SetSize(270, 26) -- Item Text row.text = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") row.text:SetPoint("LEFT", row, "LEFT", 45, 0) -- Pushed right to fit arrows row.text:SetWidth(180) row.text:SetJustifyH("LEFT") -- Remove Button row.remove = CreateFrame("Button", nil, row) row.remove:SetSize(16, 16) row.remove:SetPoint("RIGHT", row, "RIGHT", -2, 0) row.remove:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") -- UP Arrow row.up = CreateFrame("Button", nil, row) row.up:SetSize(16, 16) row.up:SetPoint("LEFT", row, "LEFT", 5, 0) row.up:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up") -- DOWN Arrow row.down = CreateFrame("Button", nil, row) row.down:SetSize(16, 16) row.down:SetPoint("LEFT", row.up, "RIGHT", 2, 0) row.down:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up") itemRows[i] = row end local _, link = GetItemInfo(id) local row = itemRows[i] row.text:SetText(link or "Loading ID: " .. id) -- Logic: Move Up row.up:SetScript("OnClick", function() if i > 1 then local temp = FarmTrackerDB.items[i - 1] FarmTrackerDB.items[i - 1] = FarmTrackerDB.items[i] FarmTrackerDB.items[i] = temp UpdateManagerList() RefreshDisplay() end end) -- Logic: Move Down row.down:SetScript("OnClick", function() if i < #FarmTrackerDB.items then local temp = FarmTrackerDB.items[i + 1] FarmTrackerDB.items[i + 1] = FarmTrackerDB.items[i] FarmTrackerDB.items[i] = temp UpdateManagerList() RefreshDisplay() end end) -- Logic: Remove row.remove:SetScript("OnClick", function() table.remove(FarmTrackerDB.items, i) UpdateManagerList() RefreshDisplay() end) -- Visibility of arrows (disable first up and last down) row.up:SetAlpha(i == 1 and 0.3 or 1) row.down:SetAlpha(i == #FarmTrackerDB.items and 0.3 or 1) row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -(i - 1) * 26) row:Show() end scrollChild:SetHeight(#FarmTrackerDB.items * 26) end m.editBox = CreateFrame("EditBox", nil, m, "InputBoxTemplate") m.editBox:SetSize(180, 20) m.editBox:SetPoint("BOTTOMLEFT", m, "BOTTOMLEFT", 20, 20) m.editBox:SetAutoFocus(false) m.editBox:SetText("Paste link/ID here...") m.editBox:SetScript("OnEditFocusGained", function(self) if self:GetText() == "Paste link/ID here..." then self:SetText("") end end) m.addBtn = CreateFrame("Button", nil, m, "UIPanelButtonTemplate") m.addBtn:SetSize(80, 22) m.addBtn:SetPoint("LEFT", m.editBox, "RIGHT", 5, 0) m.addBtn:SetText("Add Item") m.addBtn:SetScript("OnClick", function() local text = m.editBox:GetText() local id = tonumber(text) or tonumber(text:match("item:(%d+)")) if id then AddItemToList(id) m.editBox:SetText("") end end) m:SetScript("OnShow", UpdateManagerList) -- 4. Logic: Refresh Display local function GetOrCreateButton(index) if not hoverButtons[index] then local b = CreateFrame("Button", nil, f) b:SetHeight(28) b:SetScript("OnEnter", function(self) if self.itemID and not FarmTrackerDB.locked then GameTooltip:SetOwner(self, "ANCHOR_RIGHT") GameTooltip:SetItemByID(self.itemID) GameTooltip:Show() end end) b:SetScript("OnLeave", function() GameTooltip:Hide() end) hoverButtons[index] = b end return hoverButtons[index] end function RefreshDisplay() if not FarmTrackerDB then return end local displayString, totalInv, totalSess, activeIndex = "", 0, 0, 0 local elapsed = (GetTime() - startTime) / 3600 for _, btn in ipairs(hoverButtons) do btn:Hide() end for _, id in ipairs(FarmTrackerDB.items) do local name, link, _, _, _, _, _, _, _, icon = GetItemInfo(id) local count = GetItemCount(id, true) local gain = count - (sessionStartCounts[id] or count) if count > 0 or gain > 0 then activeIndex = activeIndex + 1 local price = (_G.TSM_API and TSM_API.GetCustomPriceValue("dbmarket", "i:" .. id)) or 0 totalInv = totalInv + (count * price / 10000) totalSess = totalSess + (gain * price / 10000) local rank = "" if link then local q = C_TradeSkillUI.GetItemReagentQualityByItemInfo(link) if q and q > 0 then rank = " " .. CreateAtlasMarkup(("Professions-Icon-Quality-Tier%d-Small"):format(q), 14, 14) end end displayString = string.format( "|T%s:14:14:0:0|t |cFFFFD100%s%s:|r %d |cFF00FF00(+%d)|r\n|cFF808080Inv: %.2fg|r\n\n", icon or "", name or "Loading...", rank, count, gain, (count * price / 10000)) .. displayString local btn = GetOrCreateButton(activeIndex) btn.itemID = id btn:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 10, 55 + (activeIndex - 1) * 32 + (FarmTrackerDB.goal > 0 and 15 or 0)) btn:SetWidth(f:GetWidth() - 20) if not FarmTrackerDB.locked then btn:Show() end end end local gph = elapsed > 0 and (totalSess / elapsed) or 0 local goalBar = "" if FarmTrackerDB.goal > 0 then local p = math.min(totalSess / FarmTrackerDB.goal, 1) goalBar = string.format("\nGoal: |cFF00FF00%s|r|cFF555555%s|r %.1f%%", string.rep("■", math.floor(p * 20)), string.rep("■", 20 - math.floor(p * 20)), p * 100) if p >= 1 and not goalReachedSoundPlayed then PlaySound(888, "Master") goalReachedSoundPlayed = true end end f.statsText:SetText(string.format("|cFF00FF00Sess: %.2fg|r |cFFFFFF00Total: %.2fg|r\n|cFF55AAFFGPH: %.2fg/hr|r%s", totalSess, totalInv, gph, goalBar)) f.text:SetText(displayString) f:SetHeight(f.text:GetStringHeight() + f.statsText:GetStringHeight() + 60) end -- 5. Init & Events f:RegisterEvent("ADDON_LOADED") f:RegisterEvent("BAG_UPDATE") f:RegisterEvent("PLAYER_LOGIN") f:SetScript("OnEvent", function(self, event, arg1) if event == "ADDON_LOADED" and (arg1 == "MxW.Farm" or arg1 == "FarmTracker") then FarmTrackerDB = FarmTrackerDB or {} FarmTrackerDB.items = FarmTrackerDB.items or {2840, 2841, 2770} FarmTrackerDB.scale = FarmTrackerDB.scale or 1.0 f:SetScale(FarmTrackerDB.scale) if FarmTrackerDB.pos then f:ClearAllPoints() f:SetPoint(FarmTrackerDB.pos.point, UIParent, FarmTrackerDB.pos.relativePoint, FarmTrackerDB.pos.x, FarmTrackerDB.pos.y) else f:SetPoint("BOTTOM", UIParent, "CENTER") end if FarmTrackerDB.locked then f:EnableMouse(false) f:SetMovable(false) f.resetBtn:Hide() end elseif event == "PLAYER_LOGIN" then C_Timer.After(2, function() for _, id in ipairs(FarmTrackerDB.items) do sessionStartCounts[id] = GetItemCount(id, true) C_Item.RequestLoadItemDataByID(id) end isInitialized = true f:Show() RefreshDisplay() end) elseif event == "BAG_UPDATE" and isInitialized then RefreshDisplay() end end) -- 6. Slash Commands SLASH_FARMTRACKER1 = "/ft" SlashCmdList["FARMTRACKER"] = function(msg) local cmd, arg = msg:match("^(%S*)%s*(.-)$") if cmd == "lock" then FarmTrackerDB.locked = not FarmTrackerDB.locked f:EnableMouse(not FarmTrackerDB.locked) f:SetMovable(not FarmTrackerDB.locked) if FarmTrackerDB.locked then f.resetBtn:Hide() else f.resetBtn:Show() end RefreshDisplay() elseif cmd == "goal" then FarmTrackerDB.goal = tonumber(arg) or 0 goalReachedSoundPlayed = false RefreshDisplay() elseif cmd == "scale" then local s = tonumber(arg) if s and s >= 0.5 and s <= 3.0 then FarmTrackerDB.scale = s f:SetScale(s) end elseif cmd == "manage" then m:Show() else m:Show() -- Default to showing manager if no command end end