Back to page | ← Module:Enemies/infobox View source You do not have permission to edit this page, for the following reason: This page has been protected to prevent editing or other actions. You can view and copy the source of this page. -- local Math = require([[Module:Math]]) local Tooltip = require([[Module:Tooltips]]) local Version = require([[Module:Version]]) local InfoboxBuilder = require([[Module:InfoboxBuilder]]) local DropData = mw.loadData([[Module:DropTables/data]]) local WeaponData = require([[Module:Weapons/data]]) local EnemyData = require([[Module:Enemies/data]]) local DamageTypesData = mw.loadData([[Module:DamageTypes/data]]) local DamageTypes = require([[Module:DamageTypes]]) local Table = require([[Module:Table]]) -- TODO: Move these values to their respective /data pages local health_vals = { ["Grineer"] = { f1_coef = 0.0150, f1_expo = 2.12, f2_coef = 10.733, f2_expo = 0.72, }, ["Corpus"] = { f1_coef = 0.0150, f1_expo = 2.12, f2_coef = 13.416, f2_expo = 0.55, }, ["Infestation"] = { f1_coef = 0.0225, f1_expo = 2.12, f2_coef = 16.100, f2_expo = 0.72, }, ["Orokin"] = { f1_coef = 0.0150, f1_expo = 2.10, f2_coef = 10.733, f2_expo = 0.685, }, } health_vals["Kuva Grineer"] = health_vals["Grineer"] health_vals["Corpus Amalgam"] = health_vals["Corpus"] health_vals["Infested"] = health_vals["Infestation"] health_vals["Infested Deimos"] = health_vals["Infestation"] -- This is the default case setmetatable(health_vals, { __index = function () return { f1_coef = 0.0150, f1_expo = 2.00, f2_coef = 10.733, f2_expo = 0.5, } end }) local shield_vals = { ["Grineer"] = { f1_coef = 0.0200, f1_expo = 1.75, f2_coef = 1.6000, f2_expo = 0.75, }, ["Corpus"] = { f1_coef = 0.0200, f1_expo = 1.76, f2_coef = 2.0000, f2_expo = 0.76, }, ["Infestation"] = { f1_coef = 0.0200, f1_expo = 1.75, f2_coef = 1.6000, f2_expo = 0.75, }, } shield_vals["Kuva Grineer"] = shield_vals["Grineer"] shield_vals["Corpus Amalgam"] = shield_vals["Corpus"] shield_vals["Infested"] = shield_vals["Infestation"] shield_vals["Infested Deimos"] = shield_vals["Infestation"] setmetatable(shield_vals, { __index = function () return { f1_coef = 0.0200, f1_expo = 1.75, f2_coef = 2.0000, f2_expo = 0.75, } end }) local armor_vals = {} setmetatable(armor_vals, { __index = function () return { f1_coef = 0.0050, f1_expo = 1.75, f2_coef = 0.4000, f2_expo = 0.75, } end }) local overguard_vals = {} setmetatable(overguard_vals, { __index = function () return { f1_coef = 0.0015, f1_expo = 4.00, f2_coef = 260.00, f2_expo = 0.90, } end }) local function concatif(stat, str) return stat and stat..str end --- Builds infobox group for enemy attacks. -- @function Infobox:attackGroup -- @param {InfoboxBuilder} Infobox InfoboxBuilder object reference -- @param {table} enemyData Enemy entry as seen in M:Enemies/data local function attackGroup(Infobox, enemyData) local damageHorizGroup, elems, total, highestDmgDistr, highestDmgDistrType, attackData for i, attackData in ipairs(enemyData.Stats.Attacks or {}) do attack = 'Attack'..i elems = {} highestDmgDistr = -1 for damageType, distr in pairs(attackData.DamageDistribution) do if highestDmgDistr < distr then highestDmgDistr = distr highestDmgDistrType = damageType end if damageType ~= 'Impact' and damageType ~= 'Puncture' and damageType ~= 'Slash' then table.insert(elems, damageType) end end total = attackData.TotalDamage damageHorizGroup = mw.html.create('group'):attr('layout', 'horizontal') :row(attack..'Impact', nil, attackData.DamageDistribution.Impact and Tooltip.icon('Impact', 'DamageTypes', true)..Math.formatnum(attackData.DamageDistribution.Impact * total), 'impact') :row(attack..'Puncture', nil, attackData.DamageDistribution.Puncture and Tooltip.icon('Puncture', 'DamageTypes', true)..Math.formatnum(attackData.DamageDistribution.Puncture * total), 'puncture') :row(attack..'Slash', nil, attackData.DamageDistribution.Slash and Tooltip.icon('Slash', 'DamageTypes', true)..Math.formatnum(attackData.DamageDistribution.Slash * total), 'slash') for _, elem in ipairs(elems) do damageHorizGroup:row(attack..elem, nil, attackData.DamageDistribution[elem] and Tooltip.icon(elem, 'DamageTypes', true)..Math.formatnum(attackData.DamageDistribution[elem] * total), elem) end local multishot = attackData.Multishot or 1 Infobox:group():header(attackData.AttackName) :node(damageHorizGroup) :row(attack..'Total', '[[Damage|%s]]', highestDmgDistr == 1 and Math.formatnum(total * multishot)..'[[Category:'..highestDmgDistrType..' Damage Enemies]]' or ('%s (%s%s%%)[[Category:%s Damage Enemies]]'):format(Math.formatnum(total * multishot), Tooltip.icon(highestDmgDistrType, 'DamageTypes', true), Math.round(100 * highestDmgDistr, 0.01), highestDmgDistrType), 'total-damage') :row(attack..'BurstCount', '%s', attackData.BurstCount, 'burst-count') :row(attack..'ChargeTime', '[[Fire Rate#Charged Weapons|%s]]', concatif(attackData.ChargeTime, ' s'), 'charge-time') -- As of 35.0.9, enemies no longer deal critical damage. --:row(attack..'CritChance', '[[Critical Hit|%s]]', attackData.CritChance and Math.round(100 * attackData.CritChance, 0.01)..'%', 'crit-chance') --:row(attack..'CritMultiplier', '[[Critical Hit|%s]]', concatif(attackData.CritMultiplier, 'x'), 'crit-multiplier') :row(attack..'Falloff', '[[Damage Falloff|%s]]', attackData.Falloff and ('100%% damage up to %s m
%.0f%% damage at %s m
%.0f%% max reduction'):format(attackData.Falloff.StartRange, 100 * (1 - (attackData.Falloff.Reduction or 1)), attackData.Falloff.EndRange, 100 * (attackData.Falloff.Reduction or 1)), 'damage-falloff') :row(attack..'Multishot', '[[Multishot|%s]]', attackData.Multishot and ('%d (%s damage per projectile)'):format(attackData.Multishot, Math.round(total, 0.01)), 'multishot') :row(attack..'Range', '%s', concatif(attackData.Range, ' m'), 'range') :row(attack..'Magazine', '[[Ammo#Magazine Capacity|%s]]', attackData.Magazine, 'magazine-size') :row(attack..'Reload', '[[Reload|%s]]', concatif(attackData.Reload, ' s'), 'reload-time') :row(attack..'StatusChance', '[[Status Chance|%s]]', attackData.StatusChance and Math.round(100*attackData.StatusChance, 0.01)..'%', 'status-chance') :row(attack..'ShotSpeed', '[[Projectile Speed|%s]]', concatif(attackData.ShotSpeed, ' m/s'), 'projectile-speed') :row(attack..'ShotType', '%s', attackData.ShotType, 'projectile-type') :done() end end return { buildInfobox = function(frame) local args = frame.args local name = mw.text.decode(args['Name'] or mw.title.getCurrentTitle().text) -- In the case of Stalker, error occurred because its name was same as the faction name local enemyData if name ~= "Stalker" then -- is the copying necessary? -- TODO: make the titlecaser unnecessary enemyData = Table.deepCopy(EnemyData[name] or EnemyData[name:lower():gsub('^',' '):gsub('%W%w',string.upper):gsub('^ ','')]) else enemyData = require([[Module:Enemies/data/stalker]])["Stalker"] end if not enemyData then error('No enemy data for name "'..(name or '')..'" found in [[Module:Enemies/data]]/*') end -- TODO: Move all the prep before constructing a new Infobox object into a separate local helper function -- TODO: Not sure if missionNames should have an equivalent key in the enemy data. Adding this new table since -- some usages of T:Enemy uses mission arg for the mission name instead of mission type for assassination targets. local planets, tileSets, missionNames, missions, weapons, abilities, multis, procs = {}, {}, {}, {}, {}, {}, {}, {} for _, planet in ipairs(enemyData.General.Planets or {}) do table.insert(planets, '[['..planet..']]') end for _, tileSet in ipairs(enemyData.General.TileSets or {}) do table.insert(tileSets, '[['..tileSet..']]') end for _, mission in ipairs(enemyData.General.Missions or {}) do table.insert(missions, '[['..mission..']]') end for _, weapon in ipairs(enemyData.General.Weapons or {}) do table.insert(weapons, WeaponData[weapon] and Tooltip.full(weapon, 'Weapons') or weapon) end for _, ability in ipairs(enemyData.General.Abilities or {}) do table.insert(abilities, '[['..ability..']]') end for _, multi in ipairs(enemyData.Stats.Multis or {}) do table.insert(multis, multi) end for _, proc in ipairs(enemyData.Stats.ProcResists or {}) do table.insert(procs, Tooltip.full(proc, 'DamageTypes')) end table.sort(planets) table.sort(tileSets) table.sort(missions) table.sort(weapons) table.sort(abilities) table.sort(multis) table.sort(procs) local faction, overguard, shield, health, armor, affinity, baseLevel, spawnLevel overguard = (args['Overguard'] or ''):gsub(',', '') -- TODO: Replace thousands delimiter replacement once we add all enemy data to M:Enemies/data shield = (args['Shields'] or ''):gsub(',', '') health = (args['Health'] or ''):gsub(',', '') armor = (args['Armor'] or ''):gsub(',', '') affinity = (args['Affinity'] or ''):gsub(',', '') faction = args['Faction'] ~= '' and args['Faction'] or enemyData.General.Faction or '' shield = tonumber(shield) or enemyData.Stats.Shield or 0 health = tonumber(health) or enemyData.Stats.Health or 0 armor = tonumber(armor) or enemyData.Stats.Armor or 0 overguard = tonumber(overguard) or enemyData.Stats.Overguard or 0 affinity = tonumber(affinity) or enemyData.Stats.Affinity or 0 baseLevel = tonumber(args['BaseLevel']) or enemyData.Stats.BaseLevel or 1 spawnLevel = tonumber(args['SpawnLevel']) or enemyData.Stats.SpawnLevel or baseLevel -- [[MediaWiki:EnemyInfoboxSlider.js]] will try to update all ids at once so adding -- hidden empty rows local vals = { (not(faction) or faction == '') and '' or '', (not(affinity) or affinity == 0) and '' or '', (not(overguard) or overguard == 0) and '' or '', (not(shield) or shield == 0) and '' or '', (not(health) or health == 0) and '' or '', (not(armor) or armor == 0) and '' or '', (not(armor) or armor == 0) and '' or '', (not(baseLevel) or baseLevel == 0) and '' or '', (not(spawnLevel) or spawnLevel == 0 or spawnLevel == baseLevel) and '' or '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', } local mods, resources, relics, blueprints, missionDrops, sigils, items, pigments, others = {}, {}, {}, {}, {}, {}, {}, {}, {} local enemyDrops = DropData.Enemies[name] -- Proliferating drop lists from each possible drop table that an enemy can have -- TODO: Refactor drop list builder in a local function -- TODO: This can be refactored into Module:DropTables since that module contains definitions of -- item table entries via constants (e.g. ITEM_CHANCE_COL) if (enemyDrops and not enemyDrops._IgnoreEntry) then -- Item type to module name (e.g. 'Mod' to 'Mods' for Module:Mods) local itemTypeModuleMap = { Mod = 'Mods', Resource = 'Resources', Arcane = 'Arcane', Relic = 'Void', Sigil = 'Sigils', Blueprint = 'Blueprints', } for _, mod in ipairs(enemyDrops.Mods or {}) do local tooltip = Tooltip.full(mod[1], itemTypeModuleMap[mod[2]]) table.insert(mods, ('%s %0.2f%%') :format( mod[4] and mod[4]..' '..tooltip or tooltip, enemyDrops.ModChance * mod[3] / 100) ) end for _, resource in ipairs(enemyDrops.Resources or {}) do table.insert(resources, ('%s%s %0.2f%%') :format( resource[4] and resource[4]..' ' or '', require('Module:Tooltips/icon')['Resources'](resource[1]) and Tooltip.full(resource[1], 'Resources') or '[['..resource[1]..']]', enemyDrops.ResourceChance * resource[3] / 100) ) end for _, relic in ipairs(enemyDrops.Relics or {}) do table.insert(relics, ('%s %0.2f%%'):format( Tooltip.full(relic[1], 'Void'), enemyDrops.RelicChance * relic[3] / 100) ) end for _, blueprint in ipairs(enemyDrops.Blueprints or {}) do table.insert(blueprints, ('%s %0.2f%%'):format( blueprint[1], enemyDrops.BlueprintChance * blueprint[3] / 100) ) end for _, sigil in ipairs(enemyDrops.Sigils or {}) do table.insert(sigils, ('%s %0.2f%%') :format( Tooltip.full(sigil[1], itemTypeModuleMap[sigil[2]]) or sigil[1], enemyDrops.SigilChance * sigil[3] / 100) ) end for _, item in ipairs(enemyDrops.Items or {}) do table.insert(items, ('%s %0.2f%%') :format( item[2]~='Item' and Tooltip.full(item[1], itemTypeModuleMap[item[2]]) or item[1], enemyDrops.ItemChance * item[3] / 100) ) end for _, pigment in ipairs(enemyDrops.Pigments or {}) do table.insert(pigments, ('%sx %s %0.2f%%') :format( pigment[4], pigment[2]~='Item' and Tooltip.full(pigment[1], itemTypeModuleMap[pigment[2]]) or pigment[1], enemyDrops.PigmentChance * pigment[3] / 100) ) end end local Infobox = InfoboxBuilder('WARFRAME Wiki:L10n/general.json', 'WARFRAME Wiki:L10n/meta.json', 'WARFRAME Wiki:L10n/weapons.json'):attr('type', 'enemyBox') :tag('title') :tag('default') :tag('b'):wikitext(name..'[[Category:Enemies]]'):done() :done() :done() :tag('image'):attr('source', 'Image') :tag('default'):wikitext(enemyData.General.Image or 'UnidentifiedItem.png'):done() :done() :group() :caption('CodexSecret', enemyData.General.CodexSecret and '[[Codex|%s]][[Category:Codex Secret]]' or nil, 'codex-secret') :caption('UpdateInfoboxData', '[[Module:Enemies/data|📝 %s]]', 'update-infobox-data') :caption('Description', enemyData.General.Description) :done() :group():header('%s', 'general-information') :srow('Faction', '[[Faction|%s]]', 'faction', faction, enemyData.General.Faction~='' and '[[Category:'..enemyData.General.Faction..']]', 'faction') :row('Planets', '[[Star Chart|Planet(s)]]', (planets and planets[1]) and table.concat(planets, '
')) :row('MissionNames', '[[Mission|Mission Name(s)]]', (missionNames and missionNames[1]) and table.concat(missionNames, '
')) :row('Missions', '[[Mission|Mission Type(s)]]', (missions and missions[1]) and table.concat(missions, '
')) :row('TileSets', '[[Tile Sets|Tile Set(s)]]', (tileSets and tileSets[1]) and table.concat(tileSets, '
')) :row('Type', '%s', enemyData.General.Type, 'type', '[[Category:'..enemyData.General.Type..' Enemies]]') :row('Weapons', 'Weapon(s)', (weapons and weapons[1]) and table.concat(weapons, '
')) :row('Abilities', 'Abilities', (abilities and abilities[1]) and table.concat(abilities, '
')) :done() :group():header('%s', 'statistics') :srow('Affinity', '[[Affinity#Enemy Affinity Scaling|Affinity]]', 'affinity', affinity) :srow('Overguard', '[[Overguard]]', 'overguard', overguard, '[[Category:Overguard Enemies]]') :caption('OverguardT', overguard and overguard ~= 0 and DamageTypes.healthMod('Overguard') or nil) :srow('Shields', '[[Shield]]', 'shield', shield) :srow('Health', '[[Health]]', 'health', health) :srow('Armor', '[[Armor]]', 'armor', armor) :srow('DmgReduction', '[[Damage Reduction|Dmg. Reduction]]', 'damage_redux', armor and Math.round(math.sqrt(3 * armor), 0.01), '%') :caption('Resists', DamageTypes.healthMod(enemyData.General.FactionDamageOverride or enemyData.General.Faction)) :row('Bleedout', '[[Bleedout]]', concatif(enemyData.Stats.Bleedout,' s')) :row('BodyMultis', '[[Enemy Body Parts|Body Multipliers]]', (multis and multis[1]) and table.concat(multis, '
')) :row('ProcResists', '[[Proc|Proc Immunity]]', (procs and procs[1]) and table.concat(procs, '  ')) :srow('BaseLevel', '[[Enemy Level Scaling#Scaling of Fundamental Stats|Base Level]]', 'base_level', baseLevel) :srow('SpawnLevel', '[[Enemy Level Scaling#Scaling of Fundamental Stats|Spawn Level]]', 'spawn_level', (spawnLevel and baseLevel ~= spawnLevel) and spawnLevel or 0) :done() attackGroup(Infobox, enemyData) Infobox:group() :header('Level Scaling') :tag('data'):attr('source', 'Slider') :tag('default'):wikitext(table.concat(vals)..'
JavaScript not loaded. Please refresh your browser using Ctrl+F5 on PC or Shift+R on Mac.
'):done() :done() :tag('data'):attr('source', 'SelectedLevel') :tag('label'):wikitext('{{text||Selected Level|hover=For higher enemy levels input the value manually.|cursor=help}}'):done() :tag('default'):wikitext('––'):done() :done() :tag('data'):attr('source', 'EHP') :tag('label'):wikitext('{{text||EHP|hover=Effective amount of hit points, taking health, armor and shields into account.|cursor=help}}'):done() :tag('default'):wikitext('––'):done() :done() :tag('data'):attr('source', 'SP_EHP') :tag('label'):wikitext('[[The Steel Path|Steel Path]] EHP'):done() :tag('default'):wikitext('––'):done() :done() :done() :group():header('%s', 'miscellaneous') :row('CodexScans', '[[Codex|Codex Scans]]', enemyData.General.Scans) :row('VA', 'Voice Actor', enemyData.General.Actor) :row('Introduced', '%s', enemyData.General.Introduced and Version._getVersionLink(enemyData.General.Introduced), 'introduced', enemyData.General.Introduced and Version._getVersionCategory(enemyData.General.Introduced)) :done() :group():header('%s', 'drops') :caption('NoDrops', true and not next(mods) and not next(resources) and not next(relics) and not next(blueprints) and not next(missionDrops) and not next(sigils) and not next(items) and not next(others) and 'None[[Category:Enemies With No Drops]]' or nil) :row('ModDrops', '[[File:Mod TT 20px.png|x12px|link=]] [[Mod|Mod Drops]]', table.concat(mods, '
')) :row('ResourceDrops', '[[Resources|Resource Drops]]', table.concat(resources, '
')) :row('RelicDrops', '[[Void Relic|Relic Drops]]', table.concat(relics, '
')) :row('BPDrops', '[[Foundry|Blueprint/Item Drops]]', table.concat(blueprints, '
')) :row('ItemDrops', 'Additional Item Drops', table.concat(items, '
')) :row('MissionDrops', '[[Mission|Mission Drops]]', table.concat(missionDrops, '
')) :row('SigilDrops', '[[Sigils|Sigil Drops]]', table.concat(sigils, '
')) :row('PigmentDrops', '[[Pigment|Pigment Drops]]', table.concat(pigments, '
')) -- Editor override on articles; these drops are not listed in M:DropTables/data :row('OtherDrops', 'Other Drops', table.concat(others, '
')) :done() :group():header('%s', 'official-drop-tables') :caption('official-drop-tables', 'https://www.warframe.com/droptables', 'official-drop-tables') :done() return frame:preprocess(tostring(Infobox)) end, -- TODO: Implement this function based on [[Template:EnemyHoriz]] contents buildHorizontalInfobox = function(frame) return error() end } Template used on this page: Module:Enemies/infobox/doc (edit) Return to Module:Enemies/infobox.