Module:ItemData: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Fixed Lang postfix overrides not applied in Enhanced version
Undo revision 64880 by Monster Domosed (talk) didn't work as intended
Tag: Undo
Line 435: Line 435:
end
end


-- Helping functions for [[Module:Enhanced]]
-- Helping function for [[Module:Enhanced]]
local function get_postfix_from_lang(item_name, property)
 
local key1 = item_name .. "_" .. property .. "_postfix"
local key2 = property .. "_postfix"
 
local v = lang.get_string(key1)
if v and v ~= "" then return v end
 
v = lang.get_string(key2)
if v and v ~= "" then return v end
 
return nil
end
 
 
function p.get_prop_meta(item_name, property)
function p.get_prop_meta(item_name, property)
local item = get_json_item(item_name)
local item = get_json_item(item_name)
Line 459: Line 444:


local meta = {
local meta = {
unit = nil,
unit = nil,     -- "%", "m", "s" or nil
scale = nil
scale = nil    -- { type = "Ss"/"Boon", value = number } or nil
}
}


if percentage_properties[property] then
if percentage_properties[property] then
meta.unit = "%"
meta.unit = "%"
end
if not meta.unit then
local postfix = get_postfix_from_lang(item_name, property)
if postfix and postfix ~= "" then
meta.unit = postfix
end
end
end



Revision as of 15:41, 15 April 2026

Loads data from Data:ItemData.json

Usage

