Data Functions Library

Place to post guides, observations, things related to modding that are not mods themselves.
Post Reply
Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Data Functions Library

Post by Adamo »

Since some (but only a few) recipes have normal and expensive difficulty settings, dealing with existing data can become tedious. It can also be difficult to troll through exist data to find technologies associated with recipes, and other tasks. I've been compiling a function library that is useful when running procedural algorithms during the data phase that depend on existing data. I thought it helpful to share it. I'll keep a file to download, here: http://www.shadydealings.net/fact/factsheet.lua and I'll try to keep this forum code up-to-date. Give me ideas of things that should be added, or if you find any problems. Dealing with data is my speciality, so if a data-aware function could help you with a mod project, respond here and maybe I can help by making a new function! I try to design the functions so that you can feed them the prototype table directly, or a string, in which case they will look for the relevant prototype in data.raw (Or just assume the string is a valid name, causing a load failure if you forget to extend data with the appropriate prototype!).

After doing a bunch of updates, I'll note that it's best to download the file linked above, rather than reference the code in this post. That file will be updated any time I run an upload on my mod kit (about once per day). It's onerous to update the code in this post, as I'm always fixing, improving, and tweaking these functions. But I'll leave the code here so people get an idea of what the library is all about.

I'll probably move a few control phase functions in here, like moving inventories around. But I feel like there should be a luaInventory function to do that.

Code: Select all

-- Freeware modding by Adamo
require("util")

add_recipe_to_tech = function(tech_name,recipe_name)
	if type(recipe_name) == "table" then
		add_recipes_to_tech(recipe_name)
	end
	local recipe_name = string_name_or_bust(recipe_name)
	local tech = false
	if type(tech_name) == "table"
	and tech_name.type == "technology" then
		tech = tech_name
	else
		tech = data.raw["technology"][tech_name]
	end
	if tech then
		if not tech.effects then
			tech.effects = {{
				type="unlock-recipe",
				recipe=recipe_name
			}}
		else
			table.insert(tech.effects, {
				type="unlock-recipe",
				recipe=recipe_name
			})
		end
	end
end

add_recipes_to_tech = function(tech_name,name_table)
	if type(name_table) ~= "table" then return nil end
	for _,name in pairs(name_table) do
		add_recipe_to_tech(tech_name,name)
	end
end

find_recipe_techs = function(recipe_name)
	local tech_names = {}
	local recipe_name = string_name_or_bust(recipe_name)
	for k,tech in pairs(data.raw.technology) do
		local found_tech = false
		if tech.effects then 
			for l,effect in pairs(tech.effects) do
				if effect.type == "unlock-recipe"
				and effect.recipe == recipe_name then
					table.insert(
						tech_names,
						tech.name
					)
				end
			end
		end
	end
	return tech_names
end

mirror_recipe_in_techs = function(orig_recipe,new_recipe)
	local tech_names = find_recipe_techs(orig_recipe)
	for _,tech_name in pairs(tech_names) do
		add_recipe_to_tech(tech_name,new_recipe)
	end
end

mult_recipe_energy = function(recipe,mult)
	local recipe = recipe_or_bust(recipe)
	if recipe.normal then
		recipe.normal.energy_required = 
			(recipe.normal.energy_required or 0.5)
			* mult
		if recipe.expensive then
			recipe.expensive.energy_required = 
				(recipe.expensive.energy_required or 0.5)
				* mult
		end
	else
		recipe.energy_required = 
			(recipe.energy_required or 0.5)
			* mult
	end
end

get_recipe_energy = function(recipe)
	local recipe = recipe_or_bust(recipe)
	if not recipe then return nil,0,0 end
	local normal_energy_required = 1
	local expensive_energy_required = 1
	if recipe.normal then
		normal_energy_required =
			recipe.normal.energy_required
		if recipe.expensive then
			expensive_energy_required = 
				recipe.expensive.energy_required
		end
	else
		return (recipe.energy_required or 0.5)
	end
	return normal_energy_required,expensive_energy_required
end

set_recipe_hidden = function(recipe)
	recipe = table_or_bust(recipe)
	if not recipe then return nil end
	if recipe.normal
	and type(recipe.normal) == "table" then
		recipe.normal.hidden = true
	end
	if recipe.expensive
	and type(recipe.expensive) == "table" then
		recipe.expensive.hidden = true
	end
	recipe.hidden = true
	return true
end

mult_recipe_io = function(
	recipe,
	amount_mult,
	name
)
	if name then 
		replace_recipe_io(recipe,name,name,amount_mult)
	else
		local ingredients,results = get_io_names(recipe)
		for _,name in pairs(ingredients) do
			replace_recipe_io(recipe,name,name,amount_mult)
		end
		for _,name in pairs(results) do
			replace_recipe_io(recipe,name,name,amount_mult)
		end
	end
end

replace_in_recipe_io = function(
	recipe,old_name,new_name,amount_mult
)
	replace_recipe_io(recipe,old_name,new_name,amount_mult)
end

replace_recipe_io = function(
	recipe,old_name,new_name,amount_mult
)
	local recipe = recipe_or_bust(recipe)
	if not recipe then return false end
	if amount_mult == nil 
	or type(amount_mult) ~= "number" then
		amount_mult = 1
	end
	if recipe.result then
		if recipe.result == old_name then
			recipe.result = new_name
			recipe.result_count = math.floor(
				(recipe.result_count or 1)
				* amount_mult
			)
		end
	end
	if recipe.normal and recipe.normal.result then
		if recipe.normal.result == old_name then
			recipe.normal.result = new_name
			recipe.normal.result_count = math.floor(
				(recipe.normal.result_count or 1)
				* amount_mult
			)
		end
	end
	if recipe.expensive and recipe.expensive.result then
		if recipe.expensive.result == old_name then
			recipe.expensive.result = new_name
			recipe.expensive.result_count = math.floor(
				(recipe.expensive.result_count or 1)
				* amount_mult
			)
		end
	end
	for _,ingredient in pairs(recipe.ingredients or {}) do
		if ingredient.name == old_name then
			ingredient.name = new_name
			ingredient.amount = math.ceil(
				(ingredient.amount or ingredient[2] or 1)
				* amount_mult
			)
			ingredient[2] = nil
		end
		if ingredient[1] == old_name then
			ingredient[1] = new_name
			ingredient[2] = math.ceil(
				(ingredient[2] or ingredient.amount or 1)
				* amount_mult
			)
			ingredient.amount = nil
		end
		if ingredient.name == nil
		and ingredient[1] == nil then
			ingredient = nil
		end
	end
	if recipe.normal then
		for _,ingredient
		in pairs(recipe.normal.ingredients or {}) do
			if ingredient.name == old_name then
				ingredient.name = new_name
				ingredient.amount = math.ceil(
					(ingredient.amount or ingredient[2] or 1)
					* amount_mult
				)
				ingredient[2] = nil
			end
			if ingredient[1] == old_name then
				ingredient[1] = new_name
				ingredient[2] = math.ceil(
					(ingredient[2] or ingredient.amount or 1)
					* amount_mult
				)
				ingredient.amount = nil
			end
			if ingredient.name == nil
			and ingredient[1] == nil then
				ingredient = nil
			end
		end
	end
	if recipe.expensive then
		for _,ingredient
		in pairs(recipe.expensive.ingredients or {}) do
			if ingredient.name == old_name then
				ingredient.name = new_name
				ingredient.amount = math.ceil(
					(ingredient.amount or ingredient[2] or 1)
					* amount_mult
				)
				ingredient[2] = nil
			end
			if ingredient[1] == old_name then
				ingredient[1] = new_name
				ingredient[2] = math.ceil(
					(ingredient[2] or ingredient.amount or 1)
					* amount_mult
				)
				ingredient.amount = nil
			end
			if ingredient.name == nil
			and ingredient[1] == nil then
				ingredient = nil
			end
		end
	end
	for _,result in pairs(recipe.results or {}) do
		if result.name == old_name then
			result.name = new_name
			result.amount = math.floor(
				(result.amount or result[2] or 1)
				* amount_mult
			)
		end
		if result[1] == old_name then
			result[1] = new_name
			result[2] = math.floor(
				(result[2] or result.amount or 1)
				* amount_mult
			)
			result.amount = nil
		end
		if result.name == nil
		and result[1] == nil then
			result = nil
		end
	end
	if recipe.normal then
		for _,result in pairs(recipe.normal.results or {}) do
			if result.name == old_name then
				result.name = new_name
				result.amount = math.floor(
					(result.amount or result[2] or 1)
					* amount_mult
				)
			end
			if result[1] == old_name then
				result[1] = new_name
				result[2] = math.floor(
					(result[2] or result.amount or 1)
					* amount_mult
				)
				result.amount = nil
			end
			if result.name == nil
			and result[1] == nil then
				result = nil
			end
		end
	end
	if recipe.expensive then
		for _,result
		in pairs(recipe.expensive.results or {}) do
			if result.name == old_name then
				result.name = new_name
				result.amount = math.floor(
					(result.amount or result[2] or 1)
					* amount_mult
				)
			end
			if result[1] == old_name then
				result[1] = new_name
				result[2] = math.floor(
					(result[2] or result.amount or 1)
					* amount_mult
				)
				result.amount = nil
			end
			if result.name == nil
			and result[1] == nil then
				result = nil
			end
		end
	end
