local characterPanel = nil local UI = nil local function close(parent) if table.empty(parent.subCategories) then return end for subId, _ in ipairs(parent.subCategories) do local subWidget = parent:getChildById(subId) if subWidget then subWidget:setVisible(false) end end parent:setHeight(parent.closedSize) parent.opened = false parent.Button.Arrow:setVisible(true) end local function reset() characterPanel.InfoBase.inventoryPanel:setVisible(true) characterPanel.InfoBase.outfitPanel:setVisible(false) if characterPanel.InfoBase.CharacterButton.state ~= 1 then Cyclopedia.characterButton(characterPanel.InfoBase.CharacterButton) end Cyclopedia.selectCharacterPage() characterPanel.openedCategory = nil end local function open(parent) local oldOpen = UI.openedCategory for subId, _ in ipairs(parent.subCategories) do local subWidget = parent:getChildById(subId) if subWidget then if tonumber(subWidget:getId()) == 1 then subWidget.Button.onClick(subWidget) end subWidget:setVisible(true) end end if oldOpen ~= nil and oldOpen ~= parent then close(oldOpen) end parent:setHeight(parent.openedSize) parent.opened = true parent.Button.Arrow:setVisible(false) UI.openedCategory = parent end function showCharacter() characterPanel = g_ui.loadUI("character", contentContainer) UI = characterPanel characterPanel:show() UI.selectedOption = "InfoBase" if g_game.isOnline() then local player = g_game.getLocalPlayer() UI.CharacterBase:setText(player:getName()) UI.CharacterBase.InfoLabel:setText(string.format("Level: %d\n%s", player:getLevel(), player:getVocationNameByClientId())) UI.CharacterBase.Outfit:setOutfit(player:getOutfit()) UI.InfoBase.outfitPanel.Sprite:setOutfit(player:getOutfit()) UI.InfoBase.InspectLabel:setText(tr("You are inspecting") .. ": " .. player:getName()) for i = InventorySlotFirst, InventorySlotPurse do local item = player:getInventoryItem(i) local itemWidget = UI.InfoBase.inventoryPanel["slot" .. i] if itemWidget then if item then itemWidget:setStyle("InventoryItemCyclopedia") itemWidget:setItem(item) ItemsDatabase.setRarityItem(itemWidget, itemWidget:getItem()) ItemsDatabase.setTier(itemWidget, itemWidget:getItem()) itemWidget:setIcon("") else itemWidget:setStyle(Cyclopedia.InventorySlotStyles[i].name) itemWidget:setIcon(Cyclopedia.InventorySlotStyles[i].icon) itemWidget:setItem(nil) end end end if g_game.isOnline() then Cyclopedia.createCharacterDescription() Cyclopedia.configureCharacterCategories() end end reset() controllerCyclopedia.ui.CharmsBase:setVisible(true) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) if g_game.getClientVersion() >= 1410 then controllerCyclopedia.ui.CharmsBase1410:setVisible(true) end end Cyclopedia.Character = {} Cyclopedia.Character.Achievements = {} Cyclopedia.InventorySlotStyles = { [InventorySlotHead] = { icon = "/images/game/slots/inventory-head", name = "HeadSlot" }, [InventorySlotNeck] = { icon = "/images/game/slots/inventory-neck", name = "NeckSlot" }, [InventorySlotBack] = { icon = "/images/game/slots/inventory-back", name = "BackSlot" }, [InventorySlotBody] = { icon = "/images/game/slots/inventory-torso", name = "BodySlot" }, [InventorySlotRight] = { icon = "/images/game/slots/inventory-right-hand", name = "RightSlot" }, [InventorySlotLeft] = { icon = "/images/game/slots/inventory-left-hand", name = "LeftSlot" }, [InventorySlotLeg] = { icon = "/images/game/slots/inventory-legs", name = "LegSlot" }, [InventorySlotFeet] = { icon = "/images/game/slots/inventory-feet", name = "FeetSlot" }, [InventorySlotFinger] = { icon = "/images/game/slots/inventory-finger", name = "FingerSlot" }, [InventorySlotAmmo] = { icon = "/images/game/slots/inventory-hip", name = "AmmoSlot" } } function Cyclopedia.characterAppearancesFilter(widget) local parent = widget:getParent() for i = 1, parent:getChildCount() do local child = parent:getChildByIndex(i) if child:getId() ~= "show" then child:setChecked(false) end end widget:setChecked(true) for _, data in ipairs(Cyclopedia.Character.Appearances) do if data.type == widget:getId() then data.visible = true else data.visible = false end end Cyclopedia.reloadCharacterAppearances() end function Cyclopedia.reloadCharacterAppearances() UI.CharacterAppearances.ListBase.list:destroyChildren() for _, data in ipairs(Cyclopedia.Character.Appearances) do if data.visible then local widget = g_ui.createWidget("CharacterAppearance", UI.CharacterAppearances.ListBase.list) widget.name:setText(data.name) widget.creature:setOutfit(data.outfit) widget.creature:getCreature():setStaticWalking(1000) end end end function Cyclopedia.loadCharacterAppearances(color, outfits, mounts, familiars) local data = {} local function insert(value, type) local lookData = value.lookType if type == "mounts" then lookData = value.mountId end local data_t = { visible = false, name = value.name, type = type, outfit = { auxType = 0, type = lookData, head = color.lookHead, body = color.lookBody, legs = color.lookLegs, feet = color.lookFeet, addon = outfits.addons and outfits.addons or 0 } } table.insert(data, data_t) end local function process(container, containerType) for i = 0, #container do local value = container[i] if value then insert(value, containerType) end end end process(outfits, "outfits") process(mounts, "mounts") process(familiars, "familiars") Cyclopedia.Character.Appearances = data Cyclopedia.characterAppearancesFilter(UI.CharacterAppearances.listFilter.outfits) end function Cyclopedia.characterItemsSearch(text) local filter = UI.CharacterItems.filters local activeFilters = {} for i = 1, filter:getChildCount() do local child = filter:getChildByIndex(i) if child:isChecked() then table.insert(activeFilters, child:getId()) end end for _, item in ipairs(Cyclopedia.Character.Items) do local data = item.data local name = data.name:lower() local meetsSearchCriteria = text == "" or string.find(name, text:lower()) ~= nil local meetsFilterCriteria = #activeFilters == 0 or table.contains(activeFilters, data.type) data.visible = meetsSearchCriteria and meetsFilterCriteria end Cyclopedia.reloadCharacterItems() end function Cyclopedia.characterItemsFilter(widget, force) if force then widget:setChecked(true) end local id = widget:getId() for _, item in ipairs(Cyclopedia.Character.Items) do local data = item.data if data.type == id then data.visible = widget:isChecked() end end Cyclopedia.reloadCharacterItems() end function Cyclopedia.reloadCharacterItems() UI.CharacterItems.ListBase.list:destroyChildren() UI.CharacterItems.gridBase.grid:destroyChildren() local colors = {"#484848", "#414141"} local colorIndex = 1 for _, item in ipairs(Cyclopedia.Character.Items) do local itemId, data = item.itemId, item.data if data.visible then local listItem = g_ui.createWidget("CharacterListItem", UI.CharacterItems.ListBase.list) listItem.item:setItemId(itemId) listItem.name:setText(data.name) ItemsDatabase.setRarityItem(listItem.item, listItem.item:getItem()) ItemsDatabase.setTier(listItem.item, item.tier) listItem.amount:setText(data.amount) listItem:setBackgroundColor(colors[colorIndex]) local gridItem = g_ui.createWidget("CharacterGridItem", UI.CharacterItems.gridBase.grid) gridItem.item:setItemId(itemId) gridItem.amount:setText(data.amount) ItemsDatabase.setRarityItem(gridItem.item, gridItem.item:getItem()) ItemsDatabase.setTier(gridItem.item, item.tier) colorIndex = 3 - colorIndex end end end function Cyclopedia.loadCharacterItems(data) local inventory = data.inventory local store = data.store local stash = data.stash local depot = data.depot local inbox = data.inbox Cyclopedia.Character.Items = {} local function insert(data, type) if not data then return end local thing = g_things.getThingType(data.itemId, ThingCategoryItem) local name = thing:getMarketData().name:lower() name = name ~= "" and name or "?" local data_t = { visible = false, name = name, amount = data.amount, type = type } local itemKey = data.itemId .. "-" .. (data.tier or "no_tier") local insertedItem = Cyclopedia.Character.Items[itemKey] if insertedItem and insertedItem.amount then insertedItem.amount = insertedItem.amount + data.amount else Cyclopedia.Character.Items[itemKey] = { itemId = data.itemId, tier = data.tier, data = data_t } end end local function processContainer(container, containerType) for i = 0, #container do local data = container[i] if data then insert(data, containerType) end end end processContainer(inventory, "inventory") processContainer(store, "store") processContainer(stash, "stash") processContainer(depot, "depot") processContainer(inbox, "inbox") local sortedItems = {} for _, itemData in pairs(Cyclopedia.Character.Items) do table.insert(sortedItems, itemData) end local function compareByName(a, b) local nameA = a.data.name:lower() local nameB = b.data.name:lower() if nameA ~= "?" and nameB == "?" then return true elseif nameA == "?" and nameB ~= "?" then return false else return nameA < nameB end end table.sort(sortedItems, compareByName) Cyclopedia.Character.Items = sortedItems Cyclopedia.characterItemsFilter(UI.CharacterItems.filters.inventory, true) end function Cyclopedia.loadCharacterAchievements() if not Cyclopedia.Character.Achievements.Loaded then UI.CharacterAchievements.sort:addOption("Alphabetically", 1, true) UI.CharacterAchievements.sort:addOption("By Grade", 2, true) UI.CharacterAchievements.sort:addOption("By Unlock Date", 3, true) Cyclopedia.achievementFilter(UI.CharacterAchievements.filters.accomplished) Cyclopedia.Character.Achievements.Loaded = true end end function Cyclopedia.characterItemListFilter(widget) local parent = widget:getParent() for i = 1, parent:getChildCount() do local child = parent:getChildByIndex(i) if child then child:setChecked(false) end end widget:setChecked(true) if widget:getId() == "list" then UI.CharacterItems.ListBase:setVisible(true) UI.CharacterItems.gridBase:setVisible(false) else UI.CharacterItems.ListBase:setVisible(false) UI.CharacterItems.gridBase:setVisible(true) end end function Cyclopedia.achievementFilter(widget) local parent = widget:getParent() for i = 1, parent:getChildCount() do local child = parent:getChildByIndex(i) if child then child:setChecked(false) end end if widget:getId() ~= "accomplished" then local last = Cyclopedia.Character.Achievements.lastSort last = last or 1 Cyclopedia.achievementSort(last) else UI.CharacterAchievements.ListBase.List:destroyChildren() end widget:setChecked(not widget:isChecked()) end function Cyclopedia.achievementSort(option) local tempTable = {} for id, data in pairs(ACHIEVEMENTS) do local tempData = { id = id, name = data.name, description = data.description, grade = data.grade } table.insert(tempTable, tempData) end if option == 1 then table.sort(tempTable, function(a, b) return a.name < b.name end) elseif option == 2 then table.sort(tempTable, function(a, b) return a.grade > b.grade end) end UI.CharacterAchievements.ListBase.List:destroyChildren() for _, data in pairs(tempTable) do local widget = g_ui.createWidget("Achievement", UI.CharacterAchievements.ListBase.List) widget:setId(data.id) widget.title:setText(data.name) widget.title = data.name widget:setText(data.description) widget.icon:setWidth(11 * data.grade) widget.grade = data.grade end Cyclopedia.Character.Achievements.lastSort = option end function Cyclopedia.loadCharacterRecentKills(data) UI.RecentKills.ListBase.List:destroyChildren() if not table.empty(data) then local color = "#484848" for i = 1, #data do local entry = data[i] local time = entry.timestamp local description = entry.description local status = entry.status local widget = g_ui.createWidget("CharacterKill", UI.RecentKills.ListBase.List) widget:setId(i) widget.date:setText(os.date("%Y-%m-%d, %H:%M:%S", time)) widget.description:setText(description) widget.status:setText(status) widget.color = color widget:setBackgroundColor(color) color = color == "#484848" and "#414141" or "#484848" function widget:onClick() local parent = widget:getParent() for y = 1, parent:getChildCount() do local child = parent:getChildByIndex(y) child:setChecked(false) child.date:setOn(false) child.description:setOn(false) child.status:setOn(false) end self:setChecked(not self:isChecked()) end function widget:onCheckChange() if self:isChecked() then self:setBackgroundColor("#585858") else self:setBackgroundColor(self.color) end self.date:setOn(not self:isOn()) self.description:setOn(not self:isOn()) self.status:setOn(not self:isOn()) end if i == 1 then widget:setChecked(true) end end end end function Cyclopedia.loadCharacterRecentDeaths(data) UI.RecentDeaths.ListBase.List:destroyChildren() if not table.empty(data) then local color = "#484848" for i = 1, #data do local entry = data[i] local widget = g_ui.createWidget("CharacterDeath", UI.RecentDeaths.ListBase.List) widget:setId(i) widget.date:setText(os.date("%Y-%m-%d, %H:%M:%S", entry.timestamp)) widget.cause:setText(entry.cause) widget.color = color widget:setBackgroundColor(color) color = color == "#484848" and "#414141" or "#484848" function widget:onClick() local parent = widget:getParent() for y = 1, parent:getChildCount() do local child = parent:getChildByIndex(y) child:setChecked(false) child.cause:setOn(false) child.date:setOn(false) end self:setChecked(not self:isChecked()) end function widget:onCheckChange() if self:isChecked() then self:setBackgroundColor("#585858") else self:setBackgroundColor(self.color) end self.cause:setOn(not self:isOn()) self.date:setOn(not self:isOn()) end if i == 1 then widget:setChecked(true) end end end end function Cyclopedia.loadCharacterCombatStats(data, mitigation, additionalSkillsArray, forgeSkillsArray, perfectShotDamageRanges, combatsArray, concoctionsArray) UI.CombatStats.attack.icon:setImageSource("/images/game/states/player-state-flags") UI.CombatStats.attack.icon:setImageClip((data.weaponElement * 9) .. ' 0 9 9') UI.CombatStats.attack.value:setText(data.weaponMaxHitChance) if data.weaponElementDamage > 0 then UI.CombatStats.converted.none:setVisible(false) UI.CombatStats.converted.value:setVisible(true) UI.CombatStats.converted.icon:setVisible(true) UI.CombatStats.converted.icon:setImageSource("/images/game/states/player-state-flags") UI.CombatStats.converted.icon:setImageClip((data.weaponElementType * 9) .. ' 0 9 9') UI.CombatStats.converted.value:setText(data.weaponElementDamage .. "%") else UI.CombatStats.converted.none:setVisible(true) UI.CombatStats.converted.value:setVisible(false) UI.CombatStats.converted.icon:setVisible(false) end UI.CombatStats.defence.value:setText(data.defense) UI.CombatStats.armor.value:setText(data.armor) UI.CombatStats.mitigation.value:setText(string.format("%.2f%%", mitigation)) UI.CombatStats.blessings.value:setText(string.format("%d/8", data.haveBlessings)) for i = 0, 6 do local id = "reduction_" .. i if UI.CombatStats[id] then UI.CombatStats[id]:destroy() end end UI.CombatStats.reductionNone:destroyChildren() if (next(combatsArray) == nil) then UI.CombatStats.reductionNone:setVisible(true) else UI.CombatStats.reductionNone:setVisible(true) for i = 1, #combatsArray do local widget = g_ui.createWidget("CharacterElementReduction", UI.CombatStats.reductionNone) widget:setId("reduction_" .. i) local element = Cyclopedia.clientCombat[combatsArray[i][1]] if element then widget.icon:setImageSource(element.path) widget.icon:setImageSize({ width = 9, height = 9 }) else print(string.format("WARNING: Element not found for combat array index %d with key %s.", i, tostring(combatsArray[i][1]))) end local valor = combatsArray[i][2] local porcentaje = valor / 100 local diferencia = 65535 - valor local porcentaje_negativo = diferencia / 100 local resultado if porcentaje <= porcentaje_negativo then resultado = string.format("+%.2f%%", porcentaje) widget.value:setColor("green") else resultado = string.format("-%.2f%%", porcentaje_negativo) widget.value:setColor("red") end widget.value:setText(resultado) if element then widget.name:setText(element.id) end widget:setMarginLeft(13) end end -- concoctions UI.CombatStats.concoctionPanel:destroyChildren() if concoctionsArray or next(concoctionsArray) ~= nil then for i = 1, #concoctionsArray do local widget = g_ui.createWidget("CharacterGridItem", UI.CombatStats.concoctionPanel) local itemId = concoctionsArray[i][1] widget:setId("concoction_" .. itemId) widget.item:setItemId(itemId) widget.item:setVirtual(true) local minutes = concoctionsArray[i][2] / 60 local itemName = widget.item:getItem():getMarketData().name widget.item:setTooltip(string.format("%s: %.0f minutes", itemName, minutes)) widget.amount:setVisible(false) end end local skillsIndexes = { [Skill.CriticalChance] = 1, [Skill.CriticalDamage] = 2, [Skill.LifeLeechAmount] = 3, [Skill.ManaLeechAmount] = 4 } -- Critical Chance local skillIndex = skillsIndexes[Skill.CriticalChance] local skill = additionalSkillsArray[skillIndex][2] UI.CombatStats.criticalChance.value:setText(string.format("%.2f%%", skill / 100)) if skill > 0 then UI.CombatStats.criticalChance.value:setColor("#44AD25") else UI.CombatStats.criticalChance.value:setColor("#C0C0C0") end -- Critical Damage skillIndex = skillsIndexes[Skill.CriticalDamage] skill = additionalSkillsArray[skillIndex][2] UI.CombatStats.criticalDamage.value:setText(string.format("%.2f%%", skill / 100)) if skill > 0 then UI.CombatStats.criticalDamage.value:setColor("#44AD25") else UI.CombatStats.criticalDamage.value:setColor("#C0C0C0") end -- Life Leech Amount skillIndex = skillsIndexes[Skill.LifeLeechAmount] skill = additionalSkillsArray[skillIndex][2] if skill > 0 then UI.CombatStats.lifeLeech.value:setColor("#44AD25") UI.CombatStats.lifeLeech.value:setText(string.format("%.2f%%", skill / 100)) else UI.CombatStats.lifeLeech.value:setColor("#C0C0C0") UI.CombatStats.lifeLeech.value:setText(string.format("%d%%", skill)) end -- Mana Leech Amount skillIndex = skillsIndexes[Skill.ManaLeechAmount] skill = additionalSkillsArray[skillIndex][2] if skill > 0 then UI.CombatStats.manaLeech.value:setColor("#44AD25") UI.CombatStats.manaLeech.value:setText(string.format("%.2f%%", skill / 100)) else UI.CombatStats.manaLeech.value:setColor("#C0C0C0") UI.CombatStats.manaLeech.value:setText(string.format("%d%%", skill)) end for i = 1, #forgeSkillsArray do local skillId = forgeSkillsArray[i][1] local id = "special_" .. skillId if UI.CombatStats[id] then UI.CombatStats[id]:destroy() end end local firstSpecial = true for i = 1, #forgeSkillsArray do local skillId = forgeSkillsArray[i][1] local percent = forgeSkillsArray[i][2] if percent > 0 then local widget = g_ui.createWidget("CharacterSkillBase", UI.CombatStats) widget:setId("special_" .. skillId) local specialName = { [13] = "Onslaught", [14] = "Ruse", [15] = "Momentum", [16] = "Transcendence" } if firstSpecial then widget:addAnchor(AnchorTop, "manaLeech", AnchorBottom) widget:addAnchor(AnchorLeft, "criticalHit", AnchorLeft) widget:addAnchor(AnchorRight, "parent", AnchorRight) widget:setMarginTop(5) else widget:addAnchor(AnchorTop, "prev", AnchorBottom) widget:addAnchor(AnchorLeft, "criticalHit", AnchorLeft) widget:addAnchor(AnchorRight, "parent", AnchorRight) widget:setMarginTop(0) end widget:setMarginLeft(0) local name = g_ui.createWidget("SkillNameLabel", widget) name:setText(specialName[skillId]) name:setColor("#C0C0C0") local value = g_ui.createWidget("SkillValueLabel", widget) value:setText(string.format("%.2f%%", percent / 100)) value:setColor("#C0C0C0") value:setMarginRight(2) value:setColor("#C0C0C0") firstSpecial = firstSpecial and false end end end function Cyclopedia.loadCharacterGeneralStats(data, skills) local player = g_game.getLocalPlayer() if not player then return end local function format(value) local totalMinutes = value / 60 local hours = math.floor(totalMinutes / 60) local minutes = math.floor(totalMinutes % 60) if hours < 10 then hours = "0" .. hours end if minutes < 10 then minutes = "0" .. minutes end return hours .. ":" .. minutes end Cyclopedia.setCharacterSkillValue("level", comma_value(data.level)) local text = tr("You have %s percent to go ", 100 - data.levelPercent) Cyclopedia.setCharacterSkillPercent("level", data.levelPercent, text) Cyclopedia.setCharacterSkillValue("experience", comma_value(player:getExperience())) local expGainRate = data.baseExpGain + data.XpBoostPercent local hasStoreExpBonus = data.XpBoostPercent > 0 local hasStaminaBonus = data.staminaMinutes / 60 >= 3 expGainRate = hasStaminaBonus and expGainRate * 1.5 or expGainRate local staminaBonusTime = string.format("%02d:%02d", math.floor(math.min(data.staminaMinutes, 180) / 60), math.min(data.staminaMinutes, 180) % 60) local storeExpBonusTime = format(data.XpBoostBonusRemainingTime) local expGainRateTooltip = string.format( "Your current XP gain rate amounts to %d%%.\nYour XP gain rate is calculated as follows:\n- Base XP gain rate: %d%%", expGainRate, data.baseExpGain) expGainRateTooltip = hasStoreExpBonus and expGainRateTooltip .. string.format("\n- XP boost: %d%% (%s h remaining).", data.XpBoostPercent, storeExpBonusTime) or expGainRateTooltip expGainRateTooltip = hasStaminaBonus and expGainRateTooltip .. string.format("\n- Stamina bonus: x1.5 (%s h remaining).", staminaBonusTime) or expGainRateTooltip UI.CharacterStats.expGainRate:setTooltip(expGainRateTooltip) -- UI.CharacterStats.expGainRate:setTooltipAlign(AlignTopLeft) Cyclopedia.setCharacterSkillValue("expGainRate", comma_value(expGainRate) .. "%") Cyclopedia.setCharacterSkillValue("health", comma_value(data.maxHealth)) Cyclopedia.setCharacterSkillValue("mana", comma_value(data.mana)) Cyclopedia.setCharacterSkillValue("soul", data.soul) Cyclopedia.setCharacterSkillValue("capacity", comma_value(math.floor(player:getFreeCapacity()))) if data.speed > 0 then UI.CharacterStats.speed.value:setColor("#44AD25") else UI.CharacterStats.speed.value:setColor("#C0C0C0") end Cyclopedia.setCharacterSkillValue("speed", comma_value(math.floor(data.speed))) Cyclopedia.setCharacterSkillValue("food", format(data.regenerationCondition)) local function formatTime(time) local hours = math.floor(time / 60) local minutes = time % 60 if minutes < 10 then minutes = "0" .. minutes end return hours, minutes end local staminaPercent = math.floor(100 * data.staminaMinutes / 2520) local staminaHours, staminaMinutes = formatTime(data.staminaMinutes) Cyclopedia.setCharacterSkillValue("stamina", staminaHours .. ":" .. staminaMinutes) if data.staminaMinutes > 2400 and g_game.getClientVersion() >= 1038 and player:isPremium() then local text = tr("You have %s hours and %s minutes left", staminaHours, staminaMinutes) .. "\n" .. tr("Now you will gain 50%% more experience") Cyclopedia.setCharacterSkillPercent("stamina", staminaPercent, text, "green") elseif data.staminaMinutes > 2400 and g_game.getClientVersion() >= 1038 and not player:isPremium() then local text = tr("You have %s hours and %s minutes left", staminaHours, staminaMinutes) .. "\n" .. tr( "You will not gain 50%% more experience because you aren't premium player, now you receive only 1x experience points") Cyclopedia.setCharacterSkillPercent("stamina", staminaPercent, text, "#89F013") elseif data.staminaMinutes <= 840 and data.staminaMinutes > 0 then local text = tr("You have %s hours and %s minutes left", staminaHours, staminaMinutes) .. "\n" .. tr("You gain only 50%% experience and you don't may gain loot from monsters") Cyclopedia.setCharacterSkillPercent("stamina", staminaPercent, text, "red") elseif data.staminaMinutes == 0 then local text = tr("You have %s hours and %s minutes left", staminaHours, staminaMinutes) .. "\n" .. tr("You don't may receive experience and loot from monsters") Cyclopedia.setCharacterSkillPercent("stamina", staminaPercent, text, "black") end local trainerHours, trainerMinutes = formatTime(data.offlineTrainingTime) local trainerPercent = 100 * data.offlineTrainingTime / 720 Cyclopedia.setCharacterSkillValue("trainer", trainerHours .. ":" .. trainerMinutes) Cyclopedia.setCharacterSkillPercent("trainer", trainerPercent, tr("You have %s percent", trainerPercent)) Cyclopedia.setCharacterSkillValue("magiclevel", data.magicLevel) Cyclopedia.setCharacterSkillPercent("magiclevel", data.magicLevelPercent / 100, tr("You have %s percent to go", 100 - data.magicLevelPercent / 100)) Cyclopedia.setCharacterSkillBase("magiclevel", data.magicLevel, data.baseMagicLevel) for i = Skill.Fist + 1, Skill.Fishing + 1 do local skillLevel, baseSkill, skillPercent = unpack(skills[i]) Cyclopedia.onSkillChange(player, i - 1, skillLevel, skillPercent) Cyclopedia.onBaseCharacterSkillChange(player, i - 1, baseSkill) end end function Cyclopedia.setCharacterSkillValue(id, value, color) local skill = UI.CharacterStats:recursiveGetChildById(id) local widget = skill:getChildById("value") widget:setText(value) widget:setColor(color) end function Cyclopedia.setCharacterSkillPercent(id, percent, tooltip, color) local skill = UI.CharacterStats:recursiveGetChildById(id) local widget = skill:getChildById("percent") if widget then widget:setPercent(math.floor(percent)) if tooltip then widget:setTooltip(tooltip) end if color then widget:setBackgroundColor(color) end end end function Cyclopedia.setCharacterSkillBase(id, value, baseValue) if baseValue <= 0 or value < 0 then return end local skill = UI.CharacterStats:recursiveGetChildById(id) local widget = skill:getChildById("value") if baseValue < value then widget:setColor("#44AD25") skill:setTooltip(baseValue .. " +" .. value - baseValue) elseif value < baseValue then widget:setColor("#b22222") skill:setTooltip(baseValue .. " " .. value - baseValue) else widget:setColor("#bbbbbb") skill:removeTooltip() end end function Cyclopedia.onBaseCharacterSkillChange(localPlayer, id, baseLevel) Cyclopedia.setCharacterSkillBase("skillId" .. id, localPlayer:getSkillLevel(id), baseLevel) end function Cyclopedia.onSkillChange(localPlayer, id, level, percent) Cyclopedia.setCharacterSkillValue("skillId" .. id, level) Cyclopedia.setCharacterSkillPercent("skillId" .. id, percent, tr("You have %s percent to go", 100 - percent)) Cyclopedia.onBaseCharacterSkillChange(localPlayer, id, localPlayer:getSkillBaseLevel(id)) end function Cyclopedia.selectCharacterPage() local selectedOption = UI.selectedOption UI[selectedOption]:setVisible(false) UI.InfoBase:setVisible(true) Cyclopedia.closeCharacterButtons() local oldOpen = UI.openedCategory if oldOpen ~= nil then close(oldOpen) end UI.selectedOption = "InfoBase" end function Cyclopedia.closeCharacterButtons() local size = UI.OptionsBase:getChildCount() for i = 1, size do local widget = UI.OptionsBase:getChildByIndex(i) if widget then if widget.subCategories ~= nil then for subId, _ in ipairs(widget.subCategories) do local subWidget = widget:getChildById(subId) if subWidget then subWidget.Button:setChecked(false) subWidget.Button.Arrow:setVisible(false) subWidget.Button.Icon:setChecked(false) end end else widget.Button:setChecked(false) widget.Button.Arrow:setVisible(false) widget.Button.Icon:setChecked(false) end end end end function Cyclopedia.configureCharacterCategories() UI.OptionsBase:destroyChildren() local buttons = { { text = "General Stats", icon = "/game_cyclopedia/images/character_icons/icon_generalstats", subCategories = function() local categories = { { text = "Character Stats", icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-overview", open = "CharacterStats" } } if g_game.getClientVersion() < 1410 then table.insert(categories, { text = "Combat Stats", icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-combatstats", open = "CombatStats" }) else table.insert(categories, { text = "Offence Stats", icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-combatstats", open = "OffenceStats" }) table.insert(categories, { text = "Defence Stats", icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-defence", open = "DeffenceStats" }) table.insert(categories, { text = "Misc. Stats", icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-misc", open = "MiscStats" }) end return categories end }, { text = "Battle Results", icon = "/game_cyclopedia/images/character_icons/icon_battleresults", subCategories = { { text = "Recent Deaths", icon = "/game_cyclopedia/images/character_icons/icon-character-battleresults-recentdeaths", open = "RecentDeaths" }, { text = "Recent PvP Kills", icon = "/game_cyclopedia/images/character_icons/icon-character-battleresults-recentpvpkills", open = "RecentKills" } } }, { text = "Achievements", icon = "/game_cyclopedia/images/character_icons/icon_achievement", open = "CharacterAchievements" }, { text = "Item Summary", icon = "/game_cyclopedia/images/character_icons/icon_items", open = "CharacterItems" }, { text = "Appearances", icon = "/game_cyclopedia/images/character_icons/icon_outfitsmounts", open = "CharacterAppearances" }, { text = "Store Summary", icon = "/game_cyclopedia/images/character_icons/icon-character-store", open = "StoreSummary" }, { text = "Character Titles", icon = "/game_cyclopedia/images/character_icons/icon-character-titles", open = "CharacterTitles" } } for id, button in ipairs(buttons) do local widget = g_ui.createWidget("CharacterCategoryItem", UI.OptionsBase) widget:setId(id) widget.Button.Icon:setIcon(button.icon) widget.Button.Title:setText(button.text) if button.open ~= nil then widget.open = button.open end if button.subCategories ~= nil then local subCats = button.subCategories if type(subCats) == "function" then subCats = subCats() end widget.subCategories = subCats widget.subCategoriesSize = #subCats widget.Button.Arrow:setVisible(true) for subId, subButton in ipairs(subCats) do local subWidget = g_ui.createWidget("CharacterCategoryItem", widget) subWidget:setId(subId) subWidget.Button.Icon:setIcon(subButton.icon) subWidget.Button.Title:setText(subButton.text) subWidget:setVisible(false) subWidget.open = subButton.open function subWidget.Button:onClick(test) local selectedOption = UI.selectedOption Cyclopedia.closeCharacterButtons() subWidget.Button:setChecked(true) subWidget.Button.Arrow:setVisible(true) subWidget.Button.Arrow:setImageSource("/game_cyclopedia/images/icon-arrow7x7-right") subWidget.Button.Icon:setChecked(true) UI[selectedOption]:setVisible(false) UI[subWidget.open]:setVisible(true) if subWidget.open == "CharacterStats" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.GeneralStats) g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Badges) elseif subWidget.open == "CombatStats" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.CombatStats) elseif subWidget.open == "OffenceStats" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Offencestats) elseif subWidget.open == "DeffenceStats" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Defencestats) elseif subWidget.open == "MiscStats" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Miscstats) elseif subWidget.open == "RecentDeaths" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.RecentDeaths, 23, 1) elseif subWidget.open == "RecentKills" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.RecentPVPKills, 23, 1) end UI.selectedOption = subWidget.open end if subId == 1 then subWidget:addAnchor(AnchorTop, "parent", AnchorTop) subWidget:addAnchor(AnchorHorizontalCenter, "parent", AnchorHorizontalCenter) subWidget:setMarginTop(20) else subWidget:addAnchor(AnchorTop, "prev", AnchorBottom) subWidget:addAnchor(AnchorHorizontalCenter, "parent", AnchorHorizontalCenter) subWidget:setMarginTop(-1) end end end if id == 1 then widget:addAnchor(AnchorTop, "parent", AnchorTop) widget:addAnchor(AnchorHorizontalCenter, "parent", AnchorHorizontalCenter) widget:setMarginTop(5) else widget:addAnchor(AnchorTop, "prev", AnchorBottom) widget:addAnchor(AnchorHorizontalCenter, "parent", AnchorHorizontalCenter) widget:setMarginTop(5) end function widget.Button.onClick(this) if widget.open == "CharacterAchievements" then Cyclopedia.loadCharacterAchievements() elseif widget.open == "CharacterItems" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.ItemSummary) Cyclopedia.characterItemListFilter(UI.CharacterItems.listFilter.list) elseif widget.open == "CharacterAppearances" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.OutfitsAndMounts) elseif widget.open == "StoreSummary" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.StoreSummary) end local parent = this:getParent() if parent.subCategoriesSize ~= nil then if parent.closedSize == nil then parent.closedSize = parent:getHeight() / (parent.subCategoriesSize + 1) + 15 end if parent.openedSize == nil then parent.openedSize = parent:getHeight() * (parent.subCategoriesSize + 1) - 6 end open(parent) else local oldOpen = UI.openedCategory local selectedOption = UI.selectedOption Cyclopedia.closeCharacterButtons() this.Arrow:setImageSource("/game_cyclopedia/images/icon-arrow7x7-right") this.Arrow:setVisible(true) if oldOpen ~= nil and oldOpen ~= parent then close(oldOpen) end this:setChecked(true) this.Icon:setChecked(true) UI[selectedOption]:setVisible(false) UI[parent.open]:setVisible(true) UI.selectedOption = parent.open end end end end function Cyclopedia.createCharacterDescription() UI.InfoBase.DetailsBase.List:destroyChildren() local player = g_game.getLocalPlayer() local descriptions = { { Level = player:getLevel() }, { Vocation = player:getVocationNameByClientId() }, { loyaltyTitle = "?" }, { Prey = "?" }, { Outfit = "?" }, { } } for _, description in ipairs(descriptions) do local widget = g_ui.createWidget("UIWidget", UI.InfoBase.DetailsBase.List) for key, value in pairs(description) do widget:setText(key .. ": " .. value) widget:setColor("#C0C0C0") end widget:setTextWrap(true) end end function Cyclopedia.characterButton(widget) if widget.state == 1 then widget.state = 2 widget:setIcon("/game_cyclopedia/images/icon-equipmentdetails") UI.InfoBase.inventoryPanel:setVisible(false) UI.InfoBase.outfitPanel:setVisible(true) else widget.state = 1 widget:setIcon("/game_cyclopedia/images/icon-playerdetails") UI.InfoBase.inventoryPanel:setVisible(true) UI.InfoBase.outfitPanel:setVisible(false) end end function Cyclopedia.loadCharacterBadges(showAccountInformation, playerOnline, playerPremium, loyaltyTitle, badgesVector) UI.CharacterStats.ListBadge:destroyChildren() local playerOnlineStatus = "Offline" local playerOnlineStatusColor = "#ff0000" if playerOnline == 1 then playerOnlineStatus = "Online" playerOnlineStatusColor = "#00ff00" end local accountStatus = "Free" local accountStatusColor = "#ff0000" if playerPremium == 1 then accountStatus = "Premium" accountStatusColor = "#00ff00" end if not loyaltyTitle or loyaltyTitle == "" then loyaltyTitle = "None" end Cyclopedia.setCharacterSkillValue("accountStatus", accountStatus, accountStatusColor) Cyclopedia.setCharacterSkillValue("accountOnline", playerOnlineStatus, playerOnlineStatusColor) Cyclopedia.setCharacterSkillValue("loyaltyTitle", loyaltyTitle) for _, badge in ipairs(badgesVector) do local cell = g_ui.createWidget("CharacterBadge", UI.CharacterStats.ListBadge) if cell then cell:setImageClip(getImageClip(badge[1])) cell:setTooltip(badge[2]) end end end function getImageClip(elementIndex) local elementSize = 64 local elementsPerRow = 21 local y = 0 local x = (elementIndex - 1) * elementSize local imageClip = string.format("%d %d %d %d", x, y, elementSize, elementSize) return imageClip end function Cyclopedia.onParseCyclopediaStoreSummary(xpBoostTime, dailyRewardXpBoostTime, blessings, preySlotsUnlocked, preyWildcards, hasPermanentWeeklyTaskExpansion, instantRewards, hasCharmExpansion, hirelingsObtained, hirelingSkills, houseItems) UI.StoreSummary.ListBase.List.XPBoosts.RemainingStoreXPBoostTimeValue:setText(string.format("%02d:%02d", math.floor(xpBoostTime / 3600), math.floor((xpBoostTime % 3600) / 60))) UI.StoreSummary.ListBase.List.XPBoosts.RemainingDailyRewardXPBoostTimeValue:setText(string.format("%02d:%02d", math.floor(dailyRewardXpBoostTime / 3600), math.floor((dailyRewardXpBoostTime % 3600) / 60))) local panel = UI.StoreSummary.ListBase.List.Blessings.PurchasedHouseItems for _, blessing in ipairs(blessings) do local row = g_ui.createWidget('BlessCreate', panel) row.text1:setText(blessing[1]) row.text2:setText("x" .. blessing[2]) end UI.StoreSummary.ListBase.List.preyPanel.PermanentPreySlotsValue:setText(preySlotsUnlocked) UI.StoreSummary.ListBase.List.preyPanel.PreyWildcardsValue:setText(preyWildcards) UI.StoreSummary.ListBase.List.taskBoard.permanentWeeklyTaskExpansionValue:setText(hasPermanentWeeklyTaskExpansion and "Yes" or "No") UI.StoreSummary.ListBase.List.dailyReward.InstantRewardAccessValue:setText(instantRewards) if hasCharmExpansion then UI.StoreSummary.ListBase.List.CharmPanel.CharmExpansionValue:setText("Yes") else UI.StoreSummary.ListBase.List.CharmPanel.CharmExpansionValue:setText("No") end UI.StoreSummary.ListBase.List.hirelings.PurchasedHirelingsValue:setText(hirelingsObtained) local rowHeight = 130 local maxVisibleRows = 1.6 local itemCount = #houseItems UI.StoreSummary.ListBase.List.houseItems:setHeight(math.min(itemCount, maxVisibleRows) * rowHeight) UI.StoreSummary.ListBase.List.houseItems.PurchasedHouseItems:destroyChildren() for _, item in ipairs(houseItems) do local row = g_ui.createWidget('RowStore2', UI.StoreSummary.ListBase.List.houseItems.PurchasedHouseItems) local nameLabel = row:getChildById('lblName') nameLabel:setText(item[2]) nameLabel:setTextAlign(AlignCenter) nameLabel:setMarginRight(10) row:getChildById('lblPrice'):setText(item[3]) local itemWidget = g_ui.createWidget('Item', row:getChildById('image')) itemWidget:setId(item[1]) itemWidget:setItemId(item[1]) itemWidget:fill('parent') end end -- Mapping from HardcodedSkillIds (protocol 15.x) to display names -- Used for extra damage/spell/healing skills from the server local hardcodedSkillNames = { [1] = "Magic Level", [6] = "Shielding", [7] = "Distance Fighting", [8] = "Sword Fighting", [9] = "Club Fighting", [10] = "Axe Fighting", [11] = "Fist Fighting", [13] = "Fishing" } local function getHardcodedSkillName(skillType) return hardcodedSkillNames[skillType] or "Fighting Skill" end local weaponSkillNames = { [0] = "Fist Fighting", [1] = "Club Fighting", [2] = "Sword Fighting", [3] = "Axe Fighting", [4] = "Distance Fighting", [5] = "Shielding", [6] = "Fishing", [7] = "Magic Level", [8] = "Critical Hits", [9] = "Life Leech", [10] = "Mana Leech" } local function getWeaponSkillName(skillType) return weaponSkillNames[skillType] or "Fighting Skill" end local elementNames = { [0] = "Physical", [1] = "Fire", [2] = "Earth", [3] = "Energy", [4] = "Ice", [5] = "Holy", [6] = "Death", [7] = "Healing", [8] = "Drowning", [9] = "Life Drain", [10] = "Mana Drain", [11] = "Agony" } local function getElementName(id) return elementNames[id] or "Unknown" end -- Shared rendering function for Offence/Defence/Misc stat panels -- combatTable: the element-to-info mapping table (e.g. clientCombat or Cyclopedia.clientCombat) local function renderCharacterStat(stat, leftPanel, rightPanel, combatTable) local parent = stat.parent == "right" and rightPanel or leftPanel if stat.align == "center" then local widget = g_ui.createWidget("Label", parent) local valueText = stat.value if type(valueText) == "number" and stat.percent then local percentValue = math.floor(valueText * 10000) / 100 local sign = percentValue > 0 and "+ " or "" valueText = sign .. percentValue .. "%" elseif type(valueText) == "number" then valueText = tostring(valueText) end widget:setText(" " .. valueText .. " " .. stat.name) widget:setTextOffset("29 0") widget:setHeight(16) return widget else local widget = g_ui.createWidget("CharacterSkillBase", parent) local nameLabel = g_ui.createWidget("SkillNameLabel", widget) nameLabel:setText(stat.name .. ":") if string.find(stat.name, "\n") then nameLabel:setTextWrap(true) nameLabel:setTextAutoResize(true) widget:setHeight(math.max(widget:getHeight(), nameLabel:getHeight() + 4)) end local valueLabel = g_ui.createWidget("SkillValueLabel", widget) if stat.percent and type(stat.value) == "number" then local percentValue = math.floor(stat.value * 10000) / 100 local sign = percentValue > 0 and "+ " or "" valueLabel:setText(sign .. percentValue .. "%") else valueLabel:setText(tostring(stat.value)) end if stat.color then valueLabel:setColor(stat.color) end if stat.icon then valueLabel:setMarginRight(12) local icon = g_ui.createWidget("SkillCharacterIcon", widget) icon:setMarginTop(2) icon:addAnchor(AnchorRight, "parent", AnchorRight) local elementKey = stat.element or stat.weaponElement if elementKey and combatTable then local element = combatTable[elementKey] if element then icon:setImageSource(element.path) icon:setImageSize({ width = 9, height = 9 }) end end end return widget end end local function renderAndSkipZeroStats(stats, leftPanel, rightPanel, combatTable) for _, stat in ipairs(stats) do if stat.align ~= "center" and type(stat.value) == "number" and stat.value == 0 then -- Skip stats with zero values (headers use "" which won't match) else renderCharacterStat(stat, leftPanel, rightPanel, combatTable) end end end function Cyclopedia.onCyclopediaCharacterOffenceStats(data) -- TODO UI/UX , verticalScroll, add new stats local container = UI.OffenceStats.content.statsContainer local leftPanel = container.leftPanel local rightPanel = container.rightPanel rightPanel:destroyChildren() leftPanel:destroyChildren() -- Initial Stats List local stats = { -- Left Panel Items {name = "Flat Damage and healing", value = data.flatDamage or 0, icon = false, percent = false}, {name = "From Character Level", value = data.flatDamageBase or 0, align = "center", percent = false, icon = false}, {name = "From Wheel of destiny", value = data.flatDamageWheel or 0, align = "center", percent = false, icon = false}, {name = "Attack Value", value = data.weaponAttack, icon = true, weaponElement = data.weaponElementType}, {name = "From Flat Bonus", value = data.weaponFlatModifier or 0, align = "center", icon = false}, {name = "From Magic Level", value = data.weaponDamage or 0, align = "center", icon = false}, {name = "From Equipment", value = data.weaponSkillLevel or 0, align = "center", icon = false}, {name = "From Combat Tactics", value = data.weaponSkillModifier or 0, align = "center", icon = false}, {name = "Converted Damage", value = data.weaponElementDamage, icon = true, weaponElement = data.weaponElementType, percent = true}, {name = "Life Leech", value = data.lifeLeechTotal or data.lifeLeech or 0, icon = false, percent = true}, {name = "From Equipment", value = data.lifeLeechBase or data.lifeLeechEquipament or 0, align = "center", percent = true, icon = false}, {name = "From Imbuements", value = data.lifeLeechImbuement or 0, align = "center", percent = true, icon = false}, {name = "From Wheel of Destiny", value = data.lifeLeechWheel or 0, align = "center", percent = true, icon = false}, {name = "From Event Bonus", value = data.lifeLeechEventBonus or 0, align = "center", percent = true, icon = false}, {name = "Life Gain on hit", value = data.lifeGainHit or 0, icon = false, percent = false}, {name = "Life Gain on kill", value = data.lifeGainKill or 0, icon = false, percent = false}, {name = "Mana Leech", value = data.manaLeechTotal or data.manaLeech or 0, icon = false, percent = true}, {name = "From Base", value = data.manaLeechBase or data.manaLeechEquipament or 0, align = "center", percent = true, icon = false}, {name = "From Equipment", value = data.manaLeechImbuement or 0, align = "center", percent = true, icon = false}, {name = "From Wheel", value = data.manaLeechWheel or 0, align = "center", percent = true, icon = false}, {name = "From Event", value = data.manaLeechEventBonus or 0, align = "center", percent = true, icon = false}, {name = "Mana Gain on hit", value = data.manaGainHit or 0, icon = false, percent = false}, {name = "Mana Gain on kill", value = data.manaGainKill or 0, icon = false, percent = false}, {name = "Onslaught", value = data.onslaught or 0, icon = false, percent = true}, {name = "From Equipment", value = data.onslaughtBase or 0, align = "center", percent = true, icon = false}, {name = "From Amplification", value = data.onslaughtBonus or 0, align = "center", percent = true, icon = false}, {name = "From Event Bonus", value = data.onslaughtEventBonus or 0, align = "center", percent = true, icon = false}, {name = "Damage against Specific Targets", value = "", icon = false, percent = false}, {name = "against Powerful Foes", value = data.damagePowerfulFoes or 0, align = "center", percent = true, icon = false}, } -- Dynamic Left/Mixed Panel Lists -- Dynamic target-specific damage entries from the server if data.damageSpecificTargets and #data.damageSpecificTargets > 0 then for _, target in ipairs(data.damageSpecificTargets) do table.insert(stats, {name = "against " .. (target.name or "Unknown"), value = target.value or 0, align = "center", percent = true, icon = false}) end end if data.extraDamageSkills and #data.extraDamageSkills > 0 then table.insert(stats, {name = "Auto-Attack Extra Damage", value = "", icon = false, percent = false}) for _, skill in ipairs(data.extraDamageSkills) do table.insert(stats, {name = "from " .. getHardcodedSkillName(skill.skillId), value = skill.valueA or 0, align = "center", percent = false, icon = false}) end end if data.extraDamageSpells and #data.extraDamageSpells > 0 then table.insert(stats, {name = "Extra Spell Damage", value = "", icon = false, percent = false}) for _, skill in ipairs(data.extraDamageSpells) do table.insert(stats, {name = "from " .. getHardcodedSkillName(skill.skillId), value = skill.valueA or 0, align = "center", percent = false, icon = false}) end end if data.extraHealingSpells and #data.extraHealingSpells > 0 then table.insert(stats, {name = "Extra Spell Healing", value = "", icon = false, percent = false}) for _, skill in ipairs(data.extraHealingSpells) do table.insert(stats, {name = "from " .. getHardcodedSkillName(skill.skillId), value = skill.valueA or 0, align = "center", percent = false, icon = false}) end end -- Right Panel Items (Critical Hit Section) table.insert(stats, {name = "Critical Hit", parent = "right", value = "", icon = false}) table.insert(stats, {name = " Chance", parent = "right", value = data.critChanceTotal or data.critChance or 0, percent = true, icon = false}) table.insert(stats, {name = " from flat Bonus", parent = "right", align = "center", value = data.critChanceEquipament or 0, percent = true, icon = false}) table.insert(stats, {name = " from Equipment", parent = "right", align = "center", value = data.critChanceFlat or 0, percent = true, icon = false}) table.insert(stats, {name = " from Imbuement", parent = "right", align = "center", value = data.critChanceImbuement or 0, percent = true, icon = false}) table.insert(stats, {name = " from Wheel of destiny", parent = "right", align = "center", value = data.critChanceWheel or 0, percent = true, icon = false}) table.insert(stats, {name = " from Concoction", parent = "right", align = "center", value = data.critChanceConcoction or 0, percent = true, icon = false}) -- Critical Chance by Type table.insert(stats, {name = "Critical Chance by Type", parent = "right", value = "", percent = false, icon = false}) if data.damageElements and #data.damageElements > 0 then for _, el in ipairs(data.damageElements) do table.insert(stats, { name = " from " .. getElementName(el.element), parent = "right", value = el.value, align = "center", percent = true, icon = false }) end end table.insert(stats, {name = " from Offensive Runes", parent = "right", value = data.offensiveRuneDamage or 0, align = "center", percent = true, icon = false}) table.insert(stats, {name = " from Auto Attack", parent = "right", value = data.autoAttackDamage or 0, align = "center", percent = true, icon = false}) -- Critical Damage table.insert(stats, {name = " Extra damage", parent = "right", value = data.critDamageTotal or 0, icon = false, percent = true}) table.insert(stats, {name = " from flat Bonus", parent = "right", align = "center", value = data.critDamageEquipament or 0, percent = true, icon = false}) table.insert(stats, {name = " from Equipment", parent = "right", align = "center", value = data.critDamageFlat or 0, percent = true, icon = false}) table.insert(stats, {name = " from Imbuement", parent = "right", align = "center", value = data.critDamageImbuement or 0, percent = true, icon = false}) table.insert(stats, {name = " from Wheel of destiny", parent = "right", align = "center", value = data.critDamageWheel or 0, percent = true, icon = false}) table.insert(stats, {name = " from Concoction", parent = "right", align = "center", value = data.critDamageConcoction or 0, percent = true, icon = false}) -- Critical Damage by Type table.insert(stats, {name = "Critical Damage by Type", parent = "right", value = "", percent = false, icon = false}) if data.critDamageElements and #data.critDamageElements > 0 then for _, el in ipairs(data.critDamageElements) do table.insert(stats, { name = " from " .. getElementName(el.element), parent = "right", value = el.value, align = "center", percent = true, icon = false }) end end table.insert(stats, {name = " from Offensive Runes", parent = "right", value = data.critDamageOffensiveRunes or 0, align = "center", percent = true, icon = false}) table.insert(stats, {name = " from Auto Attack", parent = "right", value = data.critDamageAutoAttack or 0, align = "center", percent = true, icon = false}) -- Cleave & Distance table.insert(stats, {name = "Cleave", parent = "right", value = data.cleavePercent or 0, icon = false, percent = true}) -- Distance Fighting Accuracy: render dynamically from parsed accuracy data if data.weaponAccuracy and #data.weaponAccuracy > 0 then table.insert(stats, {name = "Distance Fighting Accuracy", parent = "right", value = "", icon = false, percent = false}) for _, acc in ipairs(data.weaponAccuracy) do table.insert(stats, { name = " Range " .. acc.range, parent = "right", value = acc.chance or 0, align = "center", percent = true, icon = false }) end end -- Perfect Shot if data.perfectShotDamage then for i = 1, #data.perfectShotDamage do if data.perfectShotDamage[i] and data.perfectShotDamage[i] > 0 then if i == 1 then table.insert(stats, {name = "Perfect Shot Damage Bonus", parent = "right", value = "", icon = false}) end table.insert(stats, { name = " from Range " .. i, parent = "right", value = "+" .. data.perfectShotDamage[i], align = "center", icon = false }) end end end -- Target Specific & Armor Pen table.insert(stats, {name = "Damage Against Targets \nAbove 95% hit points", parent = "right", value = data.damageHighHp or 0, icon = false, percent = true}) table.insert(stats, {name = "Damage Against Targets \nBelow 30% hit points", parent = "right", value = data.damageLowHp or 0, icon = false, percent = true}) table.insert(stats, {name = "Armor Penetration", parent = "right", value = data.armorPenetration or 0, icon = false, percent = true}) -- Elemental Pierce table.insert(stats, {name = "Elemental Pierce", parent = "right", value = "", icon = false, percent = true}) if data.elementalPierce and #data.elementalPierce > 0 then for _, el in ipairs(data.elementalPierce) do table.insert(stats, { name = " from " .. getElementName(el.element), parent = "right", value = el.value, align = "center", percent = true, icon = false }) end end renderAndSkipZeroStats(stats, leftPanel, rightPanel, Cyclopedia.clientCombat) -- temp fix controllerCyclopedia:scheduleEvent(function() local height = math.max(leftPanel:getHeight() + leftPanel:getMarginTop(), rightPanel:getHeight() + rightPanel:getMarginTop()) container:setHeight(height + 10) end, 50) end function Cyclopedia.onCyclopediaCharacterDefenceStats(data) UI.DeffenceStats.rightPanel:destroyChildren() UI.DeffenceStats.leftPanel:destroyChildren() local stats = { {name = "Defence Value", value = data.defense or 0, icon = false, percent = false}, {name = "From Equipment", value = data.defenseEquipment or 0, align = "center", icon = false}, {name = "From Wheel", value = data.defenseWheel or 0, align = "center", icon = false}, {name = getWeaponSkillName(data.defenseSkillType), value = data.shieldingSkill or 0, align = "center", icon = false}, {name = "Armor Value", value = data.armor or 0, icon = false, percent = false}, {name = "Mitigation", value = data.mitigation or 0, icon = false, percent = true}, {name = "From Shielding", value = data.mitigationShield or 0, align = "center", percent = true, icon = false}, {name = "From Combat Tactics", value = data.mitigationCombatTactics or 0, align = "center", percent = true, icon = false}, {name = "From Base", value = data.mitigationBase or 0, align = "center", percent = true, icon = false}, {name = "From Equipment", value = data.mitigationEquipment or 0, align = "center", percent = true, icon = false}, {name = "From Wheel", value = data.mitigationWheel or 0, align = "center", percent = true, icon = false}, {name = "Dodge", value = data.dodgeTotal or 0, icon = false, percent = true}, {name = "From Base", value = data.dodgeBase or 0, align = "center", percent = true, icon = false}, {name = "From Amplification", value = data.dodgeBonus or 0, align = "center", percent = true, icon = false}, {name = "From Wheel", value = data.dodgeWheel or 0, align = "center", percent = true, icon = false}, {name = "Magic Shield Capacity", value = data.magicShieldCapacity or 0, icon = false, percent = false}, {name = "Flat", value = data.magicShieldCapacityFlat or 0, align = "center", icon = false}, {name = "Percent", value = data.magicShieldCapacityPercent or 0, align = "center", percent = true, icon = false}, {name = "Reflect Physical", value = data.reflectPhysical or 0, icon = false, percent = false}, {name = "Resistances", parent = "right", value = "", icon = false} } local resistanceMap = {} if data.resistances then for _, resistance in ipairs(data.resistances) do resistanceMap[resistance.element] = resistance.value end end for elementId, elementInfo in pairs(Cyclopedia.clientCombat) do local value = resistanceMap[elementId] or 0 local percentValue = value *100 local color = "#FFFFFF" if percentValue > 0 then color = "#44AD25" elseif percentValue < 0 then color = "#FF9900" end local sign = percentValue >= 0 and "+" or "" table.insert(stats, { name = " " .. elementInfo.id, parent = "right", value = sign .. string.format("%.2f", tonumber(percentValue)) .. "%", percent = false, element = elementId, icon = true, color = color }) end renderAndSkipZeroStats(stats, UI.DeffenceStats.leftPanel, UI.DeffenceStats.rightPanel, Cyclopedia.clientCombat) end function Cyclopedia.onCyclopediaCharacterMiscStats(data) if not UI or not UI.MiscStats then return end if not UI.MiscStats.miscStatsContent then return end local container = UI.MiscStats.miscStatsContent.miscStatsContainer if not container or not container.leftPanel or not container.rightPanel then return end local leftPanel = container.leftPanel local rightPanel = container.rightPanel if rightPanel.concoctions then rightPanel.concoctions:destroyChildren() end if rightPanel.cooldowns then rightPanel.cooldowns:destroyChildren() end leftPanel:destroyChildren() local stats = { -- Momentum {name = "Momentum", value = data.momentumTotal or 0, icon = false, percent = true}, {name = " from Equipment", value = data.momentumBase or 0, align = "center", percent = true, icon = false}, {name = " from Amplification", value = data.momentumBonus or 0, align = "center", percent = true, icon = false}, {name = " from Wheel", value = data.momentumWheel or 0, align = "center", percent = true, icon = false}, {name = " from Event Bonus", value = data.momentumEvent or 0, align = "center", percent = true, icon = false}, -- Dodge {name = "Dodge", value = data.dodgeTotal or 0, icon = false, percent = true}, {name = " from Equipment", value = data.dodgeBase or 0, align = "center", percent = true, icon = false}, {name = " from Amplification", value = data.dodgeBonus or 0, align = "center", percent = true, icon = false}, {name = " from Wheel", value = data.dodgeWheel or 0, align = "center", percent = true, icon = false}, {name = " from Event Bonus", value = data.dodgeEvent or 0, align = "center", percent = true, icon = false}, -- Damage Reflection {name = "Damage Reflection", value = data.damageReflectionTotal or 0, icon = false, percent = true}, {name = " from Equipment", value = data.damageReflectionBase or 0, align = "center", percent = true, icon = false}, {name = " from Amplification", value = data.damageReflectionBonus or 0, align = "center", percent = true, icon = false}, {name = " from Event Bonus", value = data.damageReflectionEvent or 0, align = "center", percent = true, icon = false}, {name = "Blessings", value = string.format("%d/%d", data.haveBlesses or 0, data.totalBlesses or 0), icon = false, percent = false} } local augmentTypes = { [1] = { name = "Mana Cost", percent = false, sign = "+" }, [2] = { name = "Base Damage", percent = true, sign = "+" }, [3] = { name = "Base Healing", percent = true, sign = "+" }, [4] = { name = "Cooldown", percent = false, sign = "-", suffix = "s" }, [5] = { name = "Critical Extra Damage", percent = true, sign = "+" }, [6] = { name = "Life Leech", percent = true, sign = "+" }, [7] = { name = "Mana Leech", percent = true, sign = "+" }, } local function addAugments(augments, header) if not augments or #augments == 0 then return end table.insert(stats, {name = header, value = "", icon = false}) for _, augment in ipairs(augments) do local spell = Spells.getSpellDataById(augment.spellId) local spellName = spell and spell.name or "Unknown Spell (" .. augment.spellId .. ")" table.insert(stats, {name = " " .. spellName, value = "", align = "center", icon = false}) local typeInfo = augmentTypes[augment.type] or { name = "Augment Type " .. augment.type, percent = false, sign = "+" } local valueText = augment.value if typeInfo.percent then local percentValue = math.floor(augment.value * 10000) / 100 valueText = typeInfo.sign .. percentValue .. "%" else valueText = typeInfo.sign .. string.format("%.1f", augment.value) .. (typeInfo.suffix or "") end table.insert(stats, {name = " " .. valueText .. " " .. typeInfo.name, value = "", align = "center", icon = false}) end end addAugments(data.weaponProficiencyAugments, "Weapon Proficiency Spell Augments") addAugments(data.wheelAugments, "Wheel of Destiny Spell Augments") addAugments(data.equippedAugments, "Equipment Spell Augments") renderAndSkipZeroStats(stats, leftPanel, rightPanel, Cyclopedia.clientCombat) -- Item grids (Concoctions) local hasConcoctions = data.concoctions and #data.concoctions > 0 if rightPanel.activeConcoctions then rightPanel.activeConcoctions:setVisible(hasConcoctions) end if rightPanel.concoctions then rightPanel.concoctions:setVisible(hasConcoctions) end if hasConcoctions then for _, v in ipairs(data.concoctions) do local widget = g_ui.createWidget("Item", rightPanel.concoctions) widget:setItemId(v.id) widget:setVirtual(true) widget:setShowCount(false) local itemName = "unknown item" local thing = g_things.getThingType(v.id, ThingCategoryItem) if thing then local marketData = thing:getMarketData() if marketData and marketData.name and marketData.name ~= "" then itemName = marketData.name end end widget.duration:setText(string.format("%dm%02d", v.duration / 60, v.duration % 60)) local durationText = v.duration > 60 and (math.floor(v.duration / 60) .. "m") or (v.duration .. "s") widget:setTooltip(tr("%s: %s", itemName, durationText)) end end -- Item grids (Foods -> cooldowns) local hasFoods = data.activeFoods and #data.activeFoods > 0 if rightPanel.consumables then rightPanel.consumables:setVisible(hasFoods) end if rightPanel.cooldowns then rightPanel.cooldowns:setVisible(hasFoods) end if hasFoods then for _, v in ipairs(data.activeFoods) do local widget = g_ui.createWidget("Item", rightPanel.cooldowns) widget:setItemId(v.id) widget:setVirtual(true) widget:setShowCount(false) widget.duration:setText(string.format("%dm%02d", v.duration / 60, v.duration % 60)) local itemName = "unknown item" local thing = g_things.getThingType(v.id, ThingCategoryItem) if thing then local marketData = thing:getMarketData() if marketData and marketData.name and marketData.name ~= "" then itemName = marketData.name end end local durationText = v.duration > 60 and (math.floor(v.duration / 60) .. "m") or (v.duration .. "s") widget:setTooltip(tr("%s: %s", itemName, durationText)) end end -- Update container height controllerCyclopedia:scheduleEvent(function() local height = math.max(leftPanel:getHeight() + leftPanel:getMarginTop(), rightPanel:getHeight() + rightPanel:getMarginTop()) container:setHeight(height + 20) end, 50) end