{{#invoke:ItemData|FUNCTION_NAME|PARAMETER1|PARAMETER2}}

Functions

  • get_cost|ITEM_NAME
    • Returns the total cost including components.
  • get_type|ITEM_NAME
    • The ItemData.json has different names for each category: Weapon = Weapon, Vitality = Armor, Spirit = Tech. This function will return Weapon, Vitality, or Spirit
  • is_active|ITEM_NAME
    • returns true/false
  • get_prop|ITEM_NAME|PROPERTY
    • Returns the value of one of the item's properties. Check the json file for item properties.
    • Automatically appends "%" to percentage-based properties and shows scaling templates (Boon/Ss) when applicable.
  • get_codename|ITEM_NAME
    • Returns the item's codename in game files
  • get_component_name|ITEM_NAME
    • Returns the display name of the item's first component (if any)
  • get_headcount
    • Returns the number of items that are not disabled
  • get_builds_into|ITEM_NAME
    • Returns a formatted list of items that use this item as a component, rendered with ItemIcon templates (e.g., "ItemIcon templates separated by commas and 'and'")
  • get_builds_into_count|ITEM_NAME
    • Returns the number of items that use this item as a component
  • get_builds_into_name|ITEM_NAME|INDEX
    • Returns the display name of the nth item (1-indexed) that uses this item as a component

Example

To get the total cost of Boundless Spirit you'd type:

{{#invoke:ItemData|get_cost|Boundless Spirit}}

and it would return:

6,400

To get Boundless Spirit's Spirit Power:

{{#invoke:ItemData|get_prop|Boundless Spirit|TechPower}}

and it would return:

30

If an item key (English name) has been provided, the item name can be replaced by:

{{#var:item_key_or_en}}

for example:

{{#invoke:ItemData|get_prop|{{#var:item_key_or_en}}|TechPower}}

To get Boundless Spirit's codename:

{{#invoke:ItemData|get_codename|Boundless Spirit}}

and it would return:

upgrade_boundless_spirit

To find what items Debuff Reducer builds into:

{{#invoke:ItemData|get_builds_into|Debuff Reducer}}

and it would return:

Spellbreaker and Unstoppable


local p = {};
local lang = require "Module:Lang"
local util_module = require "Module:Utilities"
local data = mw.loadJsonData("Data:ItemData.json")

-- A list of item properties that should have a '%' sign appended to their base value.
local percentage_properties = {
	AbilityLifestealPercentHero = true,
	AbilityLifestealPercentHeroPassive = true,
	ActivatedFireRate = true,
	ActiveBonusFireRate = true,
	ActiveBonusLifesteal = true,
	ActiveReloadPercent = true,
	AirBonusDamageGiven = true,
	AirBonusDamageTaken = true,
	AirControlPercent = true,
	AirMoveIncreasePercent = true,
	AllyPercentage = true,
	AmbushBonusFireRate = true,
	AmbushBonusMeleeDamage = true,
	AmmoReloadPercent = true,
	BaseAttackDamagePercent = true,
	BaseAttackDamagePercentAtMaxDuration = true,
	BaseAttackDamagePercentBonus = true,
	BonusAbilityDurationPercent = true,
	BonusAttackRangePercent = true,
	BonusBulletSpeedPercent = true,
	BonusClipSizePercent = true,
	BonusDamagePercent = true,
	BonusFireRate = true,
	BonusHeavyMeleeDamage = true,
	BonusMeleeDamagePercent = true,
	BonusSoulsPct = true,
	BonusZoomPercent = true,
	BuffBulletResist = true,
	BuffMeleeDamageBoost = true,
	BuffTechResist = true,
	BulletArmorReduction = true,
	BulletDamageReflectedPct = true,
	BulletLifestealPercent = true,
	BulletProcDeflectionPercent = true,
	BulletResist = true,
	BulletResistBelowThreshold = true,
	BulletResistPerStack = true,
	BulletResistReduction = true,
	CloseRangeBonusWeaponPower = true,
	CooldownBetweenChargeReduction = true,
	CooldownReduction = true,
	CooldownReductionOnChargedAbilities = true,
	CooldownReductionOnHitPct = true,
	CooldownReductionPctOnNonHeroes = true,
	CritDamagePercent = true,
	CurrentHealthDamage = true,
	DamagePctVsNonHeroes = true,
	DeathImmunityDamageReduction = true,
	DeflectionPercent = true,
	DotHealthPercent = true,
	FervorTechResist = true,
	FervorFireRate = true,
	FireRatePerKill = true,
	FireRateBonus = true,
	FireRateSlow = true,
	FireRateWhileSliding = true,
	GravityScale = true,
	GroundDashReductionPercent = true,
	HealAmpCastPercent = true,
	HealAmpReceivePenaltyPercent = true,
	HealAmpRegenPenaltyPercent = true,
	HealAmpRegenPercent = true,
	HealLifePercentOutOfCombat = true,
	HealPercentAmount = true,
	HealPercentPerHeadshot = true,
	HealthPctDamage = true,
	ItemCooldownReduction = true,
	ImbueAbilityLifesteal = true,
	ImbuedCooldownReduction = true,
	LifestealHealPercent = true,
	LightMeleeAmmo = true,
	LongRangeBonusWeaponPower = true,
	MagicIncreasePerStack = true,
	MagicResistReduction = true,
	MaxArmorStacks = true,
	MaxHealthDamage = true,
	MaxHealthLossPercent = true,
	MaxHealthPercentAsDPS = true,
	MaxHPDamage = true,
	MaxSlowPercent = true,
	MeleeDistanceScale = true,
	MeleeResistPercent = true,
	ModelScaleGrowthTooltip = true,
	MoveWhileShootingSpeedPenaltyReductionPercent = true,
	MoveWhileZoomedSpeedPenaltyReductionPercent = true,
	MovementSpeedSlow = true,
	NonHeroHealPct = true,
	NonHeroReductionPercent = true,
	NonPlayerBonusWeaponPower = true,
	NonPlayerBulletResist = true,
	OutgoingDamagePenaltyPercent = true,
	ParrySuccessHealPercentage = true,
	PercentDamage = true,
	ProcBaseAttackDamagePercent = true,
	ProcBaseAttackDamagePercentAltFire = true,
	ProcChance = true,
	ReduceFootstepSound = true,
	ReloadSpeedMultipler = true,
	ResistStealAmount = true,
	RespawnTime = true,
	RicochetDamagePercent = true,
	SelfModifier = true,
	SlideScale = true,
	SlowPercent = true,
	SlowResistancePercent = true,
	SpiritDamageReductionProc = true,
	SpiritDamageReflectedPct = true,
	StackingTechRangeMultiplier = true,
	StackingTechRadiusMultiplier = true,
	StaminaCooldownReduction = true,
	StatusResistancePercent = true,
	TechArmorDamageReduction = true,
	TechDamagePercent = true,
	TechDamageReduction = true,
	TechPowerPercent = true,
	TechRadiusMultiplier = true,
	TechRadiusMultiplierBuff = true,
	TechRangeMultiplier = true,
	TechRangeMultiplierBuff = true,
	TechResist = true,
	TechResistBelowThreshold = true,
	WeaponDamagePerStack = true,
	WeaponPowerPerStack = true,
}

local function strip_suffix(value)
	if type(value) == "number" then
		return tostring(value)
	end
	if type(value) ~= "string" then
		return tostring(value)
	end
	return value:match("[-%d%.]+") or value
end


---------------------------------------- Utility functions

-- Returns the table of a specific item.
-- Prioritizes active (non-disabled) items if multiple entries share the same name.
-- @function   get_json_item
-- @param      {string}
-- @return     {table}
local function get_json_item(name)
	-- First, try to find an active item. This handles cases where a disabled item
	-- with the same name exists in the data (e.g., old versions).
	for _, v in pairs(data) do
		if (v["Name"] == name and (v["IsDisabled"] == false or v["IsDisabled"] == nil)) then
			return v -- Return the active item immediately
		end
	end

	-- If no active item was found, fall back to finding any item with the name.
	-- This preserves functionality for items that are intentionally disabled.
	for _, v in pairs(data) do
		if (v["Name"] == name) then
			return v
		end
	end
	
	return nil -- Return nil if no item was found at all
end

-- Returns an array of item tables that have the same properties
-- @function   get_similar_items
-- @param      {string}
-- @return     {array of tables}
local function get_similar_items(property)
	local similarItems = {}
	for _, v in pairs(data) do
		if (v[property] ~= nil) then
			table.insert(similarItems, v)
		end
	end
	return similarItems
end

-- Adds commas delimiter to the thousands place
local function Format(amount)
	local contentLanguage = mw.language.getContentLanguage()
    local formatted = amount
    if(type(formatted) ~= "number") then return "<span style=\"color:red;\">Cannot format non-number value.</span>" end
    return contentLanguage:formatNum(tonumber(formatted))
end

--- Get cost of an item 
-- @function   get_cost
-- @param      {string}
-- @return     {number}
local function get_cost(item_name)
	local item = get_json_item(item_name)
	if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end
	return tonumber(item["Cost"])
end

--- Get type/slot/category of an item 
-- @function   get_type
-- @param      {string} item_name
-- @param      {boolean} raw (optional)
-- @return     {string}
local function get_type(item_name, raw)
	local item = get_json_item(item_name)
	if not item then 
		return "<span style=\"color:red;\">Item Not Found.</span>"
	end

	local slot = item["Slot"]
	if not slot then return nil end

	local key_map = {
		Weapon = 'CitadelCategoryWeapon',
		Armor = 'CitadelCategoryArmor',
		Tech = 'CitadelCategoryTech'
	}

	local key = key_map[slot]
	if not key then return nil end

	if raw then
		return lang.get_string(key, "en")
	else
		return lang.get_string(key)
	end
end


---------------------------------------- Invocable functions

--- {{#invoke:ItemData|get_cost|ITEM_NAME}} access point
p.get_cost = function(frame)
	local item_name = frame.args[1]
	return Format(get_cost(item_name))
end

-- Module access point
function p._get_cost(a)
	a = tostring(a) or '0'
	return get_cost(a)
end

-- {{#invoke:ItemData|get_type|ITEM_NAME}} access point
p.get_type = function(frame)
	local item_name = frame.args[1]
	-- Accept 'raw' as string "true" (case sensitive)
	local raw = frame.args['raw'] == 'true'
	return get_type(item_name, raw)
end

-- Module access point
function p._get_type(a)
	a = tostring(a) or ""
	return get_type(a)
end

--{{#invoke:ItemData|is_active|ITEM_NAME}}--
p.is_active = function(frame)
	local item_name = frame.args[1]
	local item = get_json_item(item_name)
	if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end
	
	if (item["Activation"] == "Passive") then
		return	false
	else 
		return true
	end
end

--{{#invoke:ItemData|get_prop|ITEM_NAME|PROPERTY}}--
--check Data:ItemData.json for properties
p.get_prop = function(frame)
	local item_name = frame.args[1]
	local property = frame.args[2]
	local raw = frame.args.raw == 'true'
	local item = get_json_item(item_name)
	if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end

	local prop_value = item[property]
	if (prop_value == nil) then return nil end

	local display_value
	local scaling_str = "" -- Default to an empty string for non-scaled values

	-- Determine the base value and scaling string (if any)
	if(type(prop_value) == "table") then
		if prop_value["Value"] ~= nil and prop_value["Scale"] ~= nil and prop_value["Scale"]["Value"] ~= nil and prop_value["Scale"]["Type"] ~= nil then
			-- This is a structured attribute with scaling data
			display_value = raw and strip_suffix(prop_value["Value"]) or tostring(prop_value["Value"])
			if not raw then
				local scale_val = tonumber(prop_value["Scale"]["Value"])
				if scale_val ~= 0 then
					local scale_type = prop_value["Scale"]["Type"]
					local rounded_scale_val = util_module.round_to_sig_fig(scale_val, 2)

					local template_title
					if scale_type == "power_increase" then
						template_title = "Template:Boon"
					else
						template_title = "Template:Ss"
					end

					local template_args = { [1] = rounded_scale_val }
					-- Add a space before the scaling template for proper separation
					scaling_str = " " .. mw.getCurrentFrame():expandTemplate{ title = template_title, args = template_args }
				end
			end
		else
			-- This is a simple list (e.g., TargetTypes), handle and return it directly
			local tableValues = {}
			for _, v in pairs(prop_value) do
				table.insert(tableValues, raw and strip_suffix(v) or tostring(v))
			end
			return table.concat(tableValues, ", ")
		end
	else
		-- This is a simple, non-table property
		display_value = raw and strip_suffix(prop_value) or tostring(prop_value)
	end

	if raw then
		return display_value
	end

	-- Assemble the final string, adding the '%' sign if necessary
	if percentage_properties[property] == true then
		return display_value .. "%" .. scaling_str
	else
		return display_value .. scaling_str
	end
end


p.get_component_name = function(frame)
	local item_name = frame.args[1]
	local slot_arg = frame.args[2]
	
	local item = get_json_item(item_name)
	if(item == nil) then return "<span style=\"color:red;\">Item Not Found.</span>" end
	
	local components = item['Components']
	--return empty string if no component found
	if(components == nil) then return nil end
	
	local slot = tonumber(slot_arg) or 1
	
	local component_key = components[slot]
	if (component_key == nil) then return nil end
	local component = data[component_key]
	local component = data[component_key]
	if (component == nil or component["IsDisabled"] == true) then return nil end
	
	return component['Name']
end

-- {{Items count}} access point
function p.get_headcount(frame)
	local item_type = frame.args[1]
	
	local iter = 0
	local tbl = get_similar_items("IsDisabled")
	for _, v in ipairs(tbl) do
		if item_type == "Standard" then
			-- return only regular items
		    if (v["IsDisabled"] == false and v["Slot"] ~= nil and v["StreetBrawl"] == false and v["Name"] ~= nil) then
			iter = iter + 1	
		   end
		elseif item_type == "Street Brawl" then
			-- return only street brawl items
        	if (v["IsDisabled"] == false and v["Slot"] ~= nil and v["StreetBrawl"] == true and v["Name"] ~= nil) then
			iter = iter + 1	
		   end
	   end
	end
	return iter
end

    
-- Gets the codename (the key in the JSON data) for a given item's display name.
-- Prioritizes active items to handle cases where disabled duplicates exist.
p.get_codename = function(frame)
	local name = frame.args[1]
	
	-- First, try to find an active item.
	for k, v in pairs(data) do
		if (v["Name"] == name and (v["IsDisabled"] == false or v["IsDisabled"] == nil)) then
			return k -- Return the codename of the active item
		end
	end
	
	-- If no active item was found, fall back to finding any item with the name.
	for k, v in pairs(data) do
		if (v["Name"] == name) then
			return k
		end
	end
	
	return nil -- Return nil if no item was found at all
end

-- {{#invoke:ItemData|get_property_upgrade|ITEM_NAME|PROPERTY}} --
p.get_property_upgrade = function(frame)
	local item_name = frame.args[1]
	local property = frame.args[2]
	local raw = frame.args.raw == 'true'

	local item = get_json_item(item_name)
	if item == nil then
		return "<span style=\"color:red;\">Item Not Found.</span>"
	end

	local upgrades = item["PropertyUpgrades"]
	if upgrades == nil then return nil end

	local value = upgrades[property]
	if value == nil then return nil end

	local result = raw and strip_suffix(value) or tostring(value)

	if raw then
		return result
	end

	-- Append % if this property is percentage-based
	if percentage_properties[property] == true then
		return result .. "%"
	else
		return result
	end
end

-- Helping function for [[Module:Enhanced]]
function p.get_prop_meta(item_name, property)
	local item = get_json_item(item_name)
	if not item then return nil end

	local raw = item[property]
	if raw == nil then return nil end

	local meta = {
		unit = nil,     -- "%", "m", "s" or nil
		scale = nil     -- { type = "Ss"/"Boon", value = number } or nil
	}

	if percentage_properties[property] then
		meta.unit = "%"
	end

	if type(raw) == "table"
		and raw.Scale
		and raw.Scale.Value
		and raw.Scale.Type
	then
		local scale_val = tonumber(raw.Scale.Value)
		if scale_val and scale_val ~= 0 then
			meta.scale = {
				type = raw.Scale.Type == "power_increase" and "Boon" or "Ss",
				value = util_module.round_to_sig_fig(scale_val, 2)
			}
		end
	end

	return meta
end

-- Builds Into functions

-- Helper: accepts item key or display name, returns the key used in data table
local function get_codename_from_input(input)
	if data[input] then return input end
	
	for k, v in pairs(data) do
		if v["Name"] == input and (v["IsDisabled"] == false or v["IsDisabled"] == nil) then
			return k
		end
	end
	for k, v in pairs(data) do
		if v["Name"] == input then return k end
	end
	return nil
end

-- Core logic: returns table of keys that have this item in their Components
local function get_component_of(item_input)
	local target_key = get_codename_from_input(item_input)
	if not target_key then return {} end
	
	local parents = {}
	for key, item in pairs(data) do
		if item.IsDisabled ~= true and item.Components and type(item.Components) == "table" then
			for _, comp in ipairs(item.Components) do
				if comp == target_key then
					table.insert(parents, key)
					break
				end
			end
		end
	end
	table.sort(parents)
	return parents
end

-- Template: {{#invoke:ItemData|get_builds_into_count|ITEM_NAME}}
p.get_builds_into_count = function(frame)
	local item = frame.args[1]
	return #get_component_of(item)
end

-- Template: {{#invoke:ItemData|get_builds_into_name|ITEM_NAME|INDEX}}
p.get_builds_into_name = function(frame)
	local item = frame.args[1]
	local idx = tonumber(frame.args[2]) or 1
	local parents = get_component_of(item)
	local key = parents[idx]
	return key and data[key] and data[key]["Name"] or ""
end

-- Template: {{#invoke:ItemData|get_builds_into|ITEM_NAME}}
-- Returns fully rendered ItemIcon templates
p.get_builds_into = function(frame)
	local item = frame.args[1]
	local parents = get_component_of(item)
	if #parents == 0 then return "" end
	
	local parts = {}
	for _, key in ipairs(parents) do
		local name = data[key] and data[key]["Name"] or key
		local icon = mw.getCurrentFrame():expandTemplate{ title = "ItemIcon", args = { name } }
		table.insert(parts, icon)
	end
	
	if #parts == 1 then return parts[1] end
	if #parts == 2 then return parts[1] .. " and " .. parts[2] end
	local last = table.remove(parts)
	return table.concat(parts, ", ") .. ", and " .. last
end

return p