Module:Equipinfo

local trim = mw.text.trim local cargo = mw.ext.cargo

---A cached version of the current frame, the interface to the parser. local currentFrame ---Holds the arguments from the template call. local args_table

local isDebug local debugStr = ''

---Return the l10n string associated with the `key`. Supplemental information can be passed via the replacement parameters `data1` and `data2`. ---@param key string ---@param data1 string ---@param data2 string ---@return string local function l10n(key, data1, data2) return currentFrame:expandTemplate{ title = 'localization/long', args = { 'getEquipInfo', key, calledFromModule = true, __data1 = data1, __data2 = data2 } } end

---Return a trimmed version of the value of the template parameter with the specified `key`. ---Return `nil` if the parameter is empty or unset. ---@param key string|number ---@return string|nil local function getArg(key) local value = args_table[key] if not value then return nil end value = trim(value) if value == '' then return nil end return value end

---Split the `str` on each `div` in it and return the result as a table. ---Original version credit: http://richard.warburton.it. This version trims each substring. ---@param div string ---@param str string ---@return table|boolean local function explode(div,str) if (div=='') then return false end local pos,arr = 0,{} -- for each divider found for st,sp in function return string.find(str,div,pos,true) end do		arr[#arr + 1] = trim(string.sub(str,pos,st-1)) -- Attach chars left of current divider pos = sp + 1 -- Jump past current divider end arr[#arr + 1] = trim(string.sub(str,pos)) -- Attach chars right of last divider return arr end

---Round the `number` on a number of digits specified by `dec`. ---@param x number ---@param dec number ---@return number local function round(x, dec) local factor = 10^(dec or 0) return math.floor(x * factor + 0.5) / factor end