end

add_ingredient = function(recipe,ingred,count,prob)
	if type(count) == "number" and count == 0 then
		return nil
	end
	if type(prob) == "number" and count == 0 then
		return nil
	end
	local recipe = recipe_or_bust(recipe)
	local ingred = io_prototype_or_bust(ingred,count,prob)
	if type(prob) ~= "number" then prob = nil end
	if not recipe or not ingred then return nil end
	if uses_ingredient(recipe,ingred.name) then
		if recipe.normal then
			if recipe.expensive then
				for _,ingredient
				in pairs(recipe.expensive.ingredients or {})
				do
					if ingredient.name == ingred.name then
						ingredient.amount =
							math.floor(
								ingredient.amount
								+ count * (prob or 1)*2
							)
					end
				end
			end
			for _,ingredient
			in pairs(recipe.normal.ingredients or {})
			do
				if ingredient.name == ingred.name then
					ingredient.amount =
						math.floor(
							ingredient.amount
							+ count * (prob or 1)
						)
				end
			end
		else
			for _,ingredient
			in pairs(recipe.ingredients or {})
			do
				if ingredient.name == ingred.name then
					ingredient.amount =
						math.floor(
							ingredient.amount
							+ count * (prob or 1)
						)
				end
			end
		end		
	else
		if recipe.normal then
			if recipe.expensive then
				table.insert(
					recipe.expensive.ingredients,
					ingred
				)
			end
			table.insert(recipe.normal.ingredients,ingred)
		else
			table.insert(recipe.ingredients,ingred)
		end
	end
end

-- might not be working right
blend_io = function(io_base,io_blend)
	local io_base = io_prototype_or_bust(io_base)
	local io_blend = io_prototype_or_bust(io_blend)
	if not io_base or not io_blend then return nil end
	count = io_blend.amount or 1
	prob = io_blend.probability
	if io_base.probability then
		io_base.probability = 
			(
				(io_base.amount or 1)
				*io_base.probability
				+(count or 1)*(prob or 1)
			)/(
				(io_base.amount or 1)
				+ (count or 1)
			)
		io_base.amount =
			math.floor(io_base.amount + count)
	else
		if prob then
			io_base.probability = 
				(
					(io_base.amount or 1)
					+(count or 1)*(prob or 1)
				)/(
					(io_base.amount or 1)
					+ (count or 1)
				)
			io_base.amount =
				math.floor(io_base.amount + count)
		else
			io_base.amount =
				math.floor(io_base.amount + count)
		end
	end
	return io_base
end

add_result = function(recipe,product,count,prob)
	if type(count) == "number" and count == 0 then
		return nil
	end
	if type(prob) == "number" and count == 0 then
		return nil
	end
	local recipe = recipe_or_bust(recipe)
	local product = io_prototype_or_bust(product,count,prob)
	if not recipe or not product then return nil end
	if type(count) ~= "number" then count = nil end
	if type(prob) ~= "number" then prob = nil end
	format_results(recipe)
	local found_result = false
	if recipe.normal then
		if recipe.expensive then
			for _,result
			in pairs(recipe.expensive.results
			or {recipe.expensive.result}
			) do
				if result.name == product.name then
					found_result = true
					result = blend_io(result,product)
				end
			end
		end
		for _,result
		in pairs(recipe.normal.results
		or {recipe.normal.result}
		) do
			if result.name == product.name then
				found_result = true
				result = blend_io(result,product)
			end
		end
	else
		for _,result
		in pairs(recipe.results
		or {recipe.result}
		) do
			if result.name == product.name then
				found_result = true
				result = blend_io(result,product)
			end
		end
	end		
	if not found_result then
		if recipe.normal then
			local exp_product = util.table.deepcopy(product)
			exp_product.amount = (exp_product.amount or 1)*2
			if recipe.expensive then
				table.insert(
					recipe.expensive.results,exp_product
				)
			end
			table.insert(recipe.normal.results,product)
		else
			table.insert(recipe.results,product)
		end
	end
end

format_results = function(recipe)
	local recipe = recipe_or_bust(recipe)
	if not recipe.results then 
		if recipe.normal then
			if recipe.expensive then
				if not recipe.expensive.results then 
					if recipe.expensive.result then
						recipe.expensive.results = {{
								type = "item",
								name = recipe.expensive.result,
								amount =
									recipe.expensive.result_count
									or 1
						}}
						if not recipe.expensive.main_product then
							recipe.expensive.main_product =
								recipe.expensive.result
						end
						recipe.expensive.result = nil
						recipe.expensive.result_count = nil
					end
				else
					if not recipe.expensive.main_product then
						recipe.expensive.main_product =
							recipe.expensive.result
					end
					recipe.expensive.result = nil
					recipe.expensive.result_count = nil
				end	
			end
			if not recipe.normal.results then 
				if recipe.normal.result then
					recipe.normal.results = {{
							type = "item",
							name = recipe.normal.result,
							amount =
								recipe.normal.result_count
								or 1
					}}
					if not recipe.normal.main_product then
						recipe.normal.main_product =
							recipe.normal.result
					end
					recipe.normal.result = nil
					recipe.normal.result_count = nil
				end
			else
				if not recipe.normal.main_product then
					recipe.normal.main_product =
						recipe.normal.result
				end
				recipe.normal.result = nil
				recipe.normal.result_count = nil
			end
		end
		if recipe.result then
			recipe.results = {{
					type = "item",
					name = recipe.result,
					amount =
						recipe.result_count
						or 1
			}}
			if not recipe.main_product then
				recipe.main_product =
					recipe.result
			end
			recipe.result = nil
			recipe.result_count = nil
		end
	else
		if not recipe.main_product then
			recipe.main_product =
				recipe.result
		end
		recipe.result = nil
		recipe.result_count = nil
	end
