---------------------------------------------------- -- SamUI v0.3.3 -- Eine vollständige UI-Alternative -- WoW Classic Anniversary TBC -- Interface 20506 -- Build 69110 ---------------------------------------------------- local _, ns = ... local module = ns:new_module("auras") local player_buff_icon_size = 30 local player_buff_spacing = 10 local player_buffs_per_row = 10 local player_buff_row_count = 4 local player_buff_maximum = player_buffs_per_row * player_buff_row_count local player_buff_anchor_offset_x = 10 local player_buff_anchor_offset_y = 2 local player_buff_duration_font_size = 10 local player_buff_duration_gap = 2 local player_buff_duration_update_interval = 0.1 local player_buffs_enabled = true local player_buff_header local chat_frame local blizzard_buff_frame local pending_player_buffs_enabled local missing_chat_frame_reported = false local missing_buff_frame_reported = false local missing_aura_api_reported = false local missing_weapon_enchant_api_reported = false local missing_buff_border_profile_reported = false local player_buff_header_failed = false local player_buff_header_error_reported = false local player_buff_duration_elapsed = 0 local button_states = setmetatable({}, { __mode = "k" }) local blizzard_visibility_hooks = setmetatable({}, { __mode = "k" }) local function has_player_aura_api() return type(C_UnitAuras) == "table" and type(C_UnitAuras.GetAuraDataByIndex) == "function" end local function has_weapon_enchant_api() return type(GetWeaponEnchantInfo) == "function" and type(GetInventoryItemTexture) == "function" and type(GetInventorySlotInfo) == "function" end local function hide_blizzard_frame_on_show(frame) if not player_buffs_enabled or not frame then return end if InCombatLockdown() and type(frame.IsProtected) == "function" and frame:IsProtected() then return end frame:Hide() end local function ensure_blizzard_visibility_hook(frame) if not frame or blizzard_visibility_hooks[frame] or type(frame.HookScript) ~= "function" then return end frame:HookScript("OnShow", hide_blizzard_frame_on_show) blizzard_visibility_hooks[frame] = true end local function get_temporary_enchant_frame_count() if type(NUM_TEMP_ENCHANT_FRAMES) == "number" and NUM_TEMP_ENCHANT_FRAMES > 0 then return NUM_TEMP_ENCHANT_FRAMES end return 3 end local function resolve_blizzard_temporary_enchant_objects() local temporary_enchant_frame = _G.TemporaryEnchantFrame ensure_blizzard_visibility_hook(temporary_enchant_frame) local maximum = get_temporary_enchant_frame_count() for index = 1, maximum do ensure_blizzard_visibility_hook(_G["TempEnchant" .. index]) end return temporary_enchant_frame, maximum end local function resolve_blizzard_frames(report_missing) chat_frame = _G.ChatFrame1 blizzard_buff_frame = _G.BuffFrame ensure_blizzard_visibility_hook(blizzard_buff_frame) resolve_blizzard_temporary_enchant_objects() if report_missing and not chat_frame and not missing_chat_frame_reported then missing_chat_frame_reported = true ns:chat_message("ChatFrame1 konnte für die SamUI-Buffs nicht aufgelöst werden", true) end if report_missing and not blizzard_buff_frame and not missing_buff_frame_reported then missing_buff_frame_reported = true ns:chat_message("Blizzards BuffFrame konnte nicht aufgelöst werden", true) end return chat_frame ~= nil, blizzard_buff_frame ~= nil end local function get_player_buff_border_offset() local designelemente = ns.designelemente local border_offset = designelemente and designelemente:get_border_offset("buffs_player") or nil if type(border_offset) ~= "number" and not missing_buff_border_profile_reported then missing_buff_border_profile_reported = true ns:chat_message("Border-Profil 'buffs_player' ist für die SamUI-Buffs nicht verfügbar", true) end return border_offset end local function get_player_buff_row_stride() local border_offset = get_player_buff_border_offset() if type(border_offset) ~= "number" then return nil end return player_buff_icon_size + (border_offset * 2) + player_buff_duration_font_size + (player_buff_duration_gap * 2) end local function format_player_buff_duration(seconds) if type(seconds) ~= "number" or seconds <= 0 then return "" end if seconds >= 3600 then return string.format("%dh", math.ceil(seconds / 3600)) elseif seconds >= 60 then return string.format("%dm", math.ceil(seconds / 60)) end return string.format("%ds", math.ceil(seconds)) end local function set_duration_text(state, seconds) if type(state) ~= "table" or not state.duration_text then return false end local text = format_player_buff_duration(seconds) if text == "" then state.duration_text:SetText("") state.duration_text:Hide() else state.duration_text:SetText(text) state.duration_text:Show() end return true end local function apply_player_buff_button_palette(state) if type(state) ~= "table" then return false end local palette = ns.palette if state.background then state.background:SetColorTexture(palette:get_surface("button")) end if state.border then state.border:SetBackdropBorderColor( palette:get_rgba("border", palette:get_alpha("opaque")) ) end if state.count_text then state.count_text:SetTextColor(palette:get_rgb("text")) end if state.duration_text then state.duration_text:SetTextColor(palette:get_rgb("text")) end return true end local function create_player_buff_button_state(button) local state = button_states[button] if state then return state end local designelemente = ns.designelemente local border_offset = get_player_buff_border_offset() if not designelemente or type(border_offset) ~= "number" then return nil end local background = button:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(button) local icon = button:CreateTexture(nil, "ARTWORK") icon:SetAllPoints(button) local border = designelemente:create_border_overlay( button, button, "buffs_player" ) if not border then return nil end button:SetPushedTexture(designelemente:get_texture("action_button_pushed")) button:SetHighlightTexture(designelemente:get_texture("action_button_highlight")) local highlight_texture = button:GetHighlightTexture() if highlight_texture then highlight_texture:SetBlendMode("ADD") end local cooldown = CreateFrame("Cooldown", nil, button, "CooldownFrameTemplate") cooldown:SetAllPoints(button) cooldown:EnableMouse(false) if type(cooldown.SetHideCountdownNumbers) == "function" then cooldown:SetHideCountdownNumbers(true) end local count_text = button:CreateFontString(nil, "OVERLAY") count_text:SetFontObject(samui_font_11) count_text:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -2, 2) count_text:SetJustifyH("RIGHT") local duration_text = button:CreateFontString(nil, "OVERLAY") duration_text:SetFontObject(samui_font_10) duration_text:SetPoint( "TOP", button, "BOTTOM", 0, -(border_offset + player_buff_duration_gap) ) duration_text:SetSize( player_buff_icon_size + (border_offset * 2), player_buff_duration_font_size ) duration_text:SetJustifyH("CENTER") duration_text:SetJustifyV("TOP") duration_text:Hide() state = { background = background, icon = icon, border = border, cooldown = cooldown, count_text = count_text, duration_text = duration_text, duration_kind = nil, expiration_time = 0, } button_states[button] = state apply_player_buff_button_palette(state) return state end local function get_player_buff_aura_data(aura_index) if not has_player_aura_api() then return nil end local success, aura_data = pcall( C_UnitAuras.GetAuraDataByIndex, "player", aura_index, "HELPFUL" ) if not success or type(aura_data) ~= "table" then return nil end return aura_data end local function update_player_buff_button(button) local aura_index = button:GetID() if type(aura_index) ~= "number" or aura_index < 1 then return false end local aura_data = get_player_buff_aura_data(aura_index) if not aura_data then return false end local state = create_player_buff_button_state(button) if not state then return false end state.duration_kind = "aura" state.icon:SetTexture(aura_data.icon) local applications = type(aura_data.applications) == "number" and aura_data.applications or 0 if applications > 1 then state.count_text:SetText(applications) else state.count_text:SetText("") end local duration = type(aura_data.duration) == "number" and aura_data.duration or 0 local expiration_time = type(aura_data.expirationTime) == "number" and aura_data.expirationTime or 0 local time_mod = type(aura_data.timeMod) == "number" and aura_data.timeMod or 1 state.expiration_time = expiration_time if duration > 0 and expiration_time > 0 then state.cooldown:SetCooldown(expiration_time - duration, duration, time_mod) set_duration_text(state, expiration_time - GetTime()) else state.cooldown:SetCooldown(0, 0) set_duration_text(state, nil) end return true end local function get_weapon_enchant_data_for_slot(slot_id) if not has_weapon_enchant_api() then return nil end local main_hand_slot = GetInventorySlotInfo("MainHandSlot") local off_hand_slot = GetInventorySlotInfo("SecondaryHandSlot") local enchant_info = { GetWeaponEnchantInfo() } local has_main_hand_enchant = enchant_info[1] local main_hand_expiration = enchant_info[2] local has_off_hand_enchant = enchant_info[5] local off_hand_expiration = enchant_info[6] if slot_id == main_hand_slot and has_main_hand_enchant then return main_hand_expiration end if slot_id == off_hand_slot and has_off_hand_enchant then return off_hand_expiration end return nil end local function update_player_weapon_buff_button(button) local slot_id = button:GetID() if type(slot_id) ~= "number" or slot_id < 1 then return false end local expiration_ms = get_weapon_enchant_data_for_slot(slot_id) if type(expiration_ms) ~= "number" then return false end local state = create_player_buff_button_state(button) if not state then return false end state.duration_kind = "weapon" state.expiration_time = 0 state.icon:SetTexture(GetInventoryItemTexture("player", slot_id)) state.cooldown:SetCooldown(0, 0) state.count_text:SetText("") set_duration_text(state, expiration_ms / 1000) return true end local function refresh_player_aura_buttons() if not player_buffs_enabled or not player_buff_header or not has_player_aura_api() then return false end for index = 1, player_buff_maximum do local button = player_buff_header:GetAttribute("child" .. index) if not button then break end if button:IsShown() then update_player_buff_button(button) end end return true end local function refresh_player_weapon_buff_buttons() if not player_buffs_enabled or not player_buff_header or not has_weapon_enchant_api() then return false end for index = 1, 2 do local button = player_buff_header:GetAttribute("tempEnchant" .. index) if button and button:IsShown() then update_player_weapon_buff_button(button) end end return true end local function refresh_player_buff_durations() if not player_buffs_enabled then return false end for button, state in pairs(button_states) do if button:IsShown() then if state.duration_kind == "aura" then if type(state.expiration_time) == "number" and state.expiration_time > 0 then set_duration_text(state, state.expiration_time - GetTime()) else set_duration_text(state, nil) end end else set_duration_text(state, nil) end end return true end local function refresh_player_buff_buttons() refresh_player_aura_buttons() refresh_player_weapon_buff_buttons() refresh_player_buff_durations() end local function set_frame_shown(frame, shown) if not frame then return false end local currently_shown = frame:IsShown() if (shown and currently_shown) or (not shown and not currently_shown) then return true end if InCombatLockdown() and type(frame.IsProtected) == "function" and frame:IsProtected() then return false end if shown then frame:Show() else frame:Hide() end return true end local function hide_blizzard_temporary_enchant_buttons() local temporary_enchant_frame, maximum = resolve_blizzard_temporary_enchant_objects() if temporary_enchant_frame and temporary_enchant_frame:IsShown() then set_frame_shown(temporary_enchant_frame, false) end for index = 1, maximum do local button = _G["TempEnchant" .. index] if button and button:IsShown() then set_frame_shown(button, false) end local duration = _G["TempEnchant" .. index .. "Duration"] if duration and duration:IsShown() then duration:Hide() end end end local function hide_blizzard_player_buffs() if not blizzard_buff_frame then resolve_blizzard_frames(false) end ensure_blizzard_visibility_hook(blizzard_buff_frame) local buff_frame_applied = blizzard_buff_frame and set_frame_shown(blizzard_buff_frame, false) or false hide_blizzard_temporary_enchant_buttons() return buff_frame_applied end local function show_blizzard_player_buffs() if not blizzard_buff_frame then resolve_blizzard_frames(false) end local buff_frame_applied = blizzard_buff_frame and set_frame_shown(blizzard_buff_frame, true) or false local temporary_enchant_frame = _G.TemporaryEnchantFrame if temporary_enchant_frame then set_frame_shown(temporary_enchant_frame, true) end return buff_frame_applied end local function show_player_buff_header() if not player_buff_header or player_buff_header_failed then return false end if player_buff_header:IsShown() then return true end if InCombatLockdown() and type(player_buff_header.IsProtected) == "function" and player_buff_header:IsProtected() then return false end local success = pcall(player_buff_header.Show, player_buff_header) if success then return true end player_buff_header_failed = true pcall(player_buff_header.Hide, player_buff_header) if not player_buff_header_error_reported then player_buff_header_error_reported = true ns:chat_message( "Die sichere SamUI-Buffvorlage konnte nicht initialisiert werden; Blizzard-Buffs bleiben aktiv", true ) end return false end local function player_buff_header_on_event(_, event, unit) if event ~= "UNIT_AURA" or (unit and unit ~= "player") then return end if not player_buffs_enabled then return end if player_buff_header_failed or not player_buff_header or not player_buff_header:IsShown() then show_blizzard_player_buffs() return end hide_blizzard_player_buffs() refresh_player_aura_buttons() end local function player_buff_header_on_update(_, elapsed) if not player_buffs_enabled then return end player_buff_duration_elapsed = player_buff_duration_elapsed + elapsed if player_buff_duration_elapsed < player_buff_duration_update_interval then return end player_buff_duration_elapsed = 0 hide_blizzard_player_buffs() refresh_player_weapon_buff_buttons() refresh_player_buff_durations() end local function create_player_buff_header() if player_buff_header then return true end if not chat_frame or InCombatLockdown() then return false end local row_stride = get_player_buff_row_stride() if type(row_stride) ~= "number" then return false end local header = CreateFrame( "Frame", "samui_player_buff_header", UIParent, "SecureAuraHeaderTemplate" ) header:Hide() header:SetPoint( "TOPLEFT", chat_frame, "TOPRIGHT", player_buff_anchor_offset_x, player_buff_anchor_offset_y ) header:SetAttribute("template", "samui_player_buff_button_template") header:SetAttribute("weaponTemplate", "samui_player_weapon_buff_button_template") header:SetAttribute("filter", "HELPFUL") header:SetAttribute("sortMethod", "INDEX") header:SetAttribute("sortDirection", "+") header:SetAttribute("separateOwn", 0) header:SetAttribute("includeWeapons", 1) header:SetAttribute("point", "TOPLEFT") header:SetAttribute("xOffset", player_buff_icon_size + player_buff_spacing) header:SetAttribute("yOffset", 0) header:SetAttribute("wrapAfter", player_buffs_per_row) header:SetAttribute("wrapXOffset", 0) header:SetAttribute("wrapYOffset", -row_stride) header:SetAttribute("maxWraps", player_buff_row_count) header:SetAttribute("minWidth", player_buff_icon_size) header:SetAttribute("minHeight", player_buff_icon_size) header:HookScript("OnEvent", player_buff_header_on_event) header:HookScript("OnUpdate", player_buff_header_on_update) header:SetAttribute("unit", "player") player_buff_header = header return true end local function apply_player_buffs_enabled(enabled) player_buffs_enabled = enabled == true resolve_blizzard_frames(true) if not player_buffs_enabled then local header_applied = not player_buff_header or set_frame_shown(player_buff_header, false) local blizzard_applied = show_blizzard_player_buffs() if not header_applied or not blizzard_applied then pending_player_buffs_enabled = false return false end pending_player_buffs_enabled = nil return true end if not has_player_aura_api() then if not missing_aura_api_reported then missing_aura_api_reported = true ns:chat_message( "C_UnitAuras.GetAuraDataByIndex ist für die SamUI-Buffs nicht verfügbar", true ) end show_blizzard_player_buffs() return false end if not has_weapon_enchant_api() then if not missing_weapon_enchant_api_reported then missing_weapon_enchant_api_reported = true ns:chat_message( "Waffenverzauberungs-API ist für die SamUI-Buffs nicht verfügbar", true ) end show_blizzard_player_buffs() return false end if not chat_frame then show_blizzard_player_buffs() return false end if not player_buff_header then if InCombatLockdown() then pending_player_buffs_enabled = true return false end if not create_player_buff_header() then show_blizzard_player_buffs() return false end end local header_applied = show_player_buff_header() if not header_applied then show_blizzard_player_buffs() pending_player_buffs_enabled = nil return false end local blizzard_applied = hide_blizzard_player_buffs() if not blizzard_applied then pending_player_buffs_enabled = true return false end pending_player_buffs_enabled = nil refresh_player_buff_buttons() return true end local function on_event(_, event) if event == "PLAYER_ENTERING_WORLD" then resolve_blizzard_frames(true) apply_player_buffs_enabled(player_buffs_enabled) return end if event == "PLAYER_REGEN_DISABLED" then if player_buffs_enabled then hide_blizzard_player_buffs() end return end if event == "PLAYER_REGEN_ENABLED" then if player_buffs_enabled then hide_blizzard_player_buffs() end if pending_player_buffs_enabled ~= nil then local pending_enabled = pending_player_buffs_enabled pending_player_buffs_enabled = nil apply_player_buffs_enabled(pending_enabled) end end end function ns:set_player_buffs_enabled(enabled) if type(enabled) ~= "boolean" then return false end player_buffs_enabled = enabled if InCombatLockdown() then pending_player_buffs_enabled = enabled if not enabled then show_blizzard_player_buffs() end return false end return apply_player_buffs_enabled(enabled) end function ns:get_player_buffs_enabled() return player_buffs_enabled end function module:apply_palette() for _, state in pairs(button_states) do apply_player_buff_button_palette(state) end end function module:initialize() ns:register_event("PLAYER_ENTERING_WORLD", self, on_event) ns:register_event("PLAYER_REGEN_DISABLED", self, on_event) ns:register_event("PLAYER_REGEN_ENABLED", self, on_event) ns.palette:register_callback("auras", function() module:apply_palette() end) end ns:register_module("auras")