Module:Killers

From Dead by Daylight Wiki
Jump to navigation Jump to search

local p = {}
local utils = require("Module:Utils")
local data = require("Module:Datatable" .. utils.lang())
local perksData = mw.loadData("Module:Datatable/Loadout" .. utils.lang())
local mathOps = require("Module:MathOps")
local perkImg = require("Module:PerkImage")
local str = require("Module:Strings")
local frame = mw.getCurrentFrame()
local _defaultSpeed = 4 --m/s | 100%

local perks = perksData.perks

p.strings = {
	killers = "Killers",
	the = "The",
	unkwnKiller = "Unknown Killer",
	realName = "Name",
	noName = "None", --When the killer doesn't have the real name (ex. Demogorgon)
	altName = "Game Alias(es)",
	gender = "Gender",
	origin = "Origin",
	realm = "Realm", --link
	power = "Power", --link
	powers = "Powers", --link
	powerNotFound = "Power not found",
	powerType = "Power [[Attack]] Type",
	spAttackTrue = "Special Attack",
	spAttackFalse = "Basic Attack",
	weapon = "Weapon",
	weapons = "Weapons",
	speed = "Movement Speed", --iconlink
	altSpeed = "Alternate Movement speed",
	metres = "metres",
	m = "m", --abbreviation of metres
	terror = "Terror Radius", --iconlink
	altTerror = "Alternate Terror Radius",
	lullabyRadius = "[[Lullaby]] Radius",
	height = "Height", --image
	tall = "Tall",
	avg = "Average",
	short = "Short",
	dlc = "DLC",
	actor = "Voice Actor",
	breathing = "Breathing",
	altBreathing = "Alternate Breathing",
	menuMusic = "Menu Music",
	terrorMusic = "Terror Radius Music",
	lullaby = "Lullaby",
	lullabies = "Lullabies",
	unknownActor = "Undisclosed Voice Actress",
	unkownPower = "Unknown Power",
	unkownSpeed = "Unknown Speed",
	cost = "Cost",
	freeCost = "Free", --this string needs to be same as the string at DLCs that are free
	
	metadata = "Metadata",
	charid = "CharID",
	themeFilename = "Theme Filename",

	mps = "m/s",
	err = "Error",
	
	diffEasy = "Difficulty Rating: " .. bclr("Easy","Easy") .. br .. brackets(i("These Killers are the easiest for a first-time Player")),
	diffModerate = "Difficulty Rating: " .. bclr("Moderate","Moderate") .. br .. brackets(i("These Killers require the Player to be comfortable with the basics of the role, though they share common mechanics with easier ones")),
	diffHard = "Difficulty Rating: " .. bclr("Hard","Hard") .. br .. brackets(i("These Killers use mechanics that are specific to them and require more practice to be effective")),
	diffVeryHard = "Difficulty Rating: " .. bclr("Very Hard","Very Hard") .. br .. brackets(i("These Killers require a high amount of practice and understanding, inexperienced Players are likely to find little success when using them")),
	diffError = biclr("orange", "Difficulty rating not found"),

}
local strings = p.strings
----------------------------------------------
if utils.lang() ~= cstr.empty and (counter or 0) < 1 then
	counter = (counter or 0) + 1
	strings = require("Module:Killers" .. utils.lang()).strings
end

p.killerDifficulties = {"Easy", "Moderate", "Hard", "Very Hard"}

function p.getCountOfKillers()
	return utils.getCount("killer")
end

function p.getKillerById(id)
	for _, killer in ipairs(data.killers) do
		if killer.id == id then
			local killerObj = table.copy(killer)
			return killerObj
		end
	end
	return nil
end

function p.getKillerByName(name) return getKillerByName(name) end --used in Loadout module
function getKillerByName(name)
	for _, killer in ipairs(data.killers) do
		if killer.shortName == name or killer.realName == name or killer.name == name or (the(killer) .. killer.name) == name then
			local killerObj = table.copy(killer)
			return killerObj
		end
	end
	return false
end

function getKillerIdByName(name)
	local result = getKillerByName(name)
	return (result and result.id) or 0
end

--Function not used on English wiki (Currently: IT)
function p.getKillerRealName(killer) --expected nickname
	killer = utils.resolveParameter(killer)
	if type(killer) == types.string then
		killer = getKillerByName(killer)
	end
	return killer.realName
end

function p.getKillerPowerNameByKillerName(name)
	local result = p.getKillerPower(getKillerByName(utils.resolveParameter(name)))
	return (result and result.name) or strings.unkownPower
end

