---------------------------------------------------- -- SamUI v0.3.3 -- Eine vollständige UI-Alternative -- WoW Classic Anniversary TBC -- Interface 20506 -- Build 69110 ---------------------------------------------------- local _, ns = ... local module = ns:new_module("leisten") local bar_height = 14 local bar_border_gap = 5 local system_update_interval = 1 local zone_text_horizontal_padding = 16 local zone_text_vertical_padding = 8 local ui_message_horizontal_padding = 16 local ui_message_vertical_padding = 8 local ui_message_spacing = 10 local ui_message_max_visible = 4 local setting_order = { "experience_enabled", "reputation_enabled", "system_enabled", } local default_settings = { experience_enabled = true, reputation_enabled = true, system_enabled = true, } local native_bar_kinds = setmetatable({}, { __mode = "k" }) local native_container_states = setmetatable({}, { __mode = "k" }) local hooked_native_containers = setmetatable({}, { __mode = "k" }) local hooked_zone_frames = setmetatable({}, { __mode = "k" }) local hooked_ui_error_frames = setmetatable({}, { __mode = "k" }) local mirror_timer_states = setmetatable({}, { __mode = "k" }) local native_manager_hooked = false local zone_text_clear_hooked = false local mirror_timer_show_hooked = false local function copy_default_settings() local settings = {} for index = 1, #setting_order do local key = setting_order[index] settings[key] = default_settings[key] end return settings end local function report_missing_frame(key, label) module.missing_frame_reports = module.missing_frame_reports or {} if module.missing_frame_reports[key] then return end module.missing_frame_reports[key] = true ns:chat_message("Blizzard-Element '" .. label .. "' konnte nicht ermittelt werden", true) end local function append_tracking_bar(target, bar) if bar then target[#target + 1] = bar end end local function resolve_blizzard_frames(report_missing) local manager = _G.StatusTrackingBarManager local bar_info = _G.StatusTrackingBarInfo local bars_enum = type(bar_info) == "table" and bar_info.BarsEnum or nil local frames = { manager = manager, containers = {}, experience = {}, reputation = {}, mirror_timers = {}, zone_frame = _G.ZoneTextFrame, subzone_frame = _G.SubZoneTextFrame, zone_text_string = _G.ZoneTextString, subzone_text_string = _G.SubZoneTextString, pvp_info_text_string = _G.PVPInfoTextString, pvp_arena_text_string = _G.PVPArenaTextString, ui_errors_frame = _G.UIErrorsFrame, } if manager and type(bars_enum) == "table" then local containers = { manager.MainStatusTrackingBarContainer, manager.SecondaryStatusTrackingBarContainer, } for index = 1, #containers do local container = containers[index] if container then frames.containers[#frames.containers + 1] = container end if container and type(container.bars) == "table" then append_tracking_bar(frames.experience, container.bars[bars_enum.Experience]) append_tracking_bar(frames.reputation, container.bars[bars_enum.Reputation]) end end end local mirror_timer_count = type(_G.MIRRORTIMER_NUMTIMERS) == "number" and _G.MIRRORTIMER_NUMTIMERS or 3 for index = 1, mirror_timer_count do local frame_name = "MirrorTimer" .. index local frame = _G[frame_name] local statusbar = _G[frame_name .. "StatusBar"] local text = _G[frame_name .. "Text"] if frame and statusbar and text then frames.mirror_timers[#frames.mirror_timers + 1] = { frame = frame, statusbar = statusbar, text = text, } end end local experience_frame = frames.experience[1] local reputation_frame = frames.reputation[1] frames.experience_status = experience_frame and experience_frame.StatusBar or nil frames.reputation_status = reputation_frame and reputation_frame.StatusBar or nil module.blizzard_frames = frames if report_missing then if #frames.experience == 0 then report_missing_frame("experience", "Blizzard-Erfahrungsleiste") end if #frames.reputation == 0 then report_missing_frame("reputation", "Blizzard-Rufleiste") end if #frames.mirror_timers == 0 then report_missing_frame("mirror_timer", "Blizzard-Atemleiste") end if not frames.zone_frame or not frames.subzone_frame or not frames.zone_text_string or not frames.subzone_text_string then report_missing_frame("zone_text", "Blizzard-Zonentext") end if not frames.ui_errors_frame then report_missing_frame("ui_errors", "Blizzard-Fehlermeldungen") end end return frames end local function resolve_minimap_environment() local minimap_module = ns.module_registry and ns.module_registry["minimap"] or nil local minimap = _G.Minimap local bag_bar = minimap_module and minimap_module.minimap_bag_bar or nil local top_bar = minimap_module and minimap_module.top_bar or nil if not minimap then return nil end return minimap, bag_bar, top_bar end local function get_stack_gap(minimap, top_bar) if top_bar and type(top_bar.GetPoint) == "function" then local _, relative_to, _, _, offset_y = top_bar:GetPoint(1) if relative_to == minimap and type(offset_y) == "number" then local gap = math.abs(offset_y) if gap > 0 then return gap end end end local border_offset = ns.designelemente and ns.designelemente:get_border_offset("frame") or 0 return bar_border_gap + ((border_offset or 0) * 2) end local function can_modify_foreign_frame(frame) if not frame then return false end if type(frame.IsProtected) == "function" and frame:IsProtected() and InCombatLockdown() then module.pending_native_visibility = true return false end return true end local function can_modify_mirror_timer(frame, statusbar) if not frame or not statusbar then return false end if not InCombatLockdown() then return true end local frame_protected = type(frame.IsProtected) == "function" and frame:IsProtected() local statusbar_protected = type(statusbar.IsProtected) == "function" and statusbar:IsProtected() if frame_protected or statusbar_protected then module.pending_mirror_timer_skin = true return false end return true end local function get_watched_faction_data() if type(C_Reputation) ~= "table" or type(C_Reputation.GetWatchedFactionData) ~= "function" then return nil end local data = C_Reputation.GetWatchedFactionData() if type(data) ~= "table" or type(data.name) ~= "string" or data.name == "" then return nil end return data end local function get_native_bar_kind(bar) return bar and native_bar_kinds[bar] or nil end local function get_runtime_setting(key) local profile_settings if type(ns.get_active_profile_section) == "function" then profile_settings = ns:get_active_profile_section("zusatzleisten") end if type(profile_settings) == "table" then local value = profile_settings[key] if type(value) == "boolean" then module.settings[key] = value return value end end return module.settings[key] == true end local function should_hide_native_kind(kind) if kind == "experience" then return get_runtime_setting("experience_enabled") elseif kind == "reputation" then return get_runtime_setting("reputation_enabled") end return false end local function get_native_container_state(container) local state = native_container_states[container] if not state then state = { hidden_by_samui = false } native_container_states[container] = state end return state end local function hide_native_container(container) if not container or not can_modify_foreign_frame(container) then return false end local state = get_native_container_state(container) state.hidden_by_samui = true container:Hide() return true end local function restore_native_container(container) local state = native_container_states[container] if not container or not state or not state.hidden_by_samui then return false end if not can_modify_foreign_frame(container) then return false end state.hidden_by_samui = false local shown_bar = type(container.GetShownBar) == "function" and container:GetShownBar() or nil if not shown_bar then return true end if type(shown_bar.UpdateAll) == "function" then shown_bar:UpdateAll() end if type(container.FadeIn) == "function" then container:FadeIn() else container:Show() end return true end local function apply_native_container_visibility(container) if not container then return false end local shown_bar = type(container.GetShownBar) == "function" and container:GetShownBar() or nil local kind = get_native_bar_kind(shown_bar) if should_hide_native_kind(kind) then return hide_native_container(container) end restore_native_container(container) return true end local function hook_native_container(container) if not container or hooked_native_containers[container] then return end hooked_native_containers[container] = true if type(container.HookScript) == "function" then container:HookScript("OnShow", function(shown_container) local shown_bar = type(shown_container.GetShownBar) == "function" and shown_container:GetShownBar() or nil if should_hide_native_kind(get_native_bar_kind(shown_bar)) then hide_native_container(shown_container) end end) end end local function hide_native_zone_text() local frames = module.blizzard_frames or resolve_blizzard_frames(false) if not frames then return false end local text_regions = { frames.zone_text_string, frames.subzone_text_string, frames.pvp_info_text_string, frames.pvp_arena_text_string, } local hidden_any = false for index = 1, #text_regions do local text_region = text_regions[index] if text_region and type(text_region.Hide) == "function" then text_region:Hide() hidden_any = true end end return hidden_any end local function hide_zone_popup() module.zone_popup_source = nil if module.zone_popup_frame then module.zone_popup_frame:Hide() end end local function resize_zone_popup(text_value) local frame = module.zone_popup_frame local text = module.zone_popup_text if not frame or not text then return false end text:SetText(text_value) local text_width = text:GetStringWidth() or 0 local text_height = text:GetStringHeight() or 0 frame:SetSize( text_width + zone_text_horizontal_padding * 2, text_height + zone_text_vertical_padding * 2 ) return true end local function show_zone_popup_from_blizzard(event) local frames = module.blizzard_frames or resolve_blizzard_frames(false) local frame = module.zone_popup_frame if not frames or not frame then return false end hide_native_zone_text() local zone_text = frames.zone_text_string and frames.zone_text_string:GetText() or "" local subzone_text = frames.subzone_text_string and frames.subzone_text_string:GetText() or "" local zone_changed = event == "ZONE_CHANGED_NEW_AREA" or zone_text ~= module.last_zone_text module.last_zone_text = zone_text local source_frame local source_text_region local display_text if zone_changed and zone_text ~= "" then source_frame = frames.zone_frame source_text_region = frames.zone_text_string display_text = zone_text elseif subzone_text ~= "" then source_frame = frames.subzone_frame source_text_region = frames.subzone_text_string display_text = subzone_text elseif zone_text ~= "" then source_frame = frames.zone_frame source_text_region = frames.zone_text_string display_text = zone_text end if not source_frame or not display_text or display_text == "" then hide_zone_popup() return false end resize_zone_popup(display_text) if source_text_region and type(source_text_region.GetTextColor) == "function" then local red, green, blue, alpha = source_text_region:GetTextColor() module.zone_popup_text:SetTextColor(red, green, blue, alpha or 1) end module.zone_popup_source = source_frame frame:SetAlpha(source_frame:GetAlpha()) frame:Show() return true end local function hook_blizzard_zone_text() local frames = module.blizzard_frames or resolve_blizzard_frames(false) local zone_frame = frames and frames.zone_frame or nil if not zone_frame then return false end hide_native_zone_text() if not hooked_zone_frames[zone_frame] and type(zone_frame.HookScript) == "function" then hooked_zone_frames[zone_frame] = true zone_frame:HookScript("OnEvent", function(_, event) show_zone_popup_from_blizzard(event) end) end if not zone_text_clear_hooked and type(_G.ZoneText_Clear) == "function" and type(hooksecurefunc) == "function" then zone_text_clear_hooked = true hooksecurefunc("ZoneText_Clear", hide_zone_popup) end return true end local function create_zone_popup(parent, anchor_frame) local frame = CreateFrame("Frame", nil, parent) frame:SetFrameStrata("LOW") frame:SetPoint("CENTER", anchor_frame, "CENTER", 0, 0) frame:EnableMouse(false) local background = frame:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(frame) background:SetTexture(ns.designelemente:get_texture("statusbar_background")) background:SetVertexColor(ns.palette:get_surface("unit_bar")) local text = frame:CreateFontString(nil, "OVERLAY") text:SetFontObject(samui_font_22) text:SetPoint("CENTER", frame, "CENTER", 0, 0) text:SetJustifyH("CENTER") text:SetTextColor(ns.palette:get_rgb("text")) local border = ns.designelemente:create_border_overlay( frame, frame, "frame", "border" ) frame:SetScript("OnUpdate", function(self) local source_frame = module.zone_popup_source if source_frame and source_frame:IsShown() then self:SetAlpha(source_frame:GetAlpha()) else hide_zone_popup() end end) frame:Hide() return frame, background, border, text end local function build_message_pattern(format_text) if type(format_text) ~= "string" or format_text == "" then return nil end local placeholder = "\001" local pattern = format_text:gsub("%%[%d%$%.%-%+]*[sd]", placeholder) pattern = pattern:gsub("([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") pattern = pattern:gsub(placeholder, ".+") return "^" .. pattern .. "$" end local function is_discovery_message(text_value) if type(text_value) ~= "string" or text_value == "" then return false end if not module.discovery_patterns then module.discovery_patterns = {} local explored_pattern = build_message_pattern(_G.ERR_ZONE_EXPLORED) local explored_xp_pattern = build_message_pattern(_G.ERR_ZONE_EXPLORED_XP) if explored_pattern then module.discovery_patterns[#module.discovery_patterns + 1] = explored_pattern end if explored_xp_pattern then module.discovery_patterns[#module.discovery_patterns + 1] = explored_xp_pattern end end for index = 1, #module.discovery_patterns do local pattern = module.discovery_patterns[index] if pattern and text_value:match(pattern) then return true end end return false end local function resize_ui_message_popup(popup, text_value) if not popup or not popup.frame or not popup.text then return false end popup.text:SetText(text_value) local text_width = popup.text:GetStringWidth() or 0 local text_height = popup.text:GetStringHeight() or 0 popup.frame:SetSize( text_width + ui_message_horizontal_padding * 2, text_height + ui_message_vertical_padding * 2 ) return true end local function layout_ui_message_pool(pool, anchor_frame) if not pool or not anchor_frame then return end local shown = {} for index = 1, #pool do local popup = pool[index] if popup.frame:IsShown() then shown[#shown + 1] = popup end end table.sort(shown, function(left, right) return (left.sequence or 0) > (right.sequence or 0) end) local offset = 0 for index = 1, #shown do local popup = shown[index] popup.frame:ClearAllPoints() popup.frame:SetPoint("TOP", anchor_frame, "TOP", 0, -offset) offset = offset + popup.frame:GetHeight() + ui_message_spacing end end local function get_ui_message_timing(ui_errors_frame) local total_time = 2 local fade_duration = 1 if ui_errors_frame and type(ui_errors_frame.GetTimeVisible) == "function" then local native_total = ui_errors_frame:GetTimeVisible() if type(native_total) == "number" and native_total > 0 then total_time = native_total end end if ui_errors_frame and type(ui_errors_frame.GetFadeDuration) == "function" then local native_fade = ui_errors_frame:GetFadeDuration() if type(native_fade) == "number" and native_fade > 0 then fade_duration = math.min(native_fade, total_time) end end return math.max(total_time - fade_duration, 0), fade_duration end local function create_ui_message_popup(parent) local frame = CreateFrame("Frame", nil, parent) frame:SetFrameStrata("HIGH") frame:EnableMouse(false) local background = frame:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(frame) background:SetTexture(ns.designelemente:get_texture("statusbar_background")) background:SetVertexColor(ns.palette:get_surface("unit_bar")) local text = frame:CreateFontString(nil, "OVERLAY") text:SetFontObject(samui_font_20) text:SetPoint("CENTER", frame, "CENTER", 0, 0) text:SetJustifyH("CENTER") local border = ns.designelemente:create_border_overlay( frame, frame, "frame", "border" ) local popup = { frame = frame, background = background, border = border, text = text, } frame:SetScript("OnUpdate", function(self, elapsed) popup.elapsed = (popup.elapsed or 0) + elapsed local hold_time = popup.hold_time or 0 local fade_duration = popup.fade_duration or 0 local total_time = hold_time + fade_duration if popup.elapsed >= total_time then self:Hide() local frames = module.blizzard_frames local anchor_frame = frames and frames.ui_errors_frame or nil layout_ui_message_pool(popup.pool, anchor_frame) return end if popup.elapsed <= hold_time or fade_duration <= 0 then self:SetAlpha(1) else self:SetAlpha(1 - ((popup.elapsed - hold_time) / fade_duration)) end end) frame:Hide() return popup end local function create_ui_message_pool(parent) local pool = {} for index = 1, ui_message_max_visible do pool[index] = create_ui_message_popup(parent) pool[index].pool = pool end return pool end local function acquire_ui_message_popup(pool) local oldest_popup for index = 1, #pool do local popup = pool[index] if not popup.frame:IsShown() then return popup end if not oldest_popup or (popup.sequence or 0) < (oldest_popup.sequence or 0) then oldest_popup = popup end end return oldest_popup end local function show_ui_message_popup(text_value, red, green, blue, alpha) local frames = module.blizzard_frames or resolve_blizzard_frames(false) local ui_errors_frame = frames and frames.ui_errors_frame or nil if not ui_errors_frame or type(text_value) ~= "string" or text_value == "" then return false end local pool = is_discovery_message(text_value) and module.discovery_message_pool or module.error_message_pool if not pool then return false end local popup = acquire_ui_message_popup(pool) if not popup then return false end module.ui_message_sequence = (module.ui_message_sequence or 0) + 1 popup.sequence = module.ui_message_sequence popup.elapsed = 0 popup.hold_time, popup.fade_duration = get_ui_message_timing(ui_errors_frame) resize_ui_message_popup(popup, text_value) if type(red) == "number" and type(green) == "number" and type(blue) == "number" then popup.text:SetTextColor(red, green, blue, type(alpha) == "number" and alpha or 1) elseif type(ui_errors_frame.GetTextColor) == "function" then local native_red, native_green, native_blue, native_alpha = ui_errors_frame:GetTextColor() popup.text:SetTextColor(native_red, native_green, native_blue, native_alpha or 1) end popup.frame:SetAlpha(1) popup.frame:Show() layout_ui_message_pool(pool, ui_errors_frame) return true end local function hide_native_ui_errors() local frames = module.blizzard_frames or resolve_blizzard_frames(false) local ui_errors_frame = frames and frames.ui_errors_frame or nil if not ui_errors_frame or type(ui_errors_frame.SetAlpha) ~= "function" then return false end ui_errors_frame:SetAlpha(0) return true end local function hook_blizzard_ui_errors() local frames = module.blizzard_frames or resolve_blizzard_frames(false) local ui_errors_frame = frames and frames.ui_errors_frame or nil if not ui_errors_frame then return false end hide_native_ui_errors() if not module.error_message_pool and ns.palette and ns.designelemente then module.error_message_pool = create_ui_message_pool(UIParent) module.discovery_message_pool = create_ui_message_pool(UIParent) end if not hooked_ui_error_frames[ui_errors_frame] and type(hooksecurefunc) == "function" and type(ui_errors_frame.AddMessage) == "function" then hooked_ui_error_frames[ui_errors_frame] = true hooksecurefunc(ui_errors_frame, "AddMessage", function(_, text_value, red, green, blue, alpha) show_ui_message_popup(text_value, red, green, blue, alpha) end) end return true end local function apply_native_visibility() local frames = module.blizzard_frames or resolve_blizzard_frames(false) if not frames then return end module.pending_native_visibility = false for index = 1, #frames.experience do native_bar_kinds[frames.experience[index]] = "experience" end for index = 1, #frames.reputation do native_bar_kinds[frames.reputation[index]] = "reputation" end for index = 1, #frames.containers do local container = frames.containers[index] hook_native_container(container) apply_native_container_visibility(container) end end local function hook_native_manager() local manager = module.blizzard_frames and module.blizzard_frames.manager or nil if native_manager_hooked or not manager or type(manager.UpdateBarsShown) ~= "function" or type(hooksecurefunc) ~= "function" then return end native_manager_hooked = true hooksecurefunc(manager, "UpdateBarsShown", function() apply_native_visibility() end) end local function create_mirror_timer_state(entry) if not entry or not entry.frame or not entry.statusbar or not entry.text then return nil end local frame = entry.frame local statusbar = entry.statusbar local state = mirror_timer_states[frame] if state then return state end if not ns.palette or not ns.designelemente or not can_modify_mirror_timer(frame, statusbar) then return nil end local native_statusbar_texture = statusbar:GetStatusBarTexture() local native_texture = native_statusbar_texture and type(native_statusbar_texture.GetTexture) == "function" and native_statusbar_texture:GetTexture() or nil if native_texture == nil then report_missing_frame("mirror_timer_texture", "Blizzard-Atemleisten-Textur") return nil end local native_regions = {} for _, region in ipairs({ frame:GetRegions() }) do native_regions[#native_regions + 1] = { region = region, shown = type(region.IsShown) == "function" and region:IsShown() or false, } end local background = statusbar:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(statusbar) background:SetTexture(ns.designelemente:get_texture("statusbar_background")) background:SetVertexColor(ns.palette:get_surface("unit_bar")) background:Hide() local text = frame:CreateFontString(nil, "OVERLAY") text:SetFontObject(samui_font_9) text:SetPoint("CENTER", statusbar, "CENTER", 0, 0) text:SetJustifyH("CENTER") text:SetTextColor(ns.palette:get_rgb("text")) text:Hide() local border = ns.designelemente:create_border_overlay( statusbar, frame, "frame", "border" ) if border then border:Hide() end state = { statusbar = statusbar, native_text = entry.text, native_height = statusbar:GetHeight(), native_texture = native_texture, native_regions = native_regions, background = background, border = border, text = text, applied = false, } mirror_timer_states[frame] = state return state end local function set_mirror_timer_native_regions(state, restore) if not state or type(state.native_regions) ~= "table" then return end for index = 1, #state.native_regions do local region_state = state.native_regions[index] local region = region_state.region if region then if restore and region_state.shown then region:Show() else region:Hide() end end end end local function restore_mirror_timer_skin(frame) local state = frame and mirror_timer_states[frame] or nil if not state or not state.applied then return true end if not can_modify_mirror_timer(frame, state.statusbar) then return false end state.statusbar:SetHeight(state.native_height) state.statusbar:SetStatusBarTexture(state.native_texture) set_mirror_timer_native_regions(state, true) state.background:Hide() state.text:Hide() if state.border then state.border:Hide() end state.applied = false return true end local function apply_breath_timer_skin(entry) local state = create_mirror_timer_state(entry) if not state or not can_modify_mirror_timer(entry.frame, entry.statusbar) then return false end state.statusbar:SetHeight(bar_height) state.statusbar:SetStatusBarTexture(ns.designelemente:get_texture("statusbar")) set_mirror_timer_native_regions(state, false) state.background:Show() state.text:SetText(state.native_text:GetText() or "") state.text:Show() if state.border then state.border:Show() end state.applied = true return true end local function prepare_mirror_timer_states() local frames = module.blizzard_frames or resolve_blizzard_frames(false) if not frames then return false end local prepared = false for index = 1, #frames.mirror_timers do if create_mirror_timer_state(frames.mirror_timers[index]) then prepared = true end end return prepared end local function apply_mirror_timer_skins() local frames = module.blizzard_frames or resolve_blizzard_frames(false) if not frames then return false end module.pending_mirror_timer_skin = false local success = true for index = 1, #frames.mirror_timers do local entry = frames.mirror_timers[index] local frame = entry.frame if frame:IsShown() and frame.timer == "BREATH" then if not apply_breath_timer_skin(entry) then success = false end elseif not restore_mirror_timer_skin(frame) then success = false end end return success end local function hook_mirror_timer_show() if mirror_timer_show_hooked then return true end if type(_G.MirrorTimer_Show) ~= "function" or type(hooksecurefunc) ~= "function" then return false end mirror_timer_show_hooked = true hooksecurefunc("MirrorTimer_Show", function() apply_mirror_timer_skins() end) return true end local function create_progress_bar(parent, create_rested_overlay) local statusbar = CreateFrame("StatusBar", nil, parent) statusbar:SetHeight(bar_height) statusbar:SetFrameLevel(parent:GetFrameLevel() + 14) statusbar:SetStatusBarTexture(ns.designelemente:get_texture("statusbar")) local background = statusbar:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(statusbar) background:SetTexture(ns.designelemente:get_texture("statusbar_background")) background:SetVertexColor(ns.palette:get_surface("unit_bar")) local current_bar = statusbar if create_rested_overlay then current_bar = CreateFrame("StatusBar", nil, statusbar) current_bar:SetAllPoints(statusbar) current_bar:SetFrameLevel(statusbar:GetFrameLevel() + 1) current_bar:SetStatusBarTexture(ns.designelemente:get_texture("statusbar")) end local text = current_bar:CreateFontString(nil, "OVERLAY") text:SetFontObject(samui_font_9) text:SetPoint("CENTER", statusbar, "CENTER", 0, 0) text:SetJustifyH("CENTER") text:SetTextColor(ns.palette:get_rgb("text")) local border = ns.designelemente:create_border_overlay( statusbar, statusbar, "frame", "border" ) statusbar:Hide() return statusbar, background, border, text, current_bar end local function create_system_bar(parent) local frame = CreateFrame("Frame", nil, parent) frame:SetHeight(bar_height) frame:SetFrameLevel(parent:GetFrameLevel() + 14) local background = frame:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(frame) background:SetColorTexture(ns.palette:get_surface("button_deep")) local border = ns.designelemente:create_border_overlay( frame, frame, "frame", "border" ) local text = frame:CreateFontString(nil, "OVERLAY") text:SetFontObject(samui_font_11) text:SetPoint("CENTER", frame, "CENTER", 0, 0) text:SetJustifyH("CENTER") text:SetTextColor(ns.palette:get_rgb("text")) frame:Hide() return frame, background, border, text end local function update_experience_bar() local frame = module.experience_bar if not frame then return false end local maximum = UnitXPMax("player") or 0 local can_show_experience = maximum > 0 if type(GameRulesUtil) == "table" and type(GameRulesUtil.CanShowExperienceBar) == "function" then can_show_experience = GameRulesUtil.CanShowExperienceBar() == true end local should_show = get_runtime_setting("experience_enabled") and can_show_experience if not should_show then frame:Hide() return false end local current = UnitXP("player") or 0 local rested = type(GetXPExhaustion) == "function" and GetXPExhaustion() or nil local rested_total = current + (type(rested) == "number" and rested or 0) local clamped_current = math.min(math.max(current, 0), maximum) local clamped_rested_total = math.min(math.max(rested_total, clamped_current), maximum) frame:SetMinMaxValues(0, maximum) frame:SetValue(clamped_rested_total) local current_bar = module.experience_current_bar if current_bar then current_bar:SetMinMaxValues(0, maximum) current_bar:SetValue(clamped_current) end local native_status = module.blizzard_frames and module.blizzard_frames.experience_status or nil local r, g, b if native_status and type(native_status.GetStatusBarColor) == "function" then r, g, b = native_status:GetStatusBarColor() end if not r or not g or not b then r, g, b = frame:GetStatusBarColor() end if r and g and b then frame:SetStatusBarColor(r, g, b, ns.palette:get_alpha("rested_xp")) if current_bar then current_bar:SetStatusBarColor(r, g, b, ns.palette:get_alpha("opaque")) end end if module.experience_text then module.experience_text:SetText( math.floor(clamped_current + 0.5) .. " / " .. math.floor(maximum + 0.5) ) end frame:Show() return true end local function update_reputation_bar() local frame = module.reputation_bar if not frame then return false end local data = get_watched_faction_data() local standing = data and data.reaction or nil local minimum = data and data.currentReactionThreshold or nil local maximum = data and data.nextReactionThreshold or nil local value = data and data.currentStanding or nil if not get_runtime_setting("reputation_enabled") or not data or type(minimum) ~= "number" or type(maximum) ~= "number" or type(value) ~= "number" then frame:Hide() return false end if maximum <= minimum then if type(MAX_REPUTATION_REACTION) == "number" and standing == MAX_REPUTATION_REACTION then minimum = 0 maximum = 1 value = 1 else frame:Hide() return false end end local normalized_maximum = maximum - minimum local normalized_value = value - minimum local clamped_value = math.min(math.max(normalized_value, 0), normalized_maximum) frame:SetMinMaxValues(0, normalized_maximum) frame:SetValue(clamped_value) if module.reputation_text then module.reputation_text:SetText( math.floor(clamped_value + 0.5) .. " / " .. math.floor(normalized_maximum + 0.5) ) end local color = type(FACTION_BAR_COLORS) == "table" and FACTION_BAR_COLORS[standing] or nil if color then frame:SetStatusBarColor(color.r, color.g, color.b) else local native_status = module.blizzard_frames and module.blizzard_frames.reputation_status or nil if native_status and type(native_status.GetStatusBarColor) == "function" then local r, g, b = native_status:GetStatusBarColor() if r and g and b then frame:SetStatusBarColor(r, g, b) end end end frame:Show() return true end local function get_system_value_token(value, warning_threshold, critical_threshold, higher_is_better) if higher_is_better then if value < critical_threshold then return "red" elseif value < warning_threshold then return "gold" end else if value > critical_threshold then return "red" elseif value > warning_threshold then return "gold" end end return "text" end local function update_system_bar() local frame = module.system_bar if not frame then return false end if not get_runtime_setting("system_enabled") then frame:Hide() return false end local fps = type(GetFramerate) == "function" and GetFramerate() or 0 local home_latency = 0 local world_latency = 0 if type(GetNetStats) == "function" then local _, _, resolved_home_latency, resolved_world_latency = GetNetStats() home_latency = resolved_home_latency or 0 world_latency = resolved_world_latency or 0 end local latency = type(world_latency) == "number" and world_latency or home_latency or 0 local fps_value = math.floor((fps or 0) + 0.5) local latency_value = math.floor((latency or 0) + 0.5) local fps_token = get_system_value_token(fps or 0, 50, 25, true) local latency_token = get_system_value_token(latency or 0, 50, 100, false) module.system_text:SetText( "System: " .. ns.palette:wrap(fps_token, fps_value .. " FPS") .. " | " .. ns.palette:wrap(latency_token, latency_value .. " ms") ) frame:Show() return true end local function layout_minimap_stack() local minimap, bag_bar, top_bar = resolve_minimap_environment() if not minimap then return false end local gap = get_stack_gap(minimap, top_bar) local anchor = minimap local ordered_frames = { module.experience_bar, module.reputation_bar, module.system_bar, } for index = 1, #ordered_frames do local frame = ordered_frames[index] if frame and frame:IsShown() then frame:ClearAllPoints() frame:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -gap) frame:SetPoint("TOPRIGHT", anchor, "BOTTOMRIGHT", 0, -gap) anchor = frame end end if bag_bar then bag_bar:ClearAllPoints() bag_bar:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -gap) bag_bar:SetPoint("TOPRIGHT", anchor, "BOTTOMRIGHT", 0, -gap) if bag_bar:IsShown() then anchor = bag_bar end end module.stack_bottom_frame = anchor module.stack_gap = gap return true end local function apply_palette() if not ns.palette then return end if module.experience_background then module.experience_background:SetVertexColor(ns.palette:get_surface("unit_bar")) end if module.reputation_background then module.reputation_background:SetVertexColor(ns.palette:get_surface("unit_bar")) end if module.system_background then module.system_background:SetColorTexture(ns.palette:get_surface("button_deep")) end if module.zone_popup_background then module.zone_popup_background:SetVertexColor(ns.palette:get_surface("unit_bar")) end if module.experience_text then module.experience_text:SetTextColor(ns.palette:get_rgb("text")) end if module.reputation_text then module.reputation_text:SetTextColor(ns.palette:get_rgb("text")) end if module.system_text then module.system_text:SetTextColor(ns.palette:get_rgb("text")) end for _, state in pairs(mirror_timer_states) do if state.background then state.background:SetVertexColor(ns.palette:get_surface("unit_bar")) end if state.text then state.text:SetTextColor(ns.palette:get_rgb("text")) end if state.border then state.border:SetBackdropBorderColor( ns.palette:get_rgba("border", ns.palette:get_alpha("opaque")) ) end end local popup_pools = { module.error_message_pool, module.discovery_message_pool, } for pool_index = 1, #popup_pools do local pool = popup_pools[pool_index] if pool then for popup_index = 1, #pool do local popup = pool[popup_index] if popup.background then popup.background:SetVertexColor(ns.palette:get_surface("unit_bar")) end if popup.border then popup.border:SetBackdropBorderColor( ns.palette:get_rgba("border", ns.palette:get_alpha("opaque")) ) end end end end local borders = { module.experience_border, module.reputation_border, module.system_border, module.zone_popup_border, } for index = 1, #borders do local border = borders[index] if border then border:SetBackdropBorderColor( ns.palette:get_rgba("border", ns.palette:get_alpha("opaque")) ) end end end local function refresh_all() resolve_blizzard_frames(false) hook_native_manager() hook_blizzard_zone_text() hook_blizzard_ui_errors() prepare_mirror_timer_states() hook_mirror_timer_show() apply_mirror_timer_skins() update_experience_bar() update_reputation_bar() update_system_bar() apply_native_visibility() layout_minimap_stack() end local function collect_profile_settings() local settings = {} for index = 1, #setting_order do local key = setting_order[index] settings[key] = module.settings[key] == true end return settings end local function apply_profile_settings(profile_settings) if type(profile_settings) ~= "table" then return false end for index = 1, #setting_order do local key = setting_order[index] local value = profile_settings[key] module.settings[key] = type(value) == "boolean" and value or default_settings[key] end refresh_all() local manager = module.blizzard_frames and module.blizzard_frames.manager or nil if manager and type(manager.UpdateBarsShown) == "function" and not InCombatLockdown() then manager:UpdateBarsShown() resolve_blizzard_frames(false) hook_native_manager() end update_experience_bar() update_reputation_bar() update_system_bar() apply_native_visibility() layout_minimap_stack() return true end local function on_event(_, event) if event == "PLAYER_REGEN_ENABLED" then if module.pending_native_visibility then apply_native_visibility() end if module.pending_mirror_timer_skin then apply_mirror_timer_skins() end return end if event == "PLAYER_XP_UPDATE" or event == "PLAYER_LEVEL_UP" or event == "UNIT_LEVEL" or event == "UPDATE_EXHAUSTION" or event == "ENABLE_XP_GAIN" or event == "DISABLE_XP_GAIN" or event == "PLAYER_MAX_LEVEL_UPDATE" then update_experience_bar() apply_native_visibility() layout_minimap_stack() return end if event == "UPDATE_FACTION" then update_reputation_bar() apply_native_visibility() layout_minimap_stack() return end if event == "ADDON_LOADED" or event == "PLAYER_ENTERING_WORLD" then resolve_blizzard_frames(event == "PLAYER_ENTERING_WORLD") hook_blizzard_zone_text() hook_blizzard_ui_errors() refresh_all() end end function ns:get_minimap_stack_anchor() local minimap = _G.Minimap return module.stack_bottom_frame or minimap, module.stack_gap end function ns:layout_minimap_stack() return layout_minimap_stack() end function ns:get_zusatzleisten_settings() local settings = {} for index = 1, #setting_order do local key = setting_order[index] settings[key] = get_runtime_setting(key) end return settings end function module:initialize() self.settings = copy_default_settings() self.system_elapsed = 0 self.pending_native_visibility = false self.pending_mirror_timer_skin = false ns:register_profile_provider("zusatzleisten", collect_profile_settings, apply_profile_settings) ns:register_event("ADDON_LOADED", self, on_event) ns:register_event("PLAYER_ENTERING_WORLD", self, on_event) ns:register_event("PLAYER_XP_UPDATE", self, on_event) ns:register_event("PLAYER_LEVEL_UP", self, on_event) ns:register_event("UNIT_LEVEL", self, on_event) ns:register_event("UPDATE_EXHAUSTION", self, on_event) ns:register_event("ENABLE_XP_GAIN", self, on_event) ns:register_event("DISABLE_XP_GAIN", self, on_event) ns:register_event("PLAYER_MAX_LEVEL_UPDATE", self, on_event) ns:register_event("UPDATE_FACTION", self, on_event) ns:register_event("PLAYER_REGEN_ENABLED", self, on_event) resolve_blizzard_frames(true) prepare_mirror_timer_states() hook_mirror_timer_show() apply_mirror_timer_skins() hook_blizzard_ui_errors() local zone_frame = self.blizzard_frames and self.blizzard_frames.zone_frame or nil if zone_frame and ns.palette and ns.designelemente then self.zone_popup_frame, self.zone_popup_background, self.zone_popup_border, self.zone_popup_text = create_zone_popup(UIParent, zone_frame) self.last_zone_text = self.blizzard_frames.zone_text_string and self.blizzard_frames.zone_text_string:GetText() or "" hook_blizzard_zone_text() end local minimap = resolve_minimap_environment() if not minimap or not ns.palette or not ns.designelemente then ns:chat_message("Zusatzleisten konnten nicht an der Minimap initialisiert werden", true) return end self.experience_bar, self.experience_background, self.experience_border, self.experience_text, self.experience_current_bar = create_progress_bar(minimap, true) self.reputation_bar, self.reputation_background, self.reputation_border, self.reputation_text = create_progress_bar(minimap, false) self.system_bar, self.system_background, self.system_border, self.system_text = create_system_bar(minimap) self.update_frame = CreateFrame("Frame") self.update_frame:SetScript("OnUpdate", function(_, elapsed) module.system_elapsed = module.system_elapsed + elapsed if module.system_elapsed >= system_update_interval then module.system_elapsed = 0 local was_shown = module.system_bar and module.system_bar:IsShown() or false update_system_bar() local is_shown = module.system_bar and module.system_bar:IsShown() or false if was_shown ~= is_shown then layout_minimap_stack() end end end) refresh_all() apply_palette() if ns.palette and type(ns.palette.register_callback) == "function" then ns.palette:register_callback("leisten", function() apply_palette() update_experience_bar() update_reputation_bar() update_system_bar() end) end end ns:register_module("leisten")