end

get_main_result = function(recipe)
	local recipe = recipe_or_bust(recipe)
	if type(recipe) ~= "table" then return end
	local result_name = nil
	local result_count = 0
	local expensive_result_count = 0
	if recipe.normal then
		if recipe.results then
			result_name =
				recipe.normal.main_product
				or recipe.normal.results[1].name
				or recipe.normal.results[1][1]
			for _,result in pairs(recipe.normal.results) do
				if (result.name == result_name)
				or (result[1] == result_name) then
					result_count =
						result.amount
						or result[2]
				end
			end
		else
			result_name = recipe.normal.result
			result_count = recipe.normal.result_count or 1
		end
		if recipe.expensive then
			if recipe.expensive.results then
				for _,result
				in pairs(recipe.expensive.results) do
					if (result.name == result_name)
					or (result[1] == result_name) then
						result_count =
							result.amount
							or result[2]
					end
				end
			else
				expensive_result_count =
					recipe.expensive.result_count or 1
			end
		end
	else
		if recipe.results then
			result_name =
				recipe.main_product
				or recipe.results[1].name
				or recipe.results[1][1]
			for _,result in pairs(recipe.results) do
				if (result.name == result_name)
				or (result[1] == result_name) then
					result_count =
						result.amount
						or result[2]
				end
			end
		else
			result_name = recipe.result
			result_count = recipe.result_count or 1
		end
	end
	return result_name,result_count,expensive_result_count
end

has_ingredients = function(recipe)
	local recipe = recipe_or_bust(recipe)
	if recipe and type(recipe) == "table" then
		if recipe.normal then
			if recipe.normal.ingredients then
				for _,ingredient
				in pairs(recipe.normal.ingredients) do
					if ingredient.amount
					and ingredient.amount > 0 then
						return true
					end
					if ingredient[2]
					and ingredient[2] > 0 then
						return true
					end
				end
			end
		end
		if recipe.ingredients then
			for _,ingredient in pairs(recipe.ingredients) do
				if ingredient.amount
				and ingredient.amount > 0 then
					return true
				end
				if ingredient[2]
				and ingredient[2] > 0 then
					return true
				end
			end
		end
	end
	return false
end

has_results = function(recipe)
	local recipe = recipe_or_bust(recipe)
	if not recipe then return false end
	if recipe.normal then
		if recipe.normal.results then
			for _,result
			in pairs(recipe.normal.results) do
				if result.amount
				and result.amount > 0 then
					return true
				end
				if result[2]
				and result[2] > 0 then
					return true
				end
			end
		end
	end
	if recipe.results then
		for _,result in pairs(recipe.results) do
			if result.amount
			and result.amount > 0 then
				return true
			end
			if result[2]
			and result[2] > 0 then
				return true
			end
		end
	end
	return false
end

get_io_names = function(recipe)
	local recipe = recipe_or_bust(recipe)
	if not recipe or recipe.type ~= "recipe" then
		return {}
	end
	local ingredients = {}
	local results = {}
	if recipe.normal then
		if recipe.expensive then
			add_strings_to_list(
				get_table_names(recipe.expensive.ingredients),
				ingredients
			)
			add_strings_to_list(
				get_table_names(recipe.expensive.results),
				results
			)
			add_string_to_list(
				recipe.expensive.result,
				results
			)
		end
		add_strings_to_list(
			get_table_names(recipe.normal.ingredients),
			ingredients
		)
		add_strings_to_list(
			get_table_names(recipe.normal.results),
			results
		)
		add_string_to_list(
			recipe.normal.result,
			results
		)
	else
		add_strings_to_list(
			get_table_names(recipe.ingredients),
			ingredients
		)
		add_strings_to_list(
			get_table_names(recipe.results),
			results
		)
		add_string_to_list(
			recipe.result,
			results
		)
	end
	return ingredients,results
end

uses_ingredient = function(recipe,name)
	local name = string_name_or_bust(name)
	local recipe = recipe_or_bust(recipe)
	if not recipe or not name then return false end
	local ingredients,results = get_io_names(recipe)
	for _,ingredient in pairs(ingredients) do
		if ingredient == name then
			return true
		end
	end
	return false
end

get_table_names = function(prototable)
	local names = {}
	local prototable = table_or_bust(prototable)
	if not prototable then return end
	for _,prototype in pairs(prototable) do
		prototype = table_or_bust(prototype)
		if prototype then
			if prototype.name
			and type(prototype.name) == "string" then
				table.insert(
					names,
					prototype.name
				)
			elseif prototype[1]
			and type(prototype[1]) == "string" then
				table.insert(
					names,
					prototype[1]
				)
			end
		end
	end
	return names
end

get_ingredient = function(recipe,index)
	local recipe = recipe_or_bust(recipe)
	if type(recipe) ~= "table" then return end
	local ingredient_name = index
	local ingredient_amount = 0
	local expensive_amount = 0
	if not has_ingredients(recipe) then return nil,0,0 end
	if recipe.normal
	and type(recipe.normal.ingredients) == "table" then
		if type(ingredient_name) == "string" then
			for _,ingredient
			in pairs(recipe.normal.ingredients) do
				if (ingredient.name == ingredient_name)
				or (ingredient[1] == ingredient_name)
				then
					ingredient_amount = 
						ingredient.amount
						or ingredient[2]
				end
			end
			if ingredient_amount == 0 then
				return nil,0,0
			end
			if recipe.expensive then
				for _,ingredient
				in pairs(recipe.expensive.ingredients or {}) do
					if (ingredient.name == ingredient_name)
					or (ingredient[1] == ingredient_name)
					then
						expensive_amount = 
							ingredient.amount
							or ingredient[2]
					end
				end
			end
		else
			if type(
				recipe.normal.ingredients[ingredient_name]
			) == "table" then
				ingredient_amount =
					recipe.normal
					.ingredients[ingredient_name].amount
					or recipe.normal
					.ingredients[ingredient_name][2]
				ingredient_name =
					recipe.normal
					.ingredients[ingredient_name].name
					or recipe.normal
					.ingredients[ingredient_name][1]
				if recipe.expensive then
					for _,ingredient
					in pairs(recipe.expensive.ingredients) do
						if (
							ingredient.name
							== ingredient_name
						) or (
							ingredient[1]
							== ingredient_name
						) then
							expensive_amount = 
								ingredient.amount
								or ingredient[2]
						end
					end
				end
			else
				return nil,0,0
			end
		end
	else
		if type(ingredient_name) == "string" then
			for _,ingredient
			in pairs(recipe.ingredients or {}) do
				if (ingredient.name == ingredient_name)
				or (ingredient[1] == ingredient_name)
				then
					ingredient_amount = 
						ingredient.amount
						or ingredient[2]
				end
			end
			if ingredient_amount == 0 then
				return nil,0,0
			end
		else
			if type(recipe.ingredients[ingredient_name])
			== "table" then
				ingredient_amount =
					recipe.ingredients[ingredient_name]
					.amount
					or recipe.ingredients[ingredient_name][2]
				ingredient_name =
					recipe.ingredients[ingredient_name].name
					or recipe.ingredients[ingredient_name][1]
			else
				return nil,0,0
			end
		end
	end
	return ingredient_name,ingredient_amount,expensive_amount
