BullseiWoWAddons / BattleGroundEnemies

A enemy display for battlegrounds
https://www.curseforge.com/wow/addons/battlegroundenemies
19 stars 11 forks source link

[Feature Request] Class-based DR icons #48

Open marco-vrinssen opened 1 month ago

marco-vrinssen commented 1 month ago

Hey, I would like to suggest the implementation of class-specific Diminishing Returns (DR) icons. This can be especially relevant for new players to create a quicker association for using CC abilities. For now, I have attached a first suggestion on how this could be achieved, but I believe that making this a toggleable option in the settings menu would provide more flexibility for different user preferences.

Thank you for your dedication to improving this addon.

Reference:


local AddonName, Data = ...
local BattleGroundEnemies = BattleGroundEnemies
local L = Data.L

local LSM = LibStub("LibSharedMedia-3.0")

local GetSpellTexture = C_Spell and C_Spell.GetSpellTexture or GetSpellTexture

local CreateFrame = CreateFrame
local BackdropTemplateMixin = BackdropTemplateMixin
local GameTooltip = GameTooltip

local IsClassic = WOW_PROJECT_ID == WOW_PROJECT_CLASSIC

local DRList = LibStub("DRList-1.0")

local defaultSettings = {
    Enabled = true,
    Parent = "Button",
    ActivePoints = 1,
    DisplayType = "Frame",
    IconSize = 20,
    Cooldown = {
        ShowNumber = true,
        FontSize = 12,
        FontOutline = "OUTLINE",
        EnableShadow = false,
        DrawSwipe = false,
        ShadowColor = {0, 0, 0, 1},
    },
    Container = {
        UseButtonHeightAsSize = true,
        IconSize = 15,
        IconsPerRow = 10,
        HorizontalGrowDirection = "rightwards",
        HorizontalSpacing = 2,
        VerticalGrowdirection = "downwards",
        VerticalSpacing = 1,
    },
    Filtering_Enabled = false,
    Filtering_Filterlist = {},
}

local options = function(location)
    return {
        ContainerSettings = {
            type = "group",
            name = L.ContainerSettings,
            order = 1,
            get = function(option)
                return Data.GetOption(location.Container, option)
            end,
            set = function(option, ...)
                return Data.SetOption(location.Container, option, ...)
            end,
            args = Data.AddContainerSettings(location.Container),
        },
        DisplayType = {
            type = "select",
            name = L.DisplayType,
            desc = L.DrTracking_DisplayType_Desc,
            values = Data.DisplayType,
            order = 2
        },
        CooldownTextSettings = {
            type = "group",
            name = L.Countdowntext,
            get = function(option)
                return Data.GetOption(location.Cooldown, option)
            end,
            set = function(option, ...)
                return Data.SetOption(location.Cooldown, option, ...)
            end,
            order = 3,
            args = Data.AddCooldownSettings(location.Cooldown)
        },
        Fake1 = Data.AddVerticalSpacing(6),
        FilteringSettings = {
            type = "group",
            name = FILTER,
            order = 4,
            args = {
                Filtering_Enabled = {
                    type = "toggle",
                    name = L.Filtering_Enabled,
                    desc = L.DrTrackingFiltering_Enabled_Desc,
                    width = 'normal',
                    order = 1
                },
                Filtering_Filterlist = {
                    type = "multiselect",
                    name = L.Filtering_Filterlist,
                    desc = L.DrTrackingFiltering_Filterlist_Desc,
                    disabled = function() return not location.Filtering_Enabled end,
                    get = function(option, key)
                        return location.Filtering_Filterlist[key]
                    end,
                    set = function(option, key, state)
                        location.Filtering_Filterlist[key] = state or nil
                    end,
                    values = Data.DrCategorys,
                    order = 2
                }
            }
        }
    }
end

local dRstates = {
    [1] = { 0, 1, 0, 1}, --green (next cc in DR time will be only half duration)
    [2] = { 1, 1, 0, 1}, --yellow (next cc in DR time will be only 1/4 duration)
    [3] = { 1, 0, 0, 1}, --red (next cc in DR time will not apply, player is immune)
}

local function drFrameUpdateStatusBorder(drFrame)
    drFrame:SetBackdropBorderColor(unpack(dRstates[drFrame:GetStatus()]))
end

local function drFrameUpdateStatusText(drFrame)
    drFrame.Cooldown.Text:SetTextColor(unpack(dRstates[drFrame:GetStatus()]))
end

local flags = {
    HasDynamicSize = true
}

local dRTracking = BattleGroundEnemies:NewButtonModule({
    moduleName = "DRTracking",
    localizedModuleName = L.DRTracking,
    flags = flags,
    defaultSettings = defaultSettings,
    options = options,
    events = {"AuraRemoved"},
    enabledInThisExpansion = true
})