function p.getKillersSpeedByName(name)
	local result = getKillerByName(utils.resolveParameter(name))
	if result then
		if type(result.speed) == types.table then
			return result.speed[0]
		else
			return result.speed
		end
	end
	return strings.unkownSpeed
end

--articleless => whether the nickname is supposed to return with "The " before the name itself
function p.getKillerNicknameByRealName(args, articleless)
	articleless = utils.bool(utils.resolveParameter(args, "articleless", true))
	name = utils.resolveParameter(args)
	for _, killer in ipairs(killers) do
		if killer.shortName == name or killer.realName == name or killer.name == name or (the(killer) .. killer.name) == name then
			return ((not articleless and the(killer) .. space) or cstr.empty) .. killer.name
		end
	end
	return strings.unkwnKiller
end

function p.resolveKillerCharTable(name)
	local dlcs = require("Module:DLCs")
	local various = require("Module:Various")
	name = utils.resolveParameter(name)
	local killerId = getKillerIdByName(name)
	local killer = utils.getCharacterById(killerId, killers)
	local result = cstr.empty
	
	if type(killer) ~= "table" then return killer end
	killer.dlcList = dlcs.getDlcsByCharacter(killer)
	killer.dlc = dlcs.getMainDlc(killer.dlcList) --Main DLC
	killer.chapterPackDlc = dlcs.getChapterPackDlc(killer.dlcList) --Chapter for case they contain an info about a discount
	killer.isDiscounted = dlcs.isDiscounted(killer.dlcList)
	killer.power = killer.power or p.getKillerPower(killer)

	result = 
	'{| class = "infoboxtable charInfoboxTable killerInfobox"' .. nl ..
	ntl ..' class = "infoboxTitle" | ' .. nl ..
	'! class = "center bold" colspan = 2 | ' .. the(killer) .. ' ' .. killer.name .. nl .. ntl .. nl ..
	
