Module:Item lists

From Hero Siege Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Item lists/doc

local p = {}
local cfg = require('Module:Config')

function p.fetchItemsOfTypeAndRarity(itemType, itemRarity)
    local cargo = mw.ext.cargo
    if itemRarity ~= "" then
		local items = cargo.query(
		    'items',
			'Item_Name, Item_Level_Requirement, Item_Tier, Item_Weapon_Class, Item_Type, Item_Lore',
            { where = "Item_Type = '" .. itemType .. "' and Item_Rarity = '" .. itemRarity .. "' and Item_Hidden = 'No'",  orderBy = 'Item_Level_Requirement', 'Item_Name'}
		)
		return items
	else
		local items = cargo.query(
		    'items',
		    'Item_Name, Item_Level_Requirement, Item_Tier, Item_Weapon_Class, Item_Type, Item_Lore',
		    { where = "Item_Type = '" .. itemType .. "'",  orderBy = 'Item_Level_Requirement', 'Item_Name'}
		)
		return items
    end
end

-- Fetch statistics for a given item
function p.fetchItemStats(itemName)
    local cargo = mw.ext.cargo
    local where = string.format("Item_Name = '%s'", p.encodeName(itemName))
    local stats = cargo.query(
        'item_stats',
        'Item_Name, Stat_ID, Spell_Name, Class_Name, Min_Value1, Max_Value1, Min_Value2, Max_Value2, Min_Value3, Max_Value3, Stat_Order',
        { where = where, orderBy = 'Stat_Order ASC' }
    )
    return stats
end

-- Fetch formatting details for all stats
function p.fetchFormatDetails()
    local cargo = mw.ext.cargo
    local formatDetails = cargo.query(
        'stat_format',
        'Stat_ID, Stat_Display, Stat_Type, Stat_Color',
        {}
    )
    local formatMap = {}
    for _, detail in ipairs(formatDetails) do
        formatMap[detail.Stat_ID] = detail
    end
    return formatMap
end

function p.formatStatLine(stat, formatDetails)
    local statDetail = formatDetails[stat.Stat_ID]
    local formattedStat = statDetail.Stat_Display or stat.Stat_ID
    if stat.Min_Value1 and stat.Min_Value1 ~= "" and tonumber(stat.Min_Value1) >= 0 then formattedStat = formattedStat:gsub("value1", p.formatRange(stat.Min_Value1, stat.Max_Value1)) end
    if stat.Min_Value1 and stat.Min_Value1 ~= "" and tonumber(stat.Min_Value1) < 0 then formattedStat = formattedStat:gsub("+value1", p.formatRange(stat.Min_Value1, stat.Max_Value1)) end
    if stat.Min_Value2 and stat.Min_Value2 ~= "" then formattedStat = formattedStat:gsub("value2", p.formatRange(stat.Min_Value2, stat.Max_Value2)) end
    if stat.Min_Value3 and stat.Min_Value3 ~= "" then formattedStat = formattedStat:gsub("value3", p.formatRange(stat.Min_Value3, stat.Max_Value3)) end
    if stat.Spell_Name and stat.Spell_Name ~= "" then
        formattedStat = formattedStat:gsub("spellvalue", string.format('<span class="item-stat-spell-link">[[%s|[%s]]]</span>', stat.Spell_Name, stat.Spell_Name))
    end
	if statDetail and statDetail.Stat_Type == "none" then formattedStat = string.format("[[%s]]", formattedStat) end
    local class = "stat-" .. (statDetail.Stat_Color or "default")
    return string.format('<p class="%s">%s</p>', class, formattedStat)
end


function p.formatRange(min, max)
    if max and max ~= '' and max ~= min then
        return "[" .. min .. "-" .. max .. "]"
    else
        return min
    end
end

function p.getTableHeader(itemType)
    if p.isArmorType(itemType) and itemType ~= 'Shield' then
        return "<tr><th>Item</th><th data-sort-type='number'>Tier</th><th data-sort-type='number' class='nomobile'>Level</th><th data-sort-type='number' class='nomobile'>Defense</th><th class='unsortable'>Stats</th></tr>"
    elseif itemType == 'Shield' then
        return "<tr><th>Item</th><th data-sort-type='number'>Tier</th><th data-sort-type='number' class='nomobile'>Level</th><th data-sort-type='number' class='nomobile'>Defense</th><th data-sort-type='number' class='nomobile'>Block</th><th class='unsortable'>Stats</th></tr>"
    elseif p.isWeaponType(itemType) then
        return "<tr><th>Item</th><th data-sort-type='number'>Tier</th><th data-sort-type='number' class='nomobile'>Level</th><th data-sort-type='number' class='nomobile'>Damage</th><th data-sort-type='number' class='nomobile'>APS</th><th data-sort-type='number' class='nomobile'>DPS</th><th class='unsortable'>Stats</th></tr>"
   elseif p.isRingOrAmulet(itemType) then
    	return "<tr><th>Item</th><th data-sort-type='number'>Tier</th><th data-sort-type='number' class='nomobile'>Level</th><th class='unsortable'>Stats</th></tr>"
    else
    	return "<tr><th>Item</th><th data-sort-type='number'>Tier</th><th data-sort-type='number' class='nomobile'>Level</th><th class='unsortable'>Stats</th></tr>"
    end