local _, playerClass = UnitClass("player")

if playerClass == "DEATHKNIGHT" then
    drClassIcons = {
        stun = GetSpellTexture(108194),         -- Asphyxiate
        disorient = GetSpellTexture(207167),    -- Blinding Sleet
        incapacitate = GetSpellTexture(108194), -- Asphyxiate
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(45524),          -- Chains of Ice
        silence = GetSpellTexture(47476),       -- Strangulate
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "DEMONHUNTER" then
    drClassIcons = {
        stun = GetSpellTexture(211881),         -- Fel Eruption
        disorient = GetSpellTexture(207684),    -- Sigil of Misery
        incapacitate = GetSpellTexture(217832), -- Imprison
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(202138),         -- Sigil of Chains
        silence = GetSpellTexture(204490),      -- Sigil of Silence
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "DRUID" then
    drClassIcons = {
        stun = GetSpellTexture(5211),           -- Mighty Bash
        disorient = GetSpellTexture(33786),     -- Cyclone
        incapacitate = GetSpellTexture(99),     -- Incapacitating Roar
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(339),            -- Entangling Roots
        silence = GetSpellTexture(15487),       -- Silence
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "EVOKER" then
    drClassIcons = {
        stun = GetSpellTexture(357210),         -- Deep Breath
        disorient = GetSpellTexture(360806),    -- Sleep Walk
        incapacitate = GetSpellTexture(6770),   -- Sap
        knockback = GetSpellTexture(357214),    -- Wing Buffet
        root = GetSpellTexture(358385),         -- Landslide
        silence = GetSpellTexture(351338),      -- Quell
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "HUNTER" then
    drClassIcons = {
        stun = GetSpellTexture(19577),          -- Intimidation
        disorient = GetSpellTexture(2094),      -- Blind
        incapacitate = GetSpellTexture(187650), -- Freezing Trap
        knockback = GetSpellTexture(13813),     -- Explosive Trap
        root = GetSpellTexture(162487),         -- Steel Trap
        silence = GetSpellTexture(147362),      -- Counter Shot
        disarm = GetSpellTexture(202900)        -- Scorpid Sting
    }
elseif playerClass == "MAGE" then
    drClassIcons = {
        stun = GetSpellTexture(389794),         -- Dragon's Breath
        disorient = GetSpellTexture(31661),     -- Dragon's Breath
        incapacitate = GetSpellTexture(118),    -- Polymorph
        knockback = GetSpellTexture(157981),    -- Blast Wave
        root = GetSpellTexture(122),            -- Frost Nova
        silence = GetSpellTexture(2139),        -- Counterspell
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "MONK" then
    drClassIcons = {
        stun = GetSpellTexture(119381),         -- Leg Sweep
        disorient = GetSpellTexture(198898),    -- Song of Chi-Ji
        incapacitate = GetSpellTexture(115078), -- Paralysis
        knockback = GetSpellTexture(116844),    -- Ring of Peace
        root = GetSpellTexture(343731),         -- Disable
        silence = GetSpellTexture(116705),      -- Spear Hand Strike
        disarm = GetSpellTexture(233759)        -- Grapple Weapon
    }
elseif playerClass == "PALADIN" then
    drClassIcons = {
        stun = GetSpellTexture(853),            -- Hammer of Justice
        disorient = GetSpellTexture(115750),    -- Blinding Light
        incapacitate = GetSpellTexture(20066),  -- Repentance
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(339),            -- Entangling Roots
        silence = GetSpellTexture(96231),       -- Rebuke
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "PRIEST" then
    drClassIcons = {
        stun = GetSpellTexture(64044),          -- Psychic Horror
        disorient = GetSpellTexture(8122),      -- Psychic Scream
        incapacitate = GetSpellTexture(88625),  -- Holy Word: Chastise
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(108920),         -- Void Tendrils
        silence = GetSpellTexture(15487),       -- Silence
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "ROGUE" then
    drClassIcons = {
        stun = GetSpellTexture(408),            -- Kidney Shot
        disorient = GetSpellTexture(2094),      -- Blind
        incapacitate = GetSpellTexture(6770),   -- Sap
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(339),            -- Entangling Roots
        silence = GetSpellTexture(1766),        -- Kick
        disarm = GetSpellTexture(207777)        -- Dismantle
    }
elseif playerClass == "SHAMAN" then
    drClassIcons = {
        stun = GetSpellTexture(305483),         -- Capacitor Totem
        disorient = GetSpellTexture(2094),      -- Blind
        incapacitate = GetSpellTexture(51514),  -- Hex
        knockback = GetSpellTexture(51490),     -- Thunderstorm
        root = GetSpellTexture(64695),          -- Earthgrab Totem
        silence = GetSpellTexture(57994),       -- Wind Shear
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "WARLOCK" then
    drClassIcons = {
        stun = GetSpellTexture(30283),          -- Shadowfury
        disorient = GetSpellTexture(5782),      -- Fear
        incapacitate = GetSpellTexture(6789),   -- Mortal Coil
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(51485),          -- Earthgrab Totem
        silence = GetSpellTexture(19647),       -- Spell Lock
        disarm = GetSpellTexture(236077)        -- Disarm
    }
elseif playerClass == "WARRIOR" then
    drClassIcons = {
        stun = GetSpellTexture(107570),         -- Storm Bolt
        disorient = GetSpellTexture(5246),      -- Intimidating Shout
        incapacitate = GetSpellTexture(6770),   -- Sap
        knockback = GetSpellTexture(132469),    -- Typhoon
        root = GetSpellTexture(6343),           -- Thunder Clap
        silence = GetSpellTexture(6552),        -- Pummel
        disarm = GetSpellTexture(236077)        -- Disarm
    }
end

local function createNewDrFrame(playerButton, container)
    local drFrame = CreateFrame("Frame", nil, container, BackdropTemplateMixin and "BackdropTemplate")
    drFrame.Cooldown = BattleGroundEnemies.MyCreateCooldown(drFrame)

    drFrame.Cooldown:SetScript("OnCooldownDone", function()
        drFrame:Remove()
    end)
    drFrame:HookScript("OnEnter", function(self)
        BattleGroundEnemies:ShowTooltip(self, function()
            if IsClassic then return end
            GameTooltip:SetSpellByID(self.spellId)
        end)
    end)

    drFrame:HookScript("OnLeave", function(self)
        if GameTooltip:IsOwned(self) then
            GameTooltip:Hide()
        end
    end)

    drFrame.Container = container

    drFrame.ApplyChildFrameSettings = function(self)
        self.Cooldown:ApplyCooldownSettings(container.config.Cooldown, false)
        self:SetDisplayType()
    end

    drFrame.GetStatus = function(self)
        local status = self.input.status
        status = (math.min(status, 3))
        return status
    end

    drFrame.SetDisplayType = function(self)
        if container.config.DisplayType == "Frame" then
            self.SetStatus = drFrameUpdateStatusBorder
        else
            self.SetStatus = drFrameUpdateStatusText
        end

        self.Cooldown.Text:SetTextColor(1, 1, 1, 1)
        self:SetBackdropBorderColor(0, 0, 0, 0)
        if self.input and self.input.status ~= 0 then self:SetStatus() end
    end

    drFrame:SetBackdrop({
        bgFile = "Interface/Buttons/WHITE8X8", --drawlayer "BACKGROUND"
        edgeFile = 'Interface/Buttons/WHITE8X8', --drawlayer "BORDER"
        edgeSize = 1
    })

    drFrame:SetBackdropColor(0, 0, 0, 0)
    drFrame:SetBackdropBorderColor(0, 0, 0, 0)

    drFrame.Icon = drFrame:CreateTexture(nil, "BORDER", nil, -1) -- -1 to make it behind the SetBackdrop bg
    drFrame.Icon:SetAllPoints()

    drFrame:ApplyChildFrameSettings()

    drFrame:Hide()
    return drFrame
end

local function setupDrFrame(container, drFrame, drDetails)
    drFrame:SetStatus()

    drFrame.spellId = drDetails.spellId

    local drCat = drDetails.drCat
    if drClassIcons and drClassIcons[drCat] then
        drFrame.Icon:SetTexture(drClassIcons[drCat])
    else
        drFrame.Icon:SetTexture(GetSpellTexture(drDetails.spellId))
    end

    local duration = DRList:GetResetTime(drCat)
    drFrame.Cooldown:SetCooldown(drDetails.startTime, duration)
end

function dRTracking:AttachToPlayerButton(playerButton)
    local container = BattleGroundEnemies:NewContainer(playerButton, createNewDrFrame, setupDrFrame)

    function container:AuraRemoved(spellId, spellName)
        local config = self.config

        local drCat = DRList:GetCategoryBySpellID(spellId)

        if not drCat then return end

        local drTrackingEnabled = not config.Filtering_Enabled or config.Filtering_Filterlist[drCat]

        if drTrackingEnabled then
            local input = self:FindInputByAttribute("drCat", drCat)
            if input then
                input = self:UpdateInput(input, {spellId = spellId})
            else
                input = self:NewInput({
                    drCat = drCat,
                    spellId = spellId
                })
            end

            input.status = (input.status or 0) + 1

            input.startTime = GetTime()
            self:Display()
        end
    end

    playerButton.DRTracking = container
end
bullsei commented 1 month ago

Hey, on my current dev version i implemented the option to select an icon for each dr category (stun, fear) . But i also like your approach, it would basically automate that selection. I could add an button for it like "set icon based on my class" i feel like that could work well and still offer the way to customize.

marco-vrinssen commented 1 month ago

Thanks so much for handling the request, I’m really happy to hear the news! I think enabling the assignment of user-defined individual Spell IDs to the different DR categories makes a lot of sense—actually, it’s even better than what I suggested and is more consistent with the behavior of other arena/battleground addons like Gladius. At the time, I thought class-based icons might be simpler to maintain and implement, especially since I’m new to addon development. It could still be helpful for newer players who don’t want to think about their CC abilities and can just click a button, but overall, I think the way you’ve done it is really great.

marco-vrinssen commented 1 month ago

Would it be possible to gain access to the current version of your development environment? ☺️

bullsei commented 1 month ago

It would be possible to push the new branch i created for that, but i am working on some features that aren't finished yet, i wanna rework the way modules are moved – basically like a editmode. There is still quite some work to do there. But sure, if you are interested in that i can push it to GitHub.

bullsei commented 1 month ago

So i published the dev branch https://github.com/BullseiWoWAddons/BattleGroundEnemies/tree/dev its nowhere near complete but i made quite some progress today, it should be working fine in pvp but the editing mode side of things needs work. If you want to check the new editing view enter /run BattleGroundEnemies.EditMode.EditModeManager:OpenEditmode() into the chat once you select a system when the config menu is open you should see it change the current selected option if you select on of the blue frames. Things i am currently working on

If you got time to check it out feedback is highly appreciated even tho its still pretty early

bullsei commented 3 weeks ago

In case you are curious the latest alpha version contains the option to select the icons based on dr category besides that it features many more new features and improvements.

bullsei commented 2 weeks ago

will close this for now since its implemented in the latest release version. Feel free to create a new issue for more suggestions or bug reports.

marco-vrinssen commented 2 weeks ago

Many, many thanks for the quick update and the excellent work; it looks great and has been working very well so far. One small issue is that in the General DR settings, when the upper section is expanded, the lower section is no longer accessible. Otherwise, everything is perfect so far, thank you!

bullsei commented 2 weeks ago

Hey, so it should still be accessible, it should be possible to scroll down. I will test this out

marco-vrinssen commented 2 weeks ago

To be precise, after resetting my settings from the previous version, I noticed that this issue affects all subareas (see highlighted in red in the attachment). Perhaps the dropdowns for managing and applying default or preset configurations could be made more compact and placed directly next to the enable checkbox, to create more space in the lower area.

Attachments: Small Sub-Container

marco-vrinssen commented 2 weeks ago

Here’s an example in the DR module where resolving the issue might be challenging. A practical solution could be implementing a global scroll container that integrates the enabled toggle, “Jump to General Options,” and “Restore Defaults” buttons alongside the module-specific settings, avoiding the complexity of nested scroll containers.

Attachments: Sub-Container Issue DR Module

bullsei commented 2 weeks ago

yeah, thats a bit of a problem with sizing, i think for the Dr tracking i will just move that section to choose own category icons to a new tab. I decresed the length of the copy settings from and restore defaults under the enable button so it will fit in one line with it. But i guess there is always the option to resize the whole options panel in the bottom right corner too. Edit: 11.0.5.5 moves the dr category icon selection into its own tab

marco-vrinssen commented 2 weeks ago

Thank you for the quick update and fix. Unfortunately, the problem persists in other categories. I assume it’s already on your roadmap, but I just wanted to confirm. I’m happy to provide additional screenshots if the issue doesn’t appear on your end, possibly due to different resolution or screen size, if that impacts the options panel layout.

bullsei commented 2 weeks ago

image for comparison this is how it looks like for me with the default options panel size. i feel like there can't be much more done than that. https://github.com/BullseiWoWAddons/BattleGroundEnemies/issues/48#issuecomment-2478573523 in comparison your screenshot it looks like the window for you is not that wide?

marco-vrinssen commented 1 week ago

It primarily affects the module-specific settings in the module section. example 3 example 2 example 1

marco-vrinssen commented 1 week ago

I think the size of the option panel might depend on the resolution. I play in Full HD. Do you play in 2K? This could be the issue.

bullsei commented 1 week ago

i am currenly using 5120 x 1440, but i mostly play in window mode, not fullscreen,

bullsei commented 5 days ago

So yeah, i see the issue when i try to use it on smaller screens, i was also thinking about adding a slider for the options panel scale to make it smaller this would mean the text and buttons got smaller but maybe that would be a good option to make it more usuable without having too short/unusable scroll frames when trying to size it to screen size. I will play around it using lower resolutions and see what would be a good solution