end

-- Might be broken
get_result = function(recipe,index)
	local recipe = recipe_or_bust(recipe)
	if not recipe then return end
	format_results(recipe)
	if not has_results(recipe) then return nil,0,0 end
	local result_name = index
	local result_amount = 0
	local expensive_amount = 0
	if recipe.normal 
	and type(recipe.normal.results) == "table" then
		if type(result_name) == "string" then
			for _,result in pairs(recipe.normal.results) do
				if (result.name == result_name)
				or (result[1] == result_name)
				then
					result_amount = 
						result.amount or result[2]
				end
			end
			if result_amount == 0 then
				return nil,0,0
			end
			if recipe.expensive then
				for _,result
				in pairs(recipe.expensive.results or {}) do
					if (result.name == result_name)
					or (result[1] == result_name)
					then
						expensive_amount = 
							result.amount
							or result[2]
					end
				end
			end
		else
			if type(
				recipe.normal.results[result_name]
			) == "table" then
				result_amount =
					recipe.normal
					.results[result_name].amount
					or recipe.normal
					.results[result_name][2]
				result_name =
					recipe.normal
					.results[result_name].name
					or recipe.normal
					.results[result_name][1]
				if recipe.expensive then
					for _,result
					in pairs(recipe.expensive.results) do
						if (
							result.name
							== result_name
						) or (
							result[1]
							== result_name
						) then
							expensive_amount = 
								result.amount
								or result[2]
						end
					end
				end
			else
				return nil,0,0
			end
		end
	else
		if type(result_name) == "string" then
			for _,result
			in pairs(recipe.results or {}) do
				if (result.name == result_name)
				or (result[1] == result_name)
				then
					result_amount = 
						result.amount
						or result[2]
				end
			end
			if result_amount == 0 then
				return nil,0,0
			end
		else
			if type(recipe.results[result_name])
			== "table" then
				result_amount =
					recipe.results[result_name]
					.amount
					or recipe.results[result_name][2]
				result_name =
					recipe.results[result_name].name
					or recipe.results[result_name][1]
			else
				return nil,0,0
			end
		end
	end
	return result_name,result_amount,expensive_amount

end

set_productivity_recipes = function(recipe_names)
	for _,recipe_name in pairs(recipe_names or {}) do
		if type(recipe_name) == "table" then
			recipe_name = recipe_name.name
		end
		for k,v in pairs({
				data.raw.module["productivity-module"],
				data.raw.module["productivity-module-2"],
				data.raw.module["productivity-module-3"]
			}) do
	  		if v.limitation then  
	      		table.insert(v.limitation, recipe_name) 
			end
		end
	end
end

set_productivity_recipe = function(recipe_name)
	set_productivity_recipes({recipe_name})
end

add_productivity_recipes = function(recipe_names)
	set_productivity_recipes(recipe_names)
end

is_productivity_recipe = function(recipe_name)
	local recipe_name = string_name_or_bust(recipe_name)
	if not data.raw.recipe[recipe_name] then return false end
	for k,listed_recipe_name in pairs(
		data.raw.module["productivity-module"].limitation
	) do
		if listed_recipe_name == recipe_name then
			return true
		end
	end
	return false
end

find_unused_layer = function()
	local unused_layers = {
		"layer-11",
		"layer-12",
		"layer-13",
		"layer-14",
		"layer-15",
	}
	for i,data_type in pairs(data.raw) do
		for j,data_obj in pairs(data_type) do
			for k,layer
			in pairs(data_obj.collision_mask or {}) do
				for l,unused_layer in pairs(unused_layers) do
					if layer == unused_layer then
						unused_layers[l] = nil
					end
				end
			end
		end
	end
	for _,layer in pairs(unused_layers) do
		return layer
	end
	return nil
end

set_pipe_distance = function(pipe, dist)
	if data.raw["pipe-to-ground"][pipe] then
		for _,connection in pairs(
			data.raw["pipe-to-ground"][pipe]
			.fluid_box.pipe_connections
		) do
			if connection.max_underground_distance then
				data.raw["pipe-to-ground"][pipe]
				.fluid_box.pipe_connections[_]
				.max_underground_distance = dist
			end
		end
	end
end

set_shift =  function(shift, tab)
	tab.shift = shift
	if tab.hr_version then
		tab.hr_version.shift = shift
	end
	return tab
end

energy_mult = function(energy,mult)
	local num = energy:match "[0-9%.]+"
	local alpha = energy:match "%a+"
	num = num*mult
	return (num..alpha)
end

energy_div = function(energy,div)
	local num = energy:match "[0-9%.]+"
	local alpha = energy:match "%a+"
	num = num/div
	return (num..alpha)
end

empty_sprite = function()
	return {
		filename = "__core__/graphics/empty.png",
		priority = "extra-high",
		width = 1,
		height = 1
	}
end

bulkypipepictures = function()
	local pipe_sprites = pipepictures()
	return {
  		north = set_shift(
  			{0, 1},
  			util.table
  			.deepcopy(pipe_sprites.straight_vertical)
		),
		south = empty_sprite(),
  		east = set_shift(
  			{-1, 0},
  			util.table
  			.deepcopy(pipe_sprites.straight_horizontal)
		),
  		west = set_shift(
  			{1, 0},
  			util.table
  			.deepcopy(pipe_sprites.straight_horizontal)
		)
	}
end

centrifugepipecovers = function()
	local covers = pipecoverspictures()
	covers.north = empty_sprite()
	return covers
end

centrifugepipepictures = function()
	local pics =  assembler3pipepictures()
	pics.north = empty_sprite()
	pics.north.hr_version = empty_sprite()
	pics.east = empty_sprite()
	pics.east.hr_version = empty_sprite()
	pics.west = empty_sprite()
	pics.west.hr_version = empty_sprite()
	return pics
end

apply_sulfur_fuel_stats = function(sulfur)
	if type(sulfur) ~= "table"
	or sulfur.type ~= "item"
	then
		log("invalid sulfur item received")
		return nil
	end
	local sulfur_fuel_type = "sulfur"
	sulfur.fuel_category = sulfur_fuel_type
	sulfur.fuel_emissions_multiplier = 12
	sulfur.fuel_value = "600kJ"
	sulfur.fuel_acceleration_multiplier = 0.4
	sulfur.fuel_glow_color={r=1,g=0.2,b=1,a=1}
	activate_basic_burning(sulfur_fuel_type)
	return sulfur
end

activate_basic_burning = function(fuel_type)
	if not data.raw["fuel-category"][fuel_type] then
		data:extend({
			{type="fuel-category",name=fuel_type}
		})
	end
	add_fuel_type(data.raw.boiler.boiler,fuel_type)
	for _,ent in pairs(data.raw.car) do
		if table_incl("chemical",get_fuel_types(ent))
		then
			add_fuel_type(ent,fuel_type)
		end
	end
	for _,ent in pairs(data.raw.reactor) do
		if table_incl("chemical",get_fuel_types(ent))
		then
			add_fuel_type(ent,fuel_type)
		end
	end
