local ADDON_SETS = { {1}, {2}, {1,2}, {3}, {1,3}, {2,3}, {1,2,3} } local outfitDir = "/outfits/" local ignoreNextOutfitWindow = 0 -- Module state local outfitWindow = nil local outfitFile = nil local outfits_list = nil local mounts_list = nil local wings_list = nil local auras_list = nil local shaders_list = nil local runeEffects_list = nil local previewDirection = 2 local addons = nil local outfitsPreset = {} local colorBoxes = {} local currentColorBox = nil local currentClotheButtonBox = nil local selectorOutfit = nil local selectorMount = nil local selectorWings = nil local selectorAuras = nil local selectorShaders = nil local selectorRuneEffect = nil local typeBox = nil local mountBox = nil local wingsBox = nil local auraBox = nil local shaderBox = nil local runeEffectBox = nil local previewBox = nil local previewPanel = nil local OutfitPanel = nil local PresetPanel = nil local cOutfit = nil local sId = nil -- Forward declarations local destroy, accept, updateOutfit, onTypeChange local setupSelector, refreshSelector, refreshPreset local selectOutfit, getOutfitListById local onColorCheckChange, onClotheCheckChange, onAddonCheckChange local onOutfitCheckChange local outfitConfig_Load, outfitConfig_Save local randomizeAll, randomizeOutfit, randomizeColor local singlelineEditorWindow, outfitConfirmationWindow function init() connect(g_game, { onOpenOutfitWindow = create, onGameEnd = destroy }) if not g_resources.directoryExists("/outfits/") then g_resources.makeDir("/outfits/") end end function terminate() disconnect(g_game, { onOpenOutfitWindow = create, onGameEnd = destroy }) destroy() end function clearSearch() local searchInput = outfitWindow.filter:getChildById("searchInput") searchInput:setText("") onSearch() end function onSearch() scheduleEvent(function() if not outfitWindow then return end local panel = (sId == "preset") and PresetPanel or OutfitPanel if not panel then return end local searchInput = outfitWindow.filter:getChildById("searchInput") local text = searchInput:getText():lower() local children = panel:getChildCount() if #text > 0 then for i = 1, children do local child = panel:getChildByIndex(i) if child then local childTitle = child:getChildById("title") if childTitle then if childTitle:getText():lower():find(text) then child:show() else child:hide() end end end end else for i = 1, children do local child = panel:getChildByIndex(i) if child then child:show() end end end end, 50) end function singlelineEditorWindow(text, options, callback) options = options or {} options.multiline = false return modules.client_textedit.edit(text, options, callback) end function outfitConfirmationWindow(title, question, callback) local window = nil local function onConfirm() window:destroy() callback() end local function closeWindow() window:destroy() end window = displayGeneralBox(title, question, { {text = tr("Yes"), callback = onConfirm}, {text = tr("No"), callback = closeWindow}, anchor = AnchorHorizontalCenter }, onConfirm, closeWindow) return window end function getOutfitListById(id) id = id or sId local list = {} local selector = nil if (id == "outfit" or id == "type") and outfits_list then list = outfits_list selector = selectorOutfit sId = "type" elseif id == "mount" and mounts_list then list = mounts_list selector = selectorMount elseif id == "wings" and wings_list then list = wings_list selector = selectorWings elseif id == "aura" and auras_list then list = auras_list selector = selectorAuras elseif id == "shader" and shaders_list then list = shaders_list selector = selectorShaders elseif id == "runeeffect" and runeEffects_list then list = runeEffects_list selector = selectorRuneEffect end return list, selector end function setupSelector(widget, id, outfit, list) widget:setId(id) local name = (id == "type" and "Outfit") or id:gsub("^%l", string.upper) outfitWindow.typeCombo:addOption(name, id) if id ~= "type" or #list == 0 then table.insert(list, 1, {0, "-"}) end local pos = 1 for i, o in pairs(list) do if (id == "shader" and outfit[id] == o[2]) or (id == "runeeffect" and outfit.runeeffect == o[1]) or outfit[id] == o[1] then pos = i end end if list[pos] then widget.outfit = list[pos] if id == "shader" then widget.creature:setOutfit({shader = list[pos][2]}) elseif id == "runeeffect" then widget.creature:setOutfit({runeeffect = list[pos][1]}) else widget.creature:setOutfit({type = list[pos][1]}) end widget.label:setText(list[pos][2]) end return widget end function refreshSelector(id) local widget, list = nil, nil if id == "type" then widget = selectorOutfit list = outfits_list elseif id == "mount" then widget = selectorMount list = mounts_list elseif id == "wings" then widget = selectorWings list = wings_list elseif id == "aura" then widget = selectorAuras list = auras_list elseif id == "shader" then widget = selectorShaders list = shaders_list elseif id == "runeeffect" then widget = selectorRuneEffect list = runeEffects_list end if not widget or not list then return false end local outfit = widget.creature:getOutfit() local pos = 1 for i, o in pairs(list) do if (id == "shader" and outfit.shader == o[2]) or (id == "wings" and outfit.wings == o[2]) or (id == "aura" and outfit.aura == o[2]) or (id == "runeeffect" and outfit.runeeffect == o[1]) or outfit.type == o[1] then pos = i break end end if list[pos] then widget.label:setText(list[pos][2]) return true end return false end function selectOutfit(widget) local list, sel = getOutfitListById() if not list or not sel then return end local pos = tonumber(widget:getId()) if sId ~= "healthBar" and sId ~= "manaBar" then local outfit = sel.creature:getOutfit() if sId == "shader" then outfit.shader = list[pos][2] elseif sId == "runeeffect" then outfit.runeeffect = list[pos][1] else outfit.type = list[pos][1] end sel.outfit = list[pos] sel.creature:setOutfit(outfit) sel.label:setText(list[pos][2]) end updateOutfit() end function onTypeChange(widgetType, option) local list = {} local current = 1 if type(widgetType) == "userdata" then sId = widgetType:getCurrentOption().data else sId = widgetType end if sId == "preset" then outfitConfig_Load() refreshPreset() return end OutfitPanel:destroyChildren() local player = g_game.getLocalPlayer() local specOutfit = player:getOutfit() list = getOutfitListById() if sId == "outfit" then sId = "type" end for i, o in pairs(list) do if (sId == "shader" and cOutfit.shader == o[2]) or (sId == "runeeffect" and cOutfit.runeeffect == o[1]) or cOutfit[sId] == o[1] then current = i end end if #list > 0 then for i = 1, #list do if sId == "runeeffect" then local w = g_ui.createWidget("RuneEffectItem", OutfitPanel) w:setId(i) w:getChildById("title"):setText(list[i][2]) if list[i][1] > 0 then w:getChildById("runeImage"):setImageSource("/data/images/runeeffects/" .. list[i][1] .. ".png") else w:getChildById("runeImage"):setImageSource("") end if current == i then w:focus() addEvent(function() OutfitPanel:ensureChildVisible(w) end) end else local w = g_ui.createWidget("OutfitCreature", OutfitPanel) w:setId(i) local tmpOutfit = { type = list[i][1], addons = list[i][3] or 0, mount = 0, shader = "", aura = 0, wings = 0, runeeffect = 0, head = specOutfit.head, body = specOutfit.body, legs = specOutfit.legs, feet = specOutfit.feet } if sId == "shader" then tmpOutfit.shader = list[i][2] if tmpOutfit.shader == "-" then tmpOutfit.shader = "" end end w:getChildById("title"):setText(list[i][2]) w:getChildById("outfit"):setOutfit(tmpOutfit) if current == i then w:focus() addEvent(function() OutfitPanel:ensureChildVisible(w) end) end end end end outfitWindow.outfits:setVisible(true) outfitWindow.presetPanel:setVisible(false) outfitWindow.presetOptions:setVisible(false) end function outfitConfig_Load() if g_resources.fileExists(outfitFile) then local status, result = pcall(function() return json.decode(g_resources.readFileContents(outfitFile)) end) if not status then return onError("Error reading config (" .. outfitFile .. "): " .. result) end outfitsPreset = result end end function outfitConfig_Save() local status, result = pcall(function() return json.encode(outfitsPreset, 2) end) if not status then print("Error saving config: " .. result) return end if #result > 104857600 then print("Config too big, not saving") return end g_resources.writeFileContents(outfitFile, result) end function refreshPreset(id) PresetPanel:destroyChildren() if outfitsPreset then for i, outfit in pairs(outfitsPreset) do local widget = g_ui.createWidget("PresetOutfitPanel", PresetPanel) widget:setId(i) widget:getChildById("title"):setText(outfit.name) widget:getChildById("mount"):setOutfit({type = outfit.mount}) widget:getChildById("creature"):setOutfit(outfit.outfit) widget.onDoubleClick = function() scheduleEvent(function() local tOutfit = { type = outfit.outfit.type, addons = outfit.outfit.addons, head = outfit.outfit.head, body = outfit.outfit.body, legs = outfit.outfit.legs, feet = outfit.outfit.feet } typeBox:setOutfit(tOutfit) mountBox:setOutfit({type = outfit.mount}) wingsBox:setOutfit({type = outfit.outfit.wings}) auraBox:setOutfit({type = outfit.outfit.aura}) shaderBox:setOutfit({shader = outfit.outfit.shader}) if runeEffectBox and outfit.outfit.runeeffect then runeEffectBox:setOutfit({runeeffect = outfit.outfit.runeeffect}) end updateOutfit() refreshSelector("type") refreshSelector("mount") refreshSelector("wings") refreshSelector("aura") refreshSelector("shader") refreshSelector("runeeffect") end, 20) end if id and i == #outfitsPreset then widget:focus() addEvent(function() PresetPanel:ensureChildVisible(widget) end) end end end outfitWindow.outfits:setVisible(false) outfitWindow.presetPanel:setVisible(true) outfitWindow.presetOptions:setVisible(true) end function presetAction(action) local text, index = nil, nil local data = typeBox:getOutfit() local mount = mountBox:getOutfit() local aura = auraBox:getOutfit() local wings = wingsBox:getOutfit() local shader = shaderBox:getOutfit() local runeeffect = runeEffectBox and runeEffectBox:getOutfit() or nil mount = (mount and mount.type) or 0 aura = (aura and aura.type) or 0 wings = (wings and wings.type) or 0 shader = (shader and shader.shader) or "" runeeffect = (runeeffect and runeeffect.runeeffect) or 0 local selected = PresetPanel:getFocusedChild() if selected then index = PresetPanel:getChildIndex(selected) text = selected:getChildById("title"):getText() end if action == "new" then local tmpTitle = selectorOutfit.label:getText() singlelineEditorWindow(tmpTitle, {title = "Enter outfit name."}, function(name) if name and #name > 1 then table.insert(outfitsPreset, { name = name, outfit = { type = data.type, head = data.head, body = data.body, legs = data.legs, feet = data.feet, addons = data.addons, aura = aura, wings = wings, shader = shader, runeeffect = runeeffect }, mount = mount }) outfitConfig_Save() refreshPreset(true) end end) elseif action == "rename" then if index and index > 0 then singlelineEditorWindow(text, {title = "Enter new name."}, function(name) if name and #name > 3 then outfitsPreset[index].name = name selected:getChildById("title"):setText(name) outfitConfig_Save() selected:setBorderColor("green") end end) end elseif action == "save" then if index and index > 0 then outfitConfirmationWindow("Confirm Save?", tr("Are you sure you want to overwrite " .. text .. "?"), function() outfitsPreset[index] = { name = text, outfit = { type = data.type, head = data.head, body = data.body, legs = data.legs, feet = data.feet, addons = data.addons, aura = aura, wings = wings, shader = shader, runeeffect = runeeffect }, mount = mount } outfitConfig_Save() refreshPreset(true) selected:setBorderColor("green") end) end elseif action == "delete" then if index and index > 0 then outfitConfirmationWindow("Confirm deletion?", tr("Are you sure you want to delete " .. text .. "?"), function() table.remove(outfitsPreset, index) outfitConfig_Save() refreshPreset(true) end) end end end function destroy() if outfitWindow then outfitWindow:destroy() outfitWindow = nil currentColorBox = nil currentClotheButtonBox = nil colorBoxes = {} addons = nil end end function create(currentOutfit, outfitList, mountList, wingList, auraList, shaderList, hpBarList, manaBarList, runeEffectList) if ignoreNextOutfitWindow and g_clock.millis() < ignoreNextOutfitWindow + 1000 then return end if outfitWindow and not outfitWindow:isHidden() then return end destroy() outfitFile = outfitDir .. g_game.getLocalPlayer():getName() .. "_outfit.json" outfits_list = outfitList mounts_list = mountList wings_list = wingList auras_list = auraList shaders_list = shaderList runeEffects_list = runeEffectList or {} cOutfit = currentOutfit outfitWindow = g_ui.displayUI("outfitwindow") selectorOutfit = setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "type", currentOutfit, outfitList) typeBox = outfitWindow.extensions.type.creature OutfitPanel = outfitWindow.outfits.list PresetPanel = outfitWindow.presetPanel.list previewPanel = outfitWindow.preview previewBox = previewPanel.creature local outfit = typeBox:getOutfit() outfit.head = currentOutfit.head outfit.body = currentOutfit.body outfit.legs = currentOutfit.legs outfit.feet = currentOutfit.feet typeBox:setOutfit(outfit) if g_game.getFeature(GamePlayerMounts) then selectorMount = setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "mount", currentOutfit, mountList) mountBox = outfitWindow.extensions.mount.creature end if g_game.getFeature(GameWingsAndAura) then selectorWings = setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "wings", currentOutfit, wingList) selectorAuras = setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "aura", currentOutfit, auraList) wingsBox = outfitWindow.extensions.wings.creature auraBox = outfitWindow.extensions.aura.creature end if g_game.getFeature(GameOutfitShaders) then selectorShaders = setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "shader", currentOutfit, shaderList) shaderBox = outfitWindow.extensions.shader.creature end if runeEffects_list and #runeEffects_list > 0 then selectorRuneEffect = setupSelector(g_ui.createWidget("OutfitSelectorPanel", outfitWindow.extensions), "runeeffect", currentOutfit, runeEffects_list) runeEffectBox = selectorRuneEffect.creature end outfitWindow.typeCombo:addOption("Preset", "preset") addons = { {value = 1, widget = outfitWindow.addon1}, {value = 2, widget = outfitWindow.addon2}, {value = 4, widget = outfitWindow.addon3} } outfitWindow.typeCombo.onOptionChange = onTypeChange for j = 0, 6 do for i = 0, 18 do local colorId = j * 19 + i local colorBox = g_ui.createWidget("ColorBox", outfitWindow.colorBoxPanel) local outfitColor = getOutfitColor(colorId) colorBox:setImageColor(outfitColor) colorBox:setId("colorBox" .. colorId) colorBox.colorId = colorId if colorId == currentOutfit.head then currentColorBox = colorBox colorBox:setChecked(true) end colorBox.onCheckChange = onColorCheckChange colorBoxes[#colorBoxes + 1] = colorBox end end if previewPanel.showmount:isChecked() then outfit.mount = currentOutfit.mount else outfit.mount = 0 end previewBox:setOutfit(currentOutfit) for _, addon in pairs(addons) do addon.widget.onCheckChange = function(self) onAddonCheckChange(self, addon.value) end end if currentOutfit.addons and currentOutfit.addons > 0 then for _, i in pairs(ADDON_SETS[currentOutfit.addons]) do addons[i].widget:setChecked(true) end end currentClotheButtonBox = outfitWindow.head outfitWindow.head.onCheckChange = onClotheCheckChange outfitWindow.primary.onCheckChange = onClotheCheckChange outfitWindow.secondary.onCheckChange = onClotheCheckChange outfitWindow.detail.onCheckChange = onClotheCheckChange previewBox:setOutfit(outfit) previewBox:setDirection(previewDirection) previewPanel.turnLeft.onClick = function() previewDirection = (previewDirection + 1) % 4 previewBox:setDirection(previewDirection) end previewPanel.turnRight.onClick = function() previewDirection = (previewDirection - 1 + 4) % 4 previewBox:setDirection(previewDirection) end local optionOutfit = modules.client_options.getOption("showoutfit") or true local optionMount = modules.client_options.getOption("showmount") or true local optionWings = modules.client_options.getOption("showwings") or true local optionAura = modules.client_options.getOption("showaura") or true local optionShader = modules.client_options.getOption("showshader") or true local optionRuneEffect = modules.client_options.getOption("showruneeffect") or true local optionAnimated = modules.client_options.getOption("animated") or true previewPanel.showoutfit:setChecked(optionOutfit) previewPanel.showmount:setChecked(optionMount) previewPanel.showwings:setChecked(optionWings) previewPanel.showaura:setChecked(optionAura) previewPanel.showshader:setChecked(optionShader) if previewPanel.showruneeffect then previewPanel.showruneeffect:setChecked(optionRuneEffect) end previewPanel.animated:setChecked(optionAnimated) previewPanel.showoutfit.onCheckChange = onOutfitCheckChange previewPanel.showmount.onCheckChange = onOutfitCheckChange previewPanel.showwings.onCheckChange = onOutfitCheckChange previewPanel.showaura.onCheckChange = onOutfitCheckChange previewPanel.showshader.onCheckChange = onOutfitCheckChange if previewPanel.showruneeffect then previewPanel.showruneeffect.onCheckChange = onOutfitCheckChange end previewPanel.animated.onCheckChange = onOutfitCheckChange onTypeChange("type") updateOutfit() end function onColorCheckChange(colorBox) local outfit = outfitWindow.extensions.type.creature:getOutfit() if colorBox == currentColorBox then colorBox.onCheckChange = nil colorBox:setChecked(true) colorBox.onCheckChange = onColorCheckChange else if currentColorBox then currentColorBox.onCheckChange = nil currentColorBox:setChecked(false) currentColorBox.onCheckChange = onColorCheckChange end currentColorBox = colorBox if currentClotheButtonBox:getId() == "head" then outfit.head = currentColorBox.colorId elseif currentClotheButtonBox:getId() == "primary" then outfit.body = currentColorBox.colorId elseif currentClotheButtonBox:getId() == "secondary" then outfit.legs = currentColorBox.colorId elseif currentClotheButtonBox:getId() == "detail" then outfit.feet = currentColorBox.colorId end previewBox:setOutfit(outfit) typeBox:setOutfit(outfit) end end function onClotheCheckChange(clotheButtonBox) local outfit = typeBox:getOutfit() if clotheButtonBox == currentClotheButtonBox then clotheButtonBox.onCheckChange = nil clotheButtonBox:setChecked(true) clotheButtonBox.onCheckChange = onClotheCheckChange else currentClotheButtonBox.onCheckChange = nil currentClotheButtonBox:setChecked(false) currentClotheButtonBox.onCheckChange = onClotheCheckChange currentClotheButtonBox = clotheButtonBox local colorId = 0 if currentClotheButtonBox:getId() == "head" then colorId = outfit.head elseif currentClotheButtonBox:getId() == "primary" then colorId = outfit.body elseif currentClotheButtonBox:getId() == "secondary" then colorId = outfit.legs elseif currentClotheButtonBox:getId() == "detail" then colorId = outfit.feet end outfitWindow:recursiveGetChildById("colorBox" .. colorId):setChecked(true) end end function onAddonCheckChange(addon, value) local outfit = typeBox:getOutfit() if addon:isChecked() then outfit.addons = outfit.addons + value else outfit.addons = outfit.addons - value end typeBox:setOutfit(outfit) previewBox:setOutfit(outfit) end function onOutfitCheckChange(addon, value) modules.client_options.setOption(addon:getId(), value) updateOutfit() end function updateOutfit() if not outfitWindow then return end local currentSelection = outfitWindow.extensions.type.outfit if not currentSelection then return end local outfit = outfitWindow.extensions.type.creature:getOutfit() local availableAddons = currentSelection[3] for _, addon in pairs(addons) do addon.widget:setChecked(false) addon.widget:setEnabled(false) end outfit.addons = 0 if selectorMount and previewPanel.showmount:isChecked() and mountBox then outfit.mount = mountBox:getOutfit().type else outfit.mount = 0 end if selectorWings and previewPanel.showwings:isChecked() and wingsBox then outfit.wings = wingsBox:getOutfit().type else outfit.wings = 0 end if selectorAuras and previewPanel.showaura:isChecked() and auraBox then outfit.aura = auraBox:getOutfit().type else outfit.aura = 0 end if selectorShaders and previewPanel.showshader:isChecked() and shaderBox then outfit.shader = shaderBox:getOutfit().shader else outfit.shader = "" end if selectorRuneEffect and runeEffectBox then outfit.runeeffect = runeEffectBox:getOutfit().runeeffect or 0 else outfit.runeeffect = 0 end previewBox:setOutfit(outfit) if not previewPanel.showoutfit:isChecked() then outfit.type = 800 outfit.shader = "" end if availableAddons and availableAddons > 0 then for _, i in pairs(ADDON_SETS[availableAddons]) do addons[i].widget:setEnabled(true) addons[i].widget:setChecked(true) end end outfit.addons = availableAddons or 0 previewBox:setOutfit(outfit) if previewPanel.animated:isChecked() then previewBox:setAnimate(true) else previewBox:setAnimate(false) end g_game.changeOutfit(outfit) end function randomizeAll() local outfitTemplate = { outfitWindow.head, outfitWindow.primary, outfitWindow.secondary, outfitWindow.detail } for i = 1, #outfitTemplate do outfitTemplate[i]:setChecked(true) colorBoxes[math.random(1, #colorBoxes)]:setChecked(true) outfitTemplate[i]:setChecked(false) end outfitTemplate[1]:setChecked(true) local out = typeBox:getOutfit() out.type = outfits_list[math.random(#outfits_list)][1] local rndMount = mounts_list[math.random(1, #mounts_list)][1] local rndWings = wings_list[math.random(1, #wings_list)][1] local rndAura = auras_list[math.random(1, #auras_list)][1] local rndShader = shaders_list[math.random(1, #shaders_list)][2] if rndShader == "-" then rndShader = "" end typeBox:setOutfit(out) if mountBox then mountBox:setOutfit({type = rndMount}) end if wingsBox then wingsBox:setOutfit({type = rndWings}) end if auraBox then auraBox:setOutfit({type = rndAura}) end out.shader = rndShader if shaderBox then shaderBox:setOutfit(out) end updateOutfit() refreshSelector("type") refreshSelector("mount") refreshSelector("wings") refreshSelector("aura") refreshSelector("shader") refreshSelector("runeeffect") end function randomizeOutfit() local out = typeBox:getOutfit() out.type = outfits_list[math.random(#outfits_list)][1] typeBox:setOutfit(out) updateOutfit() refreshSelector("type") end function randomizeColor() local outfitTemplate = { outfitWindow.head, outfitWindow.primary, outfitWindow.secondary, outfitWindow.detail } for i = 1, #outfitTemplate do outfitTemplate[i]:setChecked(true) colorBoxes[math.random(1, #colorBoxes)]:setChecked(true) outfitTemplate[i]:setChecked(false) end outfitTemplate[1]:setChecked(true) updateOutfit() end function accept() local outfit = typeBox:getOutfit() for _, child in pairs(outfitWindow.extensions:getChildren()) do local childId = child:getId() if childId == "healthBar" or childId == "manaBar" then outfit[childId] = child.bar.selected elseif child.creature and child.creature:getCreature() then if childId == "shader" then outfit[childId] = child.creature:getOutfit().shader elseif childId == "runeeffect" then outfit[childId] = child.creature:getOutfit().runeeffect or 0 else outfit[childId] = child.creature:getOutfit().type end end end g_game.changeOutfit(outfit) destroy() end