---Return a string of all possible stats, comma-separated ---@return string local function getStatList -- hard-coded for now, the template call below is a bit too expensive for this return 'ccdryLight, cciceBarrier, ccwetLight, accCalendar, accCompass, accCritterGuide, accDepthMeter, accDivingHelm, accDreamCatcher, accFishFinder, accFishingLine, accFlipper, accJarOfSouls, accLavaFishing, accMerman, accOreFinder, accRunSpeed, accStopwatch, accTackleBox, accThirdEye, accWatch, accWeatherRadio, aggro, ammoCost75, ammoCost80, arcticDivingGear, armorPenetration, arrowDamage, autoActuator, autoJump, autoPaint, autoReuseGlove, blackBelt, blockRange, boneGlove, brainOfConfusion, buffImmune, bulletDamage, canFloatInWater, CanSeeInvisibleBlocks, carpet, chloroAmmoCost80, cordage, counterWeight, dashType, dd2Accessory, desertBoots, discount, empressBrooch, endurance, equippedAnyTileRangeAcc, equippedAnyTileSpeedAcc, equippedAnyWallSpeedAcc, extraFall, fireWalk, fishingSkill, flowerBoots, frogLegJumpBoost, goldRing, gravControl2, hasCreditsSceneMusicBox, hasJumpOption_Blizzard, hasJumpOption_Cloud, hasJumpOption_Fart, hasJumpOption_Sail, hasJumpOption_Sandstorm, hasLuckyCoin, hasMoltenQuiver, hasPaladinShield, huntressAmmoCost90, iceSkate, InfoAccMechShowWires, jumpBoost, jumpSpeedBoost, kbGlove, killClothier, killGuide, lavaMax, lavaRose, lifeRegen, longInvince, magicCrit, magicCuffs, magicDamage, magicQuiver, magmaStone, manaCost, manaFlower, manaMagnet, manaRegenBonus, manaRegenDelayBonus, maxMinions, maxTurrets, meleeCrit, meleeDamage, meleeScaleGlove, meleeSpeed, minionDamage, minionKB, moonLordLegs, moveSpeed, nightVision, noFallDmg, noKnockback, npcTypeNoAggro, panic, pickSpeed, pStone, rangedCrit, rangedDamage, releaseBeesWhenHurt, rocketBoots, rocketDamage, rulerGrid, rulerLine, scope, shinyStone, skyStoneEffects, slowFall, spikedBoots, sporeSac, starCloak, starCloakIsBeeCloak, starCloakIsManaCloak, starCloakIsStarVeil, statManaMax2, strongBees, tileRangeX, tileRangeY, treasureMagnet, volatileGelatin, waterWalk, waterWalk2, wolfAcc, yoyoGlove, yoyoString' --	currentFrame:expandTemplate{ title = 'getEquipInfo/db', args = {} }	return currentFrame:callParserFunction{ name = ' end

---Apply formatting to a single value. ---Return a table `{ data1, data2 }`, where: ---* `data1` holds the actual value, formatted ---* `data2` holds supplemental information, the nature of which depends on the `stat`: --- * For most basic ± stats, it simply is empty if `data1` is negative, and not empty otherwise. --- * For `accRunSpeed` and `desertDash`, it is the value wrapped in. --- * For `buffImmune` (list of buffs), `counterWeight` (list of projectiles), and `npcTypeNoAggro` (list of npcs), it holds the number of items in the `data1` list. ---@param statvalues table The entire data table. ---@param stat string The stat name whose value is to be formatted. ---@return table|nil local function formatStatValue(statvalues, stat)

local value = statvalues[stat] -- retrieve value from data table if value == nil or value == '' then return { nil } end

if stat == 'accRunSpeed' then -- pixels/tick → tiles/sec local tps = round(value * 3.75, 2) local mph = currentFrame:expandTemplate{ title = 'mph', args = { tps .. 'tiles' } } return { tps, mph }

elseif stat == 'buffImmune' then -- buff id → name local valueList = explode('¤', value) for i, buffId in ipairs(valueList) do			valueList[i] = currentFrame:expandTemplate{ title = 'exclusive icon link', args = { currentFrame:expandTemplate{ title = 'buffNameFromId', args = { buffId, lang='en' } } } } end return { table.concat(valueList, ', '), #valueList }

elseif stat == 'counterWeight' then -- projectile id → name local valueList = explode('¤', value) for i, projectileId in ipairs(valueList) do			valueList[i] = currentFrame:expandTemplate{ title = 'exclusive icon link', args = { currentFrame:expandTemplate{ title = 'projectileNameFromId', args = { projectileId, lang='en' } } } } end return { table.concat(valueList, ', '), #valueList }

elseif stat == 'desertBoots' then -- pixels/tick → tiles/sec local tps = round(statvalues['accRunSpeed'] * 1.75 * 3.75, 2) local mph = currentFrame:expandTemplate{ title = 'mph', args = { tps .. 'tiles' } } return { tps, mph }

elseif stat == 'lavaMax' then -- ticks → return { currentFrame:expandTemplate{ title = 'duration', args = { value/60 } } }

elseif stat == 'lifeRegen' then -- half return { value/2 }

elseif stat == 'allDamage' or stat == 'magicDamage' or stat == 'meleeDamage' or stat == 'minionDamage' or stat == 'rangedDamage' or stat == 'arrowDamage' or stat == 'bulletDamage' or stat == 'rocketDamage' or stat == 'meleeSpeed' or stat == 'moveSpeed' or stat == 'pickSpeed' or stat == 'manaCost' or stat == 'endurance' then -- decimal → return { currentFrame:expandTemplate{ title = 'percent', args = { value } } }

elseif stat == 'allCrit' or stat == 'magicCrit' or stat == 'meleeCrit' or stat == 'rangedCrit' or stat == 'fishingSkill' then -- 		return { currentFrame:expandTemplate{ title = 'percent', args = { value/100 } } } -- multiplies the input by 100

elseif stat == 'npcTypeNoAggro' then -- npc id → name local valueList = explode('¤', value) for i, npcId in ipairs(valueList) do			valueList[i] = currentFrame:expandTemplate{ title = 'exclusive icon link', args = { currentFrame:expandTemplate{ title = 'npcNameFromId', args = { npcId, lang='en' } } } } end return { table.concat(valueList, ', '), #valueList } end -- no format return { value } end

---Return a table with the l10n key name and the formatted value of the `stat`. ---@param statvalues table The entire data table. ---@param stat string The stat name for which the l10n key and value is to be formatted. ---@return table formatted Contains two keys, `statFormatted` and `valueFormatted`. local function formatStat(statvalues, stat) -- format stat, for l10n key local statFormatted = '' if stat == 'accWatch' or stat == 'dashType' or stat == 'rocketBoots' then --		the values of these three stats are like a switch		e.g. Copper Watch has "accWatch=1", Gold Watch has "accWatch=3"		so depending on the value, different l10n strings are needed		e.g. for Copper Watch: "displays the time down to the hour", for Gold Watch: "displays the time down to the minute"		and the l10n keys are based on the values, e.g. "stat_accWatch-1" for Copper Watch statFormatted = 'stat_' .. stat .. '-' .. statvalues[stat] else statFormatted = 'stat_' .. stat end -- format value local valueFormatted = formatStatValue(statvalues, stat)

return { statFormatted = statFormatted, valueFormatted = valueFormatted } end

---Return a string that is the fully formatted value of the specified `stat`. ---@param statvalues table The entire data table. ---@param stat string The stat name whose value is to be formatted. ---@return string local function formatSingleStat(statvalues, stat) local formatted = formatStat(statvalues, stat) -- formatted is now: { ["statFormatted"] = "(l10n key)", ["valueFormatted"] = { data1, data2 } } -- we only want data1, nothing else return formatted['valueFormatted'][1] end

---Return a string that is a bullet list with all stats in the `statvalues` table, fully formatted including l10n. ---@param statvalues table ---@return string local function formatStatList(statvalues) -- remove individual damage/crit stats if we have allDamage/allCrit if statvalues['allDamage'] ~= nil then statvalues['magicDamage'] = nil statvalues['meleeDamage'] = nil statvalues['minionDamage'] = nil statvalues['rangedDamage'] = nil end if statvalues['allCrit'] ~= nil then statvalues['magicCrit'] = nil statvalues['meleeCrit'] = nil statvalues['rangedCrit'] = nil end

local statStr = '' -- iterate over stats, apply formatting->l10n to each one. -- stats may overwrite each other (e.g. accRunSpeed & desertBoots), so don't modify the stat table itself. for stat, value in pairs(statvalues) do		local formatted = formatStat(statvalues, stat) -- formatted is now: { ["statFormatted"] = "(l10n key)", ["valueFormatted"] = { data1, data2 } } if formatted['valueFormatted'][2] == nil or formatted['valueFormatted'][2] == '' then -- if there is no supplemental formatting information if tostring(value):sub(1, 1) ~= '-' then formatted['valueFormatted'][2] = true -- set to true if value is positive else formatted['valueFormatted'][2] = '' end local vF1FirstChar = tostring(formatted['valueFormatted'][1]):sub(1, 1) if vF1FirstChar == '-' or vF1FirstChar == '+' then formatted['valueFormatted'][1] = tostring(formatted['valueFormatted'][1]):sub(2) -- strip +/- sign end end statStr = statStr .. '' if isDebug then statStr = statStr .. table.concat({ formatted['statFormatted'], formatted['valueFormatted'][1], formatted['valueFormatted'][2] }, '§§') .. ' – '		end statStr = statStr .. l10n(formatted['statFormatted'], formatted['valueFormatted'][1], formatted['valueFormatted'][2]) .. '' end if statStr == '' then return '' end return statStr.. '' end

---Return a statvalues table from an Equipinfo cargo query result. ---@param result table ---@return table local function parseFrom(result) local statvalues = {} for _, row in ipairs(result) do -- iterate over rows for stat, value in pairs(row) do -- iterate over columns if value ~= '' then local add = false local valueFirstChar = tostring(value):sub(1, 1) if statvalues[stat] ~= nil then add = valueFirstChar == '+' or valueFirstChar == '-' -- if value starts with +/-, then do perform addition/subtraction end if valueFirstChar == '+' then value = value:sub(2) -- strip plus sign end if add then statvalues[stat] = statvalues[stat] + value else statvalues[stat] = value end end end end return statvalues end

---Pre-process the `statvalues` table: Add `allDamage`/`allCrit` if necessary, and resolve `equippedAnyTileRangeAcc`. ---@param statvalues table ---@return table statvalues local function preProcessStatvalues(statvalues) -- if all four damage values are the same and not nil, add an "allDamage" stat local allDamageSet = (statvalues['magicDamage'] and statvalues['meleeDamage'] and statvalues['minionDamage'] and statvalues['rangedDamage']) ~= nil if allDamageSet and statvalues['magicDamage'] == statvalues['meleeDamage'] and statvalues['meleeDamage'] == statvalues['minionDamage'] and statvalues['minionDamage'] == statvalues['rangedDamage'] then statvalues['allDamage'] = statvalues['magicDamage'] end

-- if all three crit chance values are the same and not nil, add an "allCrit" stat local allCritSet = (statvalues['magicCrit'] and statvalues['meleeCrit'] and statvalues['rangedCrit']) ~= nil if allCritSet and statvalues['magicCrit'] == statvalues['meleeCrit'] and statvalues['meleeCrit'] == statvalues['rangedCrit'] then statvalues['allCrit'] = statvalues['magicCrit'] end

-- if all the "equippedAnyTileRangeAcc" is set, pass its effects to "tileRangeX" and "tileRangeY" if statvalues['equippedAnyTileRangeAcc'] then statvalues['tileRangeX'] = (statvalues['tileRangeX'] or 0) + 3 statvalues['tileRangeY'] = (statvalues['tileRangeY'] or 0) + 2 end

return statvalues end

- -- main return object return {

go = function(frame) -- init cache currentFrame = frame args_table = frame.args isDebug = getArg('debug')

-- prepare cargo query local queryItemId = getArg('queryItemId') local statList = getStatList local queryItemIdList = queryItemId:gsub('₴', ',') -- input is "1234₴42₴777", but SQL expects "1234,42,777"

-- fetch data: perform cargo query local result = cargo.query('Equipinfo', statList, { where = 'itemid IN (' .. queryItemIdList .. ')' } )	if result == nil then return end local statvalues = parseFrom(result) statvalues = preProcessStatvalues(statvalues)

-- assemble output string local outputMode = getArg('outputMode') local queryStat = getArg('queryStat') local outputStr = '' if outputMode == 'raw' then -- raw: just print the value as it is in cargo outputStr = statvalues[queryStat] elseif outputMode == 'singlestat' then -- single stat: print the formatted value of one stat outputStr = formatSingleStat(statvalues, queryStat) elseif outputMode == 'listraw' then -- raw list: print the values of all stats for stat, value in pairs(statvalues) do outputStr = outputStr .. stat .. ':' .. value .. ';'		end elseif outputMode == 'list' then -- list: print a fully formatted and localized bullet list with all stats outputStr = formatStatList(statvalues) end

if isDebug then outputStr = '(debug start)' .. debugStr .. '(debug end) ' .. outputStr end

return outputStr

end, }