more-descriptions/data-final-fixes.lua

Wed, 23 Jul 2025 16:08:48 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Wed, 23 Jul 2025 16:08:48 +0300
changeset 17
df60e9144d82
parent 16
2cdb59ae5fcf
permissions
-rw-r--r--

version 1.2.0

---@type data.FeatureFlags
feature_flags = feature_flags

local function seconds(x)
	return {"time-symbol-seconds", tostring(x)}
end

local energy_zero_table = {
	["0W"] = 1,
	["0kW"] = 1,
	["0MW"] = 1,
	["0GW"] = 1,
	["0TW"] = 1,
}

---@type table<string, 1>
local recipes_that_have_productivity_research = {}

for _, technology in pairs(data.raw["technology"] or {})
do
	for _, effect in pairs(technology.effects or {})
	do
		if effect.type == "change-recipe-productivity"
		then
			recipes_that_have_productivity_research[effect.recipe] = 1
		end
	end
end

local function basic_order(a)
	return 100 + a
end

local function generic_order(a)
	return 150 + a
end

local function specific_order(a)
	return 200 + a
end

---@param x string
local function energy_zero(x)
	return x == nil or energy_zero_table[x]
end

---@param x LocalisedString
-- localised string containing too many entries? snap it into many pieces and catenate them
local function snap(x)
	if type(x) == "table" and #x > 20 and x[1] == ""
	then
		local max_per_part = math.ceil((#x - 1) / 18)
		local result = {""}
		local segment = {""}
		for k, v in pairs(x)
		do
			if k > 1
			then
				if #segment >= max_per_part + 1
				then
					table.insert(result, snap(segment))
					segment = {""}
				end
				table.insert(segment, v)
			end
		end
		if #segment > 1
		then
			table.insert(result, snap(segment))
		end
		return result
	else
		return x
	end
end

local item_categories =
{
	"item",
	"item-with-entity-data",
	"rail-planner",
	"capsule",
	"repair-tool",
	"blueprint",
	"deconstruction-item",
	"upgrade-item",
	"blueprint-book",
	"copy-paste-tool",
	"module",
	"tool",
	"gun",
	"ammo",
	"space-platform-starter-pack",
	"armor",
	"spidertron-remote",
}

local entity_categories =
{
	"container",
	"storage-tank",
	"transport-belt",
	"underground-belt",
	"splitter",
	"loader",
	"inserter",
	"electric-pole",
	"pipe",
	"pipe-to-ground",
	"pump",
	"straight-rail",
	"half-diagonal-rail",
	"curved-rail-a",
	"curved-rail-b",
	"elevated-straight-rail",
	"elevated-half-diagonal-rail",
	"elevated-curved-rail-a",
	"elevated-curved-rail-b",
	"legacy-straight-rail",
	"legacy-curved-rail",
	"rail-ramp",
	"rail-support",
	"train-stop",
	"rail-signal",
	"rail-chain-signal",
	"locomotive",
	"cargo-wagon",
	"fluid-wagon",
	"artillery-wagon",
	"car",
	"spider-vehicle",
	"logistic-robot",
	"construction-robot",
	"logistic-container",
	"roboport",
	"lamp",
	"arithmetic-combinator",
	"decider-combinator",
	"selector-combinator",
	"constant-combinator",
	"power-switch",
	"programmable-speaker",
	"display-panel",
	"boiler",
	"generator",
	"fusion-reactor",
	"fusion-generator",
	"mining-drill",
	"offshore-pump",
	"furnace",
	"assembling-machine",
	"agricultural-tower",
	"lab",
	"lightning-attractor",
	"reactor",
	"beacon",
	"rocket-silo",
	"cargo-landing-pad",
	"space-platform-hub",
	"cargo-bay",
	"asteroid-collector",
	"thruster",
	"wall",
	"gate",
	"radar",
	"land-mine",
	"ammo-turret",
	"electric-turret",
	"fluid-turret",
	"artillery-turret",
	"plant",
	"simple-entity-with-force",
	"simple-entity-with-owner",
	"electric-energy-interface",
	"linked-container",
	"proxy-container",
	"heat-interface",
	"heat-pipe",
	"lane-splitter",
	"linked-belt",
	"valve",
	"infinity-cargo-wagon",
	"infinity-container",
	"infinity-pipe",
	"burner-generator",
	"resource",
	"cargo-pod",
	"temporary-container",
	"asteroid",
	"combat-robot",
	"unit",
	"turret",
	"unit-spawner",
	"spider-unit",
	"segmented-unit",
	"cliff",
	"character",
	"fish",
	"tree",
	"simple-entity",
	"lightning",
	"corpse",
	"rail-remnants",
	"explosion",
	"particle-source",
	"fire",
	"sticker",
	"stream",
	"artillery-flare",
	"artillery-projectile",
	"projectile",
	"segment",
	"spider-leg",
	"beam",
	"character-corpse",
	"speech-bubble",
	"smoke-with-trigger",
	"entity-ghost",
	"arrow",
	"highlight-box",
	"item-entity",
	"item-request-proxy",
	"loader-1x1",
	"rocket-silo-rocket",
	"rocket-silo-rocket-shadow",
	"tile-ghost",
	"market",
	"capture-robot",
	"solar-panel",
}

-- Find all entity categories that contain prototypes that can freeze.
-- If a prototype in such a category cannot freeze, that's worth pointing out.
-- However, it's not worth pointing out that the steel chest cannot freeze,
-- since no container freezes (in vanilla anyway).
local freezable_entity_categories = {}
if feature_flags.freezing
then
	for _, category in pairs(entity_categories)
	do
		for _, entity in pairs(data.raw[category] or {})
		do
			if not energy_zero(entity.heating_energy)
			then
				freezable_entity_categories[entity.type] = 1
				break
			end
		end
	end
end

local function find_entity(name)
	for _, entity_category in pairs(entity_categories)
	do
		if data.raw[entity_category] and data.raw[entity_category][name]
		then
			return data.raw[entity_category][name]
		end
	end
	return nil
end

-- @type table<string, string>
local ammo_categories = {}

for _, ammo in pairs(data.raw.ammo)
do
	if not ammo_categories[ammo.ammo_category]
	then
		ammo_categories[ammo.ammo_category] = ""
	end
	if #ammo_categories[ammo.ammo_category] < 120
	then
		ammo_categories[ammo.ammo_category] = ammo_categories[ammo.ammo_category].."[item="..ammo.name.."]"
	elseif string.sub(ammo_categories[ammo.ammo_category], -3, -1) ~= "..."
	then
		ammo_categories[ammo.ammo_category] = ammo_categories[ammo.ammo_category] .. "..."
	end
end

---@param proto data.Prototype
---@param tooltip_field data.CustomTooltipField
local function generic_add_description(proto, tooltip_field)
	if not proto.custom_tooltip_fields
	then
		proto.custom_tooltip_fields = {}
	end
	table.insert(proto.custom_tooltip_fields, tooltip_field)
end

---@param proto data.Prototype
---@param attack_parameters  data.AttackParameters
local function add_ammo_from_attack_parameters(proto, attack_parameters)
	if attack_parameters.ammo_category and ammo_categories[attack_parameters.ammo_category]
	then
		generic_add_description(proto, {
			name = {"more-descriptions-mod.gun-accepts-ammo"},
			value = ammo_categories[attack_parameters.ammo_category],
			order = generic_order(10),
		})
	end
	for _, category in pairs(attack_parameters.ammo_categories or {})
	do
		if ammo_categories[category]
		then
			generic_add_description(proto, {
				name = {"more-descriptions-mod.gun-accepts-ammo"},
				value = ammo_categories[category],
				order = generic_order(0),
			})
		end
	end
end

for _, item_type in pairs(item_categories)
do
	for _, item in pairs(data.raw[item_type] or {})
	do
		local recycling_recipe = data.raw.recipe[item.name.."-recycling"]

		---@param tooltip_field data.CustomTooltipField
		local function add_description(tooltip_field)
			generic_add_description(item, tooltip_field)
		end
		if recycling_recipe ~= nil
		then
			local recycling_results = {""}
			for _, result in pairs(recycling_recipe.results)
			do
				table.insert(recycling_results, "[img="..result.type.."."..result.name.."]")
			end
            add_description{
                name = {"more-descriptions-mod.recycling"},
                value = {
					"more-descriptions-mod.recycling-results",
					seconds(recycling_recipe.energy_required),
					snap(recycling_results),
				},
				order = generic_order(0),
            }
		end

		if mods["promethium-quality"]
		then
			local refining_recipe = data.raw.recipe[item.name.."-refining"]
			if refining_recipe ~= nil
			then
				add_description{
					name = {"more-descriptions-mod.refining-cost"},
					value = seconds(refining_recipe.energy_required),
					order = generic_order(1),
				}
			elseif recycling_recipe ~= nil
			then
				add_description{
					name = {"more-descriptions-mod.cannot-be-refined"},
					value = "",
					order = generic_order(1),
				}
			end
		end

		if item.type == "ammo" and (item.reload_time or 0) > 0
		then
			add_description{
				name = {"more-descriptions-mod.reload-time"},
				value = seconds(tostring(item.reload_time / 60.0)),
				order = specific_order(0),
			}
		end

		if item.type == "active-defense-equipment" and item.automatic
		then
			add_description{
				name = {"more-descriptions-mod.fires-automatically"},
				value = "",
				order = specific_order(0),
			}
		end

		if item.type == "armor" and item.provides_flight
		then
			add_description{
				name = {"more-descriptions-mod.armor-provides-flight"},
				value = "",
				order = specific_order(0),
			}
		end

		if item.type == "gun"
		then
			add_ammo_from_attack_parameters(item, item.attack_parameters)
		end
	end
end

for _, recipe in pairs(data.raw.recipe)
do
	---@param tooltip_field data.CustomTooltipField
	local function add_description(tooltip_field)
		generic_add_description(recipe, tooltip_field)
	end
	if recipe.allow_productivity
	then
		add_description{
			name = {"more-descriptions-mod.allows-productivity"},
			value = "",
		}
	end

	if recipes_that_have_productivity_research[recipe.name]
	then
		add_description{
			name = {"more-descriptions-mod.recipe-has-productivity-research"},
			value = "",
		}
	end

	if recipe.result_is_always_fresh
	then
		add_description{
			name = {"more-descriptions-mod.recipe-result-is-always-fresh"},
			value = "",
		}
	end
	if recipe.reset_freshness_on_craft
	then
		add_description{
			name = {"more-descriptions-mod.recipe-result-freshness-reset"},
			value = "",
		}
	end
end

for _, entity_category in pairs(entity_categories)
do
	for _, entity in pairs(data.raw[entity_category] or {})
	do
		---@param tooltip_field data.CustomTooltipField
		local function add_description(tooltip_field)
			generic_add_description(entity, tooltip_field)
		end
		if entity.collision_box
		then
			local cb = entity.collision_box
			local width = math.ceil(cb[2][1] - cb[1][1])
			local height = math.ceil(cb[2][2] - cb[1][2])
			add_description{
				name = {"more-descriptions-mod.entity-size"},
				value = {
					"more-descriptions-mod.size",
					tostring(width),
					tostring(height),
				},
				order = basic_order(0),
			}
		end

		if entity.minable
		then
			add_description{
				name = {"more-descriptions-mod.mining-time"},
				value = seconds(entity.minable.mining_time),
				order = basic_order(1),
			}
		end

		for _, flag in pairs(entity.flags or {})
		do
			if flag == "breaths-air"
			then
				add_description{
					name = {"more-descriptions-mod.breathes-air"},
					value = "",
					order = generic_order(0),
				}
			end
		end

		if entity.type == "unit-spawner"
		then
			if (entity.time_to_capture or 0) > 0
			then
				add_description{
					name = {"more-descriptions-mod.unit-spawner-time-to-capture"},
					value = seconds(entity.time_to_capture / 60.0),
					order = specific_order(0),
				}
			end
		end

		if entity.drops_full_belt_stacks
		then
			add_description{
				name = {"more-descriptions-mod.drops-full-belt-stacks"},
				value = "",
				order = generic_order(10),
			}
		end

		if entity.heat_buffer and entity.heat_buffer.specific_heat
		then
			add_description{
				name = {"more-descriptions-mod.specific-heat"},
				value = {
					"more-descriptions-mod.energy-per-degrees-celsius",
					entity.heat_buffer.specific_heat,
				},
				order = generic_order(10),
			}
		elseif entity.energy_source
			and entity.energy_source.type == "heat"
			and entity.energy_source.specific_heat
		then
			add_description{
				name = {"more-descriptions-mod.specific-heat"},
				value = entity.energy_source.specific_heat,
				order = generic_order(10),
			}
		end

		if entity.is_military_target
		then
			add_description{
				name = {"more-descriptions-mod.is-military-target"},
				value = "",
				order = generic_order(11),
			}
		end

		if not energy_zero(entity.heating_energy or "0W")
		then
			local value = entity.heating_energy
			if entity.type == "underground-belt" or entity.type == "pipe-to-ground"
			then
				value = {"more-descriptions-mod.value-per-end", value}
			end
			add_description{
				name = {"more-descriptions-mod.heating-energy"},
				value = value,
				order = basic_order(5),
			}
		elseif freezable_entity_categories[entity.type]
		then
			add_description{
				name = {"more-descriptions-mod.no-heating-energy"},
				value = "",
				order = basic_order(5),
			}
		end

		if entity.type == "agricultural-tower"
		then
			local cb = entity.collision_box
			local W = math.ceil(cb[2][1] - cb[1][1])
			local w = (entity.growth_grid_tile_size or 3)
			-- width of the "buffer" area around the agricultural tower
			local z = (2 * w * math.ceil((W - w) / 2 / w)) + w
			-- num of growth cells extending from the edges of the tower
			local r = math.floor(entity.radius) -- why is it double..?
			add_description{
				name = {"more-descriptions-mod.agricultural-tower-num-inputs"},
				value = tostring(entity.input_inventory_size)
			}
			add_description{
				name = {"more-descriptions-mod.agricultural-tower-growth-cell-size"},
				value = {
					"more-descriptions-mod.size",
					tostring(w),
					tostring(w),
				},
				order = specific_order(0),
			}
			add_description{
				name = {"more-descriptions-mod.agricultural-tower-growth-cell-count"},
				value = tostring(4 * r * (r + (z / w))), -- maths
				order = specific_order(1),
			}
			total_size = tostring(z + 2 * r * w)
			add_description{
				name = {"more-descriptions-mod.agricultural-tower-total-size"},
				value = {
					"more-descriptions-mod.size",
					total_size,
					total_size,
				},
				order = specific_order(2),
			}
		elseif entity.type == "ammo-turret"
		then
			if entity.energy_per_shot ~= nil
			then
				add_description{
					name = {"more-descriptions-mod.energy-per-shot-fired"},
					value = entity.energy_per_shot,
					order = generic_order(11),
				}
			end
			add_ammo_from_attack_parameters(entity, entity.attack_parameters)
		elseif entity.type == "beacon"
		then
			add_description{
				name = {"more-descriptions-mod.beacon-supply-area-distance"},
				value = tostring(entity.supply_area_distance),
				order = specific_order(0),
			}
		elseif entity.type == "car"
		then
			local immunities = ""
			if (entity.immune_to_tree_impacts or false)
			then
				immunities = immunities.."[entity=tree-01]"
			end
			if (entity.immune_to_rock_impacts or false)
			then
				immunities = immunities.."[entity=big-rock]"
			end
			if (entity.immune_to_cliff_impacts or true)
			then
				immunities = immunities.."[entity=cliff]"
			end
			if immunities ~= ""
			then
				add_description{
					name = {"more-descriptions-mod.car-immune-to-impacts"},value = immunities,
					order = specific_order(0),
				}
			end
		elseif entity.type == "constant-combinator"
		then
			-- used by pushbutton mod
			if (entity.pulse_duration or 0) > 60
			then
				add_description{
					name = {"more-descriptions-mod.constant-combinator-pulse-duration"},
					value = seconds(entity.pulse_duration / 60.0),
					order = specific_order(0),
				}
			elseif (entity.pulse_duration or 0) > 0
			then
				add_description{
					name = {"more-descriptions-mod.constant-combinator-pulse-duration"},
					value = {
						"more-descriptions-mod.ticks",
						tostring(entity.pulse_duration),
					},
					order = specific_order(0),
				}
			end
		elseif (entity.type == "container" or entity.type == "logistic-container")
		then
			if entity.inventory_type == "with_filters_and_bar"
			then
				add_description{
					name = {"more-descriptions-mod.container-filters"},
					value = "",
					order = specific_order(0),
				}
			end
		elseif entity.type == "cargo-wagon"
		then
			-- all cargo wagons support filters
			add_description{
				name = {"more-descriptions-mod.container-filters"},
				value = "",
				order = specific_order(0),
			}
		elseif entity.type == "display-panel"
		then
			add_description{
				name = {"more-descriptions-mod.display-panel-max-text-width"},
				value = tostring(entity.max_text_width or 400),
				order = specific_order(0),
			}
		elseif entity.type == "logistic-robot" or entity.type == "construction-robot"
		then
			if entity.speed_multiplier_when_out_of_energy > 0
			then
				add_description{
					name = {"more-descriptions-mod.robot-speed-multiplier-when-out-of-energy"},
					value = tostring(entity.speed_multiplier_when_out_of_energy * 100),
					order = specific_order(0),
				}
			else
				add_description{
					name = {"more-descriptions-mod.robot-crashes-when-out-of-energy"},
					value = "",
					order = specific_order(0),
				}
			end
		elseif entity.type == "inserter"
		then
			if entity.bulk
			then
				add_description{
					name = {"more-descriptions-mod.inserter-bulk"},
					value = "",
					order = specific_order(0),
				}
			end
			if entity.wait_for_full_hand
			then
				add_description{
					name = {"more-descriptions-mod.inserter-wait-for-full-hand"},
					value = "",
					order = specific_order(1),
				}
			end
			if entity.enter_drop_mode_if_held_stack_spoiled
			then
				add_description{
					name = {"more-descriptions-mod.inserter-enters-drop-mode-if-held-stack-spoils"},
					value = "",
					order = specific_order(2),
				}
			end
		elseif entity.type == "land-mine"
		then
			add_description{
				name = {"more-descriptions-mod.land-mine-timeout"},
				value = seconds((entity.timeout or 120) / 60.0),
				order = specific_order(0),
			}
		elseif entity.type == "radar"
		then
			if entity.connects_to_other_radars ~= false
			then
				add_description{
					name = {"more-descriptions-mod.radar-connection"},
					value = "",
					order = specific_order(0),
				}
			end
		end
		if entity.filter_count
		then
			add_description{
				name = {"more-descriptions-mod.filter-count"},
				value = tostring(entity.filter_count),
				order = basic_order(5),
			}
		end
		for _, flag in pairs(entity.flags or {})
		do
			if flag == "no-automated-item-insertion"
			then
				add_description{
					name = {"more-descriptions-mod.no-automated-item-insertion"},
					value = "",
					order = basic_order(6),
				}
			end
		end
	end
end

if feature_flags.space_travel
then
	for _, category in pairs{"space-location", "planet"}
	do
		for _, space_location in pairs(data.raw[category])
		do
			---@param tooltip_field data.CustomTooltipField
			local function add_description(tooltip_field)
				generic_add_description(space_location, tooltip_field)
			end
			---@cast space_location data.SpaceLocationPrototype
			local new_descriptions = {}
			if (space_location.fly_condition or false)
			then
				add_description{
					name = {"more-descriptions-mod.space-location-fly-condition"},
					value = "",
					order = specific_order(0),
				}
			end
			if space_location.auto_save_on_first_trip == false -- (nil=true)
			then
				add_description{
					name = {"more-descriptions-mod.space-location-no-autosave"},
					value = "",
					order = specific_order(1),
				}
			end
		end
	end
end

mercurial