end

add_fuel_type = function(entity,fuel_type)
	entity = table_or_bust(entity)
	fuel_type = string_or_bust(fuel_type)
	if not entity or not fuel_type then return false end
	local energy_source = get_energy_source(entity)
	if not energy_source then return false end
	if is_burner(energy_source) then
		if not energy_source.fuel_categories then
			if energy_source.fuel_category then
				energy_source
				.fuel_categories = {energy_source.fuel_category}
			else
				energy_source.fuel_categories = {"chemical"}
			end
			energy_source.fuel_category = nil
		end
		add_string_to_list(fuel_type,energy_source.fuel_categories)
	end
end

get_fuel_types = function(entity)
	entity = table_or_bust(entity)
	if not entity then return {} end
	local energy_source = get_energy_source(entity)
	if not energy_source then return {} end
	if is_burner(energy_source) then
		if energy_source.fuel_categories then
			return energy_source.fuel_categories
		end
		if energy_source.fuel_category then
			return {energy_source.fuel_category}
		end
		return ({"chemical"})
	end
	return {}
end

is_burner = function(energy_source)
	energy_source = table_or_bust(energy_source)
	if not energy_source then return false end
	if energy_source.type == "burner"
	or not energy_source.type then
		return true
	end
	return false
end

get_energy_source = function(entity)
	entity = table_or_bust(entity)
	if not entity then return nil end
	if entity.burner then
		return entity.burner
	else
		return entity.energy_source
	end
end

table_or_bust = function(prototype)
	if type(prototype) == "table" then
		return prototype
	end
	return nil
end

string_or_bust = function(str)
	if type(str) == "string" then
		return str
	end
	return nil
end

io_prototype_or_bust = function(prototype,count,prob)
	local prototype = construct_io_prototype(prototype,count,prob)
	if type(prototype.name) == "string"
	and type(prototype.type) == "string"
	and type(prototype.amount) == "number" then
		return prototype
	end
	return nil
end

construct_io_prototype = function(prototype,count,prob)
	local new_prototype = {}
	if type(count) ~= "number" then count = nil end
	if type(prob) ~= "number" then prob = nil end 
	if type(prototype) == "table" then
		new_prototype.name = prototype.name or prototype[1]
		new_prototype.type = prototype.type or "item"
		new_prototype.amount = 
			count or prototype.amount 
			or prototype[2] or 1
		new_prototype.probability =
			prob or prototype.probability
	end
	if type(prototype) == "string" then
		if data.raw.fluid[prototype] then
			new_prototype.name = prototype
			new_prototype.type = "fluid"
			new_prototype.amount = count or 1
			if data.raw.item[prototype] then
				new_prototype.type = "item"
				new_prototype.probability = prob
			end
		else
			new_prototype.name = prototype
			new_prototype.type = "item"
			new_prototype.amount = count or 1
			new_prototype.probability = prob
		end
	end
	if new_prototype.type == "item" then
		if new_prototype.amount < 1 then
			new_prototype.amount = 1
			new_prototype.probability = new_prototype.amount
		end
	else
		if prob then
			new_prototype.amount = new_prototype.amount*prob
		end
	end
	return new_prototype
end

recipe_or_bust = function(prototype)
	if type(prototype) == "table"
	and prototype.type == "recipe" then
		return prototype
	end
	if type(recipe) == "string" then
		return data.raw.recipe[recipe]
	end
	return nil
end

string_name_or_bust = function(prototype_name)
	if type(prototype_name) == "string" then
		return prototype_name
	end
	if type(prototype_name) == "table" then
		return prototype_name.name
	end
	return nil
end

add_strings_to_list = function(new_strings,str_list)
	if type(new_strings) ~= "table" then
		if type(str_list) == "table" then
			return str_list
		else
			return {}
		end
	end
	for _,str in pairs(new_strings) do
		add_string_to_list(str,str_list)
	end
	if type(str_list) == "table" then
		return str_list
	else
		return {}
	end
end

add_string_to_list = function(new_str,str_list)
	if type(str_list) ~= "table" then
		if type(new_str) == "string" then
			return {new_str}
		else
			return {}
		end
	end
	for _,str in pairs(str_list) do
		if str == new_str then
			return str_list
		end
	end
	if type(new_str) == "string" then
		table.insert(str_list,new_str)
	end
	return str_list
end

table_incl = function(val,comp)
	comp = table_or_bust(comp)
	if not val or not comp then return false end
	for _,val_comp in pairs(comp) do
		if val_comp == val then
			return true
		end
	end
	return false
end


-- Reasonable efficiencies.
-- Temps determined to maintain blueprint compatibility.
-- Applied by my physics mod.
boiler_eff = 0.85
gen_eff = 0.5
furnace_eff = 0.65
reactor_eff = 0.80
chem_temp_max = 315
nuke_temp_max = 990
reactor_consump_multi = 2.01
chem_fuel_quotient = boiler_eff*gen_eff
nuke_fuel_quotient = reactor_eff*gen_eff
solar_power_output = "5.1kW"
Here is an example of how you can use it, as we do in our Stack Furnace's data-updates.lua:

Code: Select all

require("factsheet")
local energy_normal = 50
--local mintime = energy_normal/25
local use_ore_stack_size = settings.startup["ore"].value
local productivity_recipes = {}
for _,recipe in pairs(data.raw.recipe) do
  if recipe.category == "smelting" then

    local result_name,orig_result_count,exp_result_count =
      get_main_result(recipe)
    local product = data.raw.item[result_name]
    if product and type(product) == "table"
    and has_ingredients(recipe) then
      local stack_size = product.stack_size
      local input_name,orig_input_count,exp_input_count =
        get_ingredient(recipe,1)
      local io_multiplier = stack_size/orig_result_count
      if use_ore_stack_size then
        local ore = data.raw.item[input_name]
        if ore then
          stack_size = ore.stack_size
          io_multiplier = stack_size/orig_input_count
        end
      end
      local energy_multiplier = io_multiplier/energy_normal
      local orig_energy = get_recipe_energy(recipe)
      --if (orig_energy*energy_multiplier) < mintime then
      --  local emult_min = mintime/orig_energy
      --  io_multiplier = io_multiplier
      --    /energy_multiplier*emult_min
      --  energy_multiplier = emult_min
      --end
      local f,r = math.modf(orig_result_count*io_multiplier)
      if r > 0 then 
        io_multiplier = math.ceil(io_multiplier/r)
        energy_multiplier = energy_multiplier/r
      end

      local stackrecipe = util.table.deepcopy(recipe)
      stackrecipe.hidden = false
      --stackrecipe.hide_from_player_crafting = true
      stackrecipe.category = "stack-smelting"
      stackrecipe.name = stackrecipe.name.."-stack"
      mult_recipe_energy(stackrecipe,energy_multiplier)
      mult_recipe_io(stackrecipe,io_multiplier)
      mirror_recipe_in_techs(recipe,stackrecipe)
      data:extend({stackrecipe})
      if is_productivity_recipe(recipe) then
        table.insert(productivity_recipes,stackrecipe)
      end

    end
    
  end
end
set_productivity_recipes(productivity_recipes)
Last edited by Adamo on Fri Aug 30, 2019 4:31 am, edited 15 times in total.

