Files
2025-03-31 13:19:47 +02:00

440 lines
13 KiB
Lua

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 = '<Label[^>]-header="<<<STRINGSTART>>>[dD][eE][sS][cC][rR][iI][pP][tT][iI][oO][nN]<<<STRINGEND>>>"[^>]-body="([^"]*)"[^>]*/>'
local oldFormatPattern = '<Label[^>]-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('<Component.-/>') 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)