--	'<tabber>' ..
--	'|-|First=' ..
--	'First tab sample text.' ..
--	'|-|Second=' ..
--	'Second tab content goes here. ' ..
--	'|-|Third Tab Title=' ..
--	'Third tab content goes here.' ..
--	'</tabber>' ..

	'! class = "center charInfoboxImage" colspan = 2 | ' .. various.getCharPortrait(killer, nil, false) .. nl ..
	ntl .. nl
	if killer.realName ~= nil then
		result = result .. tl .. class('titleColumn') .. tl .. strings.realName .. dtl .. class('valueColumn') .. tl .. (killer.shortName or killer.realName or strings.noName) .. nl .. ntl .. nl
	end
	if killer.altName ~= nil then
		result = result .. tl ..class('titleColumn') .. tl .. strings.altName .. dtl .. class('valueColumn', 'italic') .. tl .. resolveAltName(killer) .. nl .. ntl .. nl
	end
	result = result ..
	tl .. class('titleColumn') .. tl .. strings.gender .. 
		dtl .. class('valueColumn') .. tl .. killer.gender .. nl .. ntl .. nl ..
	tl .. class('titleColumn') .. tl .. strings.origin .. 
		dtl .. class('valueColumn') .. tl .. killer.origin .. nl .. ntl .. nl
	if killer.realm ~= nil or killer.dlc ~= nil then
		local killerRealm = resolveKillerRealm(killer)
		if killerRealm ~= 0 then
			result = result .. tl .. class('titleColumn') .. tl .. link(strings.realm) .. dtl .. class('valueColumn') .. tl .. link(killerRealm) ..  nl .. ntl .. nl
		end
	end
	result = result ..
	tl .. class('titleColumn') .. tl .. link(strings.power) .. dtl .. class('valueColumn') .. tl .. ((killer.power and link(strings.powers .. '#' .. the(killer) .. killer.name .. tl .. (killer.power.name or killer.power))) or strings.powerNotFound) .. nl .. ntl .. nl
	if killer.specialAttack ~= nil then
		result = result .. tl .. class('titleColumn') .. tl .. strings.powerType .. dtl .. class('valueColumn') .. tl .. resolvePowerAttack(killer) .. nl .. ntl .. nl
	end
	if killer.weapon then
		result = result .. tl .. class('titleColumn') .. tl .. link(strings.weapon) .. dtl .. class('valueColumn') .. tl .. link(strings.weapons .. '#' .. the(killer) .. killer.name .. tl .. killer.weapon) .. nl .. ntl .. nl
	end
	result = result ..
	tl .. class('titleColumn') .. tl .. utils.IconLink(strings.speed) .. dtl .. class('valueColumn') .. tl .. resolveKillerSpeed(killer) .. nl .. ntl .. nl
	if killer.altSpeed ~= nil then
		result = result .. tl .. class('titleColumn') .. tl .. strings.altSpeed .. dtl .. class('valueColumn') .. tl .. resolveAltSpeed(killer) .. nl .. ntl .. nl
	end
	result = result ..
	tl .. class('titleColumn') .. tl .. utils.IconLink(strings.terror) .. dtl .. class('valueColumn') .. tl .. resolveTerrorRadius(killer) .. nl .. ntl .. nl
	local altTerror = resolveAltTerrorRadius(killer)
	if altTerror then
		result = result .. tl .. class('titleColumn') .. tl .. strings.altTerror .. dtl .. class('valueColumn') .. tl .. altTerror .. nl .. ntl .. nl		
	end
	local lullabyRadius = resolveLullabyRadius(killer)
	if lullabyRadius then
		result = result .. tl .. class('titleColumn') .. tl .. strings.lullabyRadius .. dtl .. class('valueColumn') .. tl .. lullabyRadius .. nl .. ntl .. nl
	end
	result = result ..
	tl .. class('titleColumn') .. tl .. strings.height .. space .. utils.IconLink(strings.height, "img") .. dtl .. class('valueColumn') .. tl .. resolveHeight(killer) .. nl .. ntl .. nl
	if killer.dlc ~= nil then
		result = result .. tl .. class('titleColumn') .. tl .. strings.dlc .. dtl .. class('valueColumn') .. tl .. dlcs.getDlcFullNameByCharacter(killer) .. space .. nl .. ntl .. nl
	end
	result = result ..
	tl .. class('titleColumn') .. tl .. strings.actor .. dtl .. class('valueColumn') .. tl .. resolveActor(killer) .. nl
	if killer.dlc and 
		(
			(not killer.dlc.retracted and (killer.dlc.cost == nil or type(killer.dlc.cost) == types.table or (type(killer.dlc.cost) == types.string and killer.dlc.cost ~= strings.freeCost)))
		or	(killer.dlc.retracted and killer.isDiscounted)
		)
		then
		local ac = utils.getCcyById(5) --we retrieve the ac and is currency (mainly due to other non-en wikis)
		local is = utils.getCcyById(3)
		dmp(killer.dlc)
		lg("ctkrv?")
		dmp(killer.chapterPackDlc)
		local isCost = (not killer.dlc.licensed and utils.commaFormat(killer.isDiscounted and ((killer.chapterPackDlc and killer.chapterPackDlc.discount and data.charISCost - killer.chapterPackDlc.discount * data.charISCost) or data.charISCost * 0.5) or data.charISCost)) or 0
		local acCost = utils.commaFormat(killer.isDiscounted and ((killer.chapterPackDlc and killer.chapterPackDlc.discount and data.charACCost - killer.chapterPackDlc.discount * data.charACCost) or data.charACCost * 0.5) or data.charACCost)
		result = result .. ntl .. nl .. 
			tl .. class('titleColumn') .. tl .. strings.cost .. dtl .. class('valueColumn') .. tl .. 
			((not killer.dlc.licensed and isCost .. space .. utils.IconLink(is.plural or is.name .. "s") .. nl) or cstr.empty) ..
			acCost .. space .. utils.IconLink(ac.plural or ac.name .. "s") .. nl
	end
	local breathingMusic = resolveBreathingMusic(killer)
	if breathingMusic then
		result = result .. ntl .. nl ..
		'| class = "titleColumn center" colspan = 2 ' .. tl .. space .. strings.breathing .. nl .. ntl .. nl ..
		'| class = "valueColumn center soundColumn" colspan = 2 ' .. tl .. space .. breathingMusic .. nl
	end
	local altBreathing = resolveAltBreathing(killer)
	if altBreathing then 
		result = result .. ntl .. nl ..
		tl .. ' class = "titleColumn center" colspan = 2 ' .. tl .. space .. strings.altBreathing .. nl .. ntl .. nl ..
		tl .. ' class = "valueColumn center soundColumn" colspan = 2 ' .. tl .. space .. altBreathing .. nl
	end
	local theme, themeSuccess = various.resolveCharacterThemeMusic(killer, true)
	if themeSuccess then
		result = result .. ntl .. nl ..
		'| class = "titleColumn center" colspan = 2 ' .. tl .. space .. strings.menuMusic .. nl .. ntl .. nl ..
		'| class = "valueColumn center soundColumn" colspan = 2 ' .. tl .. space .. theme .. nl
	end
	local terrorMusic = resolveTRMusic(killer)
	if terrorMusic then
		result = result .. ntl .. nl ..
		'| class = "titleColumn center" colspan = 2 ' .. tl .. space .. strings.terrorMusic .. nl ..	ntl .. nl ..
		'| class = "valueColumn center soundColumn" colspan = 2 ' .. tl .. space .. terrorMusic .. nl
	end

	local lullaby = resolveLullaby(killer, 1)
	local counter = ((lullaby and 1) or 0) --if you find a lullaby then set the counter to 1, otherwise 0
	local tempResult = cstr.empty
	while lullaby do
		tempResult = tempResult .. ntl .. nl ..
		'| class = "valueColumn center soundColumn" colspan = 2 ' .. tl .. space .. lullaby .. nl
		
		counter = counter + 1
		lullaby = resolveLullaby(killer, counter)
	end
	
	if counter > 0 then --if lullaby was found
		result = result .. ntl .. nl .. '| class = "titleColumn center" colspan = 2 | ' .. ((counter > 2 and strings.lullabies) or strings.lullaby) .. nl .. tempResult
	end
	
	--Metadata
	local metadataNewLine = ntl .. class("metadata") .. nl .. tl .. class("titleColumn") .. tl
	result = result ..
		ntl .. nl ..
		tl .. colspan(2) .. space .. class("metadata") .. tl .. center(b(strings.metadata)) .. nl ..
		metadataNewLine .. strings.charid .. dtl .. utils.getCharacterIdentifier(killer) .. nl ..
		(killer.theme ~= false and (not themeSuccess and various.shouldHaveTheme(killer) and metadataNewLine .. strings.themeFilename .. dtl .. theme .. nl) or cstr.empty)
		
	result = result ..
		'|}'
	
	--mw.log(result)
	return result
