util = { table = {} } function table.deepcopy(object) local lookup_table = {} local function _copy(object) if type(object) ~= "table" then return object -- don't copy factorio rich objects elseif object.__self then return object elseif lookup_table[object] then return lookup_table[object] end local new_table = {} lookup_table[object] = new_table for index, value in pairs(object) do new_table[_copy(index)] = _copy(value) end return setmetatable(new_table, getmetatable(object)) end return _copy(object) end function table.compare( tbl1, tbl2 ) if tbl1 == tbl2 then return true end for k, v in pairs( tbl1 ) do if type(v) == "table" and type(tbl2[k]) == "table" then if not table.compare( v, tbl2[k] ) then return false end else if ( v ~= tbl2[k] ) then return false end end end for k, v in pairs( tbl2 ) do if tbl1[k] == nil then return false end end return true end util.table.deepcopy = table.deepcopy util.table.compare = table.compare util.copy = util.table.deepcopy function util.distance(position1, position2) local x1 = position1[1] or position1.x local y1 = position1[2] or position1.y local x2 = position2[1] or position2.x local y2 = position2[2] or position2.y return ((x1 - x2) ^ 2 + (y1 - y2) ^ 2) ^ 0.5 end function util.positiontostr(pos) return string.format("[%g, %g]", pos[1] or pos.x, pos[2] or pos.y) end function util.formattime(ticks) local seconds = ticks / 60 local minutes = math.floor((seconds)/60) local seconds = math.floor(seconds - 60*minutes) return string.format("%d:%02d", minutes, seconds) end function util.color(hex) -- supports 'rrggbb', 'rgb', 'rrggbbaa', 'rgba', 'ww', 'w' local function h(i,j) return j and tonumber("0x"..hex:sub(i,j)) / 255 or tonumber("0x"..hex:sub(i,i)) / 15 end hex = hex:gsub("#","") return #hex == 6 and {r = h(1,2), g = h(3,4), b = h(5,6)} or #hex == 3 and {r = h(1), g = h(2), b = h(3)} or #hex == 8 and {r = h(1,2), g = h(3,4), b = h(5,6), a = h(7,8)} or #hex == 4 and {r = h(1), g = h(2), b = h(3), a = h(4)} or #hex == 2 and {r = h(1,2), g = h(1,2), b = h(1,2)} or #hex == 1 and {r = h(1), g = h(1), b = h(1)} or {r=1, g=1, b=1} end function util.premul_color(color) local r = color.r or color[1] local g = color.g or color[2] local b = color.b or color[3] local a = color.a or color[4] or 1 return { r = r and (r * a), g = g and (g * a), b = b and (b * a), a = a } end function util.mix_color(c1, c2) return { (c1.r or c1[1] or 0) * (c2.r or c2[1] or 0), (c1.g or c1[2] or 0) * (c2.g or c2[2] or 0), (c1.b or c1[3] or 0) * (c2.b or c2[3] or 0), (c1.a or c1[4] or 1) * (c2.a or c2[4] or 1) } end function util.multiply_color(c1, n) return { (c1.r or c1[1] or 0) * (n or 0), (c1.g or c1[2] or 0) * (n or 0), (c1.b or c1[3] or 0) * (n or 0), (c1.a or c1[4] or 1) * (n or 1) } end function util.get_color_with_alpha(color, alpha, normalized_alpha) local new_color = { r = color.r or color[1] or 0, g = color.g or color[2] or 0, b = color.b or color[3] or 0 } new_color.a = normalized_alpha and (new_color.r > 1 or new_color.g > 1 or new_color.b > 1) and alpha * 255 or alpha return new_color end util.direction_vectors = { [defines.direction.north] = { 0, -1}, [defines.direction.northeast] = { 1, -1}, [defines.direction.east] = { 1, 0}, [defines.direction.southeast] = { 1, 1}, [defines.direction.south] = { 0, 1}, [defines.direction.southwest] = {-1, 1}, [defines.direction.west] = {-1, 0}, [defines.direction.northwest] = {-1, -1}, } function util.moveposition(position, direction, distance) local direction_vector = util.direction_vectors[direction] if not direction_vector then error(direction .. " is not a valid or supported direction") end return {position[1] + direction_vector[1] * distance, position[2] + direction_vector[2] * distance} end function util.oppositedirection(direction) if not tonumber(direction) then error(direction .. " is not a valid direction") end return (direction + 4) % 8 end function util.multiplystripes(count, stripes) local ret = {} for _, stripe in ipairs(stripes) do for _ = 1, count do ret[#ret + 1] = stripe end end return ret end function util.by_pixel(x,y) return {x / 32, y / 32} end function util.by_pixel_hr(x,y) return {x / 64, y / 64} end function util.foreach_sprite_definition(table_, fun_) --for k, tab in pairs(table_) do fun_(table_) if table_.hr_version then fun_(table_.hr_version) end --end return table_ end function util.add_shift(a, b) if not (a and b) then return a or b end return { a[1] + b[1], a[2] + b[2] } end function util.add_shift_offset(offset_, table_) return util.foreach_sprite_definition(table_, function(tab) tab.shift = util.add_shift(tab.shift, offset_) end) end function util.mul_shift(shift, scale) if not (shift and scale) then return shift end return {shift[1] * scale, shift[2] * scale} end function util.format_number(amount, append_suffix) local suffix = "" if append_suffix then local suffix_list = { ["T"] = 1000000000000, ["B"] = 1000000000, ["M"] = 1000000, ["k"] = 1000 } for letter, limit in pairs (suffix_list) do if math.abs(amount) >= limit then amount = math.floor(amount/(limit/10))/10 suffix = letter break end end end local formatted, k = amount while true do formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') if (k==0) then break end end return formatted..suffix end function util.increment(t, k, v) t[k] = t[k] + (v or 1) end function util.conditional_return(value, data) return value and data end -- Recursively merges and/or deep-copies tables. -- Entries in later tables override entries in earlier ones, unless -- both entries are themselves tables, in which case they are recursively merged. -- Non-merged tables are deep-copied, so that the result is brand new. function util.merge(tables) local ret = {} for i, tab in ipairs(tables) do for k, v in pairs(tab) do if (type(v) == "table") then if (type(ret[k] or false) == "table") then ret[k] = util.merge{ret[k], v} else ret[k] = table.deepcopy(v) end else ret[k] = v end end end return ret end util.insert_safe = function(entity, item_dict) if not (entity and entity.valid and item_dict) then return end local items = game.item_prototypes local insert = entity.insert for name, count in pairs (item_dict) do if items[name] then insert{name = name, count = count} else log("Item to insert not valid: "..name) end end end util.remove_safe = function(entity, item_dict) if not (entity and entity.valid and item_dict) then return end local items = game.item_prototypes local remove = entity.remove_item for name, count in pairs (item_dict) do if items[name] then remove{name = name, count = count} else log("Item to remove not valid: "..name) end end end util.split_whitespace = function(string) if not string then return {} end local result = {} for w in string:gmatch("%S+") do table.insert(result, w) end return result end util.split = function(inputstr, sep) local result = {} for str in string.gmatch(inputstr, "([^"..sep.."]+)") do table.insert(result, str) end return result end util.string_starts_with = function(str, start) return str.sub(str, 1, string.len(start)) == start end util.online_players = function() log("But why?") return game.connected_players end util.clamp = function(x, lower, upper) return math.max(lower, math.min(upper, x)) end local walkable_mask = {"item-layer", "object-layer", "player-layer", "water-tile"} local is_walkable = function(mask) for k, layer in pairs (walkable_mask) do if mask[layer] then return false end end return true end util.get_walkable_tile = function() for name, tile in pairs (game.tile_prototypes) do if is_walkable(tile.collision_mask) and not tile.items_to_place_this then return name end end error("No walkable tile in prototype list") end -- This function takes 2 icons tables, and adds the second to the first, but applies scale, shift and tint to the entire second set. -- This allows you to manipulate the entire second icons table in the same way as you would manipulate a single icon when adding to the icons table. function util.combine_icons(icons1, icons2, inputs, default_icon_size) scale = inputs.scale or 1 shift = inputs.shift or {0, 0} tint = inputs.tint or {r = 1, g = 1, b = 1, a = 1} local icons = table.deepcopy(icons1) for _,icon_to_add in pairs(icons2) do local icon = {} icon.icon = icon_to_add.icon icon.icon_size = icon_to_add.icon_size or default_icon_size or error("No icon size defined for icon \n"..serpent.block(icon)) icon.scale = scale * (icon_to_add.scale or 32.0 / icon.icon_size) icon.icon_mipmaps = icon_to_add.icon_mipmaps if icon_to_add.shift then icon.shift = {icon_to_add.shift[1] * scale + shift[1], icon_to_add.shift[2] * scale + shift[2]} else icon.shift = shift end if icon_to_add.tint then icon.tint = util.mix_color(tint, icon_to_add.tint) else icon.tint = tint end table.insert(icons,icon) end return icons end local energy_chars = { k = 10^3, K = 10^3, M = 10^6, G = 10^9, T = 10^12, P = 10^15, E = 10^18, Z = 10^21, Y = 10^24 } function util.technology_icon_constant_damage(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-damage.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_speed(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-speed.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_movement_speed(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-movement-speed.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_range(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-range.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_equipment(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-equipment.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_followers(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-count.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_capacity(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-capacity.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_stack_size(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-capacity.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_productivity(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-mining-productivity.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_braking_force(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-braking-force.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.technology_icon_constant_mining(technology_icon) local icons = { { icon = technology_icon, icon_size = 256, icon_mipmaps = 4 }, { icon = "__core__/graphics/icons/technology/constants/constant-mining.png", icon_size = 128, icon_mipmaps = 3, shift = {100, 100} } } return icons end function util.parse_energy(energy) local ending = energy:sub(energy:len()) if not (ending == "J" or ending == "W") then error(ending.. " is not a valid unit of energy") end local multiplier = (ending == "W" and 1 / 60) or 1 local magnitude = energy:sub(energy:len() - 1, energy:len() - 1) if tonumber(magnitude) then return tonumber(energy:sub(1, energy:len()-1)) * multiplier end multiplier = multiplier * (energy_chars[magnitude] or error(magnitude.. " is not valid magnitude")) return tonumber(energy:sub(1, energy:len()-2)) * multiplier end function util.product_amount(product) return product.probability * (product.amount or ((product.amount_min + product.amount_max) / 2)) end function util.empty_sprite(animation_length) return { filename = "__core__/graphics/empty.png", priority = "extra-high", width = 1, height = 1, frame_count = 1, repeat_count = animation_length, direction_count = 1 } end function util.draw_as_glow(layer) layer.draw_as_glow = true if layer.hr_version then layer.hr_version.draw_as_glow = true end return layer end function util.remove_tile_references(data, array_of_tiles_to_remove) -- Does not handle: -- explicit tile filters in "selection-tool" items -- ItemPrototype::place_as_tile -- TilePrototype::next_direction -- TilePrototype::transition_merges_with_tile -- general tile transitions, only removes tile names from water_tile_type_names if (type(array_of_tiles_to_remove) ~= "table") then error("The second parameter of util.remove_tile_reference() is expected to be array of strings.") end local tiles_to_remove = {} for i, n in pairs(array_of_tiles_to_remove) do tiles_to_remove[n] = true end local remove_from_mapping = function(mapping) if not mapping then return end for _, item in pairs(mapping) do if item.tiles then for i, t in pairs(item.tiles) do if t and tiles_to_remove[t] then item.tiles[i] = nil end end end end end for k,e in pairs(data.raw["character"]) do remove_from_mapping(e.footstep_particle_triggers) remove_from_mapping(e.synced_footstep_particle_triggers) remove_from_mapping(e.footprint_particles) end for k,e in pairs(data.raw["car"]) do remove_from_mapping(e.track_particle_triggers) end if data.raw["fire"] then for k, fire in pairs(data.raw["fire"]) do if fire.burnt_patch_alpha_variations then for i, v in pairs(fire.burnt_patch_alpha_variations) do if v and v.tile and tiles_to_remove[v.tile] then fire.burnt_patch_alpha_variations[i] = nil end end end end end if water_tile_type_names then for i = #water_tile_type_names, 1, -1 do if tiles_to_remove[water_tile_type_names[i]] then table.remove(water_tile_type_names, i) end end end end local remove = table.remove util.remove_from_list = function(list, value) for k, v in pairs (list) do if v == value then remove(list, k) return true end end return false end util.list_to_map = function(list) local map = {} --Here I am trusting you not to give a list of junk. for k, value in pairs(list) do map[value] = true end return map end return util