Module:DropsLine

From Aether Project Wiki
Jump to navigation Jump to search
This is the documentation page. It will be transcluded into the main module page. See Template:Documentation for more information

This module implements {{DropsLine}}.



local p = {}
local pageTitle = mw.title.getCurrentTitle()
local pageTitleString = pageTitle.fullText
local namespace = pageTitle.nsText
local fileNamespace = mw.site.namespaces[6].name
local lang = mw.language.getContentLanguage()

local i18n = {
	playerKillRef = "Dropped only when kill credit is given to the player",
	playerKillRefName = "player-kill",
	playerKillYes = "yes",
	playerKillNo = "no",
	versionJavaOnly = "Only in Java Edition",
	versionBedrockOnly = "Only in Bedrock Edition",
	versionBoth = "both",
	imageIconPrefix = "Invicon ",
	blankImageName = "Air",
	
	-- Placeholders will automatically be replaced with: imageName, pageTitleString, itemName, quantity
	imageAltText = "$1: $2 drops $3 with quantity $4",
	
	-- SMW properties
	smwSubnamePrefix = "DROP",
	smwDroppedFrom = "Dropped from",
	smwVersion = "Version",
	smwDroppedItem = "Dropped item",
	smwDroppedItemNote = "Dropped item note",
	smwQuantity = "Quantity",
	smwQuantityText = "Quantity text",
	smwLootingQuantity = "Looting quantity",
	smwDropChance = "Drop chance",
	smwLootingChance = "Looting chance",
	smwPlayerKillRequired = "Player kill required",
	smwRollChance = "Roll chance",
	smwRollChanceText = "Roll chance text",
	smwRollChanceNote = "Roll chance note",
	smwItemName = "Item name",
	smwDropJSON = "Drop JSON"
}

-- format a number for printing
function p.formatNum(number)
	if number then
		return lang:formatNum(tonumber(string.format("%.2f", number)))
	end
	return ''
end

function p.quantityText(quantity, low, high, chance)
	local text = quantity
	if low and high then
		if low > high then
			low, high = high, low
		end
		
		if low == high then
			text = low
		else
			text = low .. '–' .. high
		end
	end
	if chance and chance ~= 0 and chance ~= 100 then
		text = text .. ' (' .. p.formatNum(chance) .. '%)'
	end
	return tostring(text)
end

function p.main(frame)
	return p._main(frame:getParent().args)
end

