Add other mods
This commit is contained in:
47
Blueprints/Lua/Autorun/init.lua
Normal file
47
Blueprints/Lua/Autorun/init.lua
Normal file
@@ -0,0 +1,47 @@
|
||||
--this is the entry point for the code. This runs all other scripts.
|
||||
|
||||
--get the local path and save it as a global. only autorun files can get the path in this way!
|
||||
blue_prints = {}
|
||||
blue_prints.path = ...
|
||||
-- Always use forward slashes internally, both Windows and Linux can handle this
|
||||
blue_prints.path = blue_prints.path and blue_prints.path:gsub("\\", "/") or ""
|
||||
|
||||
-- Set up save path - will be normalized in read_write.lua functions
|
||||
blue_prints.save_path = "LocalMods/Blueprints_saved_blueprints"
|
||||
|
||||
blue_prints.most_recent_circuitbox = nil
|
||||
blue_prints.time_delay_between_loops = 150
|
||||
blue_prints.component_batch_size = 10
|
||||
blue_prints.current_gui_page = nil
|
||||
blue_prints.most_recently_used_blueprint_name = nil
|
||||
blue_prints.most_recent_folder = "[Root Directory]" -- Default to root directory
|
||||
blue_prints.unit_tests_enabled = false
|
||||
|
||||
dofile(blue_prints.path .. "/Lua/gui/cs_required_warning.lua")
|
||||
|
||||
if CSActive then --CSActive is if csharp scripts are enabled. This mod requires them.
|
||||
--setup
|
||||
dofile(blue_prints.path .. "/Lua/register_types.lua")
|
||||
dofile(blue_prints.path .. "/Lua/utilities/read_write.lua") -- Load read_write before first_time_setup
|
||||
dofile(blue_prints.path .. "/Lua/first_time_setup.lua")
|
||||
|
||||
--utilities
|
||||
dofile(blue_prints.path .. "/Lua/utilities/utilities.lua")
|
||||
dofile(blue_prints.path .. "/Lua/utilities/safety_checks.lua")
|
||||
|
||||
--core logic
|
||||
dofile(blue_prints.path .. "/Lua/save_blueprint.lua")
|
||||
dofile(blue_prints.path .. "/Lua/load_blueprint.lua")
|
||||
dofile(blue_prints.path .. "/Lua/delete_blueprint.lua")
|
||||
dofile(blue_prints.path .. "/Lua/commands.lua")
|
||||
dofile(blue_prints.path .. "/Lua/unit_tests.lua")
|
||||
|
||||
--gui
|
||||
dofile(blue_prints.path .. "/Lua/gui/gui_buttons_frame.lua")
|
||||
dofile(blue_prints.path .. "/Lua/gui/load_gui.lua")
|
||||
dofile(blue_prints.path .. "/Lua/gui/save_gui.lua")
|
||||
dofile(blue_prints.path .. "/Lua/gui/clear_gui.lua")
|
||||
dofile(blue_prints.path .. "/Lua/gui/delay_slider.lua")
|
||||
dofile(blue_prints.path .. "/Lua/gui/popup_gui.lua")
|
||||
--dofile(blue_prints.path .. "/Lua/gui/custom_gui_example.lua")
|
||||
end
|
115
Blueprints/Lua/commands.lua
Normal file
115
Blueprints/Lua/commands.lua
Normal file
@@ -0,0 +1,115 @@
|
||||
if SERVER then return end --prevents it from running on the server
|
||||
|
||||
|
||||
local configDescriptions = {}
|
||||
configDescriptions["commands"] = "you can use blueprints or bp"
|
||||
configDescriptions["load"] = "load a blueprint. EX: bp load reactor_controller"
|
||||
configDescriptions["save"] = "save a blueprint. EX: bp save reactor_controller"
|
||||
configDescriptions["need"] = "get requirements for a blueprint. EX: bp need reactor_controller"
|
||||
configDescriptions["delete"] = "delete a blueprint. EX: bp delete reactor_controller"
|
||||
configDescriptions["list"] = "list all saved files. EX: bp list"
|
||||
configDescriptions["toggle"] = "toggle things on and off. EX: bp toggle tests"
|
||||
configDescriptions["clear"] = "Remove all components and labels from a circuitbox. EX: bp clear"
|
||||
|
||||
|
||||
|
||||
local function checkStringAgainstTags(targetString, tags) --this is needed to run the command line args
|
||||
for tag, _ in pairs(tags) do
|
||||
if targetString == tag then
|
||||
return true -- Match found
|
||||
end
|
||||
end
|
||||
return false -- No match found
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
local function runCommand(command)
|
||||
if command[1] == nil or command[1] == "help" or command[1] == "commands" then
|
||||
for key, value in pairs(configDescriptions) do
|
||||
print(key .. ": " .. value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if command[1] == "load" then
|
||||
if command[2] ~= nil then
|
||||
print("Attempting to build blueprint")
|
||||
blue_prints.construct_blueprint(command[2])
|
||||
else
|
||||
print("No filename given. EX: bp load file_name.txt")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if command[1] == "save" then
|
||||
if command[2] ~= nil then
|
||||
print("Attempting to save blueprint")
|
||||
blue_prints.save_blueprint(command[2])
|
||||
else
|
||||
print("No filename given. EX: bp save file_name.txt")
|
||||
end
|
||||
end
|
||||
|
||||
if command[1] == "need" then
|
||||
if command[2] ~= nil then
|
||||
print("Attempting to get blueprint requirements")
|
||||
blue_prints.print_requirements_of_circuit(command[2])
|
||||
blue_prints.check_what_is_needed_for_blueprint(command[2])
|
||||
else
|
||||
print("No filename given. EX: bp need file_name.txt")
|
||||
end
|
||||
end
|
||||
|
||||
if command[1] == "delete" then
|
||||
if command[2] ~= nil then
|
||||
print("Attempting to delete blueprint")
|
||||
blue_prints.delete_blueprint(command[2])
|
||||
else
|
||||
print("No filename given. EX: bp delete file_name.txt")
|
||||
end
|
||||
end
|
||||
|
||||
if command[1] == "clear" then
|
||||
blue_prints.clear_circuitbox()
|
||||
end
|
||||
|
||||
if command[1] == "list" then
|
||||
blue_prints.print_all_saved_files()
|
||||
end
|
||||
|
||||
if command[1] == "unit_tests" then
|
||||
blue_prints.unit_tests_enabled = true
|
||||
blue_prints.unit_test_all_blueprint_files()
|
||||
end
|
||||
|
||||
if command[1] == "toggle" then
|
||||
if command[2] == "tests" then
|
||||
blue_prints.unit_tests_enabled = not blue_prints.unit_tests_enabled
|
||||
print("tests enabled: " .. tostring(blue_prints.unit_tests_enabled))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if checkStringAgainstTags(command[1], configDescriptions) then
|
||||
--print("Match found!")
|
||||
else
|
||||
print("Command not recognized. type bp to see available commands.")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Game.AddCommand("blueprints", "configures blueprints", function (command)
|
||||
runCommand(command)
|
||||
end)
|
||||
|
||||
|
||||
Game.AddCommand("bp", "configures blueprints abbreviated", function (command)
|
||||
runCommand(command)
|
||||
end)
|
||||
|
||||
|
||||
|
||||
|
||||
|
31
Blueprints/Lua/delete_blueprint.lua
Normal file
31
Blueprints/Lua/delete_blueprint.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
if SERVER then return end --prevents it from running on the server
|
||||
|
||||
function blue_prints.delete_blueprint(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.normalizePath(blue_prints.save_path .. "/" .. provided_path)
|
||||
|
||||
if File.Exists(file_path) then
|
||||
local success = blue_prints.safeFileOperation(os.remove, file_path)
|
||||
if success then
|
||||
print("File deleted successfully.")
|
||||
else
|
||||
-- Try alternate path if first attempt fails
|
||||
local alt_path = file_path:gsub("LocalMods/", "local_mods/")
|
||||
success = blue_prints.safeFileOperation(os.remove, alt_path)
|
||||
if success then
|
||||
print("File deleted successfully.")
|
||||
else
|
||||
print("Error deleting file")
|
||||
end
|
||||
end
|
||||
else
|
||||
print("file not found")
|
||||
print("saved designs:")
|
||||
blue_prints.print_all_saved_files()
|
||||
end
|
||||
end
|
81
Blueprints/Lua/first_time_setup.lua
Normal file
81
Blueprints/Lua/first_time_setup.lua
Normal file
@@ -0,0 +1,81 @@
|
||||
if SERVER then return end --prevents it from running on the server
|
||||
|
||||
-- Function to write text to a file
|
||||
local function writeFile(path, text)
|
||||
return blue_prints.writeFile(path, text)
|
||||
end
|
||||
|
||||
-- Recursively copy a directory structure
|
||||
local function copy_directory_structure(source, destination)
|
||||
-- Normalize paths
|
||||
source = blue_prints.normalizePath(source)
|
||||
destination = blue_prints.normalizePath(destination)
|
||||
|
||||
-- Ensure destination exists
|
||||
if not blue_prints.createFolder(destination) then
|
||||
print("Failed to create destination directory: " .. destination)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Get all files and directories using our safe functions
|
||||
local files = blue_prints.getFiles(source)
|
||||
local directories = blue_prints.getDirectories(source)
|
||||
|
||||
-- Copy files
|
||||
for _, filepath in pairs(files) do
|
||||
if string.match(filepath, "%.txt$") then
|
||||
local filename = filepath:match("([^/\\]+)$")
|
||||
local file_content = blue_prints.readFileContents(filepath)
|
||||
if file_content then
|
||||
local dest_path = blue_prints.normalizePath(destination .. "/" .. filename)
|
||||
if not File.Exists(dest_path) then
|
||||
writeFile(dest_path, file_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Recursively copy subdirectories
|
||||
for _, dir in pairs(directories) do
|
||||
local dir_name = dir:match("([^/\\]+)$")
|
||||
if dir_name then
|
||||
local source_subdir = blue_prints.normalizePath(source .. "/" .. dir_name)
|
||||
local dest_subdir = blue_prints.normalizePath(destination .. "/" .. dir_name)
|
||||
copy_directory_structure(source_subdir, dest_subdir)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Create base directory first
|
||||
if not blue_prints.ensureBaseDirectory() then
|
||||
print("Failed to create blueprint directory")
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Check if this is first run by looking for existing content
|
||||
local existing_files = blue_prints.getFiles(blue_prints.save_path)
|
||||
local existing_dirs = blue_prints.getDirectories(blue_prints.save_path)
|
||||
local is_first_run = #existing_files == 0 and #existing_dirs == 0
|
||||
|
||||
if is_first_run then
|
||||
-- Look for starter_blueprints directory
|
||||
local starter_blueprints_path = blue_prints.normalizePath(blue_prints.path .. "/starter_blueprints")
|
||||
if File.DirectoryExists(starter_blueprints_path) then
|
||||
-- Copy the entire directory structure
|
||||
if copy_directory_structure(starter_blueprints_path, blue_prints.save_path) then
|
||||
print("Successfully copied starter blueprints")
|
||||
else
|
||||
print("Failed to copy starter blueprints")
|
||||
end
|
||||
else
|
||||
print("No starter_blueprints directory found at: " .. starter_blueprints_path)
|
||||
-- Create default folders if no starter blueprints exist
|
||||
local defaultFolders = {"General", "Reactor", "Navigation", "Weapons", "Medical"}
|
||||
for _, folder in ipairs(defaultFolders) do
|
||||
local folderPath = blue_prints.normalizePath(blue_prints.save_path .. "/" .. folder)
|
||||
blue_prints.createFolder(folderPath)
|
||||
end
|
||||
end
|
||||
end
|
54
Blueprints/Lua/gui/clear_gui.lua
Normal file
54
Blueprints/Lua/gui/clear_gui.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
if SERVER then return end -- we don't want server to run GUI code.
|
||||
|
||||
local resolution = blue_prints.getScreenResolution()
|
||||
local run_once_at_start = false
|
||||
|
||||
local function check_and_rebuild_frame()
|
||||
local new_resolution = blue_prints.getScreenResolution()
|
||||
if new_resolution ~= resolution or run_once_at_start == false then
|
||||
|
||||
local spacer = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.04), blue_prints.gui_button_frame_list.Content.RectTransform), "", nil, nil, GUI.Alignment.Center)
|
||||
|
||||
local button = GUI.Button(GUI.RectTransform(Vector2(1, 0.1), blue_prints.gui_button_frame_list.Content.RectTransform), "Clear Circuitbox", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
button.OnClicked = function ()
|
||||
|
||||
local message_box = GUI.MessageBox('Are you sure you want to clear the box?', 'This will remove all components, labels and wires.', {'Cancel', 'Clear Box'})
|
||||
|
||||
local cancel_button = nil
|
||||
local clear_button = nil
|
||||
|
||||
if message_box.Buttons[0] == nil then --this is if no one has registered it. If some other mod registers it I dont want it to break.
|
||||
cancel_button = message_box.Buttons[1]
|
||||
clear_button = message_box.Buttons[2]
|
||||
else --if its been registered, it will behave as a csharp table
|
||||
cancel_button = message_box.Buttons[0]
|
||||
clear_button = message_box.Buttons[1]
|
||||
end
|
||||
|
||||
clear_button.Color = Color(160, 160, 255) -- Base color (more blue)
|
||||
clear_button.HoverColor = Color(190, 190, 255) -- Lighter blue when hovering
|
||||
|
||||
cancel_button.OnClicked = function ()
|
||||
message_box.Close()
|
||||
end
|
||||
|
||||
clear_button.OnClicked = function ()
|
||||
blue_prints.clear_circuitbox()
|
||||
GUI.AddMessage('Circuitbox Cleared', Color.White)
|
||||
message_box.Close()
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
resolution = new_resolution
|
||||
run_once_at_start = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Hook.Patch("Barotrauma.Items.Components.CircuitBox", "AddToGUIUpdateList", function()
|
||||
check_and_rebuild_frame()
|
||||
end, Hook.HookMethodType.After)
|
43
Blueprints/Lua/gui/cs_required_warning.lua
Normal file
43
Blueprints/Lua/gui/cs_required_warning.lua
Normal file
@@ -0,0 +1,43 @@
|
||||
if SERVER then return end -- we don't want server to run GUI code.
|
||||
|
||||
if CSActive then return end -- dont show the warning if CS is on
|
||||
|
||||
-- our main frame where we will put our custom GUI
|
||||
local frame = GUI.Frame(GUI.RectTransform(Vector2(1, 1)), nil)
|
||||
frame.CanBeFocused = false
|
||||
|
||||
-- popup frame
|
||||
local popup = GUI.Frame(GUI.RectTransform(Vector2(1, 1), frame.RectTransform, GUI.Anchor.Center), nil)
|
||||
popup.CanBeFocused = false
|
||||
popup.Visible = true
|
||||
|
||||
local popupContent = GUI.Frame(GUI.RectTransform(Vector2(0.4, 0.6), popup.RectTransform, GUI.Anchor.Center))
|
||||
local popupList = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), popupContent.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.15), popupList.Content.RectTransform), "WARNING", nil, nil, GUI.Alignment.Center)
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), popupList.Content.RectTransform), "You are using Blueprints without enabling csharp scripting.", nil, nil, GUI.Alignment.Center)
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), popupList.Content.RectTransform), 'Go to the main menu. (which has singleplayer, multiplayer, etc)', nil, nil, GUI.Alignment.Center)
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), popupList.Content.RectTransform), 'In the main menu, click the "Open LuaCs Settings" button in the top left.', nil, nil, GUI.Alignment.Center)
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), popupList.Content.RectTransform), 'Then hit the "enable csharp scripting" check box.', nil, nil, GUI.Alignment.Center)
|
||||
local coloredText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.10), popupList.Content.RectTransform), "Blueprints will not function without this.", nil, nil, GUI.Alignment.Center)
|
||||
coloredText.TextColor = Color(255, 0, 0) --red
|
||||
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.10), popupList.Content.RectTransform), '', nil, nil, GUI.Alignment.Center)
|
||||
|
||||
local closeButton = GUI.Button(GUI.RectTransform(Vector2(1, 0.1), popupList.Content.RectTransform), "Close", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
closeButton.OnClicked = function ()
|
||||
popup.Visible = not popup.Visible
|
||||
end
|
||||
|
||||
|
||||
Hook.Patch("Barotrauma.GameScreen", "AddToGUIUpdateList", function()
|
||||
frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
||||
|
||||
Hook.Patch("Barotrauma.NetLobbyScreen", "AddToGUIUpdateList", function(self, ptable)
|
||||
frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
||||
|
||||
Hook.Patch("Barotrauma.SubEditorScreen", "AddToGUIUpdateList", function()
|
||||
frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
108
Blueprints/Lua/gui/custom_gui_example.lua
Normal file
108
Blueprints/Lua/gui/custom_gui_example.lua
Normal file
@@ -0,0 +1,108 @@
|
||||
--[[
|
||||
This example shows how to create a basic custom GUI. The GUI will appear top right of your in game screen.
|
||||
--]]
|
||||
|
||||
if SERVER then return end -- we don't want server to run GUI code.
|
||||
|
||||
local modPath = ...
|
||||
|
||||
-- our main frame where we will put our custom GUI
|
||||
local frame = GUI.Frame(GUI.RectTransform(Vector2(1, 1)), nil)
|
||||
frame.CanBeFocused = false
|
||||
|
||||
-- menu frame
|
||||
local menu = GUI.Frame(GUI.RectTransform(Vector2(1, 1), frame.RectTransform, GUI.Anchor.Center), nil)
|
||||
menu.CanBeFocused = false
|
||||
menu.Visible = false
|
||||
|
||||
-- put a button that goes behind the menu content, so we can close it when we click outside
|
||||
local closeButton = GUI.Button(GUI.RectTransform(Vector2(1, 1), menu.RectTransform, GUI.Anchor.Center), "", GUI.Alignment.Center, nil)
|
||||
closeButton.OnClicked = function ()
|
||||
menu.Visible = not menu.Visible
|
||||
end
|
||||
|
||||
-- a button top right of our screen to open a sub-frame menu
|
||||
local button = GUI.Button(GUI.RectTransform(Vector2(0.2, 0.2), frame.RectTransform, GUI.Anchor.TopRight), "Custom GUI Example", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
button.RectTransform.AbsoluteOffset = Point(25, 200)
|
||||
button.OnClicked = function ()
|
||||
menu.Visible = not menu.Visible
|
||||
end
|
||||
|
||||
local menuContent = GUI.Frame(GUI.RectTransform(Vector2(0.4, 0.6), menu.RectTransform, GUI.Anchor.Center))
|
||||
local menuList = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), menuContent.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform), "This is a sample text!", nil, nil, GUI.Alignment.Center)
|
||||
|
||||
for i = 1, 10, 1 do
|
||||
local coloredText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.025), menuList.Content.RectTransform), "This is some colored text!", nil, nil, GUI.Alignment.Center)
|
||||
coloredText.TextColor = Color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
|
||||
end
|
||||
|
||||
local textBox = GUI.TextBox(GUI.RectTransform(Vector2(1, 0.2), menuList.Content.RectTransform), "This is a text box")
|
||||
textBox.OnTextChangedDelegate = function (textBox)
|
||||
print(textBox.Text)
|
||||
end
|
||||
|
||||
local tickBox = GUI.TickBox(GUI.RectTransform(Vector2(1, 0.2), menuList.Content.RectTransform), "This is a tick box")
|
||||
tickBox.Selected = true
|
||||
tickBox.OnSelected = function ()
|
||||
print(tickBox.Selected)
|
||||
end
|
||||
|
||||
local numberInput = GUI.NumberInput(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform), NumberType.Float)
|
||||
numberInput.MinValueFloat = 0
|
||||
numberInput.MaxValueFloat = 1000
|
||||
numberInput.valueStep = 1
|
||||
numberInput.OnValueChanged = function ()
|
||||
print(numberInput.FloatValue)
|
||||
end
|
||||
|
||||
local scrollBar = GUI.ScrollBar(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform), 0.1, nil, "GUISlider")
|
||||
scrollBar.Range = Vector2(0, 100)
|
||||
scrollBar.BarScrollValue = 50
|
||||
scrollBar.OnMoved = function ()
|
||||
print(scrollBar.BarScrollValue)
|
||||
end
|
||||
|
||||
local someButton = GUI.Button(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform), "This is a button", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
someButton.OnClicked = function ()
|
||||
print("button")
|
||||
end
|
||||
|
||||
local dropDown = GUI.DropDown(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform), "This is a dropdown", 3, nil, false)
|
||||
dropDown.AddItem("First Item", 0)
|
||||
dropDown.AddItem("Second Item", 1)
|
||||
dropDown.AddItem("Third Item", 2)
|
||||
dropDown.OnSelected = function (guiComponent, object)
|
||||
print(object)
|
||||
end
|
||||
|
||||
local multiDropDown = GUI.DropDown(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform), "This is a multi-dropdown", 3, nil, true)
|
||||
multiDropDown.AddItem("First Item", 0)
|
||||
multiDropDown.AddItem("Second Item", 1)
|
||||
multiDropDown.AddItem("Third Item", 2)
|
||||
multiDropDown.OnSelected = function (guiComponent, object)
|
||||
for value in multiDropDown.SelectedDataMultiple do
|
||||
print(value)
|
||||
end
|
||||
end
|
||||
|
||||
local imageFrame = GUI.Frame(GUI.RectTransform(Point(65, 65), menuList.Content.RectTransform), "GUITextBox")
|
||||
imageFrame.RectTransform.MinSize = Point(0, 65)
|
||||
local sprite = ItemPrefab.GetItemPrefab("bandage").InventoryIcon
|
||||
local image = GUI.Image(GUI.RectTransform(Vector2(1, 1), imageFrame.RectTransform, GUI.Anchor.Center), sprite)
|
||||
image.ToolTip = "Bandages are pretty cool"
|
||||
|
||||
|
||||
local customImageFrame = GUI.Frame(GUI.RectTransform(Point(128, 128), menuList.Content.RectTransform), "GUITextBox")
|
||||
customImageFrame.RectTransform.MinSize = Point(138, 138)
|
||||
--local customSprite = Sprite(modPath .. "/luasmall.png")
|
||||
GUI.Image(GUI.RectTransform(Point(65, 65), customImageFrame.RectTransform, GUI.Anchor.Center), customSprite)
|
||||
|
||||
Hook.Patch("Barotrauma.GameScreen", "AddToGUIUpdateList", function()
|
||||
frame.AddToGUIUpdateList()
|
||||
end)
|
||||
|
||||
Hook.Patch("Barotrauma.SubEditorScreen", "AddToGUIUpdateList", function()
|
||||
frame.AddToGUIUpdateList()
|
||||
end)
|
37
Blueprints/Lua/gui/delay_slider.lua
Normal file
37
Blueprints/Lua/gui/delay_slider.lua
Normal file
@@ -0,0 +1,37 @@
|
||||
if SERVER then return end
|
||||
|
||||
local resolution = blue_prints.getScreenResolution()
|
||||
local run_once_at_start = false
|
||||
|
||||
local function check_and_rebuild_frame()
|
||||
local new_resolution = blue_prints.getScreenResolution()
|
||||
if new_resolution ~= resolution or run_once_at_start == false then
|
||||
|
||||
local spacer = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.08), blue_prints.gui_button_frame_list.Content.RectTransform), "", nil, nil, GUI.Alignment.Center)
|
||||
|
||||
-- Create the label
|
||||
local label = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.08), blue_prints.gui_button_frame_list.Content.RectTransform), "Load/Clear Delay", nil, nil, GUI.Alignment.Center)
|
||||
|
||||
local scrollBar = GUI.ScrollBar(GUI.RectTransform(Vector2(1, 0.1), blue_prints.gui_button_frame_list.Content.RectTransform), 0.1, nil, "GUISlider")
|
||||
|
||||
scrollBar.Range = Vector2(150, 1000)
|
||||
|
||||
if run_once_at_start == false then
|
||||
scrollBar.BarScrollValue = blue_prints.time_delay_between_loops
|
||||
run_once_at_start = true
|
||||
end
|
||||
|
||||
scrollBar.OnMoved = function ()
|
||||
local truncatedValue = math.floor(scrollBar.BarScrollValue) -- Truncate to nearest integer
|
||||
scrollBar.ToolTip = "Delay for loading. Increase on laggier servers. Current Value: " .. truncatedValue .. "ms"
|
||||
--print(truncatedValue)
|
||||
blue_prints.time_delay_between_loops = truncatedValue
|
||||
end
|
||||
|
||||
resolution = new_resolution
|
||||
end
|
||||
end
|
||||
|
||||
Hook.Patch("Barotrauma.Items.Components.CircuitBox", "AddToGUIUpdateList", function()
|
||||
check_and_rebuild_frame()
|
||||
end, Hook.HookMethodType.After)
|
33
Blueprints/Lua/gui/gui_buttons_frame.lua
Normal file
33
Blueprints/Lua/gui/gui_buttons_frame.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
if SERVER then return end -- we don't want server to run GUI code.
|
||||
|
||||
local resolution = blue_prints.getScreenResolution()
|
||||
local run_once_at_start = false
|
||||
|
||||
--gui frame to hold the UI buttons that open the various GUIs
|
||||
blue_prints.gui_button_frame = GUI.Frame(GUI.RectTransform(Vector2(1, 1)), nil)
|
||||
blue_prints.gui_button_frame.CanBeFocused = false
|
||||
blue_prints.gui_button_frame.Visible = true
|
||||
|
||||
--this is needed to handle different resolutions
|
||||
local function check_and_rebuild_frame()
|
||||
local new_resolution = blue_prints.getScreenResolution()
|
||||
if new_resolution ~= resolution or run_once_at_start == false then
|
||||
blue_prints.gui_button_frame = GUI.Frame(GUI.RectTransform(Vector2(1, 1)), nil)
|
||||
blue_prints.gui_button_frame.CanBeFocused = false
|
||||
|
||||
blue_prints.gui_button_frame_content = GUI.Frame(GUI.RectTransform(Vector2(0.1, 0.15), blue_prints.gui_button_frame.RectTransform, GUI.Anchor.CenterRight))
|
||||
blue_prints.gui_button_frame_list = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), blue_prints.gui_button_frame_content.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
local spacer = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.04), blue_prints.gui_button_frame_list.Content.RectTransform), "", nil, nil, GUI.Alignment.Center)
|
||||
local title = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.08), blue_prints.gui_button_frame_list.Content.RectTransform), "Blueprints", nil, nil, GUI.Alignment.Center)
|
||||
title.TextColor = Color(180, 180, 255)
|
||||
|
||||
resolution = new_resolution
|
||||
run_once_at_start = true
|
||||
end
|
||||
end
|
||||
|
||||
Hook.Patch("Barotrauma.Items.Components.CircuitBox", "AddToGUIUpdateList", function()
|
||||
check_and_rebuild_frame()
|
||||
blue_prints.gui_button_frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
309
Blueprints/Lua/gui/load_gui.lua
Normal file
309
Blueprints/Lua/gui/load_gui.lua
Normal file
@@ -0,0 +1,309 @@
|
||||
if SERVER then return end
|
||||
|
||||
local resolution = blue_prints.getScreenResolution()
|
||||
local run_once_at_start = false
|
||||
local folder_states = {} -- Track collapsed state of folders
|
||||
local button_height = 45
|
||||
|
||||
-- Function to move blueprints from a folder to root
|
||||
local function moveFilesToRoot(folderPath)
|
||||
if folderPath == "[Root Directory]" then return true end
|
||||
|
||||
local fullFolderPath = blue_prints.normalizePath(blue_prints.save_path .. "/" .. folderPath)
|
||||
local rootPath = blue_prints.normalizePath(blue_prints.save_path)
|
||||
local files = File.GetFiles(fullFolderPath)
|
||||
local success = true
|
||||
|
||||
if files then
|
||||
for _, filepath in ipairs(files) do
|
||||
if string.match(filepath, "%.txt$") then
|
||||
-- Read file content
|
||||
local content = blue_prints.readFileContents(filepath)
|
||||
if content then
|
||||
-- Get just the filename
|
||||
local filename = filepath:match("([^/\\]+)$")
|
||||
-- Create new path in root
|
||||
local newPath = blue_prints.normalizePath(rootPath .. "/" .. filename)
|
||||
-- Write to new location
|
||||
if not blue_prints.writeFile(newPath, content) then
|
||||
success = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
-- Function to delete a folder and its contents
|
||||
local function deleteFolder(folderPath)
|
||||
if folderPath == "[Root Directory]" then return false end
|
||||
|
||||
local fullFolderPath = blue_prints.normalizePath(blue_prints.save_path .. "/" .. folderPath)
|
||||
|
||||
-- Move all files to root first
|
||||
if not moveFilesToRoot(folderPath) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Try to delete the folder
|
||||
local success = pcall(function()
|
||||
File.DeleteDirectory(fullFolderPath)
|
||||
end)
|
||||
|
||||
-- If first attempt fails, try alternate path
|
||||
if not success then
|
||||
local altPath = fullFolderPath:gsub("LocalMods/", "local_mods/")
|
||||
success = pcall(function()
|
||||
File.DeleteDirectory(altPath)
|
||||
end)
|
||||
end
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
local function count_blueprints_in_folder(folderPath)
|
||||
local files = File.GetFiles(folderPath)
|
||||
local count = 0
|
||||
if files then
|
||||
for _, filepath in ipairs(files) do
|
||||
if string.match(filepath, "%.txt$") then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
local function formatFolderHeaderText(folderName, isExpanded, blueprintCount)
|
||||
return string.format("%s %s (%d blueprints)",
|
||||
isExpanded and "▼" or "▶",
|
||||
folderName,
|
||||
blueprintCount)
|
||||
end
|
||||
|
||||
local function generate_load_gui()
|
||||
blue_prints.current_gui_page = GUI.Frame(GUI.RectTransform(Vector2(1, 1), blue_prints.gui_button_frame.RectTransform, GUI.Anchor.Center),
|
||||
nil)
|
||||
blue_prints.current_gui_page.CanBeFocused = false
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
|
||||
-- Background close button
|
||||
local closeButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(1, 1), blue_prints.current_gui_page.RectTransform, GUI.Anchor.Center), "",
|
||||
GUI.Alignment.Center, nil)
|
||||
closeButton.OnClicked = function()
|
||||
blue_prints.current_gui_page.Visible = not blue_prints.current_gui_page.Visible
|
||||
end
|
||||
|
||||
local menuContent = GUI.Frame(GUI.RectTransform(Vector2(0.4, 0.6), blue_prints.current_gui_page.RectTransform,
|
||||
GUI.Anchor.Center))
|
||||
local mainList = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), menuContent.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
-- Title
|
||||
local title_text = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.08), mainList.Content.RectTransform),
|
||||
"LOAD BLUEPRINT", nil, nil, GUI.Alignment.Center)
|
||||
title_text.TextScale = 1.5
|
||||
title_text.TextColor = Color(200, 200, 200)
|
||||
|
||||
-- Instructions
|
||||
local instruction_text = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.25), mainList.Content.RectTransform),
|
||||
'Click a button to load. Hover over the button to see its description. Click folder headers to expand/collapse.\n\n' ..
|
||||
'If the base component is not available, FPGAs will be used instead. These components must be in your main inventory, not a toolbelt/backpack etc.\n\n' ..
|
||||
'Click anywhere outside this box to cancel.',
|
||||
nil, nil, GUI.Alignment.TopLeft)
|
||||
instruction_text.Wrap = true
|
||||
instruction_text.TextColor = Color(200, 200, 200)
|
||||
instruction_text.Padding = Vector4(10, 5, 10, 5)
|
||||
|
||||
local function createBlueprintButton(filename, filepath, description, componentCount, contentList)
|
||||
local buttonContainer = GUI.Frame(GUI.RectTransform(Vector2(1, 0.10), contentList.Content.RectTransform,
|
||||
GUI.Anchor.TopCenter))
|
||||
buttonContainer.RectTransform.MinSize = Point(0, button_height)
|
||||
buttonContainer.RectTransform.MaxSize = Point(9999, button_height)
|
||||
|
||||
local button_label = filename .. " - " .. tostring(componentCount) .. " FPGAs"
|
||||
local leftButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(0.90, 1), buttonContainer.RectTransform, GUI.Anchor.CenterLeft),
|
||||
button_label, GUI.Alignment.CenterLeft, "GUIButtonSmall")
|
||||
leftButton.TextBlock.Padding = Vector4(80, 0, 0, 0)
|
||||
|
||||
if description then
|
||||
description = description:gsub("
", "\n"):gsub("
", "\n")
|
||||
leftButton.ToolTip = description
|
||||
else
|
||||
leftButton.ToolTip = "No description available"
|
||||
end
|
||||
|
||||
leftButton.OnClicked = function()
|
||||
blue_prints.construct_blueprint(filepath)
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
end
|
||||
|
||||
local rightButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(0.10, 1), buttonContainer.RectTransform, GUI.Anchor.CenterRight),
|
||||
"Delete", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
rightButton.ToolTip = "Delete " .. filename
|
||||
rightButton.Color = Color(255, 80, 80)
|
||||
rightButton.HoverColor = Color(255, 120, 120)
|
||||
|
||||
rightButton.OnClicked = function()
|
||||
local message_box = GUI.MessageBox('Delete Blueprint?',
|
||||
'Are you sure you want to delete "' .. filename .. '"?',
|
||||
{ 'Cancel', 'Delete' })
|
||||
|
||||
local cancel_button = nil
|
||||
local delete_button = nil
|
||||
|
||||
if message_box.Buttons[0] == nil then
|
||||
cancel_button = message_box.Buttons[1]
|
||||
delete_button = message_box.Buttons[2]
|
||||
else
|
||||
cancel_button = message_box.Buttons[0]
|
||||
delete_button = message_box.Buttons[1]
|
||||
end
|
||||
|
||||
delete_button.Color = Color(255, 80, 80)
|
||||
delete_button.HoverColor = Color(255, 120, 120)
|
||||
|
||||
cancel_button.OnClicked = function() message_box.Close() end
|
||||
delete_button.OnClicked = function()
|
||||
blue_prints.delete_blueprint(filepath)
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
GUI.AddMessage('Blueprint Deleted', Color.White)
|
||||
message_box.Close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Process folders
|
||||
local folders = blue_prints.getFolderList()
|
||||
for _, folderName in ipairs(folders) do
|
||||
local folderPath = folderName == "[Root Directory]" and "" or folderName
|
||||
local fullFolderPath = blue_prints.normalizePath(blue_prints.save_path .. "/" .. folderPath)
|
||||
local blueprintCount = count_blueprints_in_folder(fullFolderPath)
|
||||
|
||||
folder_states[folderName] = folder_states[folderName] or false
|
||||
|
||||
-- Create folder container
|
||||
local folderContainer = GUI.Frame(GUI.RectTransform(Vector2(1, 0.10), mainList.Content.RectTransform,
|
||||
GUI.Anchor.TopCenter))
|
||||
folderContainer.RectTransform.MinSize = Point(0, button_height)
|
||||
folderContainer.RectTransform.MaxSize = Point(9999, button_height)
|
||||
|
||||
-- Create folder button with 90% width
|
||||
local headerButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(0.90, 1), folderContainer.RectTransform, GUI.Anchor.CenterLeft),
|
||||
formatFolderHeaderText(folderName, folder_states[folderName], blueprintCount),
|
||||
GUI.Alignment.CenterLeft, "GUIButtonSmall")
|
||||
headerButton.TextColor = Color(150, 150, 255)
|
||||
headerButton.HoverColor = Color(180, 180, 255, 0.5)
|
||||
headerButton.ForceUpperCase = 1
|
||||
headerButton.TextBlock.Padding = Vector4(0, 0, 0, 0)
|
||||
|
||||
-- Add delete button for folder (except root)
|
||||
if folderName ~= "[Root Directory]" then
|
||||
local deleteButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(0.10, 1), folderContainer.RectTransform, GUI.Anchor.CenterRight),
|
||||
"Delete", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
deleteButton.ToolTip = "Delete folder and move contents to root"
|
||||
deleteButton.Color = Color(255, 80, 80)
|
||||
deleteButton.HoverColor = Color(255, 120, 120)
|
||||
|
||||
deleteButton.OnClicked = function()
|
||||
local message_box = GUI.MessageBox('Delete Folder?',
|
||||
'Are you sure you want to delete "' ..
|
||||
folderName .. '"?\nAll blueprints will be moved to the root folder.',
|
||||
{ 'Cancel', 'Delete' })
|
||||
|
||||
local cancel_button = nil
|
||||
local delete_button = nil
|
||||
|
||||
if message_box.Buttons[0] == nil then
|
||||
cancel_button = message_box.Buttons[1]
|
||||
delete_button = message_box.Buttons[2]
|
||||
else
|
||||
cancel_button = message_box.Buttons[0]
|
||||
delete_button = message_box.Buttons[1]
|
||||
end
|
||||
|
||||
delete_button.Color = Color(255, 80, 80)
|
||||
delete_button.HoverColor = Color(255, 120, 120)
|
||||
|
||||
cancel_button.OnClicked = function() message_box.Close() end
|
||||
delete_button.OnClicked = function()
|
||||
if deleteFolder(folderName) then
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
GUI.AddMessage('Folder Deleted', Color.White)
|
||||
message_box.Close()
|
||||
-- Regenerate GUI to show updated folder structure
|
||||
Timer.Wait(function()
|
||||
blue_prints.current_gui_page = generate_load_gui()
|
||||
blue_prints.current_gui_page.Visible = true
|
||||
end, 100)
|
||||
else
|
||||
GUI.AddMessage('Failed to delete folder', Color(255, 0, 0))
|
||||
message_box.Close()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local files = File.GetFiles(fullFolderPath)
|
||||
local size_of_listbox = #files * button_height + 20
|
||||
|
||||
local contentList = GUI.ListBox(GUI.RectTransform(Vector2(1, 0.1), mainList.Content.RectTransform))
|
||||
contentList.RectTransform.MinSize = Point(0, size_of_listbox)
|
||||
contentList.RectTransform.MaxSize = Point(999999, size_of_listbox)
|
||||
contentList.Visible = folder_states[folderName]
|
||||
|
||||
-- Process files
|
||||
if files then
|
||||
for _, filepath in ipairs(files) do
|
||||
if string.match(filepath, "%.txt$") then
|
||||
local filename = filepath:match("([^/\\]+)%.txt$")
|
||||
local xmlContent = blue_prints.readFileContents(filepath)
|
||||
local description = blue_prints.get_description_from_xml(xmlContent)
|
||||
local componentCount = blue_prints.get_component_count_from_xml(xmlContent)
|
||||
|
||||
createBlueprintButton(filename, folderPath .. "/" .. filename, description, componentCount,
|
||||
contentList)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
headerButton.OnClicked = function()
|
||||
folder_states[folderName] = not folder_states[folderName]
|
||||
contentList.Visible = folder_states[folderName]
|
||||
headerButton.Text = formatFolderHeaderText(folderName, folder_states[folderName], blueprintCount)
|
||||
end
|
||||
end
|
||||
|
||||
return blue_prints.current_gui_page
|
||||
end
|
||||
|
||||
local function check_and_rebuild_frame()
|
||||
local new_resolution = blue_prints.getScreenResolution()
|
||||
if new_resolution ~= resolution or run_once_at_start == false then
|
||||
|
||||
local spacer = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.04), blue_prints.gui_button_frame_list.Content.RectTransform), "", nil, nil, GUI.Alignment.Center)
|
||||
|
||||
local button = GUI.Button(GUI.RectTransform(Vector2(1, 0.1), blue_prints.gui_button_frame_list.Content.RectTransform), "Load Blueprint", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
button.OnClicked = function()
|
||||
if blue_prints.current_gui_page then
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
end
|
||||
blue_prints.current_gui_page = generate_load_gui()
|
||||
blue_prints.current_gui_page.Visible = true
|
||||
end
|
||||
|
||||
resolution = new_resolution
|
||||
run_once_at_start = true
|
||||
end
|
||||
end
|
||||
|
||||
Hook.Patch("Barotrauma.Items.Components.CircuitBox", "AddToGUIUpdateList", function()
|
||||
check_and_rebuild_frame()
|
||||
end, Hook.HookMethodType.After)
|
138
Blueprints/Lua/gui/popup_gui.lua
Normal file
138
Blueprints/Lua/gui/popup_gui.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
if SERVER then return end -- we don't want server to run GUI code.
|
||||
|
||||
-- Create main frame for all popups
|
||||
blue_prints.popup_frame = GUI.Frame(GUI.RectTransform(Vector2(1, 1)), nil)
|
||||
blue_prints.popup_frame.CanBeFocused = false
|
||||
blue_prints.current_popup = nil
|
||||
|
||||
function blue_prints.show_popup(config)
|
||||
-- Hide any existing popup
|
||||
if blue_prints.current_popup then
|
||||
blue_prints.current_popup.Visible = false
|
||||
end
|
||||
|
||||
-- Create new popup frame
|
||||
local popup = GUI.Frame(GUI.RectTransform(Vector2(1, 1), blue_prints.popup_frame.RectTransform, GUI.Anchor.Center), nil)
|
||||
popup.CanBeFocused = false
|
||||
popup.Visible = true
|
||||
blue_prints.current_popup = popup
|
||||
|
||||
-- Background dimming
|
||||
local backgroundButton = GUI.Button(GUI.RectTransform(Vector2(1, 1), popup.RectTransform), "", GUI.Alignment.Center, nil)
|
||||
backgroundButton.Color = Color(0, 0, 0, 100)
|
||||
|
||||
-- Content container
|
||||
local popupContent = GUI.Frame(GUI.RectTransform(Vector2(0.4, 0.6), popup.RectTransform, GUI.Anchor.Center))
|
||||
local popupList = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), popupContent.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
-- Title
|
||||
if config.title then
|
||||
local titleText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.15), popupList.Content.RectTransform),
|
||||
config.title, nil, nil, GUI.Alignment.Center)
|
||||
titleText.TextScale = 1.5
|
||||
titleText.TextColor = Color(200, 200, 200)
|
||||
titleText.Wrap = true
|
||||
end
|
||||
|
||||
-- Message lines
|
||||
if config.messages then
|
||||
for _, message in ipairs(config.messages) do
|
||||
local messageBlock = GUI.TextBlock(
|
||||
GUI.RectTransform(Vector2(1, message.height or 0.05), popupList.Content.RectTransform),
|
||||
message.text or message,
|
||||
nil, nil, GUI.Alignment.Center
|
||||
)
|
||||
messageBlock.Wrap = true
|
||||
messageBlock.TextColor = message.color or Color(200, 200, 200)
|
||||
if message.padding then
|
||||
messageBlock.Padding = message.padding
|
||||
else
|
||||
messageBlock.Padding = Vector4(10, 5, 10, 5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Optional spacer
|
||||
if config.addSpacer then
|
||||
GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.10), popupList.Content.RectTransform),
|
||||
'', nil, nil, GUI.Alignment.Center)
|
||||
end
|
||||
|
||||
-- Buttons
|
||||
if config.buttons then
|
||||
for _, buttonConfig in ipairs(config.buttons) do
|
||||
local button = GUI.Button(
|
||||
GUI.RectTransform(Vector2(1, 0.1), popupList.Content.RectTransform),
|
||||
buttonConfig.text,
|
||||
GUI.Alignment.Center,
|
||||
"GUIButtonSmall"
|
||||
)
|
||||
if buttonConfig.color then
|
||||
button.Color = buttonConfig.color
|
||||
end
|
||||
button.OnClicked = function()
|
||||
if buttonConfig.onClick then
|
||||
buttonConfig.onClick()
|
||||
end
|
||||
popup.Visible = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Close on background click
|
||||
backgroundButton.OnClicked = function()
|
||||
popup.Visible = false
|
||||
end
|
||||
|
||||
return popup
|
||||
end
|
||||
|
||||
-- Add to GUI update list for all relevant screens
|
||||
Hook.Patch("Barotrauma.GameScreen", "AddToGUIUpdateList", function()
|
||||
blue_prints.popup_frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
||||
|
||||
Hook.Patch("Barotrauma.NetLobbyScreen", "AddToGUIUpdateList", function(self, ptable)
|
||||
blue_prints.popup_frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
||||
|
||||
Hook.Patch("Barotrauma.SubEditorScreen", "AddToGUIUpdateList", function()
|
||||
blue_prints.popup_frame.AddToGUIUpdateList()
|
||||
end, Hook.HookMethodType.After)
|
||||
|
||||
--[[
|
||||
--use like this in other scripts:
|
||||
blue_prints.show_popup({
|
||||
title = "Load Failed",
|
||||
messages = {
|
||||
{
|
||||
text = "Your circuit has failed to load.",
|
||||
height = 0.1 -- Taller block for wrapped text
|
||||
},
|
||||
{
|
||||
text = "Your blueprint file might be from an earlier version of Blueprints and nothing is actually wrong. Try saving it again (overwriting the original) to update your blueprint file to the latest version.",
|
||||
height = 0.2
|
||||
},
|
||||
{
|
||||
text = "If you are certain your file is up to date try loading it again. Do not move or change anything during loading: the loaded circuit must match the blueprint file EXACTLY in order for you not to see this message.",
|
||||
height = 0.2
|
||||
},
|
||||
{
|
||||
text = "This also means if the inputs change any values in any component the unit test will also fail. Same thing if you have some value that changes over time, like RGB or timing values from an oscillator. So the circuit might still be ok, it just does not match your save exactly.",
|
||||
height = 0.2
|
||||
},
|
||||
{
|
||||
text = "If none of these exceptions apply and the problem persists please report this bug on the steam workshop page or the discord. Include a download link to your saved blueprint file and a screenshot of your console text (you can see that by hitting F3)",
|
||||
--color = Color(255, 0, 0), -- Red text
|
||||
height = 0.2
|
||||
}
|
||||
},
|
||||
addSpacer = true,
|
||||
buttons = {
|
||||
{
|
||||
text = "Close",
|
||||
onClick = function() end
|
||||
}
|
||||
}
|
||||
})
|
||||
--]]
|
239
Blueprints/Lua/gui/save_gui.lua
Normal file
239
Blueprints/Lua/gui/save_gui.lua
Normal file
@@ -0,0 +1,239 @@
|
||||
if SERVER then return end -- we don't want server to run GUI code.
|
||||
|
||||
local resolution = blue_prints.getScreenResolution()
|
||||
local run_once_at_start = false
|
||||
|
||||
-- Forward declarations
|
||||
local check_and_rebuild_frame
|
||||
local create_folder_modal
|
||||
local generate_save_gui
|
||||
|
||||
create_folder_modal = function()
|
||||
-- Create a new modal frame that covers the entire screen
|
||||
local modalFrame = GUI.Frame(GUI.RectTransform(Vector2(1, 1), frame.RectTransform, GUI.Anchor.Center), nil)
|
||||
modalFrame.CanBeFocused = false
|
||||
|
||||
-- Darkened background
|
||||
local backgroundButton = GUI.Button(GUI.RectTransform(Vector2(1, 1), modalFrame.RectTransform), "",
|
||||
GUI.Alignment.Center, nil)
|
||||
backgroundButton.Color = Color(0, 0, 0, 100)
|
||||
|
||||
-- Modal content container - make it consistent with load_gui width (0.4)
|
||||
local modalContent = GUI.Frame(GUI.RectTransform(Vector2(0.4, 0.6), modalFrame.RectTransform, GUI.Anchor.Center))
|
||||
local menuList = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), modalContent.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
-- Title - matching load_gui style
|
||||
local titleText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform),
|
||||
"CREATE NEW FOLDER", nil, nil, GUI.Alignment.Center)
|
||||
titleText.TextScale = 2.0
|
||||
titleText.Wrap = false
|
||||
|
||||
-- Spacer
|
||||
local spacer1 = GUI.Frame(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform))
|
||||
|
||||
-- Folder name label
|
||||
local folderNameText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform),
|
||||
"Folder Name:", nil, nil, GUI.Alignment.CenterLeft)
|
||||
|
||||
-- Text input - full width like in save_gui
|
||||
local textBox = GUI.TextBox(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform), "")
|
||||
|
||||
-- Spacer
|
||||
local spacer2 = GUI.Frame(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform))
|
||||
|
||||
-- Button container for proper centering
|
||||
local buttonContainer = GUI.Frame(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform))
|
||||
|
||||
-- Create and Cancel buttons with proper spacing
|
||||
local cancelButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(0.45, 1), buttonContainer.RectTransform, GUI.Anchor.CenterLeft),
|
||||
"Cancel", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
local createButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(0.45, 1), buttonContainer.RectTransform, GUI.Anchor.CenterRight),
|
||||
"Create", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
-- Button handlers
|
||||
cancelButton.OnClicked = function()
|
||||
modalFrame.Visible = false
|
||||
modalFrame.RemoveFromGUIUpdateList()
|
||||
-- Ensure main save window is visible
|
||||
if blue_prints.current_gui_page then
|
||||
blue_prints.current_gui_page.Visible = true
|
||||
end
|
||||
end
|
||||
|
||||
backgroundButton.OnClicked = cancelButton.OnClicked
|
||||
|
||||
createButton.OnClicked = function()
|
||||
if textBox.Text and textBox.Text ~= "" then
|
||||
local success, result = blue_prints.createNewFolder(textBox.Text)
|
||||
if success then
|
||||
-- Close modal
|
||||
modalFrame.Visible = false
|
||||
modalFrame.RemoveFromGUIUpdateList()
|
||||
|
||||
-- Refresh save window to show new folder
|
||||
if blue_prints.current_gui_page then
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
end
|
||||
blue_prints.current_gui_page = generate_save_gui()
|
||||
blue_prints.current_gui_page.Visible = true
|
||||
|
||||
GUI.AddMessage("Folder created successfully", Color(0, 255, 0))
|
||||
else
|
||||
GUI.AddMessage("Failed to create folder: " .. result, Color(255, 0, 0))
|
||||
end
|
||||
else
|
||||
GUI.AddMessage("Please enter a folder name", Color(255, 0, 0))
|
||||
end
|
||||
end
|
||||
|
||||
return modalFrame
|
||||
end
|
||||
|
||||
generate_save_gui = function()
|
||||
blue_prints.current_gui_page = GUI.Frame(GUI.RectTransform(Vector2(1, 1), blue_prints.gui_button_frame.RectTransform, GUI.Anchor.Center),
|
||||
nil)
|
||||
blue_prints.current_gui_page.CanBeFocused = false
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
|
||||
-- Background close button
|
||||
local closeButton = GUI.Button(
|
||||
GUI.RectTransform(Vector2(1, 1), blue_prints.current_gui_page.RectTransform, GUI.Anchor.Center), "",
|
||||
GUI.Alignment.Center, nil)
|
||||
closeButton.OnClicked = function()
|
||||
blue_prints.current_gui_page.Visible = not blue_prints.current_gui_page.Visible
|
||||
end
|
||||
|
||||
local menuContent = GUI.Frame(GUI.RectTransform(Vector2(0.4, 0.6), blue_prints.current_gui_page.RectTransform,
|
||||
GUI.Anchor.Center))
|
||||
local menuList = GUI.ListBox(GUI.RectTransform(Vector2(1, 1), menuContent.RectTransform, GUI.Anchor.BottomCenter))
|
||||
|
||||
-- Title
|
||||
local title_text = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform), "SAVE BLUEPRINT",
|
||||
nil, nil, GUI.Alignment.Center)
|
||||
title_text.TextScale = 1.5
|
||||
title_text.TextColor = Color(200, 200, 200)
|
||||
title_text.Wrap = false
|
||||
|
||||
-- Instructions
|
||||
local instruction_text = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.20), menuList.Content.RectTransform),
|
||||
'Enter a filename and select a folder. If using an existing filename, the old file will be overwritten.\n\n' ..
|
||||
'A label with the name "Description" will be used as the reminder text when loading.\n\n' ..
|
||||
'Click anywhere outside this box to cancel.',
|
||||
nil, nil, GUI.Alignment.TopLeft)
|
||||
instruction_text.Wrap = true
|
||||
instruction_text.TextColor = Color(200, 200, 200)
|
||||
instruction_text.Padding = Vector4(10, 5, 10, 5)
|
||||
|
||||
-- Create New Folder Button
|
||||
local createFolderButton = GUI.Button(GUI.RectTransform(Vector2(1, 0.08), menuList.Content.RectTransform),
|
||||
"Create New Folder", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
createFolderButton.OnClicked = function()
|
||||
-- Hide the save window temporarily
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
-- Show the folder creation modal
|
||||
local modalFrame = create_folder_modal()
|
||||
end
|
||||
|
||||
local spacer1 = GUI.Frame(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform))
|
||||
|
||||
-- Folder Selection
|
||||
local folderText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform),
|
||||
"Select Folder:", nil, nil, GUI.Alignment.CenterLeft)
|
||||
|
||||
local folderDropDown = GUI.DropDown(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform),
|
||||
"Select Folder", nil, nil, false)
|
||||
|
||||
-- Add folders to dropdown with numeric indices
|
||||
local folders = blue_prints.getFolderList()
|
||||
for i, folder in ipairs(folders) do
|
||||
folderDropDown.AddItem(folder, i)
|
||||
end
|
||||
|
||||
-- Select the most recently used folder if it exists
|
||||
local selectedIndex = 1 -- Default to first item
|
||||
for i, folder in ipairs(folders) do
|
||||
if folder == blue_prints.most_recent_folder then
|
||||
selectedIndex = i
|
||||
break
|
||||
end
|
||||
end
|
||||
folderDropDown.Select(selectedIndex - 1) -- -1 because dropdown uses 0-based indexing
|
||||
|
||||
-- Store folder list for reference
|
||||
local folderLookup = folders
|
||||
|
||||
local spacer2 = GUI.Frame(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform))
|
||||
|
||||
-- Filename Section
|
||||
local filenameText = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform),
|
||||
"Filename:", nil, nil, GUI.Alignment.CenterLeft)
|
||||
|
||||
local filenameTextBox = GUI.TextBox(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform),
|
||||
"Your filename here")
|
||||
|
||||
-- Set the text box to the most recently used name if available
|
||||
if blue_prints.most_recently_used_blueprint_name ~= nil then
|
||||
filenameTextBox.Text = blue_prints.most_recently_used_blueprint_name
|
||||
end
|
||||
|
||||
local spacer3 = GUI.Frame(GUI.RectTransform(Vector2(1, 0.05), menuList.Content.RectTransform))
|
||||
|
||||
-- Save Button
|
||||
local save_button = GUI.Button(GUI.RectTransform(Vector2(1, 0.1), menuList.Content.RectTransform),
|
||||
"Save", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
save_button.OnClicked = function()
|
||||
if filenameTextBox.Text and filenameTextBox.Text ~= "" then
|
||||
-- Get the selected index and use it to look up the folder name
|
||||
local selectedIndex = (tonumber(folderDropDown.SelectedData) or 1) - 1
|
||||
local selectedFolder = folderLookup[selectedIndex + 1] -- +1 because Lua arrays start at 1
|
||||
|
||||
--print("Selected index:", selectedIndex) -- Debug print
|
||||
--print("Selected folder:", selectedFolder) -- Debug print
|
||||
|
||||
if selectedFolder == "[Root Directory]" then
|
||||
--print("Saving to root directory")
|
||||
blue_prints.save_blueprint(filenameTextBox.Text)
|
||||
else
|
||||
--print("Saving to folder:", selectedFolder)
|
||||
blue_prints.save_blueprint(filenameTextBox.Text, selectedFolder)
|
||||
end
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
GUI.AddMessage('File Saved', Color.White)
|
||||
else
|
||||
GUI.AddMessage('Please enter a filename', Color(255, 0, 0))
|
||||
end
|
||||
end
|
||||
|
||||
return blue_prints.current_gui_page
|
||||
end
|
||||
|
||||
check_and_rebuild_frame = function()
|
||||
local new_resolution = blue_prints.getScreenResolution()
|
||||
if new_resolution ~= resolution or run_once_at_start == false then
|
||||
|
||||
local spacer = GUI.TextBlock(GUI.RectTransform(Vector2(1, 0.04), blue_prints.gui_button_frame_list.Content.RectTransform), "", nil, nil, GUI.Alignment.Center)
|
||||
|
||||
local button = GUI.Button(GUI.RectTransform(Vector2(1, 0.1), blue_prints.gui_button_frame_list.Content.RectTransform), "Save Blueprint", GUI.Alignment.Center, "GUIButtonSmall")
|
||||
|
||||
button.OnClicked = function()
|
||||
if blue_prints.current_gui_page ~= nil then
|
||||
blue_prints.current_gui_page.Visible = false
|
||||
end
|
||||
blue_prints.current_gui_page = nil
|
||||
blue_prints.current_gui_page = generate_save_gui()
|
||||
blue_prints.current_gui_page.Visible = true
|
||||
end
|
||||
|
||||
resolution = new_resolution
|
||||
run_once_at_start = true
|
||||
end
|
||||
end
|
||||
|
||||
Hook.Patch("Barotrauma.Items.Components.CircuitBox", "AddToGUIUpdateList", function()
|
||||
check_and_rebuild_frame()
|
||||
end, Hook.HookMethodType.After)
|
1264
Blueprints/Lua/load_blueprint.lua
Normal file
1264
Blueprints/Lua/load_blueprint.lua
Normal file
File diff suppressed because it is too large
Load Diff
19
Blueprints/Lua/register_types.lua
Normal file
19
Blueprints/Lua/register_types.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
--used to create netlimitedstrings
|
||||
LuaUserData.RegisterType("Barotrauma.NetLimitedString")
|
||||
blue_prints.net_limited_string_type = LuaUserData.CreateStatic("Barotrauma.NetLimitedString", true)
|
||||
|
||||
--for accessing component values inside components
|
||||
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.Item"], "GetInGameEditableProperties")
|
||||
LuaUserData.RegisterType("System.ValueTuple`2[System.Object,Barotrauma.SerializableProperty]")
|
||||
|
||||
--used to create immutable arrays
|
||||
LuaUserData.RegisterType("System.Collections.Immutable.ImmutableArray")
|
||||
LuaUserData.RegisterType("System.Collections.Immutable.ImmutableArray`1")
|
||||
blue_prints.immutable_array_type = LuaUserData.CreateStatic("System.Collections.Immutable.ImmutableArray", false)
|
||||
|
||||
--register types for components not in lua yet
|
||||
LuaUserData.RegisterType('Barotrauma.Items.Components.ConnectionSelectorComponent')
|
||||
LuaUserData.RegisterType('Barotrauma.Items.Components.DemultiplexerComponent')
|
||||
LuaUserData.RegisterType('Barotrauma.Items.Components.MultiplexerComponent')
|
527
Blueprints/Lua/save_blueprint.lua
Normal file
527
Blueprints/Lua/save_blueprint.lua
Normal file
@@ -0,0 +1,527 @@
|
||||
if SERVER then return end --prevents it from running on the server
|
||||
|
||||
|
||||
-- Delimiter constants
|
||||
local STRING_START = "<<<STRINGSTART>>>"
|
||||
local STRING_END = "<<<STRINGEND>>>"
|
||||
|
||||
-- Encodes a string to be safely stored in XML attributes
|
||||
local function encodeAttributeString(str)
|
||||
if str == nil then return "" end
|
||||
|
||||
-- First, escape any existing delimiters in the content
|
||||
local escaped = str:gsub(STRING_START, "\\<<<STRINGSTART>>>")
|
||||
:gsub(STRING_END, "\\<<<STRINGEND>>>")
|
||||
|
||||
-- Clean the string of control characters
|
||||
escaped = blue_prints.clean_string(escaped)
|
||||
|
||||
-- Wrap the escaped content with delimiters
|
||||
return STRING_START .. escaped .. STRING_END
|
||||
end
|
||||
|
||||
-- Decodes a string that was stored in XML attributes
|
||||
local function decodeAttributeString(str)
|
||||
if str == nil then return "" end
|
||||
|
||||
-- Check if the string has our delimiters
|
||||
local content = str:match(STRING_START .. "(.-)" .. STRING_END)
|
||||
if not content then
|
||||
-- If no delimiters found, return original string (for backward compatibility)
|
||||
return str
|
||||
end
|
||||
|
||||
-- Unescape any escaped delimiters
|
||||
return content:gsub("\\<<<STRINGSTART>>>", STRING_START)
|
||||
:gsub("\\<<<STRINGEND>>>", STRING_END)
|
||||
end
|
||||
|
||||
local function processLabelStrings(xmlString)
|
||||
-- Process header and body attributes in Label tags
|
||||
return xmlString:gsub('(<Label[^>]-)(header="([^"]*)")', function(prefix, full, content)
|
||||
return prefix .. 'header="' .. encodeAttributeString(content) .. '"'
|
||||
end):gsub('(<Label[^>]-)(body="([^"]*)")', function(prefix, full, content)
|
||||
return prefix .. 'body="' .. encodeAttributeString(content) .. '"'
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
-- Function to process input/output node labels
|
||||
local function processNodeLabels(xmlString)
|
||||
-- Pattern to match InputNode or OutputNode sections
|
||||
local function processNodeSection(nodeSection)
|
||||
-- Process ConnectionLabelOverride tags within the node section
|
||||
return nodeSection:gsub('(<ConnectionLabelOverride[^>]-value=)"([^"]*)"', function(prefix, content)
|
||||
-- Don't re-encode if already encoded
|
||||
if content:match("^" .. STRING_START .. ".*" .. STRING_END .. "$") then
|
||||
return prefix .. '"' .. content .. '"'
|
||||
end
|
||||
return prefix .. '"' .. encodeAttributeString(content) .. '"'
|
||||
end)
|
||||
end
|
||||
|
||||
-- Process InputNode sections
|
||||
xmlString = xmlString:gsub("(<InputNode.-</InputNode>)", processNodeSection)
|
||||
|
||||
-- Process OutputNode sections
|
||||
xmlString = xmlString:gsub("(<OutputNode.-</OutputNode>)", processNodeSection)
|
||||
|
||||
return xmlString
|
||||
end
|
||||
|
||||
|
||||
-- Modified function to add encoded attributes to components
|
||||
local function add_encoded_attribute_to_component(xmlContent, targetId, attributeName, attributeValue)
|
||||
-- First encode the attribute value
|
||||
local encodedValue = encodeAttributeString(attributeValue)
|
||||
|
||||
-- Function to add the attribute to the specific Component element
|
||||
local function modifyComponent(componentString)
|
||||
local id = componentString:match('id="(%d+)"')
|
||||
if id and tonumber(id) == targetId then
|
||||
-- Create the full attribute string with the encoded value
|
||||
local attributeStr = string.format('%s=%s', attributeName, encodedValue)
|
||||
return componentString:gsub('/>$', ' ' .. blue_prints.escapePercent(attributeStr) .. ' />')
|
||||
else
|
||||
return componentString
|
||||
end
|
||||
end
|
||||
|
||||
-- Find the CircuitBox element
|
||||
local circuitBoxStart, circuitBoxEnd = xmlContent:find('<CircuitBox.->')
|
||||
local circuitBoxEndTag = xmlContent:find('</CircuitBox>', circuitBoxEnd)
|
||||
if not circuitBoxStart or not circuitBoxEndTag then
|
||||
print("CircuitBox element not found")
|
||||
return xmlContent
|
||||
end
|
||||
|
||||
-- Extract the CircuitBox content
|
||||
local circuitBoxContent = xmlContent:sub(circuitBoxEnd + 1, circuitBoxEndTag - 1)
|
||||
|
||||
-- Modify the specific Component element
|
||||
local modifiedCircuitBoxContent = circuitBoxContent:gsub('<Component.-/>', modifyComponent)
|
||||
|
||||
-- Replace the original CircuitBox content with the modified content
|
||||
return xmlContent:sub(1, circuitBoxEnd) .. modifiedCircuitBoxContent .. xmlContent:sub(circuitBoxEndTag)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Function to extract components and their IDs
|
||||
local function extractComponents(xmlString)
|
||||
local components = {}
|
||||
for id, position in xmlString:gmatch('<Component id="(%d+)" position="([^"]+)"') do
|
||||
components[#components + 1] = {id = tonumber(id), position = position}
|
||||
end
|
||||
table.sort(components, function(a, b) return a.id < b.id end)
|
||||
return components
|
||||
end
|
||||
|
||||
-- Function to create ID mapping
|
||||
local function createIdMapping(components)
|
||||
local idMap = {}
|
||||
local newId = 0
|
||||
for _, component in ipairs(components) do
|
||||
idMap[component.id] = newId
|
||||
newId = newId + 1
|
||||
end
|
||||
return idMap
|
||||
end
|
||||
|
||||
-- Function to update component IDs and wire targets
|
||||
local function updateXml(xmlString, idMap)
|
||||
-- Update component IDs
|
||||
xmlString = xmlString:gsub('<Component id="(%d+)"', function(id)
|
||||
return string.format('<Component id="%d"', idMap[tonumber(id)])
|
||||
end)
|
||||
|
||||
-- Update wire targets
|
||||
xmlString = xmlString:gsub('target="(%d*)"', function(target)
|
||||
if target ~= "" then
|
||||
local newTarget = idMap[tonumber(target)]
|
||||
if newTarget then
|
||||
return string.format('target="%d"', newTarget)
|
||||
else
|
||||
print("Warning: No mapping found for target " .. target)
|
||||
return string.format('target="%s"', target)
|
||||
end
|
||||
else
|
||||
return 'target=""'
|
||||
end
|
||||
end)
|
||||
|
||||
return xmlString
|
||||
end
|
||||
|
||||
-- Main function to renumber components and update wire targets
|
||||
local function renumber_components(xmlString)
|
||||
local components = extractComponents(xmlString)
|
||||
local idMap = createIdMapping(components)
|
||||
return updateXml(xmlString, idMap)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local function find_id_within_component(component_string)
|
||||
|
||||
-- Define the pattern to match the id attribute value
|
||||
local pattern = 'id="(%d+)"'
|
||||
|
||||
-- Extract the id value using string.match
|
||||
local id_value = string.match(component_string, pattern)
|
||||
|
||||
-- Print the result
|
||||
if id_value then
|
||||
return id_value
|
||||
else
|
||||
return ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
local function find_specific_component(xmlContent, target_component)
|
||||
-- Split the XML content into lines
|
||||
local lines = {}
|
||||
for line in string.gmatch(xmlContent, "[^\r\n]+") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
-- Define the pattern to match the component
|
||||
local pattern = "<Component.-/->"
|
||||
|
||||
-- Initialize counters
|
||||
local count = 0
|
||||
|
||||
-- Iterate through each line to find the specific component
|
||||
for i, line in ipairs(lines) do
|
||||
-- Check for components in the current line
|
||||
for component in string.gmatch(line, pattern) do
|
||||
count = count + 1
|
||||
if count == target_component then
|
||||
-- Return both the line number and the line content
|
||||
return i, line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Return nil, nil if the component was not found
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
|
||||
local function count_component_number(xmlContent)
|
||||
|
||||
-- Define the pattern to match the component
|
||||
local pattern = "<Component.-/->"
|
||||
|
||||
-- Initialize counter
|
||||
local count = 0
|
||||
|
||||
-- Use string.gmatch to iterate over all matches
|
||||
for _ in string.gmatch(xmlContent, pattern) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
return count
|
||||
|
||||
end
|
||||
|
||||
|
||||
local function swap_lines_in_string(xmlContent, line1, line2)
|
||||
-- Split the text into lines
|
||||
local lines = {}
|
||||
for line in string.gmatch(xmlContent, "[^\r\n]+") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
-- Swap the specified lines
|
||||
if line1 <= #lines and line2 <= #lines then
|
||||
lines[line1], lines[line2] = lines[line2], lines[line1]
|
||||
else
|
||||
print("Error: Line numbers out of range.")
|
||||
end
|
||||
|
||||
-- Join the lines back into a single string
|
||||
local swapped_text = table.concat(lines, "\n")
|
||||
|
||||
return swapped_text
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function put_components_in_order(xmlContent)
|
||||
|
||||
local something_in_xml_changed = false
|
||||
local number_of_components = count_component_number(xmlContent)
|
||||
|
||||
for i = 1, number_of_components-1 do
|
||||
local first_line_number, first_line_content = find_specific_component(xmlContent, i)
|
||||
local second_line_number, second_line_content = find_specific_component(xmlContent, i+1)
|
||||
|
||||
--print(first_line_content)
|
||||
--print(second_line_content)
|
||||
--print("-----")
|
||||
|
||||
local first_id = find_id_within_component(first_line_content)
|
||||
local second_id = find_id_within_component(second_line_content)
|
||||
|
||||
if tonumber(first_id) > tonumber(second_id) then
|
||||
--print("comparing" .. first_id .. " to " .. second_id)
|
||||
xmlContent = swap_lines_in_string(xmlContent, first_line_number, second_line_number)
|
||||
something_in_xml_changed = true
|
||||
end
|
||||
end
|
||||
|
||||
if something_in_xml_changed then return put_components_in_order(xmlContent) end
|
||||
|
||||
--print(xmlContent)
|
||||
|
||||
return xmlContent
|
||||
|
||||
end
|
||||
|
||||
|
||||
local function remove_attribute_from_components(xmlContent, attributeName)
|
||||
-- Function to remove the specified attribute from a component
|
||||
local function removeAttribute(componentString)
|
||||
-- Pattern to match traditional XML attributes
|
||||
local pattern1 = '%s*' .. attributeName .. '="[^"]+"'
|
||||
|
||||
-- Pattern to match encoded strings with delimiters
|
||||
local pattern2 = '%s*' .. attributeName .. '=<<<STRINGSTART>>>[^<]*<<<STRINGEND>>>'
|
||||
|
||||
-- Remove both types of attributes
|
||||
local result = componentString:gsub(pattern1, '')
|
||||
result = result:gsub(pattern2, '')
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Find all Component elements and process them
|
||||
local function processComponents(content)
|
||||
return content:gsub('<Component.-/>', removeAttribute)
|
||||
end
|
||||
|
||||
-- Find the CircuitBox element
|
||||
local circuitBoxStart, circuitBoxEnd = xmlContent:find('<CircuitBox.->')
|
||||
local circuitBoxEndTag = xmlContent:find('</CircuitBox>', circuitBoxEnd)
|
||||
if not circuitBoxStart or not circuitBoxEndTag then
|
||||
print("CircuitBox element not found")
|
||||
return xmlContent
|
||||
end
|
||||
|
||||
-- Extract and process the CircuitBox content
|
||||
local circuitBoxContent = xmlContent:sub(circuitBoxEnd + 1, circuitBoxEndTag - 1)
|
||||
local modifiedCircuitBoxContent = processComponents(circuitBoxContent)
|
||||
|
||||
-- Replace the original CircuitBox content with the modified content
|
||||
return xmlContent:sub(1, circuitBoxEnd) .. modifiedCircuitBoxContent .. xmlContent:sub(circuitBoxEndTag)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
local function clean_component_whitespace(xmlContent)
|
||||
local function cleanComponent(componentString)
|
||||
-- Don't touch anything between STRINGSTART and STRINGEND
|
||||
local parts = {}
|
||||
local lastPos = 1
|
||||
|
||||
-- Find start and end positions of all encoded strings
|
||||
local startPos = componentString:find('<<<STRINGSTART>>>', lastPos, true)
|
||||
while startPos do
|
||||
local endPos = componentString:find('<<<STRINGEND>>>', startPos, true)
|
||||
if not endPos then break end
|
||||
|
||||
-- Add the part before the encoded string
|
||||
local beforePart = componentString:sub(lastPos, startPos - 1)
|
||||
beforePart = beforePart:gsub('%s+', ' ') -- collapse multiple spaces to single space
|
||||
table.insert(parts, beforePart)
|
||||
|
||||
-- Add the encoded string unchanged
|
||||
table.insert(parts, componentString:sub(startPos, endPos + 13))
|
||||
|
||||
lastPos = endPos + 14
|
||||
startPos = componentString:find('<<<STRINGSTART>>>', lastPos, true)
|
||||
end
|
||||
|
||||
-- Add any remaining part after the last encoded string
|
||||
if lastPos <= #componentString then
|
||||
local remaining = componentString:sub(lastPos)
|
||||
remaining = remaining:gsub('%s+', ' ')
|
||||
table.insert(parts, remaining)
|
||||
end
|
||||
|
||||
return table.concat(parts)
|
||||
end
|
||||
|
||||
-- Find all Component tags and process them
|
||||
local circuitBoxStart, circuitBoxEnd = xmlContent:find('<CircuitBox.->')
|
||||
local circuitBoxEndTag = xmlContent:find('</CircuitBox>', circuitBoxEnd)
|
||||
if not circuitBoxStart or not circuitBoxEndTag then
|
||||
return xmlContent
|
||||
end
|
||||
|
||||
local circuitBoxContent = xmlContent:sub(circuitBoxEnd + 1, circuitBoxEndTag - 1)
|
||||
local modifiedContent = circuitBoxContent:gsub('<Component.-/>', cleanComponent)
|
||||
|
||||
return xmlContent:sub(1, circuitBoxEnd) .. modifiedContent .. xmlContent:sub(circuitBoxEndTag)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Function to round a number to the nearest integer
|
||||
local function round(num)
|
||||
return math.floor(num + 0.5)
|
||||
end
|
||||
|
||||
-- Function to round the position and size attributes in a Label element
|
||||
local function round_attributes(label)
|
||||
-- Round the position attribute
|
||||
if label:find('position="') then
|
||||
local pos_x, pos_y = label:match('position="([^,]+),([^"]+)"')
|
||||
local rounded_position = string.format('position="%d,%d"', round(tonumber(pos_x)), round(tonumber(pos_y)))
|
||||
label = label:gsub('position="[^"]+"', rounded_position)
|
||||
end
|
||||
|
||||
-- Round the size attribute
|
||||
if label:find('size="') then
|
||||
local size_x, size_y = label:match('size="([^,]+),([^"]+)"')
|
||||
local rounded_size = string.format('size="%d,%d"', round(tonumber(size_x)), round(tonumber(size_y)))
|
||||
label = label:gsub('size="[^"]+"', rounded_size)
|
||||
end
|
||||
|
||||
if label:find('pos="') then
|
||||
local pos_x, pos_y = label:match('pos="([^,]+),([^"]+)"')
|
||||
local rounded_position = string.format('pos="%d,%d"', round(tonumber(pos_x)), round(tonumber(pos_y)))
|
||||
label = label:gsub('pos="[^"]+"', rounded_position)
|
||||
end
|
||||
|
||||
return label
|
||||
end
|
||||
|
||||
-- Function to process the entire XML string
|
||||
local function round_position_values(xml_string)
|
||||
local processed_string = ""
|
||||
|
||||
-- Process each line of the XML string
|
||||
for line in xml_string:gmatch("[^\r\n]+") do
|
||||
-- Find and process Label elements
|
||||
if line:find("<Label") or line:find("<Component") or line:find("<InputNode") or line:find("<OutputNode") then
|
||||
line = round_attributes(line)
|
||||
end
|
||||
processed_string = processed_string .. line .. "\n"
|
||||
end
|
||||
|
||||
return processed_string
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function blue_prints.prepare_circuitbox_xml_for_saving()
|
||||
if blue_prints.most_recent_circuitbox == nil then print("no circuitbox detected") return end
|
||||
|
||||
local sacrificial_xml = XElement("Root")
|
||||
blue_prints.most_recent_circuitbox.Save(sacrificial_xml)
|
||||
local circuitbox_xml = tostring(sacrificial_xml)
|
||||
|
||||
|
||||
|
||||
local components = blue_prints.most_recent_circuitbox.GetComponentString("CircuitBox").Components
|
||||
|
||||
for i, component in ipairs(components) do
|
||||
-- Add the class name to component
|
||||
local class_name = nil
|
||||
for value in component.Item.Components do
|
||||
local target_string = tostring(value)
|
||||
local target_string = target_string:match("([^%.]+)$")
|
||||
if string.find(target_string, "Component") then
|
||||
class_name = target_string
|
||||
break
|
||||
end
|
||||
end
|
||||
if class_name ~= nil then
|
||||
circuitbox_xml = add_encoded_attribute_to_component(circuitbox_xml, component.ID, 'Class', class_name)
|
||||
else
|
||||
print("Error: couldn't find class name! Report this bug on the workshop page please! Component causing the problem: " .. tostring(component.Item.Prefab.Identifier))
|
||||
end
|
||||
|
||||
-- Add any values stored inside the component
|
||||
local my_editables = component.Item.GetInGameEditableProperties(false)
|
||||
for tuple in my_editables do
|
||||
local field_name = tostring(tuple.Item2.name)
|
||||
local field_value = tostring(tuple.Item2.GetValue(tuple.Item1))
|
||||
|
||||
field_name = blue_prints.clean_string(field_name)
|
||||
field_value = blue_prints.clean_string(field_value)
|
||||
|
||||
circuitbox_xml = add_encoded_attribute_to_component(
|
||||
circuitbox_xml,
|
||||
component.ID,
|
||||
field_name,
|
||||
field_value
|
||||
)
|
||||
end
|
||||
|
||||
-- Add the item prefab itself
|
||||
circuitbox_xml = add_encoded_attribute_to_component(
|
||||
circuitbox_xml,
|
||||
component.ID,
|
||||
'item',
|
||||
tostring(component.Item.Prefab.Identifier)
|
||||
)
|
||||
end
|
||||
|
||||
--remove stuff that shouldnt be there that gets added inside sub editor only
|
||||
circuitbox_xml = remove_attribute_from_components(circuitbox_xml, "InventoryIconColor")
|
||||
circuitbox_xml = remove_attribute_from_components(circuitbox_xml, "ContainerColor")
|
||||
circuitbox_xml = remove_attribute_from_components(circuitbox_xml, "SpriteDepthWhenDropped")
|
||||
|
||||
-- Process Label strings
|
||||
circuitbox_xml = processLabelStrings(circuitbox_xml)
|
||||
|
||||
-- Process Input/Output node labels
|
||||
circuitbox_xml = processNodeLabels(circuitbox_xml)
|
||||
|
||||
-- Cleanup and formatting
|
||||
circuitbox_xml = put_components_in_order(circuitbox_xml)
|
||||
circuitbox_xml = renumber_components(circuitbox_xml)
|
||||
circuitbox_xml = round_position_values(circuitbox_xml)
|
||||
circuitbox_xml = clean_component_whitespace(circuitbox_xml)
|
||||
|
||||
return circuitbox_xml
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function blue_prints.save_blueprint(provided_path, folder)
|
||||
if Character.Controlled == nil then print("you dont have a character") return end
|
||||
if blue_prints.most_recent_circuitbox == nil then print("no circuitbox detected") return end
|
||||
|
||||
-- Default to "General" folder if none specified
|
||||
folder = folder or "General"
|
||||
|
||||
-- Store the folder and filename for future use
|
||||
blue_prints.most_recent_folder = folder
|
||||
blue_prints.most_recently_used_blueprint_name = provided_path:gsub("%.txt$", "") -- Remove .txt if present
|
||||
|
||||
-- Prepare the circuit box XML
|
||||
local circuitbox_xml = blue_prints.prepare_circuitbox_xml_for_saving()
|
||||
|
||||
-- Save using the folder-aware function
|
||||
return blue_prints.saveWithFolder(provided_path, folder, circuitbox_xml)
|
||||
end
|
223
Blueprints/Lua/unit_tests.lua
Normal file
223
Blueprints/Lua/unit_tests.lua
Normal file
@@ -0,0 +1,223 @@
|
||||
|
||||
|
||||
|
||||
|
||||
local function all_lines_the_same(filename, comparison_string)
|
||||
local file = io.open(filename, "r")
|
||||
if not file then
|
||||
print("Error: Unable to open file")
|
||||
return false
|
||||
end
|
||||
|
||||
local file_lines = {}
|
||||
for line in file:lines() do
|
||||
table.insert(file_lines, line)
|
||||
end
|
||||
file:close()
|
||||
|
||||
local string_lines = {}
|
||||
for line in comparison_string:gmatch("[^\r\n]+") do
|
||||
table.insert(string_lines, line)
|
||||
end
|
||||
|
||||
local max_lines = math.max(#file_lines, #string_lines)
|
||||
local all_lines_same = true
|
||||
|
||||
for i = 1, max_lines do
|
||||
local file_line = file_lines[i] or ""
|
||||
local string_line = string_lines[i] or ""
|
||||
|
||||
if file_line ~= string_line then
|
||||
all_lines_same = false
|
||||
print(string.format("Mismatch at line %d:", i))
|
||||
print("File: " .. file_line)
|
||||
print("String: " .. string_line)
|
||||
print()
|
||||
end
|
||||
end
|
||||
|
||||
return all_lines_same
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
local function check_file_against_string(file_path, comparison_string)
|
||||
local ignore_prefixes = {'<link', '<Item name="" identifier="circuitbox"', '<input name="signal_', '<output name="signal_', '<ItemContainer QuickUseMovesItemsInside="False"', '<Holdable Attached="True"', '<requireditem items="wrench"', '<Wire id=', '</input', '</output'}
|
||||
local ignore_anywhere = {'header="Blueprints" body="Circuit made with Blueprints. 
 
 Get it now on the steam workshop!"'}
|
||||
local file = io.open(file_path, "r")
|
||||
if not file then
|
||||
return false, "Unable to open file"
|
||||
end
|
||||
local file_lines = {}
|
||||
for line in file:lines() do
|
||||
table.insert(file_lines, line:match("^%s*(.+)"))
|
||||
end
|
||||
file:close()
|
||||
local comparison_lines = {}
|
||||
for line in comparison_string:gmatch("[^\r\n]+") do
|
||||
local should_ignore = false
|
||||
for _, prefix in ipairs(ignore_prefixes) do
|
||||
if line:match("^%s*" .. prefix) then
|
||||
should_ignore = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not should_ignore then
|
||||
for _, ignore_string in ipairs(ignore_anywhere) do
|
||||
if line:match(ignore_string) then
|
||||
should_ignore = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not should_ignore then
|
||||
table.insert(comparison_lines, line:match("^%s*(.+)"))
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, comparison_line in ipairs(comparison_lines) do
|
||||
local found = false
|
||||
for _, file_line in ipairs(file_lines) do
|
||||
if file_line == comparison_line then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
print("Line not found in file: " .. comparison_line)
|
||||
return false, "Line not found in file: " .. comparison_line
|
||||
end
|
||||
end
|
||||
return true, "All lines found in file"
|
||||
end
|
||||
--]]
|
||||
|
||||
|
||||
local function clean_string_for_comparison(string_to_clean)
|
||||
|
||||
string_to_clean = string_to_clean:gsub(' ', '') --remove all whitespace
|
||||
string_to_clean = string_to_clean:gsub('LinkToChat="false"', '') --link to chat is a special case because some servers do not allow it.
|
||||
string_to_clean = string_to_clean:gsub('LinkToChat="true"', '')
|
||||
|
||||
return string_to_clean
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function check_file_against_string(file_path, comparison_string)
|
||||
local file_path = blue_prints.normalizePath(file_path)
|
||||
local file_content = blue_prints.readFileContents(file_path)
|
||||
if not file_content then
|
||||
return false, "Unable to open file"
|
||||
end
|
||||
|
||||
local include_prefixes = {'<InputNode' , '<OutputNode' , '<ConnectionLabelOverride', '<Component', '<From name=' , '<To name=', '<Label id='}
|
||||
local ignore_anywhere = {'header="Blueprints" body="Circuit made with Blueprints. 
 
 Get it now on the steam workshop!"'}
|
||||
|
||||
|
||||
local file_lines = {}
|
||||
for line in file:lines() do
|
||||
local should_ignore = false
|
||||
for _, ignore_string in ipairs(ignore_anywhere) do
|
||||
if line:match(ignore_string) then
|
||||
should_ignore = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for _, prefix in ipairs(include_prefixes) do
|
||||
if line:match("^%s*" .. prefix) and should_ignore == false then
|
||||
table.insert(file_lines, line:match("^%s*(.+)"))
|
||||
--print("including line from file: " .. line)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
|
||||
local comparison_lines = {}
|
||||
for line in comparison_string:gmatch("[^\r\n]+") do
|
||||
|
||||
local should_ignore = false
|
||||
for _, ignore_string in ipairs(ignore_anywhere) do
|
||||
if line:match(ignore_string) then
|
||||
should_ignore = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for _, prefix in ipairs(include_prefixes) do
|
||||
if line:match("^%s*" .. prefix) and should_ignore == false then
|
||||
table.insert(comparison_lines, line:match("^%s*(.+)"))
|
||||
--print("including line from string: " .. line)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, comparison_line in ipairs(comparison_lines) do
|
||||
comparison_line = clean_string_for_comparison(comparison_line) --remove anything that should be ignored
|
||||
local found = false
|
||||
for _, file_line in ipairs(file_lines) do
|
||||
file_line = clean_string_for_comparison(file_line) --remove anything that should be ignored
|
||||
if file_line == comparison_line then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
print("Line not found in file: " .. comparison_line)
|
||||
return false, "Line not found in file: " .. comparison_line
|
||||
end
|
||||
end
|
||||
|
||||
for _, file_line in ipairs(file_lines) do
|
||||
file_line = clean_string_for_comparison(file_line) --remove anything that should be ignored
|
||||
local found = false
|
||||
for _, comparison_line in ipairs(comparison_lines) do
|
||||
comparison_line = clean_string_for_comparison(comparison_line) --remove anything that should be ignored
|
||||
if file_line == comparison_line then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
print("Line not found in string: " .. file_line)
|
||||
return false, "Line not found in string: " .. file_line
|
||||
end
|
||||
end
|
||||
|
||||
return true, "All lines found in file"
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function blue_prints.loading_complete_unit_test(path_to_loaded_file, loaded_circuit_xml)
|
||||
local result = check_file_against_string(path_to_loaded_file, loaded_circuit_xml)
|
||||
if not result then
|
||||
print('‖color:red‖"blueprint that failed its test: "' .. path_to_loaded_file .. '‖end‖')
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
function blue_prints.unit_test_all_blueprint_files()
|
||||
local saved_files = blue_prints.getFiles(blue_prints.save_path)
|
||||
current_delay = 0
|
||||
|
||||
for name, value in pairs(saved_files) do
|
||||
if string.match(value, "%.txt$") then
|
||||
local filename = value:match("([^/\\]+)$")
|
||||
local filename = string.gsub(filename, "%.txt$", "")
|
||||
|
||||
Timer.Wait(function() blue_prints.construct_blueprint(filename) end, current_delay)
|
||||
current_delay = current_delay + 12000
|
||||
end
|
||||
end
|
||||
end
|
300
Blueprints/Lua/utilities/read_write.lua
Normal file
300
Blueprints/Lua/utilities/read_write.lua
Normal file
@@ -0,0 +1,300 @@
|
||||
if SERVER then return end --prevents it from running on the server
|
||||
|
||||
-- Platform-agnostic file path handling
|
||||
function blue_prints.normalizePath(path)
|
||||
-- Replace Windows backslashes with forward slashes
|
||||
path = path:gsub("\\", "/")
|
||||
|
||||
-- Remove any double slashes
|
||||
path = path:gsub("//+", "/")
|
||||
|
||||
-- Remove trailing slash if present
|
||||
path = path:gsub("/$", "")
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
-- Safe file operations with error handling
|
||||
function blue_prints.safeFileOperation(operation, ...)
|
||||
local success, result = pcall(operation, ...)
|
||||
if not success then
|
||||
print("File operation failed: " .. tostring(result))
|
||||
return nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Enhanced file reading with platform checks
|
||||
function blue_prints.readFileContents(path)
|
||||
path = blue_prints.normalizePath(path)
|
||||
|
||||
-- Try direct read first
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
-- If direct read fails, try alternate path format
|
||||
local alt_path = path:gsub("LocalMods/", "local_mods/")
|
||||
file = io.open(alt_path, "r")
|
||||
if file then
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Enhanced directory checking
|
||||
function blue_prints.checkDirectory(path)
|
||||
path = blue_prints.normalizePath(path)
|
||||
|
||||
if not File.DirectoryExists(path) then
|
||||
-- Try creating with normalized path
|
||||
local success = blue_prints.safeFileOperation(File.CreateDirectory, path)
|
||||
if not success then
|
||||
-- Try alternate path format
|
||||
local alt_path = path:gsub("LocalMods/", "local_mods/")
|
||||
success = blue_prints.safeFileOperation(File.CreateDirectory, alt_path)
|
||||
if not success then
|
||||
print("Failed to create directory: " .. path)
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Enhanced file listing that handles both Windows and Linux paths
|
||||
function blue_prints.getFiles(path)
|
||||
path = blue_prints.normalizePath(path)
|
||||
|
||||
-- Try to get files with current path format
|
||||
local success, files = pcall(function() return File.GetFiles(path) end)
|
||||
if success and files and #files > 0 then
|
||||
return files
|
||||
end
|
||||
|
||||
-- If that fails, try with alternate path format
|
||||
success, files = pcall(function()
|
||||
return File.GetFiles(path:gsub("LocalMods/", "local_mods/"))
|
||||
end)
|
||||
if success and files and #files > 0 then
|
||||
return files
|
||||
end
|
||||
|
||||
-- If no files found, return empty table instead of nil
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Write file with platform compatibility
|
||||
function blue_prints.writeFile(path, content)
|
||||
path = blue_prints.normalizePath(path)
|
||||
path = path:gsub("%s", "_") -- Replace spaces with underscores
|
||||
|
||||
local file, err = io.open(path, "w")
|
||||
if file then
|
||||
file:write(content)
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Log the error
|
||||
print("Failed to write file: " .. path .. " with error: " .. tostring(err))
|
||||
|
||||
-- Try alternate path if direct write fails
|
||||
local alt_path = path:gsub("LocalMods/", "local_mods/")
|
||||
file, err = io.open(alt_path, "w")
|
||||
if file then
|
||||
file:write(content)
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Log the error
|
||||
print("Failed to write file: " .. alt_path .. " with error: " .. tostring(err))
|
||||
return false
|
||||
end
|
||||
|
||||
-- Debug helper function
|
||||
function blue_prints.printSystemInfo()
|
||||
print("Operating system: " .. (package.config:sub(1, 1) == '\\' and "Windows" or "Unix-like"))
|
||||
print("Save path: " .. blue_prints.save_path)
|
||||
print("Normalized save path: " .. blue_prints.normalizePath(blue_prints.save_path))
|
||||
end
|
||||
|
||||
-- Creates a folder if it doesn't exist, handling platform-specific path issues
|
||||
function blue_prints.createFolder(folderPath)
|
||||
folderPath = blue_prints.normalizePath(folderPath)
|
||||
|
||||
if not File.DirectoryExists(folderPath) then
|
||||
-- Try creating with normalized path
|
||||
local success = blue_prints.safeFileOperation(File.CreateDirectory, folderPath)
|
||||
if not success then
|
||||
-- Try alternate path format for different platforms
|
||||
local alt_path = folderPath:gsub("LocalMods/", "local_mods/")
|
||||
success = blue_prints.safeFileOperation(File.CreateDirectory, alt_path)
|
||||
if not success then
|
||||
print("Failed to create folder: " .. folderPath)
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Modified save function that handles folder paths
|
||||
function blue_prints.saveWithFolder(provided_path, folder, content)
|
||||
if not provided_path or not folder or not content then
|
||||
print("Error: Missing required parameters for save")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Create a complete path including the folder
|
||||
local fullPath
|
||||
|
||||
-- If folder is [Root Directory], save directly in the base save path
|
||||
if folder == "[Root Directory]" then
|
||||
fullPath = blue_prints.save_path
|
||||
else
|
||||
-- Clean and normalize the folder name
|
||||
folder = folder:gsub("[^%w%s%-_]", "") -- Remove special characters
|
||||
folder = folder:gsub("%s+", "_") -- Replace spaces with underscores
|
||||
fullPath = blue_prints.normalizePath(blue_prints.save_path .. "/" .. folder)
|
||||
end
|
||||
|
||||
-- Ensure the folder exists
|
||||
if not blue_prints.createFolder(fullPath) then
|
||||
print("Error: Could not create or access folder: " .. folder)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Add .txt extension if not present
|
||||
if not string.match(provided_path, "%.txt$") then
|
||||
provided_path = provided_path .. ".txt"
|
||||
end
|
||||
|
||||
-- Create the full file path
|
||||
local file_path = blue_prints.normalizePath(fullPath .. "/" .. provided_path)
|
||||
|
||||
-- Write the file
|
||||
if blue_prints.writeFile(file_path, content) then
|
||||
print("Blueprint saved to " .. file_path)
|
||||
return true
|
||||
else
|
||||
print("Error: Could not save blueprint")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Ensures the base blueprints directory exists
|
||||
function blue_prints.ensureBaseDirectory()
|
||||
local basePath = blue_prints.normalizePath(blue_prints.save_path)
|
||||
if not File.DirectoryExists(basePath) then
|
||||
local success = blue_prints.safeFileOperation(File.CreateDirectory, basePath)
|
||||
if not success then
|
||||
-- Try alternate path format
|
||||
local alt_path = basePath:gsub("LocalMods/", "local_mods/")
|
||||
success = blue_prints.safeFileOperation(File.CreateDirectory, alt_path)
|
||||
if not success then
|
||||
print("Failed to create base directory: " .. basePath)
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Gets all folders in the blueprints directory
|
||||
function blue_prints.getFolders(path)
|
||||
-- Ensure base directory exists first
|
||||
if not blue_prints.ensureBaseDirectory() then
|
||||
return {}
|
||||
end
|
||||
|
||||
path = blue_prints.normalizePath(path)
|
||||
|
||||
local folders = {}
|
||||
local success = pcall(function()
|
||||
folders = File.GetDirectories(path)
|
||||
end)
|
||||
|
||||
if not success or not folders or #folders == 0 then
|
||||
-- Try alternate path format
|
||||
local alt_path = path:gsub("LocalMods/", "local_mods/")
|
||||
success = pcall(function()
|
||||
folders = File.GetDirectories(alt_path)
|
||||
end)
|
||||
end
|
||||
|
||||
return folders or {}
|
||||
end
|
||||
|
||||
-- Gets list of folder names in a user-friendly format
|
||||
function blue_prints.getFolderList()
|
||||
local folders = blue_prints.getFolders(blue_prints.save_path)
|
||||
local folderNames = { "[Root Directory]" } -- Allow saving in root directory
|
||||
|
||||
for _, fullPath in pairs(folders) do
|
||||
-- Extract just the folder name from the full path
|
||||
local folderName = fullPath:match("([^/\\]+)$")
|
||||
if folderName then
|
||||
table.insert(folderNames, folderName)
|
||||
end
|
||||
end
|
||||
|
||||
return folderNames
|
||||
end
|
||||
|
||||
-- Creates a new folder in the blueprints directory
|
||||
function blue_prints.createNewFolder(folderName)
|
||||
if not blue_prints.ensureBaseDirectory() then
|
||||
return false, "Cannot create base directory"
|
||||
end
|
||||
|
||||
if not folderName or folderName == "" then
|
||||
return false, "Invalid folder name"
|
||||
end
|
||||
|
||||
-- Clean the folder name
|
||||
folderName = folderName:gsub("[^%w%s%-_]", ""):gsub("%s+", "_")
|
||||
local folderPath = blue_prints.normalizePath(blue_prints.save_path .. "/" .. folderName)
|
||||
|
||||
-- Check if folder already exists
|
||||
if File.DirectoryExists(folderPath) then
|
||||
return false, "Folder already exists"
|
||||
end
|
||||
|
||||
-- Create the folder
|
||||
if blue_prints.createFolder(folderPath) then
|
||||
return true, folderName
|
||||
else
|
||||
return false, "Failed to create folder"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Modified Directory Functions
|
||||
function blue_prints.getDirectories(path)
|
||||
path = blue_prints.normalizePath(path)
|
||||
|
||||
-- Try to get directories with current path format
|
||||
local success, dirs = pcall(function() return File.GetDirectories(path) end)
|
||||
if success and dirs and #dirs > 0 then
|
||||
return dirs
|
||||
end
|
||||
|
||||
-- If that fails, try with alternate path format
|
||||
success, dirs = pcall(function()
|
||||
return File.GetDirectories(path:gsub("LocalMods/", "local_mods/"))
|
||||
end)
|
||||
if success and dirs and #dirs > 0 then
|
||||
return dirs
|
||||
end
|
||||
|
||||
-- If no directories found, return empty table instead of nil
|
||||
return {}
|
||||
end
|
156
Blueprints/Lua/utilities/safety_checks.lua
Normal file
156
Blueprints/Lua/utilities/safety_checks.lua
Normal file
@@ -0,0 +1,156 @@
|
||||
if SERVER then return end --prevents it from running on the server
|
||||
|
||||
function blue_prints.validate_blueprint_xml(xmlString)
|
||||
if not xmlString then
|
||||
return false, "No XML content provided"
|
||||
end
|
||||
|
||||
-- Check for required main elements
|
||||
if not xmlString:match("<CircuitBox.-</CircuitBox>") then
|
||||
return false, "Missing CircuitBox element"
|
||||
end
|
||||
if not xmlString:match("<InputNode[^>]+>") then
|
||||
return false, "Missing InputNode"
|
||||
end
|
||||
if not xmlString:match("<OutputNode[^>]+>") then
|
||||
return false, "Missing OutputNode"
|
||||
end
|
||||
|
||||
-- Validate input/output node positions
|
||||
local inputNode = xmlString:match("<InputNode[^>]+>")
|
||||
local outputNode = xmlString:match("<OutputNode[^>]+>")
|
||||
|
||||
if inputNode then
|
||||
local posX, posY = inputNode:match('pos="([%d%.%-]+),([%d%.%-]+)"')
|
||||
if not (posX and posY and tonumber(posX) and tonumber(posY)) then
|
||||
return false, "Invalid InputNode position"
|
||||
end
|
||||
end
|
||||
|
||||
if outputNode then
|
||||
local posX, posY = outputNode:match('pos="([%d%.%-]+),([%d%.%-]+)"')
|
||||
if not (posX and posY and tonumber(posX) and tonumber(posY)) then
|
||||
return false, "Invalid OutputNode position"
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate components
|
||||
for component in xmlString:gmatch('<Component.-/>') do
|
||||
-- Check required attributes
|
||||
local id = component:match('id="(%d+)"')
|
||||
local posX, posY = component:match('position="([%-%d%.]+),([%-%d%.]+)"')
|
||||
|
||||
-- Check both old and new format for item and class
|
||||
local item = component:match('item="([^"]+)"') or
|
||||
component:match('item=<<<STRINGSTART>>>([^<]+)<<<STRINGEND>>>')
|
||||
local class = component:match('Class="([^"]+)"') or
|
||||
component:match('Class=<<<STRINGSTART>>>([^<]+)<<<STRINGEND>>>')
|
||||
|
||||
if not (id and posX and posY and item and class) then
|
||||
return false, "Invalid component definition - missing required attributes"
|
||||
end
|
||||
|
||||
-- Validate position values are numbers
|
||||
if not (tonumber(posX) and tonumber(posY)) then
|
||||
return false, "Invalid component position values"
|
||||
end
|
||||
|
||||
-- Validate ID is a number
|
||||
if not tonumber(id) then
|
||||
return false, "Invalid component ID"
|
||||
end
|
||||
|
||||
-- Check if item prefab exists
|
||||
if item == "oscillatorcomponent" then item = "oscillator"
|
||||
elseif item == "concatenationcomponent" then item = "concatcomponent"
|
||||
elseif item == "exponentiationcomponent" then item = "powcomponent"
|
||||
elseif item == "regexfind" then item = "regexcomponent"
|
||||
elseif item == "signalcheck" then item = "signalcheckcomponent"
|
||||
elseif item == "squareroot" then item = "squarerootcomponent" end
|
||||
|
||||
local itemPrefab = ItemPrefab.GetItemPrefab(item)
|
||||
if not itemPrefab then
|
||||
return false, "Invalid item prefab: " .. tostring(item)
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate wire connections
|
||||
for wire in xmlString:gmatch("<Wire.-</Wire>") do
|
||||
local id = wire:match('id="(%d+)"')
|
||||
if not id or not tonumber(id) then
|
||||
return false, "Invalid wire ID"
|
||||
end
|
||||
|
||||
-- Check wire prefab
|
||||
local prefab = wire:match('prefab="([^"]+)"')
|
||||
if not prefab then
|
||||
return false, "Missing wire prefab"
|
||||
end
|
||||
|
||||
local wirePrefab = ItemPrefab.GetItemPrefab(prefab)
|
||||
if not wirePrefab then
|
||||
return false, "Invalid wire prefab: " .. tostring(prefab)
|
||||
end
|
||||
|
||||
-- Check connections
|
||||
local fromName, fromTarget = wire:match('<From name="([^"]+)" target="([^"]*)"')
|
||||
local toName, toTarget = wire:match('<To name="([^"]+)" target="([^"]*)"')
|
||||
|
||||
if not (fromName and toName) then
|
||||
return false, "Invalid wire connections"
|
||||
end
|
||||
|
||||
-- Validate signal names format
|
||||
if fromTarget == "" and not fromName:match("^signal_%w+%d+$") then
|
||||
return false, "Invalid input signal name format"
|
||||
end
|
||||
if toTarget == "" and not toName:match("^signal_%w+%d+$") then
|
||||
return false, "Invalid output signal name format"
|
||||
end
|
||||
end
|
||||
|
||||
-- Validate labels
|
||||
for label in xmlString:gmatch('<Label.-/>') do
|
||||
local id = label:match('id="(%d+)"')
|
||||
local posX, posY = label:match('position="([%d%.%-]+),([%d%.%-]+)"')
|
||||
local sizeW, sizeH = label:match('size="([%d%.%-]+),([%d%.%-]+)"')
|
||||
local color = label:match('color="([^"]+)"')
|
||||
|
||||
if not (id and posX and posY and sizeW and sizeH and color) then
|
||||
return false, "Invalid label definition - missing required attributes"
|
||||
end
|
||||
|
||||
-- Validate numeric values
|
||||
if not (tonumber(posX) and tonumber(posY) and
|
||||
tonumber(sizeW) and tonumber(sizeH)) then
|
||||
return false, "Invalid label position or size values"
|
||||
end
|
||||
|
||||
-- Validate color format (basic check for hex color)
|
||||
if not color:match("^#[0-9A-Fa-f]+$") then
|
||||
return false, "Invalid label color format"
|
||||
end
|
||||
end
|
||||
|
||||
-- If all checks pass
|
||||
return true, "Blueprint XML is valid"
|
||||
end
|
||||
|
||||
function blue_prints.validate_blueprint_file(provided_path)
|
||||
-- Add .txt extension if not present
|
||||
if not string.match(provided_path, "%.txt$") then
|
||||
provided_path = provided_path .. ".txt"
|
||||
end
|
||||
|
||||
-- Normalize the path
|
||||
local file_path = blue_prints.normalizePath(blue_prints.save_path .. "/" .. provided_path)
|
||||
|
||||
-- Try to read the file content using our safe read function
|
||||
local xmlContent = blue_prints.readFileContents(file_path)
|
||||
if not xmlContent then
|
||||
return false, "Could not read file: " .. provided_path
|
||||
end
|
||||
|
||||
-- Use the XML string validator to check the content
|
||||
return blue_prints.validate_blueprint_xml(xmlContent)
|
||||
end
|
439
Blueprints/Lua/utilities/utilities.lua
Normal file
439
Blueprints/Lua/utilities/utilities.lua
Normal file
@@ -0,0 +1,439 @@
|
||||
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)
|
||||
|
Reference in New Issue
Block a user