Difference between revisions of "Module:Infobox Monster"

From Idlescape Wiki
Jump to navigation Jump to search
(fixed zones)
m (bug)
 
Line 42: Line 42:
 
s = s .. ',' -- ending comma
 
s = s .. ',' -- ending comma
 
end
 
end
s = string.gsub(s, "[%s%[%]]", "")
+
s = string.gsub(s, "[%[%]]", ""):gsub(",%s",",")
 
     local t = {} -- table to collect fields
 
     local t = {} -- table to collect fields
 
     local fieldstart = 1
 
     local fieldstart = 1

Latest revision as of 13:22, 5 July 2024


local p ={}
local findId = require("Module:FindId")
local infoboxModule = require('Module:Infobox')
local monstersData = mw.loadData("Module:Monsters/data")
local normalMonsters = mw.loadData("Module:Monsters stats normal/data")
local dungeonMonsters = mw.loadData("Module:Monsters stats dungeon/data")
local headerCount = 1
local labelCount = 1
local dataCount = 1
local defaultAffinities = {
    { Melee = 1 },
    { Magic = 1 },
    { Range = 1 },
    { Piercing = 1 },
    { Blunt = 1 },
    { Slashing = 1 },
    { Fire = 1 },
    { Ice = 1 },
    { Nature = 1 },
    { Chaos = 1 },
    { Posion = 1 },
    { Lightning = 1 }
}
local affinitiesIcon = {
	[1] = "[[File:Melee splash.png|20px|link=Combat#Affinities]]",
	[2] = "[[File:Magic splash.png|20px|link=Combat#Affinities]]",
	[3] = "[[File:Range splash.png|20px|link=Combat#Affinities]]",
	[4] = "[[File:Stab splash.png|20px|link=Combat#Affinities]]",
	[5] = "[[File:Crush splash.png|20px|link=Combat#Affinities]]",
	[6] = "[[File:Slash splash.png|20px|link=Combat#Affinities]]",
	[7] = "[[File:Fire_splash.png|20px|link=Combat#Affinities]]",
	[8] = "[[File:Ice_splash.png|20px|link=Combat#Affinities]]",
	[9] = "[[File:Nature_splash.png|20px|link=Combat#Affinities]]",
	[10] = "[[File:Chaos_splash.png|20px|link=Combat#Affinities]]",
	[11] = "[[File:Poison_splash.png|20px|link=Combat#Affinities]]",
	[12] = "[[File:Lightning_splash.png|20px|link=Combat#Affinities]]",
}

-- Convert from CSV string to table (converts a single line of a CSV file)
local function fromCSV(s)
	if string.sub(s, -1) ~= ',' then
		s = s .. ',' -- ending comma
	end
	s = string.gsub(s, "[%[%]]", ""):gsub(",%s",",")
    local t = {} -- table to collect fields
    local fieldstart = 1
    repeat
        local nexti = string.find(s, ',', fieldstart)
        table.insert(t, string.sub(s, fieldstart, nexti - 1))
        fieldstart = nexti + 1
    until fieldstart > string.len(s)
    return t
end

local function pairsByKeys(t, f)
    local a = {}
    local orgi_key_type
    local orgi_key_numbered
    for n in pairs(t) do
        if tonumber(n) == nil then
            table.insert(a, n)
            orgi_key_type = "word"
        elseif type(n) == "number" then
            table.insert(a, n)
            orgi_key_type = "int"
        elseif type(n) == "string" and type(tonumber(n) == "number") then
            orgi_key_type = "number"
            table.insert(a, tonumber(n))
        end
    end
    table.sort(a, f)
    local key
    local value
    local i = 0             -- iterator variable
    local iter = function() -- iterator function
        i = i + 1
        if a[i] == nil then
            return nil
        elseif orgi_key_type == "word" or orgi_key_type == "int" then
            key = a[i]
            value = t[a[i]]
        elseif orgi_key_type == "number" then
            key = tostring(a[i])
            value = t[tostring(a[i])]
        end
        return key, value
    end
    return iter