-- Generates a row in the drops table
function p._main(args)
	local version = args.version or i18n.versionBoth
	local imageName = args.image
	local name = args.name or ''
	local nameLink = args.namelink or name
	local nameNote = args.namenote
	local quantity = args.quantity or 1
	local lootingQuantity = args.lootingquantity or 0
	local quantityLimit = args.quantitylimit -- Corresponds to "limit" in datapacks. Only used by strays in vanilla je (up to 1.20.2)
	local dropChance = args.dropchance or 100
	dropChance = string.gsub(dropChance, '%%', '')
	dropChance = tonumber(dropChance) -- gsub returns 2 vars, so we have to split up the gsub and tonumber
	local lootingChance = args.lootingchance or 0
	lootingChance = string.gsub(lootingChance, '%%', '')
	lootingChance = tonumber(lootingChance) -- gsub returns 2 vars, so we have to split up the gsub and tonumber
	local playerKillRequired = args.playerkill or i18n.playerKillNo
	local rollChance = args.rollchance or 1
	local rollChanceNote = args.rollchancenote
	
	-- setup: quantity. Quantity is needed for the image selection of experience orbs, so we do the setup here.
	local _, _, lowQuantity, highQuantity = string.find(quantity, '(%-?%d+)%-(%-?%d+)')
	lowQuantity = tonumber(lowQuantity) or tonumber(quantity)
	highQuantity = tonumber(highQuantity) or tonumber(quantity)
	
	if lowQuantity > highQuantity then
		lowQuantity, highQuantity = highQuantity, lowQuantity
	end
	
	-- setup: looting quantity
	local _, _, lootingLowQuantity, lootingHighQuantity = string.find(lootingQuantity, '(%-?%d+)%-(%-?%d+)')
	lootingLowQuantity = tonumber(lootingLowQuantity) or tonumber(lootingQuantity)
	lootingHighQuantity = tonumber(lootingHighQuantity) or tonumber(lootingQuantity)
	
	if lootingLowQuantity > lootingHighQuantity then
		lootingLowQuantity, lootingHighQuantity = lootingHighQuantity, lootingLowQuantity
	end
	
	-- quantity scaling with looting
	local looting1Low = lowQuantity
	local looting2Low = lowQuantity
	local looting3Low = lowQuantity
	if lootingHighQuantity > lootingLowQuantity then
		looting1Low = lowQuantity + lootingLowQuantity
		looting2Low = looting1Low + lootingLowQuantity
		looting3Low = looting2Low + lootingLowQuantity
	end
	
	local looting1High = highQuantity + lootingHighQuantity
	local looting2High = looting1High + lootingHighQuantity
	local looting3High = looting2High + lootingHighQuantity
	
	-- dropChance scaling with looting. incompatible with a lowQuantity <= 0 and quantityLimit, since that calculates its own non-linear scaling
	local looting1Chance, looting2Chance, looting3Chance
	if dropChance ~= 0 and lootingChance ~= 0 then
		looting1Chance = dropChance + lootingChance
		looting2Chance = looting1Chance + lootingChance
		looting3Chance = looting2Chance + lootingChance
	end
	
	-- A few vanilla mobs use negative lower bounds for their quantity rolls to fake a lower dropChance.
	-- This behavior is unintuitive so we translate it to a [0-n] bounds with a (k%) of dropping
	-- This isn't a linear scaling for the looting levels, so we cannot reuse the already set values.
	if highQuantity > lowQuantity and lowQuantity < 0 then
		dropChance = (highQuantity / ((highQuantity - lowQuantity) + 1)) * 100
		looting1Chance = (looting1High / ((looting1High - looting1Low) + 1)) * 100
		looting2Chance = (looting2High / ((looting2High - looting2Low) + 1)) * 100
		looting3Chance = (looting3High / ((looting3High - looting3Low) + 1)) * 100
		lowQuantity = 1
		looting1Low = 1
		looting2Low = 1
		looting3Low = 1
	end
	
	-- Quantity limit is really weird and does rounding so only 0-0.45 maps to 0, while 0.5-1 maps to one. 
	-- So every +1 to the range is actually +50% to the count that is above 0.
	-- its only used for strays, and the formula would be different for anything that has a different range
	-- so this is just hard coded to work for strays.
	if quantityLimit then
		if highQuantity > lowQuantity then
			dropChance = (highQuantity / ((highQuantity - lowQuantity) + 1)) * 100
			looting1Chance = (math.pow(2, 1 + 1) - 1) / (math.pow(2, 1 + 1)) * 100
			looting2Chance = (math.pow(2, 2 + 1) - 1) / (math.pow(2, 2 + 1)) * 100
			looting3Chance = (math.pow(2, 3 + 1) - 1) / (math.pow(2, 3 + 1)) * 100
			lowQuantity = 1
			looting1Low = 1
			looting2Low = 1
			looting3Low = 1
		end
		quantityLimit = tonumber(quantityLimit)
		if highQuantity and highQuantity > quantityLimit then highQuantity = quantityLimit end
		if looting1High and looting1High > quantityLimit then looting1High = quantityLimit end
		if looting2High and looting2High > quantityLimit then looting2High = quantityLimit end
		if looting3High and looting3High > quantityLimit then looting3High = quantityLimit end
	end
	
	-- setup: image
	-- Generate file name from item name
	if imageName then
		if imageName == '' then -- if its blank, use a blank image
			imageName = i18n.imageIconPrefix .. i18n.blankImageName .. '.png'
		end
	else
		imageName = i18n.imageIconPrefix .. name .. '.png' -- Auto generate an image from the drop name
	end
	
	local image = mw.ustring.format('[[%s:%s|link=%s|alt=%s|32x32px|class=pixel-image]]', fileNamespace, imageName, nameLink, i18n.imageAltText)
		:gsub("$1", imageName)
		:gsub("$2", pageTitleString)
		:gsub("$3", name)
		:gsub("$4", quantity)
	
	-- item image
	local ret = mw.html.create('tr')
		:css('text-align', 'center')
		-- image
		:tag('td')
			:addClass('sprite-image')
			:css('min-width', '32px')
			:wikitext(image)
			:done()
	
	-- item name
	local nameTagString = name
	if nameLink ~= '' then
		nameTagString = string.format('[[%s|%s]]', nameLink, name)
	end
	
	local nameTag = ret:tag('td')
		:css('text-align', 'left')
		:wikitext(nameTagString)
	
	if version == 'je' then
		nameTag:wikitext(mw.getCurrentFrame():extensionTag{ name='sub', content='(JE)', args = {title=i18n.versionJavaOnly, style='cursor:help; margin-left:3px;'} })
	elseif version == 'be' then
		nameTag:wikitext(mw.getCurrentFrame():extensionTag{ name='sub', content='(BE)', args = {title=i18n.versionBedrockOnly, style='cursor:help; margin-left:3px;'} })
	end
	
	if nameNote then
		nameTag:wikitext(mw.getCurrentFrame():extensionTag{ name='ref', content=nameNote, args = {group='d'}})
	end
	
	-- roll chance
	local rollChanceText = rollChance
	if rollChance == 1 then
		if dropChance == 100 then
			rollChanceText = "100%"
		else
			rollChanceText = p.formatNum(dropChance) .. '%&ndash;' .. p.formatNum(looting3Chance) .. '%'
		end
	end
	
	local rollChanceTag = ret:tag('td'):wikitext(rollChanceText)
	if playerKillRequired == i18n.playerKillYes then
		rollChanceTag:wikitext(mw.getCurrentFrame():extensionTag{ name='ref', content=i18n.playerKillRef, args = {group='d', name=i18n.playerKillRefName}})
	end
	if rollChanceNote then
		rollChanceTag:wikitext(mw.getCurrentFrame():extensionTag{ name='ref', content=rollChanceNote, args = {group='d'}})
	end
	
	-- quantity text
	ret:tag('td'):wikitext(p.quantityText(quantity, lowQuantity, highQuantity, dropChance)):done()
	ret:tag('td'):wikitext(p.quantityText(quantity, looting1Low, looting1High, looting1Chance)):done()
	ret:tag('td'):wikitext(p.quantityText(quantity, looting2Low, looting2High, looting2Chance)):done()
	ret:tag('td'):wikitext(p.quantityText(quantity, looting3Low, looting3High, looting3Chance)):done()
	ret:done()
	
	-- Put data into SMW
	-- The only top level params that are needed is stuff that would be used as selectors
	-- everything else is put into a json blob for flexibility
	local onMain = false
	if namespace == '' or namespace == mw.site.namespaces[4].name then
		onMain = true
	end
	if onMain then
		local smw_sub = {}
		local droppedFrom = pageTitleString
		
		local subname = i18n.smwSubnamePrefix .. '_' .. name .. '_' .. quantity
		local smw_json = {
			[i18n.smwDroppedFrom] = droppedFrom,
			[i18n.smwVersion] = version,
			[i18n.smwDroppedItem] = name,
			[i18n.smwDroppedItemNote] = nameNote,
			[i18n.smwQuantity] = quantity,
			[i18n.smwQuantityText] = {
				p.quantityText(quantity, lowQuantity, highQuantity, dropChance),
				p.quantityText(quantity, looting1Low, looting1High, looting1Chance),
				p.quantityText(quantity, looting2Low, looting2High, looting2Chance),
				p.quantityText(quantity, looting3Low, looting3High, looting3Chance)
			},
			[i18n.smwLootingQuantity] = lootingQuantity,
			[i18n.smwDropChance] = dropChance,
			[i18n.smwLootingChance] = lootingChance,
			[i18n.smwPlayerKillRequired] = playerKillRequired,
			[i18n.smwRollChance] = rollChance,
			[i18n.smwRollChanceText] = rollChanceText,
			[i18n.smwRollChanceNote] = rollChanceNote
		}
		mw.logObject(smw_json)
		
		local smw_sub = { -- the actual SMW sub-object
			[i18n.smwItemName] = name,
			[i18n.smwDroppedFrom] = droppedFrom,
			[i18n.smwDropJSON] = mw.text.jsonEncode(smw_json)
		}
		mw.smw.subobject(smw_sub, subname)
	end
	
	return tostring(ret)
end

return p