local util = {} util.min = math.min util.max = math.max util.floor = math.floor util.abs = math.abs util.sqrt = math.sqrt util.sin = math.sin util.cos = math.cos util.atan = math.atan util.atan2 = math.atan2 util.pi = math.pi util.remove = table.remove util.insert = table.insert util.str_gsub = string.gsub function util.deep_copy (t) return table.deepcopy(t) end function util.shallow_copy (t) -- shallow-copy a table if type(t) ~= "table" then return t end local meta = getmetatable(t) local target = {} for k, v in pairs(t) do target[k] = v end setmetatable(target, meta) return target end function util.remove_from_table(list, item) local index = 0 for _,_item in ipairs(list) do if item == _item then index = _ break end end if index > 0 then util.remove(list, index) end end function util.shuffle (tbl) size = #tbl for i = size, 1, -1 do --local rand = 1 + math.floor(size * (math.random() - 0.0000001)) local rand = math.random(size) tbl[i], tbl[rand] = tbl[rand], tbl[i] end return tbl end function util.random_from_array (tbl) --return tbl[1 + math.floor(#tbl * (math.random() - 0.0000001))] return tbl[math.random(#tbl)] end function util.area_add_position(area, position) local area2 = table.deepcopy(area) for k1, v1 in pairs(area2) do for k2, v2 in pairs(v1) do if k2 == 1 or k2 == "x" then v1[k2] = v2 + (position.x or position[1]) elseif k2 == 2 or k2 == "y" then v1[k2] = v2 + (position.y or position[2]) end end end return area2 end function util.area_extend(area, range) local area2 = table.deepcopy(area) for k1, v1 in pairs(area2) do local m = 1 if k1 == 1 or k1 == "left_top" then m = -1 end for k2, v2 in pairs(v1) do v1[k2] = v2 + range * m end end return area2 end function util.position_to_area(position, radius) return {{x = position.x - radius, y = position.y - radius}, {x = position.x + radius, y = position.y + radius}} end function util.position_to_tile(position) return {x = math.floor(position.x or position[1]), y = math.floor(position.y or position[2])} end function util.tile_to_position(tile_position) return {x = math.floor(tile_position.x)+0.5, y = math.floor(tile_position.y)+0.5} end function util.tile_to_area(tile_position, margin) tile_position = {x = math.floor(tile_position.x or tile_position[1]), y = math.floor(tile_position.y or tile_position[2])} margin = margin or 0.01 return { {tile_position.x+margin, tile_position.y+margin}, {tile_position.x+1-margin, tile_position.y+1-margin} } end function util.position_to_xy_string(position) return util.xy_to_string(position.x, position.y) end function util.xy_to_string(x, y) return util.floor(x) .. "_" .. util.floor(y) end function util.lerp(a, b, alpha) return a + (b - a) * alpha end function util.lerp_angles(a, b, alpha) local da = b - a if da < -0.5 then da = da + 1 elseif da > 0.5 then da = da - 1 end local na = a + da * alpha if na < 0 then na = na + 1 elseif na > 1 then na = na - 1 end return na end function util.array_to_vector(array) return {x = array[1], y = array[2]} end function util.vectors_delta(a, b) -- from a to b if not a and b then return 0 end return {x = b.x - a.x, y = b.y - a.y} end function util.vectors_delta_length(a, b) return util.vector_length_xy(b.x - a.x, b.y - a.y) end function util.vector_length(a) return util.sqrt(a.x * a.x + a.y * a.y) end function util.vector_length_xy(x, y) return util.sqrt(x * x + y * y) end function util.vector_dot(a, b) return a.x * b.x + a.y * b.y end function util.vector_multiply(a, multiplier) return {x = a.x * multiplier, y = a.y * multiplier} end function util.vector_dot_projection(a, b) local n = util.vector_normalise(a) local d = util.vector_dot(n, b) return {x = n.x * d, y = n.y * d} end function util.vector_normalise(a) local length = util.vector_length(a) return {x = a.x/length, y = a.y/length} end function util.vector_set_length(a, length) local old_length = util.vector_length(a) if old_length == 0 then return {x = 0, y = -length} end return {x = a.x/old_length*length, y = a.y/old_length*length} end function util.vector_cross(a, b) -- N = i(a2b3 - a3b2) + j(a3b1 - a1b3) + k(a1b2 - a2b1) return { x = (a.y or a[2]) * (b.z or b[3]) - (a.z or a[3]) * (b.y or b[2]), y = (a.z or a[3]) * (b.x or b[1]) - (a.x or a[1]) * (b.z or b[3]), z = (a.x or a[1]) * (b.y or b[2]) - (a.y or a[2]) * (b.x or b[1]), } end function util.orientation_from_to(a, b) return util.vector_to_orientation_xy(b.x - a.x, b.y - a.y) end function util.orientation_to_vector(orientation, length) return {x = length * util.sin(orientation * 2 * util.pi), y = -length * util.cos(orientation * 2 * util.pi)} end function util.rotate_vector(orientation, a) if orientation == 0 then return {x = a.x, y = a.y} else return { x = -a.y * util.sin(orientation * 2 * util.pi) + a.x * util.sin((orientation + 0.25) * 2 * util.pi), y = a.y * util.cos(orientation * 2 * util.pi) -a.x * util.cos((orientation + 0.25) * 2 * util.pi)} end end function util.vectors_add(a, b) return {x = a.x + b.x, y = a.y + b.y} end function util.vectors_add3(a, b, c) return {x = a.x + b.x + c.x, y = a.y + b.y + c.y} end function util.lerp_vectors(a, b, alpha) return {x = a.x + (b.x - a.x) * alpha, y = a.y + (b.y - a.y) * alpha} end function util.move_to(a, b, max_distance, eliptical) -- move from a to b with max_distance. -- if eliptical, reduce y change (i.e. turret muzzle flash offset) local eliptical_scale = 0.9 local delta = util.vectors_delta(a, b) if eliptical then delta.y = delta.y / eliptical_scale end local length = util.vector_length(delta) if (length > max_distance) then local partial = max_distance / length delta = {x = delta.x * partial, y = delta.y * partial} end if eliptical then delta.y = delta.y * eliptical_scale end return {x = a.x + delta.x, y = a.y + delta.y} end function util.vector_to_orientation(v) return util.vector_to_orientation_xy(v.x, v.y) end function util.vector_to_orientation_xy(x, y) return util.atan2(y, x) / util.pi / 2 end function util.direction_to_orientation(direction) if direction == defines.direction.north then return 0 elseif direction == defines.direction.northeast then return 0.125 elseif direction == defines.direction.east then return 0.25 elseif direction == defines.direction.southeast then return 0.375 elseif direction == defines.direction.south then return 0.5 elseif direction == defines.direction.southwest then return 0.625 elseif direction == defines.direction.west then return 0.75 elseif direction == defines.direction.northwest then return 0.875 end return 0 end function util.direction_to_string(direction) if direction == defines.direction.north then return "north" elseif direction == defines.direction.northeast then return "northeast" elseif direction == defines.direction.east then return "east" elseif direction == defines.direction.southeast then return "southeast" elseif direction == defines.direction.south then return "south" elseif direction == defines.direction.southwest then return "southwest" elseif direction == defines.direction.west then return "west" elseif direction == defines.direction.northwest then return "northwest" end return 0 end function util.signal_to_string(signal) return signal.type .. "__" .. signal.name end function util.signal_container_add(container, signal, count) if signal then if not container[signal.type] then container[signal.type] = {} end if container[signal.type][signal.name] then container[signal.type][signal.name].count = container[signal.type][signal.name].count + count else container[signal.type][signal.name] = {signal = signal, count = count} end end end function util.signal_container_add_inventory(container, entity, inventory) local inv = entity.get_inventory(inventory) if inv then local contents = inv.get_contents() for item_type, item_count in pairs(contents) do util.signal_container_add(container, {type="item", name=item_type}, item_count) end end end function util.signal_container_get(container, signal) if container[signal.type] and container[signal.type][signal.name] then return container[signal.type][signal.name] end end function util.signal_from_wires(red, green, signal) local value = 0 if red then value = value + red.get_signal(signal) or 0 end if green then value = value + green.get_signal(signal) or 0 end return value end util.char_to_multiplier = { m = 0.001, c = 0.01, d = 0.1, h = 100, k = 1000, M = 1000000, G = 1000000000, T = 1000000000000, P = 1000000000000000, } function util.string_to_number(str) str = ""..str local number_string = "" local last_char = nil for i = 1, #str do local c = str:sub(i,i) if c == "." or (c == "-" and i == 1) or tonumber(c) ~= nil then number_string = number_string .. c else last_char = c break end end if last_char and util.char_to_multiplier[last_char] then return tonumber(number_string) * util.char_to_multiplier[last_char] end return tonumber(number_string) end function util.replace(str, what, with) what = util.str_gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1") -- escape pattern with = util.str_gsub(with, "[%%]", "%%%%") -- escape replacement str = util.str_gsub(str, what, with) return str --only return the first variable from str_gsub end function util.split(s, delimiter) result = {}; for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match); end return result; end function util.parse_with_prefix(s, prefix) if string.sub(s, 1, string.len(prefix)) == prefix then return string.sub(s, string.len(prefix)+1) end end function util.overwrite_table(table_weak, table_strong) for k,v in pairs(table_strong) do table_weak[k] = v end return table_weak end function util.table_contains(table, check) for k,v in pairs(table) do if v == check then return true end end return false end function util.table_to_string(table) return serpent.block( table, {comment = false, numformat = '%1.8g' } ) end function util.values_to_string(table) local string = "" for _, value in pairs(table) do string = ((string == "") and "" or ", ") .. string .. value end return string end function util.math_log(value, base) --logb(a) = logc(a) / logc(b) return math.log(value)/math.log(base) end function util.seconds_to_clock(seconds, use_days) local seconds = tonumber(seconds) if seconds <= 0 then return "0"; else local days = 0 if use_days then days = math.floor(seconds/3600/24) end local hours = math.floor(seconds/3600 - days*24) local mins = math.floor(seconds/60 - hours*60 - days*1440) local secs = math.floor(seconds - mins *60 - hours*3600 - days*86400) local s_hours = string.format("%02.f",hours); local s_mins = string.format("%02.f", mins); local s_secs = string.format("%02.f", secs); if days > 0 then return days.."d:"..s_hours..":"..s_mins..":"..s_secs end if hours > 0 then return s_hours..":"..s_mins..":"..s_secs end if mins > 0 then return s_mins..":"..s_secs end if secs == 0 then return "0" end return s_secs end end function util.to_rail_grid(number_or_position) if type(number_or_position) == "table" then return {x = util.to_rail_grid(number_or_position.x), y = util.to_rail_grid(number_or_position.y)} end return math.floor(number_or_position / 2) * 2 end function util.format_fuel(fuel, ceil) return string.format("%.2f",(fuel or 0) / 1000).."k" end function util.format_energy(fuel, ceil) if ceil then return math.ceil((fuel or 0) / 1000000000).."GJ" else return math.floor((fuel or 0) / 1000000000).."GJ" end end function util.direction_to_vector (direction) if direction == defines.direction.east then return {x=1,y=0} end if direction == defines.direction.north then return {x=0,y=-1} end if direction == defines.direction.northeast then return {x=1,y=-1} end if direction == defines.direction.northwest then return {x=-1,y=-1} end if direction == defines.direction.south then return {x=0,y=1} end if direction == defines.direction.southeast then return {x=1,y=1} end if direction == defines.direction.southwest then return {x=-1,y=1} end if direction == defines.direction.west then return {x=-1,y=0} end end function util.sign(x) if x<0 then return -1 elseif x>0 then return 1 else return 0 end end function util.find_first_descendant_by_name(gui_element, name) for _, child in pairs(gui_element.children) do if child.name == name then return child end local found = util.find_first_descendant_by_name(child, name) if found then return found end end end function util.find_descendants_by_name(gui_element, name, all_found) local found = all_found or {} for _, child in pairs(gui_element.children)do if child.name == name then table.insert(found, child) end util.find_descendants_by_name(child, name, found) end return found end function util.find_damage_types_from_trigger_items(trigger_items) local damage_types = {} for _, trigger_item in pairs(trigger_items) do if trigger_item.action_delivery then for _, action_delivery in pairs(trigger_item.action_delivery) do if action_delivery.target_effects then --action_delivery.type == "instant" for _, target_effect in pairs(action_delivery.target_effects) do if target_effect.type == "damage" and target_effect.damage and target_effect.damage.type then damage_types[target_effect.damage.type] = true end end end --"instant", "projectile", "flame-thrower", "beam", "stream", "artillery". local beam_or_projectile if action_delivery.beam then beam_or_projectile = game.entity_prototypes[action_delivery.beam] end if action_delivery.projectile then beam_or_projectile = game.entity_prototypes[action_delivery.projectile] end if beam_or_projectile then if beam_or_projectile.attack_result then local damage_types_2 = util.find_damage_types_from_trigger_items(beam_or_projectile.attack_result) for damage_type, b in pairs(damage_types_2) do damage_types[damage_type] = true end end if beam_or_projectile.final_attack_result then local damage_types_2 = util.find_damage_types_from_trigger_items(beam_or_projectile.final_attack_result) for damage_type, b in pairs(damage_types_2) do damage_types[damage_type] = true end end end end end end return damage_types end function util.separate_points(positions, separation, max_iterations) positions = table.deepcopy(positions) --if true then return positions end if not max_iterations then max_iterations = 20 end local overshoot = 1.05 -- increse slightly to allow early finish local i = 1 local continue = true while i <= max_iterations and continue do i = i + 1 continue = false local forces = {} for a, pos_a in pairs(positions) do for b, pos_b in pairs(positions) do if a ~= b then local delta = Util.vectors_delta(pos_a, pos_b) local length = Util.vector_length(delta) if length < separation then local force = Util.vector_set_length(delta, (separation - length) / 2) if forces[a] then forces[a].x = forces[a].x + force.x forces[a].y = forces[a].y + force.y else forces[a] = force end end end end end for a, pos_a in pairs(positions) do local force = forces[a] if force then local length = Util.vector_length(force) if length > separation / 2 then force = Util.vector_set_length(force, separation / 2) end pos_a.x = pos_a.x - force.x * overshoot pos_a.y = pos_a.y - force.y * overshoot continue = true end end end return positions end function util.safe_destroy(entity) if not entity.valid then return end if entity.type == "linked-container" then entity.link_id = 0 entity.destroy() else entity.destroy() end end function util.HSVToRGB( hue, saturation, value ) --https://gist.github.com/GigsD4X/8513963 -- Returns the RGB equivalent of the given HSV-defined color -- (adapted from some code found around the web) -- If it's achromatic, just return the value if saturation == 0 then return value; end; -- Get the hue sector local hue_sector = math.floor( hue / 60 ); local hue_sector_offset = ( hue / 60 ) - hue_sector; local p = value * ( 1 - saturation ); local q = value * ( 1 - saturation * hue_sector_offset ); local t = value * ( 1 - saturation * ( 1 - hue_sector_offset ) ); if hue_sector == 0 then return value, t, p; elseif hue_sector == 1 then return q, value, p; elseif hue_sector == 2 then return p, value, t; elseif hue_sector == 3 then return p, q, value; elseif hue_sector == 4 then return t, p, value; elseif hue_sector == 5 then return value, p, q; end; end; return util