end

local function tchelper(first, rest)
    return first:upper()..rest:lower()
end

local function capitalize(s)
	s = s:gsub("(%a)([%w_']*)", tchelper):gsub(" Of "," of "):gsub(" The "," the "):gsub("Ii","II")
	return s
end

local function tablelength(T)
    local count = 0
    for _ in pairs(T) do count = count + 1 end
    return count
end

local function h()
	local s = "header" .. headerCount
	headerCount = headerCount + 1
	labelCount = headerCount
	dataCount = headerCount
	return s
end

local function sbreak()
	local s = "sbreak" .. headerCount
	headerCount = headerCount + 1
	labelCount = headerCount
	dataCount = headerCount
	return s
end

local function l()
	local s = "label" .. labelCount
	dataCount = labelCount
	labelCount = labelCount + 1
	headerCount = labelCount
	return s
end

local function d()
	local s = "data" .. dataCount
	dataCount = dataCount + 1
	headerCount = dataCount
	labelCount = dataCount
	return s
end

local function sl()
	local s = "s" .. l{}
	return s
end

local function sd()
	local s = "s" .. d{}
	return s
end

local function rc()
	local s = "rowclass" .. labelCount
	return s
end

---fetches an monster object from monsters/data module
---@param id number
---@return table|nil
local function getMonster(id)
    local mosnter = monstersData[tostring(id)]
    if mosnter then
        return mosnter
    end
    return nil
end

local function getMonsterStats(id)
	local mosnterStats = normalMonsters[tostring(id)]
	if not mosnterStats then
		mosnterStats = dungeonMonsters[tostring(id)]
		if not mosnterStats then
			return nil
		end
	end
	return mosnterStats
end

local function calcThreat(monster)
    local damageThreat = 2
    local weaponThreat = 3
    local armorThreat = 5
    local attackSpeedThreat = 3
    local attackSpeedThreatLevel = 2.4 / monster.attackSpeed
    local attackSpeedThreatFinal = attackSpeedThreatLevel * attackSpeedThreat
    local potentialDamageThreatFinal =
        (monster.attack +
            monster.strength +
            monster.magic +
            monster.range) *
        damageThreat
    local weaponThreatFinal =
        (monster.weapon.dexterity +
            monster.weapon.intellect +
            monster.weapon.strength) *
        weaponThreat
    local targetArmorRating =
        monster.armor.protection +
        monster.armor.resistance +
        monster.armor.agility * 1.5
    local armorThreatFinal = (targetArmorRating + monster.defense * 10) * armorThreat
    local baseThreat = (potentialDamageThreatFinal + weaponThreatFinal) * attackSpeedThreatFinal + armorThreatFinal
    return math.floor(baseThreat)
end

local function fullUrl(url)
	local newUrl = url
	if url:sub(1,5) == "https" then
		return newUrl
	end
	if url:sub(1,1) ~= "/" then
		newUrl = "/" .. newUrl
	end
	newUrl = "https://www.play.idlescape.com" .. newUrl
	return newUrl
end

local function createImgTag(monster)
    local url = fullUrl(monster.image)
    local attrs = {
        src = fullUrl(monster.image),
        alt = monster.name,
        width = "150px"
    }
    local img = mw.html.create('img'):attr(attrs)
    return tostring(img)
end

local function createOffAffinity(args, monsterStats)
	for index, value in pairsByKeys(defaultAffinities) do
		for affinity, affinityValue in pairs(value) do
			if monsterStats.offensiveDamageAffinity[affinity] then
				affinityValue = monsterStats.offensiveDamageAffinity[affinity]
				args[sl()] = affinitiesIcon[index]
				if affinityValue > 1 then
                    args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>"
                elseif affinityValue < 1 then
                    args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>"
				else
					args[sd()] = (affinityValue - 1) * 100 .. "%"
                end
			else
				args[sl()] = affinitiesIcon[index]
				args[sd()] = (affinityValue - 1) * 100 .. "%"
			end
			if index % 3 == 0 then
				args["bodyclass"] = "equal-space"
				args[sbreak()] ="yes"
			end
		end
	end
	return args