end

-- Display all staff items with their stats in a formatted table
function p.displayItemType(frame)
	local itemType = frame.args[1]
	local itemRarity = frame.args[2] or '';
	--local header
	--if itemRarity and itemRarity ~= '' then
		--header = itemRarity
	--else
		--header = itemType
	--end
	--local itemType = "Shield"
	--local itemRarity = "Satanic"
    local items = p.fetchItemsOfTypeAndRarity(itemType, itemRarity)
    local formatMap = p.fetchFormatDetails()
    local output = {}
    local headerSet = false
	local tierSortOrder = p.getTiers()
	
	if #items == 0 then
		return ""
	end
	
    for _, item in ipairs(items) do
        if not headerSet then
        	--table.insert(output, string.format("\n== %ss ==\n", header))
            table.insert(output, "<table class='wikitable sortable'>" .. p.getTableHeader(item.Item_Type))
            headerSet = true
        end
		local tierValue = tierSortOrder[item.Item_Tier] or 0
        local stats = p.fetchItemStats(item.Item_Name)
        local statLines = {}
        local defense, block, damage, aps, dps = 'N/A', 'N/A', 'N/A', 'N/A', 'N/A'
        local minRangeAS, maxRangeAS, medianAS, minRangeED, maxRangeED, medianED, minRangeDPS, maxRangeDPS, medianDPS, minDamage, maxDamage, minAttackSpeed, maxAttackSpeed = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
        
    	for _, stat in ipairs(stats) do
    		if stat.Stat_ID == 'enhanced_defense_percent' or stat.Stat_ID == "enhanced_defense_based_on_level_percent" or stat.Stat_ID == 'enhanced_dmg_percent' or stat.Stat_ID == "enhanced_dmg_based_on_level_percent" then
        		minRangeED = tonumber(stat.Min_Value1) + minRangeED
        		maxRangeED = tonumber(stat.Max_Value1) or tonumber(stat.Min_Value1) + maxRangeED
    		elseif stat.Stat_ID == 'attack_speed_percent' then
    			minRangeAS = tonumber(stat.Min_Value1)
    			maxRangeAS = tonumber(stat.Max_Value1) or tonumber(stat.Min_Value1)
    		end
		end
		table.insert(statLines, '<div class="item-list item-stats">')
        for _, stat in ipairs(stats) do
            if stat.Stat_ID == 'attack_dmg_base' or stat.Stat_ID == 'attacks_per_second_base' or stat.Stat_ID == 'defense_base' or stat.Stat_ID == 'block_chance_base' then
                if stat.Stat_ID == 'defense_base' and stat.Max_Value1 ~= nil then
            		local minDefense = tonumber(stat.Min_Value1) * (1 + minRangeED/100)
            		local maxDefense = tonumber(stat.Max_Value1) * (1 + maxRangeED/100)
            		medianDefense = (minDefense + maxDefense) / 2  -- Calculate the median value.
                    defense = string.format('<span style="display:none;">%.2f</span>%s-%s', medianDefense, math.ceil(minDefense), math.ceil(maxDefense))
                elseif stat.Stat_ID == 'defense_base' and stat.Max_Value1 == nil then
            		local minDefense = tonumber(stat.Min_Value1) * (1 + minRangeED/100)
            		local maxDefense = tonumber(stat.Min_Value1) * (1 + maxRangeED/100)
            		medianDefense = (minDefense + maxDefense) / 2  -- Calculate the median value.
                    defense = string.format('<span style="display:none;">%.2f</span>%s-%s', medianDefense, math.ceil(minDefense), math.ceil(maxDefense))
                elseif stat.Stat_ID == 'block_chance_base' then
                    block = string.format('%s', stat.Min_Value1)
                    end
                if stat.Stat_ID == 'attack_dmg_base' then
                    minDamage = tonumber(stat.Min_Value1) * (1 + minRangeED/100)
            		maxDamage = tonumber(stat.Max_Value1) * (1 + maxRangeED/100)
            		medianDamage = (minDamage + maxDamage) / 2  -- Calculate the median value.
                    damage = string.format('<span style="display:none;">%.2f</span>%s-%s', medianDamage, math.ceil(minDamage), math.ceil(maxDamage))
                elseif stat.Stat_ID == 'attacks_per_second_base' then
                    minAttackSpeed = (tonumber(stat.Min_Value1) * (1 + minRangeAS/100)) * 100
                    minAttackSpeed = math.floor(minAttackSpeed)
                    minAttackSpeed = minAttackSpeed / 100
            		maxAttackSpeed = (tonumber(stat.Min_Value1) * (1 + maxRangeAS/100)) * 100
                    maxAttackSpeed = math.floor(maxAttackSpeed)
                    maxAttackSpeed = maxAttackSpeed / 100
            		medianAttackSpeed = (minAttackSpeed + maxAttackSpeed) / 2  -- Calculate the median value.
            		if minAttackSpeed == maxAttackSpeed then
            			aps = string.format('<span style="display:none;">%.2f</span>%s', medianAttackSpeed, minAttackSpeed)
            		else
                    	aps = string.format('<span style="display:none;">%.2f</span>%s-%s', medianAttackSpeed, minAttackSpeed, maxAttackSpeed)
            		end
                end
            elseif stat.Stat_ID == 'socketed_flat' then
            	if stat.Max_Value1 and stat.Max_Value1 ~= "" then
	    			table.insert(statLines, string.format('<p class="stat-default">Socketed (%s-%s)</p>', stat.Min_Value1, stat.Max_Value1))            		
	    		else
	    			table.insert(statLines, string.format('<p class="stat-default">Socketed (%s)</p>', stat.Min_Value1))	    			
	    		end
        	else
                table.insert(statLines, p.formatStatLine(stat, formatMap))
    		end
        end
        if aps ~= 'N/A' and damage ~= 'N/A' then
        	minRangeDPS = math.ceil(minDamage * minAttackSpeed)
			maxRangeDPS = math.ceil(maxDamage * maxAttackSpeed)
			medianDPS = (minRangeDPS + maxRangeDPS) / 2
			
			dps = string.format('<span style="display:none;">%.2f</span>%s-%s', medianDPS, minRangeDPS, maxRangeDPS)
        end
        if item.Item_Type == "Potion" then table.insert(statLines, string.format('<p class="item-lore">%s</p>', item.Item_Lore)) end
        table.insert(statLines, '</div>')
        if p.isArmorType(item.Item_Type) and item.Item_Type ~= 'Shield' then
            table.insert(output, string.format("<tr><td style='width:150px'><div class='item-col %s'>[[%s]][[File:%s|link=%s]]</div></td><td style='width: 75px; text-align:center' class='tier-%s' data-sort-value='%d'>%s</td><td style='width: 75px;text-align:center' class='nomobile'>%s</td><td style='width:100px;text-align:center' class='nomobile'>%s</td><td style='width:400px;text-align:center'>%s</td></tr>",
            	itemRarity,
            	item.Item_Name,
            	cfg.imageFormat(item.Item_Type, item.Item_Name),
            	item.Item_Name,
            	item.Item_Tier,
            	tierValue,
            	item.Item_Tier,
            	item.Item_Level_Requirement,
            	defense,
            	table.concat(statLines)))
        elseif item.Item_Type == 'Shield' then
            table.insert(output, string.format("<tr><td style='width:150px'><div class='item-col %s'>[[%s]][[File:%s|link=%s]]</div></td><td style='width: 75px; text-align:center' class='tier-%s' data-sort-value='%d'>%s</td><td style='width: 75px;text-align:center' class='nomobile'>%s</td><td style='width:100px;text-align:center' class='nomobile'>%s</td><td style='width:100px;text-align:center' class='nomobile'>%s</td><td style='width:400px;text-align:center'>%s</td></tr>",
            	itemRarity,
            	item.Item_Name,
            	cfg.imageFormat(item.Item_Type, item.Item_Name),
            	item.Item_Name,
            	item.Item_Tier,
            	tierValue,
            	item.Item_Tier,
            	item.Item_Level_Requirement,
            	defense,
            	block,
            	table.concat(statLines)))
        elseif p.isWeaponType(item.Item_Type) then
            table.insert(output, string.format("<tr><td style='width:150px'><div class='item-col %s'>[[%s]][[File:%s|link=%s]]</div></td><td style='width: 75px; text-align:center' class='tier-%s' data-sort-value='%d'>%s</td><td style='width: 75px;text-align:center' class='nomobile'>%s</td><td style='width:100px;text-align:center' class='nomobile'>%s</td><td style='width:100px;text-align:center' class='nomobile'>%s</td><td style='width:100px;text-align:center' class='nomobile'>%s</td><td style='width:400px;text-align:center'>%s</td></tr>",
            	itemRarity,
            	item.Item_Name,
            	cfg.imageFormat(item.Item_Type, item.Item_Name),
            	item.Item_Name,
            	item.Item_Tier,
            	tierValue or "",
            	item.Item_Tier or "",
            	item.Item_Level_Requirement or "",
            	damage or "",
            	aps or "",
            	dps or "",
            	table.concat(statLines)))
        elseif p.isRingOrAmulet(item.Item_Type) then
        	table.insert(output, string.format("<tr><td style='width:150px'><div class='item-col %s'>[[%s]][[File:%s|link=%s]]</div></td><td style='width: 75px; text-align:center' class='tier-%s' data-sort-value='%d'>%s</td><td style='width: 75px;text-align:center' class='nomobile'>%s</td><td style='width:400px;text-align:center'>%s</td></tr>",
            	itemRarity,
            	item.Item_Name,
            	cfg.imageFormat(item.Item_Type, item.Item_Name),
            	item.Item_Name,
            	item.Item_Tier,
            	tierValue or "",
            	item.Item_Tier or "",
            	item.Item_Level_Requirement or "",
            	table.concat(statLines)))
        elseif item.Item_Type == "Charm" or item.Item_Type == "Rune" or item.Item_Type == "Jewel" or item.Item_Type == "Gem" then
        	table.insert(output, string.format("<tr><td style='width:150px'><div class='item-col %s'>[[%s]][[File:%s|link=%s]]</div></td><td style='width: 75px; text-align:center' class='tier-%s' data-sort-value='%d'>%s</td><td style='width: 75px;text-align:center' class='nomobile'>%s</td><td style='width:400px;text-align:center'>%s</td></tr>",
            	itemRarity,
            	item.Item_Name,
            	cfg.imageFormat(item.Item_Type, item.Item_Name),
            	item.Item_Name,
            	item.Item_Tier,
            	tierValue or "",
            	item.Item_Tier or "",
            	item.Item_Level_Requirement or "",
            	table.concat(statLines)))
        elseif item.Item_Type == "Potion"  then
        	table.insert(output, string.format("<tr><td style='width:150px'><div class='item-col %s'>[[%s]][[File:%s|link=%s]]</div></td><td style='width: 75px; text-align:center' class='tier-%s' data-sort-value='%d'>%s</td><td style='width: 75px;text-align:center' class='nomobile'>%s</td><td style='width:400px;text-align:center'>%s</td></tr>",
            	itemRarity,
            	item.Item_Name,
            	cfg.imageFormat(item.Item_Type, item.Item_Name),
            	item.Item_Name,
            	item.Item_Tier,
            	tierValue or "",
            	item.Item_Tier or "",
            	item.Item_Level_Requirement or "",
            	table.concat(statLines)))
        end
    end

    table.insert(output, "</table>")
    return table.concat(output)
