if SERVER then return end --prevents it from running on the server function blue_prints.clean_string(str) local cleaned = str -- Common control characters cleaned = cleaned:gsub("\r", "") -- Carriage Return cleaned = cleaned:gsub("\n", "") -- Line Feed cleaned = cleaned:gsub("\t", "") -- Tab cleaned = cleaned:gsub("\f", "") -- Form Feed cleaned = cleaned:gsub("\b", "") -- Backspace cleaned = cleaned:gsub("\v", "") -- Vertical Tab cleaned = cleaned:gsub("\a", "") -- Bell (Alert) cleaned = cleaned:gsub("\027", "") -- Escape cleaned = cleaned:gsub("\000", "") -- Null byte cleaned = cleaned:gsub("\x1A", "") -- EOF (Control-Z) cleaned = cleaned:gsub("%z", "") -- Additional null bytes cleaned = cleaned:gsub("%c", "") -- Any remaining control characters -- Remove Unicode zero-width characters cleaned = cleaned:gsub("\u{200B}", "") -- Zero-width space cleaned = cleaned:gsub("\u{200C}", "") -- Zero-width non-joiner cleaned = cleaned:gsub("\u{200D}", "") -- Zero-width joiner cleaned = cleaned:gsub("\u{FEFF}", "") -- Zero-width no-break space (BOM) return cleaned end function blue_prints.escapePercent(str) return str:gsub("%%", "%%%%") end function blue_prints.getScreenResolution() if Screen and Screen.Selected and Screen.Selected.Cam then return Screen.Selected.Cam.Resolution end return nil end function blue_prints.get_description_from_xml(xmlString) --remove all whitespace local function trim(s) return s:match("^%s*(.-)%s*$") end -- Define both patterns local newFormatPattern = ']-header="<<>>[dD][eE][sS][cC][rR][iI][pP][tT][iI][oO][nN]<<>>"[^>]-body="([^"]*)"[^>]*/>' local oldFormatPattern = ']-header="[dD][eE][sS][cC][rR][iI][pP][tT][iI][oO][nN]"[^>]-body="([^"]*)"[^>]*/>' -- Try matching new format first local body = xmlString:match(newFormatPattern) -- If not found, try old format if not body then body = xmlString:match(oldFormatPattern) end if not body then return nil end --The trim(body) call takes the description text we found and removes any whitespace (spaces, tabs, newlines) from the beginning and end of the text before returning it. return trim(body) end function blue_prints.get_component_count_from_xml(xmlString) local count = 0 for _ in xmlString:gmatch('') do count = count + 1 end return count end function blue_prints.get_xml_content_as_string_from_path(provided_path) -- Check if the filename already ends with .txt if not string.match(provided_path, "%.txt$") then -- Add .txt if it's not already present provided_path = provided_path .. ".txt" end local file_path = (blue_prints.save_path .. "/" .. provided_path) local xmlContent, err = blue_prints.readFile(file_path) if xmlContent then return xmlContent else print("file not found") print("saved designs:") blue_prints.print_all_saved_files() end end function blue_prints.print_requirements_of_circuit(provided_path) local xmlContent = blue_prints.get_xml_content_as_string_from_path(provided_path) if xmlContent then -- In the usage section: local inputs, outputs, components, wires, labels, inputNodePos, outputNodePos = blue_prints.parseXML(xmlContent) print("This circuit uses: ") local identifierCounts = {} -- Count occurrences for _, component in ipairs(components) do --local prefab = ItemPrefab.GetItemPrefab(component.item) local identifier = component.item identifierCounts[identifier] = (identifierCounts[identifier] or 0) + 1 end -- Print the counts for identifier, count in pairs(identifierCounts) do print(identifier .. ": " .. count) end end end function blue_prints.check_what_is_needed_for_blueprint(provided_path) local xmlContent = blue_prints.get_xml_content_as_string_from_path(provided_path) if xmlContent then -- In the usage section: local inputs, outputs, components, wires, labels, inputNodePos, outputNodePos = blue_prints.parseXML(xmlContent) -- Check inventory for required components local missing_components = blue_prints.check_inventory_for_requirements(components) local all_needed_items_are_present = true for _, count in pairs(missing_components) do if count > 0 then all_needed_items_are_present = false break end end if all_needed_items_are_present then print("All required components are present!") else print("You are missing: ") for name, count in pairs(missing_components) do if count > 0 then print(name .. ": " .. count) end end print("You can also use an equivalent amount of FPGAs") end end end function blue_prints.get_components_currently_in_circuitbox(passed_circuitbox) local components = passed_circuitbox.GetComponentString("CircuitBox").Components local resourceCounts = {} for i, component in pairs(components) do --print(tostring(component.UsedResource.Identifier)) resourceCounts[tostring(component.UsedResource.Identifier)] = (resourceCounts[tostring(component.UsedResource.Identifier)] or 0) + 1 end return resourceCounts end function blue_prints.getFurthestRightElement(components, labels, inputNodePos, outputNodePos) local furthestRight = {x = -math.huge, y = 0, element = nil, type = nil} local offset_adjustment = 256 -- Check components for _, component in ipairs(components) do if component.position.x + offset_adjustment > furthestRight.x then furthestRight.x = component.position.x + offset_adjustment furthestRight.y = component.position.y furthestRight.element = component furthestRight.type = "component" end end -- Check labels for _, label in ipairs(labels) do --print(label.header, label.size.width) local rightEdge = label.position.x + (label.size.width / 2) if rightEdge > furthestRight.x then furthestRight.x = rightEdge furthestRight.y = label.position.y furthestRight.element = label furthestRight.type = "label" end end -- Check input node if inputNodePos and inputNodePos.x + offset_adjustment > furthestRight.x then --input and output nodes dont check width furthestRight.x = inputNodePos.x + offset_adjustment furthestRight.y = inputNodePos.y furthestRight.element = inputNodePos furthestRight.type = "inputNode" end -- Check output node if outputNodePos and outputNodePos.x + offset_adjustment > furthestRight.x then furthestRight.x = outputNodePos.x + offset_adjustment furthestRight.y = outputNodePos.y furthestRight.element = outputNodePos furthestRight.type = "outputNode" end return furthestRight end function blue_prints.print_all_saved_files() local saved_files = blue_prints.getFiles(blue_prints.save_path) for name, value in pairs(saved_files) do if string.match(value, "%.txt$") then local filename = value:match("([^/\\]+)$") -- Match after last slash or backslash local filename = string.gsub(filename, "%.txt$", "") --cut out the .txt at the end local xml_of_file = blue_prints.readFileContents(value) local description_of_file = blue_prints.get_description_from_xml(xml_of_file) local number_of_components_in_file = blue_prints.get_component_count_from_xml(xml_of_file) print("-------------") local print_string = '‖color:white‖' .. filename .. '‖end‖' .. " - (" .. number_of_components_in_file .. " fpgas) " if description_of_file ~= nil then print_string = print_string .. " - " .. '‖color:yellow‖' .. description_of_file .. '‖end‖' end print(print_string) end end end function blue_prints.readFile(path) local file = io.open(path, "r") if not file then return nil, "Failed to open file: " .. path end local content = file:read("*all") file:close() return content end function blue_prints.isInteger(str) return str and not (str == "" or str:find("%D")) end function blue_prints.isFloat(str) local n = tonumber(str) return n ~= nil and math.floor(n) ~= n end function blue_prints.removeKeyFromTable(tbl, keyToRemove) local newTable = {} for k, v in pairs(tbl) do if k ~= keyToRemove then newTable[k] = v end end return newTable end function blue_prints.hexToRGBA(hex) -- Remove the '#' if present hex = hex:gsub("#", "") -- Check if it's a valid hex color if #hex ~= 6 and #hex ~= 8 then return nil, "Invalid hex color format" end -- Convert hex to decimal local r = tonumber(hex:sub(1, 2), 16) local g = tonumber(hex:sub(3, 4), 16) local b = tonumber(hex:sub(5, 6), 16) local a = 255 -- If alpha channel is provided if #hex == 8 then a = tonumber(hex:sub(7, 8), 16) end -- Return Color object return Color(r, g, b, a) end function blue_prints.getNthValue(tbl, n) local count = 0 for key, value in pairs(tbl) do count = count + 1 if count == n then return value end end return nil -- Return nil if there are fewer than n items end function blue_prints.string_to_bool(passed_string) -- Convert to lower case for case-insensitive comparison local lower_string = string.lower(passed_string) -- Check for common true values if lower_string == "true" or lower_string == "1" then return true elseif lower_string == "false" or lower_string == "0" then return false else -- Handle unexpected cases (could also return nil or error) error("Invalid string for boolean conversion: " .. passed_string) end end function blue_prints.get_circuit_box_lock_status() -- First verify we have a selected circuitbox if blue_prints.most_recent_circuitbox == nil then print("No circuitbox detected") return false end -- Get the CircuitBox component local circuit_box = blue_prints.most_recent_circuitbox.GetComponentString("CircuitBox") if circuit_box == nil then print("Could not find CircuitBox component") return false end -- Return true if either permanently or temporarily locked return circuit_box.Locked or circuit_box.TemporarilyLocked end -- Usage example: -- local is_locked = blue_prints.get_circuit_box_lock_status() function blue_prints.count_circuit_elements_in_box() if blue_prints.most_recent_circuitbox == nil then print("no circuitbox detected") return 0, 0, 0 end local circuit_box = blue_prints.most_recent_circuitbox.GetComponentString("CircuitBox") -- Initialize counts to 0 local num_components = 0 local num_labels = 0 local num_wires = 0 -- Only count if the collections exist if circuit_box.Components then num_components = #circuit_box.Components end if circuit_box.Labels then num_labels = #circuit_box.Labels end if circuit_box.Wires then num_wires = #circuit_box.Wires end return num_components, num_labels, num_wires end function blue_prints.calculate_clear_delay() -- Get current counts of all elements local num_components, num_labels, num_wires = blue_prints.count_circuit_elements() -- Calculate individual delays for each type local component_delay = 0 local label_delay = 0 local wire_delay = 0 -- Only calculate delays if we have elements to clear if num_components > 0 then component_delay = math.ceil(num_components / blue_prints.component_batch_size) * blue_prints.time_delay_between_loops end if num_labels > 0 then label_delay = math.ceil(num_labels / blue_prints.component_batch_size) * blue_prints.time_delay_between_loops end if num_wires > 0 then wire_delay = math.ceil(num_wires / blue_prints.component_batch_size) * blue_prints.time_delay_between_loops end -- Get minimum delay and add buffer time --wire delay doesnt matter because wires are automatically removed when comps are removed --there is some extra buffer in there to account for wires directly from input to output local time_delay = component_delay + label_delay + 500 return time_delay end --save a reference to the most recently interacted circuit box Hook.Patch("Barotrauma.Items.Components.CircuitBox", "AddToGUIUpdateList", function(instance, ptable) blue_prints.most_recent_circuitbox = instance.Item end, Hook.HookMethodType.After)