end

local function createDeffAffinity(args, monsterStats)
	for index, value in pairsByKeys(defaultAffinities) do
		for affinity, affinityValue in pairs(value) do
			if monsterStats.defensiveDamageAffinity[affinity] then
				affinityValue = monsterStats.defensiveDamageAffinity[affinity]
				args[sl()] = affinitiesIcon[index]
				if affinityValue > 1 then
                    args[sd()] = "<span style=\"color:#4caf50\">" .. (affinityValue - 1) * 100 .. "%<span/>"
                elseif affinityValue < 1 then
                    args[sd()] = "<span style=\"color:#f44336\">" .. (affinityValue - 1) * 100 .. "%<span/>"
				else
					args[sd()] = (affinityValue - 1) * 100 .. "%"
                end
			else
				args[sl()] = affinitiesIcon[index]
				args[sd()] = (affinityValue - 1) * 100 .. "%"
			end
			if index % 3 == 0 then
				args["bodyclass"] = "equal-space"
				args[sbreak()] ="yes"
			end
		end
	end
	return args
end

local function getZonesLink(zones)
	local s = ""
	if zones then
		for index, value in ipairs(zones) do
			s = s .. "[[" .. capitalize(value) .. "]]"
			if tablelength(zones) > index then
				s = s .. ", "
			end
		end
	end
	return s
end

local function createInfobox(monster, monsterStats, zones, dps)
    local args = {}

    args.autoheaders = "y"
	args.subbox = "no"
	args.bodystyle = " "
	args.title = monster.name
    args.image = createImgTag(monster)
	args[l()] = "Zones"
	args[d()] = getZonesLink(zones)
    args[h()] = "Offensive Stats"
	args[sl()] = "Attack Speed"
	args[sd()] = tostring(monsterStats.attackSpeed)
	args[sl()] = "DPS"
	args[sd()] = tostring(dps)
	args[sl()] = "Crit Chance"
	args[sd()] = tostring(monsterStats.offensiveCritical.chance * 100) .. "%"
	args[sl()] = "Crit Multiplier"
	args[sd()] = tostring(monsterStats.offensiveCritical.damageMultiplier * 100) .. "%"
    args[h()] = "Offensive Affinities"
	createOffAffinity(args, monsterStats)
	args[h()] = "Defensive Stats"
	args[sl()] = "Threat"
	args[sd()] = calcThreat(monsterStats)
	args[sl()] = "Crit Avoidance"
	args[sd()] = tostring(monsterStats.defensiveCritical.chance * 100) .. "%"
	args[sl()] = "Crit Reduction"
	args[sd()] = tostring(monsterStats.defensiveCritical.damageMultiplier * 100) .. "%"
	args[h()] = "Defensive Affinities"
	createDeffAffinity(args, monsterStats)

	for key, data in pairs(args) do
		if string.find(key, "data") then
			args[key] = tostring(data)
		end
	end

	return infoboxModule.infobox(args)
end

function  p.infoboxMonster(frame)
    local args = frame:getParent().args
	return p._infoboxMonster(args)
end

function p._infoboxMonster(_args)
	local zones = fromCSV(_args["zones"])
	local dps = _args["DPS"]
    local name
	local id
	local monster
	local monsterStats
	local infobox
    if _args[1] then
		name = _args[1]
	else
		name = mw.title.getCurrentTitle().text
	end
	id = findId._findId({name, "monster"})
    if not id then
        return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters Ids/data maybe outdated."
    end
	monster = getMonster(id)
	if not monster then
        return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. The Module:Monsters/data maybe outdated."
    end
	monsterStats = getMonsterStats(id)
	if not monsterStats then
        return "<div style=\"color:red\"> No monster named '" .. name .. "'</div>. Module:Monsters stats normal/data and Module:Monsters stats dungeon/data maybe outdated."
    end
    infobox = createInfobox(monster, monsterStats, zones, dps)
	return infobox
end

return p