---------------------------------------------------- -- SamUI v0.3.3 -- Eine vollständige UI-Alternative -- WoW Classic Anniversary TBC -- Interface 20506 -- Build 69110 ---------------------------------------------------- local _, ns = ... local module = ns:new_module("verbesserungen") local setting_order = { "fast_loot_enabled", "auto_repair_mode", "auto_sell_enabled", "totem_bar_enabled", } local default_settings = { fast_loot_enabled = false, auto_repair_mode = "player", auto_sell_enabled = true, totem_bar_enabled = true, } local valid_repair_modes = { disabled = true, player = true, guild = true, } local loot_lockout_seconds = 0.10 local merchant_repair_delay = 0.20 local guild_repair_followup_delay = 0.40 local master_loot_method = 2 local poor_item_quality = 0 local totem_bar_anchor_distance = 30 local totem_button_size = 30 local totem_button_spacing = 10 local totem_slot_order = { 1, -- Feuer 2, -- Erde 3, -- Wasser 4, -- Luft } 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 normalize_repair_mode(value) if valid_repair_modes[value] then return value end return default_settings.auto_repair_mode end local game_menu_frame_states = setmetatable({}, { __mode = "k" }) local game_menu_hook_installed = false local game_menu_missing_reported = false local main_panel_inset = ns.designelemente:get_border_offset("frame") local title_panel_height = 28 local title_border_gap = 5 local title_anchor_gap = title_border_gap + (main_panel_inset * 2) local static_popup_frame_states = setmetatable({}, { __mode = "k" }) local static_popup_hook_installed = false local static_popup_skin_keys = { CONFIRM_PROFESSION = true, CAMP = true, } local function set_invisible(region) if region and type(region.SetAlpha) == "function" then region:SetAlpha(0) end end local function hide_stock_game_menu_artwork(frame) set_invisible(frame.Border) local header = frame.Header if not header then return end for _, region in ipairs({ header:GetRegions() }) do if region and type(region.GetObjectType) == "function" and region:GetObjectType() == "Texture" then set_invisible(region) end end set_invisible(header.Text) end local function update_game_menu_title_text(frame, state) if not state or not state.title_text then return end local header = frame.Header local stock_title = header and header.Text if stock_title and type(stock_title.GetText) == "function" then state.title_text:SetText(stock_title:GetText() or "") end end local function ensure_game_menu_style(frame) local state = game_menu_frame_states[frame] if state then update_game_menu_title_text(frame, state) return state end local palette = ns.palette local designelemente = ns.designelemente if not palette or not designelemente then return nil end state = {} game_menu_frame_states[frame] = state local main_anchor = CreateFrame("Frame", nil, frame) main_anchor.ignoreInLayout = true main_anchor:EnableMouse(false) main_anchor:SetPoint("TOPLEFT", frame, "TOPLEFT", main_panel_inset, -main_panel_inset) main_anchor:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -main_panel_inset, main_panel_inset) state.main_anchor = main_anchor local background = frame:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(main_anchor) background:SetColorTexture(palette:get_surface("panel_solid")) state.background = background state.border = designelemente:create_border_overlay(main_anchor, frame, "frame") local header = frame.Header if header then local title_anchor = CreateFrame("Frame", nil, frame) title_anchor.ignoreInLayout = true title_anchor:EnableMouse(false) title_anchor:SetPoint("BOTTOMLEFT", main_anchor, "TOPLEFT", 0, title_anchor_gap) title_anchor:SetPoint("BOTTOMRIGHT", main_anchor, "TOPRIGHT", 0, title_anchor_gap) title_anchor:SetHeight(title_panel_height) state.title_anchor = title_anchor local title_background = frame:CreateTexture(nil, "BACKGROUND") title_background:SetAllPoints(title_anchor) title_background:SetColorTexture(palette:get_surface("button_deep")) state.title_background = title_background state.title_border = designelemente:create_border_overlay(title_anchor, frame, "frame") local title_text = title_anchor:CreateFontString(nil, "OVERLAY") title_text:SetPoint("CENTER", title_anchor, "CENTER") title_text:SetFontObject(samui_font_12) title_text:SetTextColor(palette:get_rgb("gold")) state.title_text = title_text end update_game_menu_title_text(frame, state) return state end function module:apply_game_menu_skin(frame) frame = frame or _G.GameMenuFrame if not frame then return false end local state = ensure_game_menu_style(frame) if not state then return false end hide_stock_game_menu_artwork(frame) update_game_menu_title_text(frame, state) return true end local function install_game_menu_hooks() if game_menu_hook_installed then return true end local mixin = _G.GameMenuFrameMixin if type(mixin) ~= "table" or type(mixin.InitButtons) ~= "function" then return false end hooksecurefunc(mixin, "InitButtons", function(frame) module:apply_game_menu_skin(frame) end) game_menu_hook_installed = true return true end local function handle_addon_loaded(_, _, addon_name) if addon_name ~= "Blizzard_GameMenu" then return end if install_game_menu_hooks() then ns:unregister_event("ADDON_LOADED", module, handle_addon_loaded) module:apply_game_menu_skin() return end if not game_menu_missing_reported then ns:chat_message("Blizzard-Frame 'GameMenuFrame' ist nicht verfügbar", true) game_menu_missing_reported = true end end local function resolve_visible_static_popup(which) if type(which) ~= "string" or type(_G.StaticPopup_Visible) ~= "function" then return nil end local _, frame = StaticPopup_Visible(which) return frame end local function ensure_static_popup_style(frame) local state = static_popup_frame_states[frame] if state then return state end local palette = ns.palette local designelemente = ns.designelemente local stock_background = frame and frame.BG if not palette or not designelemente or not stock_background or type(stock_background.GetAlpha) ~= "function" or type(stock_background.SetAlpha) ~= "function" then return nil end local main_anchor = CreateFrame("Frame", nil, frame) main_anchor.ignoreInLayout = true main_anchor:EnableMouse(false) main_anchor:SetPoint( "TOPLEFT", frame, "TOPLEFT", main_panel_inset, -main_panel_inset ) main_anchor:SetPoint( "BOTTOMRIGHT", frame, "BOTTOMRIGHT", -main_panel_inset, main_panel_inset ) local background = frame:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(main_anchor) background:SetColorTexture(palette:get_surface("panel_solid")) local border = designelemente:create_border_overlay( main_anchor, frame, "frame" ) if not border then background:Hide() main_anchor:Hide() return nil end background:Hide() border:Hide() state = { main_anchor = main_anchor, background = background, border = border, stock_background = stock_background, stock_background_alpha = stock_background:GetAlpha(), } static_popup_frame_states[frame] = state return state end local function apply_static_popup_skin(frame) local state = ensure_static_popup_style(frame) if not state then return false end state.stock_background:SetAlpha(0) state.background:Show() state.border:Show() return true end local function restore_static_popup_skin(frame) local state = static_popup_frame_states[frame] if not state then return false end state.stock_background:SetAlpha(state.stock_background_alpha) state.background:Hide() state.border:Hide() return true end local function apply_static_popup_palette() local palette = ns.palette if not palette then return false end for _, state in pairs(static_popup_frame_states) do if state.background then state.background:SetColorTexture( palette:get_surface("panel_solid") ) end if state.border then state.border:SetBackdropBorderColor( palette:get_rgba( "border", palette:get_alpha("opaque") ) ) end end return true end local function handle_static_popup_shown(which) local frame = resolve_visible_static_popup(which) if not frame then return end if static_popup_skin_keys[which] then apply_static_popup_skin(frame) return end restore_static_popup_skin(frame) end local function install_static_popup_hook() if static_popup_hook_installed then return true end if type(_G.StaticPopup_Show) ~= "function" or type(_G.StaticPopup_Visible) ~= "function" then return false end hooksecurefunc(_G, "StaticPopup_Show", handle_static_popup_shown) static_popup_hook_installed = true return true end local function is_player_shaman() local _, class_file = UnitClass("player") return class_file == "SHAMAN" end local function apply_totem_cooldown_font(cooldown) if not cooldown then return false end if type(cooldown.SetCountdownFont) == "function" then cooldown:SetCountdownFont("samui_font_14_outline") return true end if type(cooldown.GetRegions) ~= "function" then return false end local regions = { cooldown:GetRegions() } for index = 1, #regions do local region = regions[index] if region and type(region.GetObjectType) == "function" and region:GetObjectType() == "FontString" and type(region.SetFontObject) == "function" then region:SetFontObject(samui_font_14_outline) return true end end return false end local function resolve_blizzard_totem_frame(report_missing) if module.blizzard_totem_frame then return module.blizzard_totem_frame end local frame = _G.TotemFrame local pool = frame and frame.totemPool if not frame or not pool or type(pool.EnumerateActive) ~= "function" then if report_missing and not module.blizzard_totem_frame_warning_shown then module.blizzard_totem_frame_warning_shown = true ns:chat_message("Blizzard-TotemFrame ist nicht vollständig verfügbar", true) end return nil end module.blizzard_totem_frame = frame return frame end local function get_blizzard_totem_button_for_slot(slot) local frame = resolve_blizzard_totem_frame(true) if not frame then return nil end for button in frame.totemPool:EnumerateActive() do if button and button.slot == slot then return button end end return nil end local function set_totem_destroy_click_target(button, slot) if not button or InCombatLockdown() then return false end local blizzard_button = get_blizzard_totem_button_for_slot(slot) if not blizzard_button then button:SetAttribute("clickbutton2", nil) return false end button:SetAttribute("clickbutton2", blizzard_button) return true end local function get_totem_bar_height() if type(ns.get_minimap_top_bar_metrics) ~= "function" then return nil end local _, height = ns:get_minimap_top_bar_metrics() if type(height) ~= "number" or height <= 0 then return nil end return height end local function create_totem_button(parent, slot, size, palette, designelemente) local button = CreateFrame("Button", nil, parent, "SecureActionButtonTemplate") button:SetSize(size, size) button:RegisterForClicks("RightButtonUp") button:SetAttribute("type2", "click") button:SetAttribute("useOnKeyDown", false) button:SetFrameLevel(parent:GetFrameLevel() + 7) local background = button:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(button) background:SetColorTexture(palette:get_surface("button")) local icon = button:CreateTexture(nil, "ARTWORK") icon:SetPoint("TOPLEFT", button, "TOPLEFT", 2, -2) icon:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -2, 2) local cooldown = CreateFrame("Cooldown", nil, button, "CooldownFrameTemplate") cooldown:SetAllPoints(button) cooldown:EnableMouse(false) apply_totem_cooldown_font(cooldown) local border = designelemente:create_border_overlay( button, button, "minimap_top_button" ) button.samui_totem_background = background button.samui_totem_icon = icon button.samui_totem_cooldown = cooldown button.samui_totem_border = border button:Hide() return button end local function create_totem_bar() if not is_player_shaman() then return false end if module.totem_bar then return true end local player_frame = _G.samui_unitframe_player local palette = ns.palette local designelemente = ns.designelemente local bar_height = get_totem_bar_height() if not player_frame or not palette or not designelemente or not bar_height then ns:chat_message("Totemleiste konnte nicht initialisiert werden", true) return false end local bar = CreateFrame("Frame", nil, UIParent) bar:SetPoint( "TOP", player_frame, "BOTTOM", 0, -totem_bar_anchor_distance ) bar:SetSize(bar_height, bar_height) bar:SetFrameLevel(player_frame:GetFrameLevel() + 14) local background = bar:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(bar) background:SetColorTexture(palette:get_surface("button_deep")) local buttons = {} local button_size = totem_button_size for index = 1, #totem_slot_order do local slot = totem_slot_order[index] buttons[slot] = create_totem_button( bar, slot, button_size, palette, designelemente ) end module.totem_bar = bar module.totem_bar_background = background module.totem_buttons = buttons module.totem_bar_height = bar_height module.totem_button_size = button_size bar:Hide() return true end local function apply_totem_bar_palette() local palette = ns.palette if not palette then return false end if module.totem_bar_background then module.totem_bar_background:SetColorTexture( palette:get_surface("button_deep") ) end if module.totem_buttons then for index = 1, #totem_slot_order do local slot = totem_slot_order[index] local button = module.totem_buttons[slot] if button and button.samui_totem_background then button.samui_totem_background:SetColorTexture( palette:get_surface("button") ) end if button and button.samui_totem_border then button.samui_totem_border:SetBackdropBorderColor( palette:get_rgba("border", palette:get_alpha("opaque")) ) end end end return true end local function update_totem_button(button, start_time, duration, icon) if not button then return end button.samui_totem_icon:SetTexture(icon) CooldownFrame_Set( button.samui_totem_cooldown, start_time or 0, duration or 0, 1 ) apply_totem_cooldown_font(button.samui_totem_cooldown) end local function update_totem_bar() if not is_player_shaman() then return false end if module.settings.totem_bar_enabled ~= true then if module.totem_bar and not InCombatLockdown() then module.totem_bar:Hide() end return true end if not create_totem_bar() then return false end if InCombatLockdown() then module.pending_totem_layout = true return false end local active_totems = {} for index = 1, #totem_slot_order do local slot = totem_slot_order[index] local _, totem_name, start_time, duration, icon = GetTotemInfo(slot) local button = module.totem_buttons[slot] local is_active = type(totem_name) == "string" and totem_name ~= "" if is_active then active_totems[#active_totems + 1] = { slot = slot, button = button, start_time = start_time, duration = duration, icon = icon, } else button:SetAttribute("clickbutton2", nil) button:Hide() end end local active_count = #active_totems if active_count == 0 then module.totem_bar:Hide() module.pending_totem_layout = nil return true end local bar_height = module.totem_bar_height local button_size = module.totem_button_size local bar_width = (active_count * button_size) + ((active_count - 1) * totem_button_spacing) module.totem_bar:SetSize(bar_width, bar_height) for index = 1, active_count do local totem = active_totems[index] local button = totem.button button:ClearAllPoints() button:SetPoint( "LEFT", module.totem_bar, "LEFT", (index - 1) * (button_size + totem_button_spacing), 0 ) update_totem_button( button, totem.start_time, totem.duration, totem.icon ) set_totem_destroy_click_target(button, totem.slot) button:Show() end module.totem_bar:Show() module.pending_totem_layout = nil return true end local function collect_profile_settings() return { fast_loot_enabled = module.settings.fast_loot_enabled == true, auto_repair_mode = normalize_repair_mode(module.settings.auto_repair_mode), auto_sell_enabled = module.settings.auto_sell_enabled == true, totem_bar_enabled = module.settings.totem_bar_enabled == true, } end local function apply_profile_settings(profile_settings) if type(profile_settings) ~= "table" then return false end local fast_loot_enabled = profile_settings.fast_loot_enabled local auto_sell_enabled = profile_settings.auto_sell_enabled local totem_bar_enabled = profile_settings.totem_bar_enabled module.settings.fast_loot_enabled = type(fast_loot_enabled) == "boolean" and fast_loot_enabled or default_settings.fast_loot_enabled module.settings.auto_repair_mode = normalize_repair_mode(profile_settings.auto_repair_mode) module.settings.auto_sell_enabled = type(auto_sell_enabled) == "boolean" and auto_sell_enabled or default_settings.auto_sell_enabled module.settings.totem_bar_enabled = type(totem_bar_enabled) == "boolean" and totem_bar_enabled or default_settings.totem_bar_enabled update_totem_bar() return true end local function should_auto_loot_current_interaction() local auto_loot = GetCVarBool("autoLootDefault") == true if IsModifiedClick("AUTOLOOTTOGGLE") then auto_loot = not auto_loot end return auto_loot end local function handle_fast_loot() if module.settings.fast_loot_enabled ~= true or not should_auto_loot_current_interaction() then return end local now = GetTime() if type(module.last_fast_loot_time) == "number" and now - module.last_fast_loot_time < loot_lockout_seconds then return end module.last_fast_loot_time = now local loot_method = type(C_PartyInfo) == "table" and type(C_PartyInfo.GetLootMethod) == "function" and C_PartyInfo.GetLootMethod() or nil local is_master_loot = loot_method == master_loot_method local loot_threshold = is_master_loot and GetLootThreshold() or nil for slot_index = GetNumLootItems(), 1, -1 do local _, _, _, currency_id, quality, locked, _, _, _, is_coin = GetLootSlotInfo(slot_index) if not locked then local should_loot = not is_master_loot if is_master_loot then should_loot = is_coin == true or currency_id ~= nil or ( type(quality) == "number" and type(loot_threshold) == "number" and quality < loot_threshold ) end if should_loot then LootSlot(slot_index) end end end end local function sell_poor_items() if module.settings.auto_sell_enabled ~= true or type(C_Container) ~= "table" or type(C_Container.GetContainerNumSlots) ~= "function" or type(C_Container.GetContainerItemInfo) ~= "function" or type(C_Container.UseContainerItem) ~= "function" then return false end local maximum_bag_id = type(NUM_BAG_SLOTS) == "number" and NUM_BAG_SLOTS or 4 for bag_id = 0, maximum_bag_id do local slot_count = C_Container.GetContainerNumSlots(bag_id) or 0 for slot_index = slot_count, 1, -1 do local item_info = C_Container.GetContainerItemInfo(bag_id, slot_index) if item_info and item_info.quality == poor_item_quality and item_info.hasNoValue ~= true and item_info.isLocked ~= true then C_Container.UseContainerItem(bag_id, slot_index) end end end return true end local function merchant_is_open() local merchant_frame = _G.MerchantFrame return merchant_frame and type(merchant_frame.IsShown) == "function" and merchant_frame:IsShown() == true end local function repair_from_player() if not CanMerchantRepair() then return false end local repair_cost, can_repair = GetRepairAllCost() if not can_repair or type(repair_cost) ~= "number" or repair_cost <= 0 then return false end if GetMoney() < repair_cost then return false end RepairAllItems(false) return true end local function attempt_auto_repair(merchant_session) if merchant_session ~= module.merchant_session or not merchant_is_open() or not CanMerchantRepair() then return end local repair_mode = normalize_repair_mode(module.settings.auto_repair_mode) if repair_mode == "disabled" then return end local repair_cost, can_repair = GetRepairAllCost() if not can_repair or type(repair_cost) ~= "number" or repair_cost <= 0 then return end if repair_mode ~= "guild" then repair_from_player() return end local withdraw_limit = type(GetGuildBankWithdrawMoney) == "function" and GetGuildBankWithdrawMoney() or 0 local can_use_guild = type(CanGuildBankRepair) == "function" and CanGuildBankRepair() == true and type(withdraw_limit) == "number" and (withdraw_limit < 0 or withdraw_limit >= repair_cost) if not can_use_guild then repair_from_player() return end RepairAllItems(true) C_Timer.After(guild_repair_followup_delay, function() if merchant_session ~= module.merchant_session or not merchant_is_open() or normalize_repair_mode(module.settings.auto_repair_mode) ~= "guild" then return end repair_from_player() end) end local function handle_merchant_show() module.merchant_session = (module.merchant_session or 0) + 1 local merchant_session = module.merchant_session sell_poor_items() C_Timer.After(merchant_repair_delay, function() attempt_auto_repair(merchant_session) end) end local function handle_merchant_closed() module.merchant_session = (module.merchant_session or 0) + 1 end local function schedule_totem_bar_update() if module.totem_update_scheduled then return end if InCombatLockdown() then module.pending_totem_layout = true return end module.totem_update_scheduled = true C_Timer.After(0, function() module.totem_update_scheduled = nil update_totem_bar() end) end local function handle_gameplay_event(_, event) if event == "LOOT_READY" then handle_fast_loot() elseif event == "MERCHANT_SHOW" then handle_merchant_show() elseif event == "MERCHANT_CLOSED" then handle_merchant_closed() elseif event == "PLAYER_TOTEM_UPDATE" or event == "PLAYER_ENTERING_WORLD" then schedule_totem_bar_update() elseif event == "PLAYER_REGEN_ENABLED" and module.pending_totem_layout then schedule_totem_bar_update() end end function module:initialize() self.settings = copy_default_settings() self.last_fast_loot_time = nil self.merchant_session = 0 ns:register_profile_provider("verbesserungen", collect_profile_settings, apply_profile_settings) ns:register_event("LOOT_READY", self, handle_gameplay_event) ns:register_event("MERCHANT_SHOW", self, handle_gameplay_event) ns:register_event("MERCHANT_CLOSED", self, handle_gameplay_event) if is_player_shaman() then resolve_blizzard_totem_frame(false) schedule_totem_bar_update() ns:register_event("PLAYER_ENTERING_WORLD", self, handle_gameplay_event) ns:register_event("PLAYER_TOTEM_UPDATE", self, handle_gameplay_event) ns:register_event("PLAYER_REGEN_ENABLED", self, handle_gameplay_event) end if ns.palette and type(ns.palette.register_callback) == "function" then ns.palette:register_callback("verbesserungen", apply_totem_bar_palette) ns.palette:register_callback( "verbesserungen_static_popups", apply_static_popup_palette ) end SLASH_SAMUI_RELOAD1 = "/rl" SlashCmdList["SAMUI_RELOAD"] = function() ReloadUI() end if install_game_menu_hooks() then module:apply_game_menu_skin() else ns:register_event("ADDON_LOADED", self, handle_addon_loaded) end if not install_static_popup_hook() then ns:chat_message( "Blizzard-StaticPopup-System ist nicht verfügbar", true ) end end ns:register_module("verbesserungen")