slippycheeze
Filter Inserter
Filter Inserter
Posts: 587
Joined: Sun Jun 09, 2019 10:40 pm
Contact:

Re: Data Functions Library

Post by slippycheeze »

Adamo wrote:
Wed Aug 28, 2019 7:40 am
Since some (but only a few) recipes have normal and expensive difficulty settings, dealing with existing data can become tedious. It can also be difficult to troll through exist data to find technologies associated with recipes, and other tasks. I've been compiling a function library that is useful when running procedural algorithms during the data phase that depend on existing data. I thought it helpful to share it.
The ASWIL library/mod published on the portal aims to do the same thing. A significant number of other "core library" mods no doubt include the same things. I'd ... encourage you to check if any of those might achieve your aims, and instead see about combining them.

I mean, another library to do this isn't wrong, or terrible, or anything bad really. It just means choice, like, 50 or so of reimplementations of the exact same logic. :)

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Data Functions Library

Post by Adamo »

slippycheeze wrote:
Wed Aug 28, 2019 6:54 pm
I don't trust other people. ;)

slippycheeze
Filter Inserter
Filter Inserter
Posts: 587
Joined: Sun Jun 09, 2019 10:40 pm
Contact:

Re: Data Functions Library

Post by slippycheeze »

Adamo wrote:
Wed Aug 28, 2019 7:07 pm
slippycheeze wrote:
Wed Aug 28, 2019 6:54 pm
I don't trust other people. ;)
Fair enough. Why should anyone trust your library?

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Data Functions Library

Post by Adamo »

slippycheeze wrote:
Wed Aug 28, 2019 7:36 pm
Fair enough. Why should anyone trust your library?
I would only expect someone to use it if they found it to be useful. :) But I am very paranoid.

slippycheeze
Filter Inserter
Filter Inserter
Posts: 587
Joined: Sun Jun 09, 2019 10:40 pm
Contact:

Re: Data Functions Library

Post by slippycheeze »

Adamo wrote:
Wed Aug 28, 2019 7:43 pm
slippycheeze wrote:
Wed Aug 28, 2019 7:36 pm
Fair enough. Why should anyone trust your library?
I would only expect someone to use it if they found it to be useful. :) But I am very paranoid.
Yes, and quite unreasonably so, see also Reflections on Trusting Trust. So, why not examine the existing libraries and determine which, if any, are useful to you? If that isn't something you can do, you should probably reexamine the assumption that you can ever publish code in a way that is useful to others.

Ref: Thompson, K. (1984). Reflections on trusting trust. Commun. ACM, 27(8), 761-763.

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Data Functions Library

Post by Adamo »

slippycheeze wrote:
Wed Aug 28, 2019 7:52 pm
Excuse me, but in what universe did giving me a lesson on software libraries, and suggesting I reexamine my assumptions, seem like a better option than just moving along to the next forum thread after deciding you weren't interested in this library? The code here is here for anyone who wants to use it. It is freeware. If someone wants to integrate it into their standard library, great! Or use it as is? Fine. Or don't use it? Fine, too! Get out of here with that shit. "Re-examine the assumption". Give me a break. You didn't even understand my point about paranoia: I was talking about being paranoid about data collisions and integrity errors, not about other people.
Last edited by Adamo on Thu Aug 29, 2019 8:25 pm, edited 1 time in total.

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Data Functions Library

Post by eradicator »

Adamo wrote:
Wed Aug 28, 2019 9:03 pm
just moving along to the next forum thread after deciding you weren't interested in this library?
Exactly what i decided to do when i initially saw this thread and pondered if i should comment it with "Don't get your hopes up about other people using your code. If you need it yourself that's fine. But other people won't be using it".
Adamo wrote:
Wed Aug 28, 2019 9:03 pm
You didn't even understand my point about paranoia: I was talking about about being paranoid about data collisions and integrity errors, not about other people.
From personal observation: The more self-confident(?) any factorio modder gets, the more likely they are to write their own library instead of using a common one. It's kind of a sad state becaues it prevents any library from becoming the "community approved" library, and everyone just keeps using their barely-tested personal abominations (yes, me included).

Also as "Freeware" is not a license even if someone wanted to use your code, they couldn't because it has no clear license. And actually the word "Freeware" is more commonly used for software that has no monetary-cost but is licensed in a way that forbids modifications or reusage etc.

Btw, Randall as always gets it to the point :p https://xkcd.com/927/
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

slippycheeze
Filter Inserter
Filter Inserter
Posts: 587
Joined: Sun Jun 09, 2019 10:40 pm
Contact:

Re: Data Functions Library

Post by slippycheeze »

Adamo wrote:
Wed Aug 28, 2019 9:03 pm
slippycheeze wrote:
Wed Aug 28, 2019 7:52 pm
Excuse me, but in what universe did giving me a lesson on software libraries
Well, the one in which you started by introducing another library of functions that duplicate the very many libraries that also aim to do the same thing. I'd initially hope that perhaps you might contribute to an existing library so that there was *one* place where bugs were fixed -- especially the ones caused by insufficient validation, or in your words, that "being paranoid about data collisions and integrity errors" would resolve.

Subsequently I was confused by the idea that you would declare that you cannot possibly trust other people to do this right, but that for some reason people should trust you enough to use your library. I still have no idea why you would imagine that you alone are trustable on this front, or why working with others would somehow prevent you adding sufficient validation or "paranoia" to the code.
Adamo wrote:
Wed Aug 28, 2019 9:03 pm
You didn't even understand my point about paranoia: I was talking about about being paranoid about data collisions and integrity errors, not about other people.
Adamo wrote:
Wed Aug 28, 2019 7:07 pm
I don't trust other people. ;)
I'm pretty sure that you would need me to be way better of a mind reader to understand that this statement was about data integrity, rather than about "[Adamo] don't trust other people". I took you as saying what you meant.

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Data Functions Library

Post by Adamo »

slippycheeze wrote:
Thu Aug 29, 2019 4:35 pm
If you want to ask me to contribute to a project, then I'm all about it. But this is a library I wrote for myself. I'm not sure why you're trying to justify acting rudely about it. I looked at what was available. They didn't serve all of the needs I wanted. Besides this, I enjoy and am good at doing this sort of "back-end hook" work. This library is for me, and used throughout my mods. I simply wanted to share it in case it would help anyone else. And if someone has something that they need to have done, or if a library is missing some function, then they can use mine, or I can write something for them, or whatever. I'm open and I hope they ask. But I've been modding for many years, and those sorts of "library mods" rarely last. There are 5 already, as you said -- and many more if you count the ones that fizzled. This is simply a sheet of functions. Freeware. Use it as you will. You will find that my PARANOIA serves these functions well, as I am constantly checking and rechecking the data integrity of inputs.

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Data Functions Library

Post by Adamo »

eradicator wrote:
Thu Aug 29, 2019 10:54 am
From personal observation: The more self-confident(?) any factorio modder gets, the more likely they are to write their own library instead of using a common one. It's kind of a sad state becaues it prevents any library from becoming the "community approved" library, and everyone just keeps using their barely-tested personal abominations (yes, me included).
Btw, Randall as always gets it to the point :p https://xkcd.com/927/
I agree with you. This is precisely why all I did was share my function sheet. This is what I use, and I'm adding to it constantly, and I keep it up-to-date on that link I put above. If people want to use it, great! But I don't think it will become the de facto standard. I just hope that it will either help a few people who know what they're doing and don't want to spend the time going through the work for one of my algorithms, or help someone see learn how to deal with the data structures, or anything at all -- maybe it moves someone to ask me a question, and that's great, too.