end

function p.extractRange(range)
    local minRange, maxRange = string.match(range, "(%d+)-(%d+)")
    minRange = tonumber(minRange)
    maxRange = tonumber(maxRange)
    return minRange, maxRange
end

function p.getTiers()
	local tierSortOrder = {
    SS = 6,
    S = 5,
    A = 4,
    B = 3,
    C = 2,
    D = 1
}
	return tierSortOrder
end

function p.decodeHTML(itemName)
    itemName = itemName:gsub("&apos;", "'")
    itemName = itemName:gsub("&#39;", "'")
    itemName = itemName:gsub("&amp;", "&")
    return itemName
end

function p.encodeName(itemName)
	local decoded = p.decodeHTML(itemName)
	local encodedItemName = decoded:gsub("'", "''")
	return encodedItemName
end


function p.isWeaponType(itemType)
	local weaponTypes = {
    Sword = true,
    Dagger = true,
    Mace = true,
    Axe = true,
    Claw = true,
    Polearm = true,
    Chainsaw = true,
    Staff = true,
    Cane = true,
    Wand = true,
    Book = true,
    Spellblade = true,
    Bow = true,
    Gun = true,
    Flask = true,
    ["Throwing Weapon"] = true }
    
    return weaponTypes[itemType] or false
end

function p.isArmorType(itemType)
	local armorTypes = {
		Helmet = true,
		["Body Armor"] = true,
		Gloves = true,
		Belt = true,
		Boots = true,
		Shield = true,
	}
	return armorTypes[itemType] or false
end

function p.isRingOrAmulet(itemType)
	local ringOrAmulet = {
		Amulet = true,
		Ring = true,
	}
	return ringOrAmulet[itemType] or false
end

function p.isCharmOrRune(itemType)
	local isCharmOrRune = {
		Charm = true,
		Rune = true
	}
	return isCharmOrRune[itemType] or false
end

function p.isMiscType(itemType) 
	local miscTypes = {
	Consumable = true,
	Potion = true,
	Key = true,
	Material = true,
	Collectible = true
	}
	
	return miscTypes[itemType] or false
end


return p