end

function p.getKillerPower(killer)
	local pwrData = require("Module:Datatable/Loadout" .. utils.lang())
	if killer and pwrData.powers[killer.id] then
		return pwrData.powers[killer.id]
	end
	for pwrName, pwr in pairs(pwrData.powers) do
		if killer and pwr.killer == killer.id then
			local power = table.copy(pwr)
			power.name = power.name or pwrName
			return power
		end
	end
	return false
end

function resolveAltTerrorRadius(killer)
	if not killer.altRadius then return false end
	local result = killer.altRadius
	if type(killer.altRadius) == types.table then
		result = cstr.empty
		for i, radius in ipairs(killer.altRadius) do
			result = result .. radius[1] .. space .. strings.metres .. space .. brackets(radius[2]) .. ((i < #killer.altRadius and dnl) or cstr.empty)
		end
	end
	return result
end

function resolveActor(killer)
	local result = strings.uknownActor
	
	if type(killer.actor) == types.string then
		result = killer.actor
	elseif type(killer.actor) == types.table then
		result = cstr.empty
		for i, actor in ipairs(killer.actor) do
			result = result .. actor .. ((i < #killer.actor and dnl) or cstr.empty)
		end
	end
	return result
end

function resolveLullabyRadius(killer)
	if not killer.lullabyRadius then return false end
	local result = cstr.empty
	
	if type(killer.lullabyRadius) == "table" then
		for i, lullaby in ipairs(killer.lullabyRadius) do
			result = result .. lullaby[1] .. space .. strings.metres .. space .. brackets(lullaby[2])
			if i < #killer.lullabyRadius then
				result = result .. dnl
			end
		end
	else
		result = (killer.lullabyRadius and killer.lullabyRadius .. space ..strings.metres) or cstr.empty
	end
	
	return result
end

function resolveBreathingMusic(killer)
	local file = (killer.techName or killer.name) .. space .. 'Breathing'
	local valid = utils.isValidFileName(file, cstr.ogg)
	return (valid and link(cstr.file .. file .. dot .. cstr.ogg)) or valid
end

function resolveAltBreathing(killer, index)
	local file = (killer.techName or killer.name) .. space .. 'Breathing' .. space .. (index or 2)
	valid = utils.isValidFileName(file, cstr.ogg)
	return (valid and link(cstr.file .. file .. dot .. cstr.ogg)) or valid
end

function resolveTRMusic(killer)
	local file = 'TerrorRadius_' .. (killer.techName or killer.name)
	local valid = utils.isValidFileName(file, cstr.ogg)
	return (valid and link(cstr.file .. file .. dot .. cstr.ogg)) or valid
end

function resolveLullaby(killer, index)
	local file = (killer.techName or killer.name) .. space .. 'Lullaby' .. ((index and space .. index) or cstr.empty)
	local valid = utils.isValidFileName(file, cstr.ogg)
	return (valid and link(cstr.file .. file .. dot .. cstr.ogg)) or valid
end

function resolveAltName(killer)
	if type(killer.altName) == "string" then return '"' .. killer.altName .. '"'
	elseif type(killer.altName) == "table" then
		local result = ""
		for i, name in ipairs(killer.altName) do
			result = result .. '"' .. name .. '"'
			if i < #killer.altName then 
				result = result .. dnl
			end
		end
		return result
	end
	
	return strings.err
end

function resolvePowerAttack(killer)
	local result = cstr.empty
	
	if killer.specialAttack then 
		result = strings.spAttackTrue
	else
		result = strings.spAttackFalse
	end
	
	if killer.altAttackNote ~= nil then
		result = result .. nl .. "(" .. killer.altAttackNote .. ")"
	end
	
	return result
end

function resolveKillerRealm(killer)
	if killer.dlc == nil then
		for _, realm in ipairs(realms) do
			if realm.id == killer.realm then return realm.name end
		end
	else
		for _, realm in ipairs(realms) do
			if type(realm.dlc) == types.number and realm.dlc == resolveDlcProperty(killer) then return realm.name
			elseif type(realm.dlc) == types.table then
				for _, dlcId in ipairs(realm.dlc) do
					if dlcId == resolveDlcProperty(killer) then return realm.name end
				end
			end
		end
	end
	return 0
end

--check if the killer has populated dlc as a table
function resolveDlcProperty(killer)
	return ((type(killer.dlc) == types.number and killer.dlc) or (type(killer.dlc) == types.table and killer.dlc.id) or -1)
end

function resolveKillerSpeed(killer)
	local result = cstr.empty
	
	if type(killer.speed) == "number" then
		result = getPercentSpeed(killer.speed) .. space .. '%' .. space .. b(bar) .. space .. killer.speed .. space .. strings.mps
	else --so the killer speed is table
		result = getPercentSpeed(killer.speed[1]) .. space .. '%' .. space .. b(bar) .. space .. killer.speed[1] .. space .. strings.mps .. space .. brackets(killer.speed[2])
	end

	return result
end

function resolveAltSpeed(killer)
	local result = cstr.empty
	local speedPercent
	
	for i, altSpeed in ipairs(killer.altSpeed) do
		speedPercent = getPercentSpeed(altSpeed[1])
		
		result = result .. speedPercent .. " % " .. b(bar) .. space .. altSpeed[1] .. space .. strings.mps
		if altSpeed[2] ~= nil then
			result = result .. pg .. "(" .. altSpeed[2] .. ")"
		end
		
		if i < #killer.altSpeed then 
			result = result .. "<hr>" --.. nl .. nl
		end
	end
	
	return result
end

function getPercentSpeed(speed)
	return (speed / _defaultSpeed) * 100
end

function resolveTerrorRadius(killer)
	local result = cstr.empty
	
	if type(killer.radius) == types.number then
		return killer.radius .. space .. strings.metres
	elseif type(killer.radius) == types.table then
		for i, radius in ipairs(killer.radius) do
			result = result .. radius[1] --metres value
			if radius[2] ~= nil then --note for alt speed, if there is a note for the speed the abbreviation for metres is used instead of full name
				result = result .. strings.m .. space .. brackets(radius[2])
			else
				result = result .. space .. strings.metres
			end
			if radius[3] ~= nil then
				result = utils.tooltip(result, radius[3])
			end
			if i < #killer.radius then 
				result = result .. nl .. nl
			end
		end
	end
	return result
end

function resolveHeight(killer)
	if		killer.height == 'T'	then return strings.tall
	elseif	killer.height == 'A'	then return strings.avg
	elseif	killer.height == 'S'	then return strings.short
	else								 return strings.err
	end
end

function p.getKillerDifficultyDesc(killerName, difficulty)
	difficulty = difficulty or utils.resolveParameter(killerName, "diff", true) or utils.resolveParameter(killerName, 2, true)
	killerName = utils.resolveParameter(killerName, 1)
	
	local killer = p.getKillerByName(killerName)
	local diffNumber = tonumber((killer and killer.diff) or difficulty)
	
	if diffNumber then
		return strings["diff" .. string.replace(p.killerDifficulties[diffNumber], space, cstr.empty)]
	else
		return strings["diff" .. ((killer and killer.diff or strings.replace(killer.diff, space, cstr.empty)) or difficulty or string.replace(difficulty, space, cstr.empty) or "Error")]
	end
end

return p