Module:CreepMap/CreepSpot

From Liquipedia Warcraft Wiki

local Abbreviation = require('Module:Abbreviation')
local Array = require('Module:Array')
local Class = require('Module:Class')
local Logic = require('Module:Logic')
local String = require('Module:StringUtils')
local Variables = require('Module:Variables')

local SPOT_ICON = {
	easy = '[[File:Wc3 easy creep spot.png|20px|link=]]',
	medium = '[[File:Wc3 medium creep spot.png|22px|link=]]',
	hard = '[[File:Wc3 hard creep spot.png|24px|link=]]',
}
local DIFFICULTY = {
	easy = 'Easy',
	medium = 'Medium',
	hard = 'Hard',
}
local CREEP_COLORS = {
	'sapphire-text',
	'cinnabar-text',
	'forest-green-text',
	'red-violet-text',
}

local ITEM_DATAS = {
	{index = 1, colorClass = 'sapphire-text'},
	{index = '1-2', colorClass = 'sapphire-text'},
	{index = '1-3', colorClass = 'sapphire-text'},
	{index = '1-4', colorClass = 'sapphire-text'},
	{index = '1-5', colorClass = 'sapphire-text'},
	{index = 2, colorClass = 'cinnabar-text'},
	{index = '2-2', colorClass = 'cinnabar-text'},
	{index = 3, colorClass = 'forest-green-text'},
	{index = 4, colorClass = 'red-violet-text'},
}

local _cache = {}

local CreepSpot = {}

function CreepSpot.run(args)
	args = args or {}

	local imageWidth = tonumber(Variables.varDefault('imgwidth', 0))
	local imageHeight = tonumber(Variables.varDefault('imgheight', 0))

	local display = mw.html.create('div')
		:addClass('creep-spot creep-spot-' .. (args.spot or '|medium'))
		:css('position', 'absolute')
		:css('left', ((tonumber(args.x) or 0) * imageWidth - 12) .. 'px')
		:css('top', ((tonumber(args.y) or 0) * imageHeight - 12) .. 'px')
		:wikitext(SPOT_ICON[args.spot] or SPOT_ICON.medium)

	local difficulty = DIFFICULTY[args.spot] or DIFFICULTY.medium

	local popup = display:tag('div')
		:addClass('creep-spot-popup')

	local creepLevel = 0
	local levels = {}
	local creeps = {}
	local level1, level2, level3, level4, level5
	for i = 1, 5 do
		creeps[i] = CreepSpot._fetchCreep(args['creep' .. i]) or {}
		levels[i] = tonumber((creeps[i].extradata or {}).unitlevel) or 0
		creepLevel = creepLevel + (tonumber(args['creepcount' .. i]) or 1) * levels[i]
	end

	-- popup header
	popup:tag('div')
		:addClass('creep-spot-popup-header')
		:wikitext(difficulty .. ' Creep Spot [')
		:tag('i'):wikitext(Abbreviation.make{text = creepLevel, title = 'Total Creep Level'}):done()
		:wikitext(']')

	local popupBody = popup:tag('div')
		:addClass('creep-spot-popup-body')

	local spotTable = popupBody:tag('table')
		:addClass('wikitable wikitable-striped')
		:css('margin-top', '-1px')
		:css('margin-bottom', '0px')
		:css('width', '100%')
		:css('font-size', '85%')
		:css('line-height', '90%')
		:css('text-align', 'center')
		:node(CreepSpot._spotTableHeader1())
		:node(CreepSpot._spotTableHeader2())

	for index, creep in ipairs(creeps) do
		spotTable:node(CreepSpot._unitRow{
			creep = creep,
			level = levels[index],
			count = tonumber(args['creepcount' .. index]) or 1,
			item = args['item' .. index],
			colorClass = CREEP_COLORS[index],
		})
	end

	local itemTable = popupBody:tag('table')
		:addClass('wikitable wikitable-striped')
		:css('margin-top', '-1px')
		:css('margin-bottom', '0px')
		:css('width', '100%')
		:css('font-size', '85%')
		:css('line-height', '90%')
		:css('text-align', 'center')
		:node(CreepSpot._itemTableHeader())

	for _, itemData in ipairs(ITEM_DATAS) do
		itemTable:node(CreepSpot._itemRow{
			item = args['item' .. itemData.index],
			itemname = args['itemname' .. itemData.index],
			level = args['itemlevel' .. itemData.index],
			percentage = args['itempercentage' .. itemData.index],
			colorClass = itemData.colorClass,
		})
	end

	return display
end

function CreepSpot._spotTableHeader1()
	return mw.html.create('tr')
		:tag('th')
			:attr('colspan', 5)
			:css('padding', '2px')
			:tag('span')
				:css('font-size', '85%')
				:css('line-height', '80%')
				:wikitext('Creeps')
				:done()
			:done()
