more-descriptions/data-final-fixes.lua

Wed, 23 Jul 2025 14:46:54 +0300

author
Teemu Piippo <teemu.s.piippo@gmail.com>
date
Wed, 23 Jul 2025 14:46:54 +0300
changeset 16
2cdb59ae5fcf
parent 7
023ee666a3d6
child 17
df60e9144d82
permissions
-rw-r--r--

Update to Factorio 2.0.60

---@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

---@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 LocalisedString
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

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

		local add_ammo_from_attack_parameters = function(attack_parameters)
			if attack_parameters.ammo_category and ammo_categories[attack_parameters.ammo_category]
			then
				add_description{
					name = {"more-descriptions-mod.gun-accepts-ammo"},
					value = ammo_categories[attack_parameters.ammo_category]}
			end
			for _, category in pairs(attack_parameters.ammo_categories or {})
			do
				if ammo_categories[category]
				then
					add_description{
						name = {"more-descriptions-mod.gun-accepts-ammo"},
						value = ammo_categories[category]}
				end
			end
		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),
				},
            }
		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),
				}
			elseif recycling_recipe ~= nil
			then
				add_description{
					name = {"more-descriptions-mod.cannot-be-refined"},
					value = ""}
			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)),
			}
		end

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

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

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

		local entity = item.place_result and find_entity(item.place_result) or nil
		if entity ~= nil
		then
			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),
					},
				}
			end

			if entity.drops_full_belt_stacks
			then
				add_description{
					name = {"more-descriptions-mod.drops-full-belt-stacks"},
					value = ""}
			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,
					},
				}
			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,
				}
			end

			if entity.is_military_target
			then
				add_description{
					name = {"more-descriptions-mod.is-military-target"},
					value = "",
				}
			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,
				}
			elseif freezable_entity_categories[entity.type]
			then
				add_description{
					name = {"more-descriptions-mod.no-heating-energy"},
					value = "",
				}
			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),
					}
				}
				add_description{
					name = {"more-descriptions-mod.agricultural-tower-growth-cell-count"},
					value = tostring(4 * r * (r + (z / w)))
				}
				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,
					},
				}
			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}
				end
				add_ammo_from_attack_parameters(entity.attack_parameters)
			elseif entity.type == "beacon"
			then
				add_description{
					name = {"more-descriptions-mod.beacon-supply-area-distance"},
					value = tostring(entity.supply_area_distance)
				}
			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}
				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),
					}
				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),
						},
					}
				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 = "",
					}
				end
			elseif entity.type == "cargo-wagon"
			then
				-- all cargo wagons support filters
				add_description{
					name = {"more-descriptions-mod.container-filters"},
					value = "",
				}
			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),
				}
			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),
					}
				else
					add_description{
						name = {"more-descriptions-mod.robot-crashes-when-out-of-energy"},
						value = "",
					}
				end
			elseif entity.type == "inserter"
			then
				if entity.bulk
				then
					add_description{
						name = {"more-descriptions-mod.inserter-bulk"},
						value = tostring(entity.filter_count),
					}
				end
				if entity.wait_for_full_hand
				then
					add_description{
						name = {"more-descriptions-mod.inserter-wait-for-full-hand"},
						value = "",
					}
				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 = "",
					}
				end
			elseif entity.type == "land-mine"
			then
				add_description{
					name = {"more-descriptions-mod.land-mine-timeout"},
					value = seconds((entity.timeout or 120) / 60.0),
				}
			elseif entity.type == "radar"
			then
				if entity.connects_to_other_radars ~= false
				then
					add_description{
						name = {"more-descriptions-mod.radar-connection"},
						value = "",
					}
				end
			end
			if entity.filter_count
			then
				add_description{
					name = {"more-descriptions-mod.filter-count"},
					value = tostring(entity.filter_count),
				}
			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 = "",
					}
				end
			end
		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

-- Stuff mostly specific for entities that you don't place with items (like biters)
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.minable
		then
			add_description{
				name = {"more-descriptions-mod.mining-time"},
				value = seconds(entity.minable.mining_time),
			}
		end

		for _, flag in pairs(entity.flags or {})
		do
			if flag == "breaths-air"
			then
				add_description{
					name = {"more-descriptions-mod.breathes-air"},
					value = "",
				}
			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),
				}
			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 = "",
				}
			end
			if space_location.auto_save_on_first_trip == false -- (nil=true)
			then
				add_description{
					name = {"more-descriptions-mod.space-location-no-autosave"},
					value = "",
				}
			end
		end
	end
end

mercurial