Actually, I find the hardest thing with my factorio modding library to be that I don't have a great way to TEST the library. I'm not planning to spend the time to setup a bunch of test prototypes (although any good programmer should), and there's no simple way of which I'm aware to just tell the game to test the data and give you detailed results, only a "run it and see if you get errors" process. If you know of any functionality for doing that, please let me know.

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Data Functions Library

Post by eradicator »

Apart from the missing license issue (if you don't understand why "freeware" is not a license feel free to ask someone you trust, or google), i just noticed that all your functions are globally scoped. This is a huge red-flag for cross-mod compatibility which you claim to be paranoid about. Also the reason most libraries don't last is because they have no documentation, so only the author understands them. Where is your documentation? How do you expect anyone to even know what your library can do without documentation? (If they have to read the code they might just as well write it themselfs.)
Adamo wrote:
Thu Aug 29, 2019 7:04 pm
Besides this, I enjoy and am good at doing this sort of "back-end hook" work.
Five minute fact check:
recipe.expensive does not imply recipe.normal -> at least two broken functions
default value for energy_required is 0.5 not 1 -> at least two broken functions
...these are things that are on the wiki. So you didn't even read the most basic documentation. Meh. Recipes are one of the more difficult things to handle in data stage and writing good libraries for them requires time and patience.
Adamo wrote:
Thu Aug 29, 2019 7:09 pm
Actually, I find the hardest thing with my factorio modding library to be that I don't have a great way to TEST the library. I'm not planning to spend the time to setup a bunch of test prototypes (although any good programmer should), and there's no simple way of which I'm aware to just tell the game to test the data and give you detailed results, only a "run it and see if you get errors" process. If you know of any functionality for doing that, please let me know.
Oh. So you realized yourself that you're not a good programmer. Not sure how that changes the overall situation. Testing a program (==a mod) if it works correctly is not something that the engine can just do "automagically", because if that was possible then all programs in the world would be completely bug-free. Any prototypes that don't throw errors are fine except for bugs in the engine itself. There's also --check-unused-prototype-data but that produces 60k+ lines of logspam just from base.

Ye know, i don't want to sound hostile. It just sounds like you're new to factorio (which is strange considering your forum join date) and exited that you can do stuff (great!), but haven't yet realized how little you know about the modding internals. And so you're shouting at people who are (maybe clumsily) actually trying to help you. Not a good way to start off a converstation.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Adamo
Filter Inserter
Filter Inserter
Posts: 479
Joined: Sat May 24, 2014 7:00 am
Contact:

Re: Data Functions Library

Post by Adamo »

eradicator wrote:
Thu Aug 29, 2019 7:38 pm
LOL. You found this message to be a good way to go about a response, rather than just pointing out the issues you found as fixables?

I'm aware of the assumptions I've made that don't go precisely along with the hard prototype requirements. Some have further fixes planned. You could have just, you know, reported the problems you found, so that I could fix them.

The energy defaulting to 1 instead of 0.5 is just a simple mistake and easily fixed.

They are all globally scoped, yes. This is because this is a MODDING PROGRAM (yes, it is, and any set of computer instructions is a program). This is supposed to be fun. The names are unique enough that they probably won't cause collisions. But if we find that to be a problem, we can do something about it. I think with lua you can even require a file into a table. Live free, man.

Your interpretive skills are lacking. Clearly, I am not asking for the program to do "automagic", which is why a proper test program would have prototypes to test the algorithms against. I was asking if the program had the ability to spit out the final data.raw table, to check that results are coming out as expected. I saw there's a mod for it, anyway. Next time, just stick to reporting the problems and leave your opinion where it belongs.
Last edited by Adamo on Thu Aug 29, 2019 8:06 pm, edited 1 time in total.

Bilka
Factorio Staff
Factorio Staff
Posts: 3135
Joined: Sat Aug 13, 2016 9:20 am
Contact:

Re: Data Functions Library

Post by Bilka »

eradicator wrote:
Thu Aug 29, 2019 7:38 pm
There's also --check-unused-prototype-data but that produces 60k+ lines of logspam just from base.
No it doesn't.

Code: Select all

   0.001 2019-08-29 22:00:16; Factorio 0.17.66 (build 46866, win64, steam)
   0.001 Operating system: Windows 10 (version 1809) 
   0.001 Program arguments: "C:\Users\Erik\Documents\SteamLibrary\steamapps\common\Factorio\bin\x64\Factorio.exe" "--check-unused-prototype-data" 
   0.001 Read data path: C:/Users/Erik/Documents/SteamLibrary/steamapps/common/Factorio/data
   0.001 Write data path: C:/Users/Erik/AppData/Roaming/Factorio [241829/475869MB]
   0.001 Binaries path: C:/Users/Erik/Documents/SteamLibrary/steamapps/common/Factorio/bin
   0.024 System info: [CPU: AMD Ryzen 7 2700X Eight-Core Processor, 16 cores, RAM: 4091/16334 MB, page: 5755/18766 MB, virtual: 4343/134217727 MB, extended virtual: 0 MB]
   0.024 Display options: [FullScreen: 0] [VSync: 1] [UIScale: automatic (100.0%)] [Native DPI: 1] [Screen: 255] [Special: 0000] [Lang: en]
   0.030 Available displays: 2
   0.030  [1]: \\.\DISPLAY2 - NVIDIA GeForce GTX 1050 Ti {0x05, [0,0], 1920x1080, 32bit, 60Hz}
   0.031  [0]: \\.\DISPLAY1 - NVIDIA GeForce GTX 1050 Ti {0x01, [-1280,0], 1280x1024, 32bit, 60Hz}
   0.082 [Direct3D11] Display: 0, Output: 0, DisplayAdapter: 0, RenderingAdapter: 0; d3dcompiler_47.dll
   0.180 Initialised Direct3D[0]: NVIDIA GeForce GTX 1050 Ti; id: 10de-1c82; driver: nvldumdx.dll 23.21.13.9135
   0.180   D3D Feature Level: 11.1, DXGI 1.5+, SwapChain: 3,flip-discard,-,-,-,none
   0.180   [Local Video Memory] Budget: 3415MB, CurrentUsage: 1MB, Reservation: 0/1808MB
   0.180   [Non-Local Vid.Mem.] Budget: 7350MB, CurrentUsage: 0MB, Reservation: 0/3879MB
   0.180   Tiled resources: Tier 2
   0.180   Unified Memory Architecture: No
   0.180   BGR 565 Supported: Yes
   0.180   MaximumFrameLatency: 3, GPUThreadPriority: 0
   0.180 Graphics settings preset: very-high
   0.180   Dedicated video memory size 4018 MB
   0.234 Desktop composition is active.
   0.234 Graphics options: [Graphics quality: high] [Video memory usage: all] [Light scale: 25%] [DXT: high-quality]
   0.234                   [Max load threads: 32] [Max texture size: 0] [Tex.Stream.: 0] [Rotation quality: normal] [Color: 32bit]
   0.263 DSound: Starting _dsound_update thread
   0.263 DSound: Enter _dsound_update; tid=1788
   0.453 Info ModManager.cpp:241: Found duplicate mod IndustrialRevolution, using higher version (0.2.7 > 0.2.6).
   0.471 Info ModManager.cpp:241: Found duplicate mod IndustrialRevolution, using higher version (0.2.8 > 0.2.7).
   0.490 Info ModManager.cpp:241: Found duplicate mod IndustrialRevolution, using higher version (0.2.9 > 0.2.8).
   0.502 Loading mod core 0.0.0 (data.lua)
   0.711 Loading mod base 0.17.66 (data.lua)
   1.484 Loading mod base 0.17.66 (data-updates.lua)
   1.661 Checksum for core: 2403267992
   1.661 Checksum of base: 3948013453
   1.974 Finished checking unused prototype data. Number of properties that were used: 61224
   2.041 Loading sounds...
   4.587 Info PlayerData.cpp:70: Local player-data.json unavailable
   4.587 Info PlayerData.cpp:73: Cloud player-data.json available, timestamp 1567053166
   4.782 Initial atlas bitmap size is 16384
   4.828 Created atlas bitmap 16384x16364 [none]
   4.833 Created atlas bitmap 16384x11144 [none]
   4.833 Created atlas bitmap 8192x3892 [decal]
   4.834 Created atlas bitmap 16384x4320 [low-object, linear-minification]
   4.835 Created atlas bitmap 8192x3744 [mipmap, linear-minification, linear-magnification, linear-mip-level]
   4.836 Created atlas bitmap 16384x6880 [terrain, mipmap, linear-minification, linear-mip-level]
   4.836 Created atlas bitmap 4096x1600 [smoke, mipmap, linear-minification, linear-magnification]
   4.837 Created atlas bitmap 4096x1744 [icon, not-compressed, mipmap, linear-minification, linear-magnification, linear-mip-level]
   4.837 Created atlas bitmap 2048x192 [icon-background, not-compressed, mipmap, linear-minification, linear-magnification, linear-mip-level, ]
   4.837 Created atlas bitmap 8192x1496 [alpha-mask]
   4.840 Created atlas bitmap 16384x12772 [shadow, linear-magnification, alpha-mask]
   4.841 Created atlas bitmap 8192x2352 [shadow, mipmap, linear-magnification, alpha-mask]
   7.052 Atlases loaded from disk cache.
   7.242 Texture processor created (2048). GPU accelerated compression Supported: yes, Enabled: yes/yes. Test passed. YCoCgDXT PSNR: 35.83, BC3 PSNR: 33.82
   7.247 Parallel Sprite Loader initialized (threads: 0)
   7.274 Sprites loaded
   7.335 Generated mipmaps (4) for atlas [3] of size 16384x4320   
   7.346 Generated mipmaps (3) for atlas [4] of size 8192x3744   
   7.357 Generated mipmaps (3) for atlas [5] of size 16384x6880   
   7.368 Generated mipmaps (3) for atlas [6] of size 4096x1600   
   7.379 Generated mipmaps (5) for atlas [7] of size 4096x1744   
   7.391 Generated mipmaps (3) for atlas [11] of size 8192x2352   
   7.391 Custom mipmaps uploaded.
   7.465 Factorio initialised
0.502 Loading mod core 0.0.0 (data.lua)
0.711 Loading mod base 0.17.66 (data.lua)
1.484 Loading mod base 0.17.66 (data-updates.lua)
1.661 Checksum for core: 2403267992
1.661 Checksum of base: 3948013453
here's where the unused data would go
1.974 Finished checking unused prototype data. Number of properties that were used: 61224
I'm an admin over at https://wiki.factorio.com. Feel free to contact me if there's anything wrong (or right) with it.

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: Data Functions Library

Post by eradicator »

Bilka wrote:
Thu Aug 29, 2019 8:02 pm
eradicator wrote:
Thu Aug 29, 2019 7:38 pm
There's also --check-unused-prototype-data but that produces 60k+ lines of logspam just from base.
No it doesn't.
Nice to know. Maybe it was some sort of bug. I still have the log from 0.17.23 with 62k lines.

Code: Select all

   5.156 Warning PrototypeLoader.cpp:132: Value ROOT.fluid-turret.flamethrower-turret.base_picture.south.layers[0].filename was not used.
   5.156 Warning PrototypeLoader.cpp:132: Value ROOT.fluid-turret.flamethrower-turret.base_picture.south.layers[0].line_length was not used.
   5.156 Warning PrototypeLoader.cpp:132: Value ROOT.fluid-turret.flamethrower-turret.base_picture.south.layers[0].width was not used.
   5.156 Warning PrototypeLoader.cpp:132: Value ROOT.fluid-turret.flamethrower-turret.base_picture.south.layers[0].height was not used.
   5.156 Warning PrototypeLoader.cpp:132: Value ROOT.fluid-turret.flamethrower-turret.base_picture.south.layers[0].axially_symmetrical was not used.
Adamo wrote:
Thu Aug 29, 2019 8:00 pm
Next time, just stick to reporting the problems and leave your opinion where it belongs.
Sure thing, i'll leave this opinion of mine where it belongs - right here on this forum:

Why would anyone want to help a guy who shouts at them as if they were slaves? You haven't even addressed the very first issue i mentiond. You're overconfident, rude, and worst of all you seem to think that's an ok approach to collaboration.

The most important problem i have thus to report is: Your current attitude is incompatible with your goal.

Quote unquote "LOL".
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

slippycheeze
Filter Inserter
Filter Inserter
Posts: 587
Joined: Sun Jun 09, 2019 10:40 pm
Contact:

Re: Data Functions Library

Post by slippycheeze »

Adamo wrote:
Thu Aug 29, 2019 7:09 pm
Actually, I find the hardest thing with my factorio modding library to be that I don't have a great way to TEST the library.
https://github.com/JonasJurczok/faketorio – "Run automatic tests for your mod inside Factorio"

The obvious alternative is that you can simply write the tests in Lua and attach them to a trigger in a mod or scenario (if you need specific map content) designed for the purpose, and fire them off automatically from your initialization code / on_tick handler at start. Which isn't far from what that code does, though it seems like it has a bunch of (untested by me) helpers to make map-entity and similar testing work smoothly.

I am still constrained by stupid US employment law that I can't give you any code myself, but there isn't really anything fancy to it. I have no investment in (or need of) map content helpers for tests at this time.
Adamo wrote:
Thu Aug 29, 2019 8:00 pm
I was asking if the program had the ability to spit out the final data.raw table, to check that results are coming out as expected. I saw there's a mod for it, anyway.

Code: Select all

log(serpent.block(data.raw))
-- or if my memory didn't serve, and log uses a localized string, log({"", serpent.block(data.raw)})
I'm 99 percent certain that mod does nothing else, but I have also not checked. The main advantage it'd have would be that it made an effort to load last of all.

Personally, I'd watch for errors in the log, and then use the runtime values in `game.*_prototypes` though. The final interpretation of the data is what you need to care about, not the intermediate form. For an example of why, a recipe with `ingredients={}, normal={...content...}` is valid, and only the content in `normal` matters for ingredients etc, the top level is ignore. I'm 99 percent sure that was demonstrably ignoring it even if the top `ingredients` was filled, but again, not going to go dig back through Discord to confirm.

Post Reply

Return to “Modding discussion”