end

function CreepSpot._spotTableHeader2()
	return mw.html.create('tr')
		:tag('th')
			:css('padding', '2px')
			:css('width', '200px')
			:wikitext('Unit')
			:done()
		:tag('th')
			:css('padding', '2px')
			:css('width', '40px')
			:wikitext('Count')
			:done()
		:tag('th')
			:css('padding', '2px')
			:css('width', '40px')
			:wikitext('Level')
			:done()
		:tag('th')
			:css('padding', '2px')
			:css('width', '40px')
			:wikitext('[[Experience|XP]]')
			:done()
		:tag('th')
			:css('padding', '2px')
			:css('width', '40px')
			:wikitext('Item')
			:done()
end

function CreepSpot._fetchCreep(creep)
	if Logic.isEmpty(creep) then
		return {extradata = {}}
	end

	local creepData = mw.ext.LiquipediaDB.lpdb('datapoint', {
		conditions = '[[type::unit]] AND [[pagename::' .. creep:gsub(' ', '_') .. ']]',
		query = 'name, pagename, extradata, image',
		order = 'name asc',
		limit = 1,
	})[1]

	if not creep then
		return {extradata = {}}
	end

	return creepData
end

function CreepSpot._unitRow(props)
	if props.level == 0 then
		return
	end

	local creep = props.creep

	return mw.html.create('tr')
		:tag('td')
			:css('text-align', 'left')
			:css('overflow', 'hidden')
			:css('text-overflow', 'ellipsis')
			:css('white-space', 'nowrap')
			:wikitext('[[File:' .. creep.image .. '|20px|link=]] [[' .. creep.pagename .. '|' .. creep.name .. ']]')
			:done()
		:tag('td'):wikitext(props.count):done()
		:tag('td'):wikitext(props.level):done()
		:tag('td'):wikitext(creep.extradata.experience or 0):done()
		:tag('td'):addClass(props.colorClass or 'sapphire-text'):wikitext(props.item and '◈' or nil):done()
end

function CreepSpot._itemTableHeader()
	return mw.html.create('tr')
		:tag('th')
			:attr('colspan', 4)
			:css('padding', '2px')
			:tag('span')
				:css('font-size', '85%')
				:css('line-height', '80%')
				:wikitext('Items')
				:done()
			:done()
end

function CreepSpot._itemRow(props)
	if not props.item then
		return
	end

	local percentageDisplay = ''
	if props.percentage then
		local pct = tonumber(props.percentage)
		if pct then
			percentageDisplay = string.format('%.1f%%', pct)
		else
			percentageDisplay = props.percentage
		end
	end

	-- Determine the item description text
	local itemDescription
	if props.itemname then
		-- Use custom name if provided
		itemDescription = props.itemname
	elseif props.level then
		-- Use level-based format if level is provided
		itemDescription = 'Level ' .. props.level .. ', ' .. props.item
	else
		-- Fall back to 'Custom drop'
		itemDescription = 'Custom drop'
	end

	return mw.html.create('tr')
		:tag('td')
			:addClass(props.colorClass)
			:css('width', '25px')
			:wikitext('◈')
			:done()
		:tag('td')
			:css('text-align', 'left')
			:css('width', '125px')
			:css('text-align', 'left')
			:css('overflow', 'hidden')
			:css('text-overflow', 'ellipsis')
			:css('white-space', 'nowrap')
			:wikitext(itemDescription)
			:done()
		:tag('td'):wikitext(CreepSpot._fetchItems(props)):done()
		:tag('td')
			:css('width', '50px')
			:css('font-size', '80%')
			:wikitext(percentageDisplay)
			:done()
end

function CreepSpot._fetchItems(props)
	local conditions = '[[type::item]]'
	if props.level then
		conditions = conditions .. ' AND [[extradata_level::' .. props.level .. ']] AND [[information::' .. props.item .. ' Items]]'
	else
		local items = Array.map(mw.text.split(props.item, ','), String.trim)
		local itemConds = {}
		for _, item in ipairs(items) do
			table.insert(itemConds, '[[pagename::' .. item:gsub(' ', '_').. ']]')
		end
		conditions = conditions .. ' AND (' .. table.concat(itemConds, ' OR ') .. ')'
	end

	local itemData = mw.ext.LiquipediaDB.lpdb('datapoint', {
		conditions = conditions,
		query = 'name, pagename, image',
		order = 'name asc',
		limit = 5000,
	})

	local display = {}
	for _, item in ipairs(itemData) do
		table.insert(display, '[[File:' .. item.image .. '|20px|' .. item.name .. '|link=' .. item.pagename .. ']]')
	end

	return table.concat(display, ' ')
end

return Class.export(CreepSpot)