diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_10K_Fluid.json b/ContentLib/RecipePatches/Recipe_DS_Drive_10K_Fluid.json index 6ca75d8..db884da 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_10K_Fluid.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_10K_Fluid.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Fluid.json b/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Fluid.json index 7693955..fe467e9 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Fluid.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Fluid.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Gas.json b/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Gas.json index 1ca9f73..edeb317 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Gas.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_120K_Gas.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_16K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_16K.json index 53818ca..6898af7 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_16K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_16K.json @@ -34,7 +34,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_16K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_1K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_1K.json index 9d17c03..9df10ae 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_1K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_1K.json @@ -26,7 +26,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_1K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_20K_Fluid.json b/ContentLib/RecipePatches/Recipe_DS_Drive_20K_Fluid.json index ab8c256..5788b03 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_20K_Fluid.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_20K_Fluid.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Fluid.json b/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Fluid.json index 2e7d1eb..e370610 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Fluid.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Fluid.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Gas.json b/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Gas.json index 34644cd..0a61a9f 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Gas.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_240K_Gas.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_256.json b/ContentLib/RecipePatches/Recipe_DS_Drive_256.json index bac804d..5dfa428 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_256.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_256.json @@ -26,7 +26,7 @@ "UnlockedBy": [ "Schem_DS_T3_2_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_2K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_2K.json index 3bcd678..b7c7166 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_2K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_2K.json @@ -26,7 +26,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_2K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_32K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_32K.json index fbcd043..d7a235b 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_32K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_32K.json @@ -34,7 +34,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_32K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 60.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Fluid.json b/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Fluid.json index 495561b..c3ade70 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Fluid.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Fluid.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Gas.json b/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Gas.json index f6ff4f0..62b4fba 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Gas.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_40K_Gas.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_4K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_4K.json index 08a43ff..677d031 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_4K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_4K.json @@ -26,7 +26,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_4K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_512.json b/ContentLib/RecipePatches/Recipe_DS_Drive_512.json index 991013a..43476d5 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_512.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_512.json @@ -26,7 +26,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_512_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Fluid.json b/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Fluid.json index b7f6ec2..689e0bd 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Fluid.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Fluid.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Gas.json b/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Gas.json index 6316a40..fd3f616 100644 Binary files a/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Gas.json and b/ContentLib/RecipePatches/Recipe_DS_Drive_60K_Gas.json differ diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_64K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_64K.json index fc7fa12..54b8521 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_64K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_64K.json @@ -38,7 +38,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_64K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 60.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Drive_8K.json b/ContentLib/RecipePatches/Recipe_DS_Drive_8K.json index 9a98ff3..ce324d8 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Drive_8K.json +++ b/ContentLib/RecipePatches/Recipe_DS_Drive_8K.json @@ -30,7 +30,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Drives_8K_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 45.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_Multitool.json b/ContentLib/RecipePatches/Recipe_DS_Multitool.json index 2f2ae28..4ebb4bf 100644 --- a/ContentLib/RecipePatches/Recipe_DS_Multitool.json +++ b/ContentLib/RecipePatches/Recipe_DS_Multitool.json @@ -30,7 +30,7 @@ "UnlockedBy": [ "Schematic_DS_Mam_Adapters_Universal_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 1.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_NetworkCable_Item.json b/ContentLib/RecipePatches/Recipe_DS_NetworkCable_Item.json index a9bbb7e..d61f1aa 100644 --- a/ContentLib/RecipePatches/Recipe_DS_NetworkCable_Item.json +++ b/ContentLib/RecipePatches/Recipe_DS_NetworkCable_Item.json @@ -26,7 +26,7 @@ "UnlockedBy": [ "Schem_DS_T3_1_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 5.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_WirelessExtender.json b/ContentLib/RecipePatches/Recipe_DS_WirelessExtender.json index 7f09dd3..bf4e896 100644 --- a/ContentLib/RecipePatches/Recipe_DS_WirelessExtender.json +++ b/ContentLib/RecipePatches/Recipe_DS_WirelessExtender.json @@ -30,7 +30,7 @@ "UnlockedBy": [ "Schem_DS_T6_2_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 1.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/ContentLib/RecipePatches/Recipe_DS_WirelessTablet.json b/ContentLib/RecipePatches/Recipe_DS_WirelessTablet.json index f2bbc92..694f4c3 100644 --- a/ContentLib/RecipePatches/Recipe_DS_WirelessTablet.json +++ b/ContentLib/RecipePatches/Recipe_DS_WirelessTablet.json @@ -30,7 +30,7 @@ "UnlockedBy": [ "Schem_DS_T6_2_C" ], - "ManualManufacturingMultiplier": 0.0, + "ManualManufacturingDuration": 1.0, "VariablePowerConsumptionConstant": 0.0, "VariablePowerConsumptionFactor": 1.0, "ClearIngredients": true, diff --git a/magefile.go b/magefile.go index 3992d38..888981c 100644 --- a/magefile.go +++ b/magefile.go @@ -21,27 +21,34 @@ import ( ) const ( - zipName = "DigitalStorageTweaks.zip" - contentDir = "./ContentLib" - pluginFile = "./DigitalStorageTweaks.uplugin" - schemaBaseURL = "https://raw.githubusercontent.com/budak7273/ContentLib_Documentation/main/JsonSchemas/" - schemaDir = "./schema" + zipName = "DigitalStorageTweaks.zip" + contentDir = "./ContentLib" + pluginFile = "./DigitalStorageTweaks.uplugin" + schemaPath = "./schema/CL_Recipe.json" // Local schema path ) -var binaryExtensions = map[string]bool{ - ".png": true, ".jpg": true, ".jpeg": true, ".bmp": true, ".gif": true, - ".dds": true, ".tga": true, ".psd": true, ".fbx": true, ".uasset": true, - ".umap": true, -} +var ( + // Binary file extensions to skip line ending conversion + binaryExtensions = map[string]bool{ + ".png": true, ".jpg": true, ".jpeg": true, ".bmp": true, ".gif": true, + ".dds": true, ".tga": true, ".psd": true, ".fbx": true, ".uasset": true, + ".umap": true, + } +) +// Default target var Default = Build +// Build runs the full pipeline func Build() { mg.SerialDeps(Validate, Package) } +// Package creates the distribution zip func Package() error { fmt.Println("Packaging files...") + + // Create target directories for _, dir := range []string{"Windows", "WindowsServer", "LinuxServer"} { if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("creating %s: %w", dir, err) @@ -50,126 +57,140 @@ func Package() error { return fmt.Errorf("copying to %s: %w", dir, err) } } + + // Create zip file if err := createZip(zipName, "Windows", "WindowsServer", "LinuxServer"); err != nil { return fmt.Errorf("creating zip: %w", err) } + + // Clean temp dirs return Clean("Windows", "WindowsServer", "LinuxServer") } - func Validate() error { fmt.Println("Validating files...") + + const ( + baseSchemaURL = "https://raw.githubusercontent.com/budak7273/ContentLib_Documentation/main/JsonSchemas/" + localSchemaDir = "./schema" + ) + compiler := jsonschema.NewCompiler() + + // Disable remote fetching compiler.LoadURL = func(url string) (io.ReadCloser, error) { return nil, fmt.Errorf("remote schema loading disabled: %s", url) } - if err := filepath.Walk(schemaDir, func(path string, info os.FileInfo, err error) error { - if err != nil || info.IsDir() || filepath.Ext(path) != ".json" { + // Walk through the schema directory and add all .json files + err := filepath.Walk(localSchemaDir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { return err } - relPath, err := filepath.Rel(schemaDir, path) + + if filepath.Ext(path) != ".json" { + return nil + } + + relPath, err := filepath.Rel(localSchemaDir, path) if err != nil { - return err + return fmt.Errorf("resolving relative path: %w", err) } - id := schemaBaseURL + filepath.ToSlash(relPath) + + // Build the full schema ID + schemaID := baseSchemaURL + filepath.ToSlash(relPath) + data, err := ioutil.ReadFile(path) if err != nil { - return err + return fmt.Errorf("reading %s: %w", path, err) } - return compiler.AddResource(id, bytes.NewReader(data)) - }); err != nil { + + err = compiler.AddResource(schemaID, bytes.NewReader(data)) + if err != nil { + return fmt.Errorf("adding schema %s: %w", schemaID, err) + } + + return nil + }) + if err != nil { return fmt.Errorf("failed to load schemas: %w", err) } - schema, err := compiler.Compile(schemaBaseURL + "CL_Recipe.json") + // Main schema (must match its $id) + mainSchemaID := baseSchemaURL + "CL_Recipe.json" + + // Compile the main schema + schema, err := compiler.Compile(mainSchemaID) if err != nil { return fmt.Errorf("invalid main schema: %w", err) } - var failed []string - paths := []string{pluginFile} - _ = filepath.Walk(contentDir, func(path string, info os.FileInfo, err error) error { - if err == nil && !info.IsDir() { - paths = append(paths, path) + // Validate plugin file + if err := validateFile(pluginFile, schema); err != nil { + return fmt.Errorf("plugin file: %w", err) + } + + // Validate all JSON files in the content directory + return filepath.Walk(contentDir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err } - return nil + return validateFile(path, schema) }) - - for _, path := range paths { - if err := validateFile(path, schema); err != nil { - failed = append(failed, fmt.Sprintf("%s: %v", path, err)) - } - } - - if len(failed) > 0 { - fmt.Println("Validation errors:") - for _, msg := range failed { - fmt.Println(" -", msg) - } - return fmt.Errorf("%d file(s) failed validation", len(failed)) - } - - fmt.Println("All files validated successfully.") - return nil } func validateFile(path string, schema *jsonschema.Schema) error { - if binaryExtensions[filepath.Ext(path)] { + // Skip binary files + ext := filepath.Ext(path) + if binaryExtensions[ext] { return nil } - if filepath.Ext(path) == ".json" { + + if ext == ".json" { return validateJSON(path, schema) } return validateEncoding(path) } func validateJSON(path string, schema *jsonschema.Schema) error { - data, err := ioutil.ReadFile(path) + content, err := ioutil.ReadFile(path) if err != nil { - return err + return fmt.Errorf("failed to read JSON file: %w", err) } - data = sanitizeJSONBytes(data) - var lines [][]byte - for _, line := range bytes.Split(data, []byte("\n")) { - trim := bytes.TrimSpace(line) - if !bytes.HasPrefix(trim, []byte("//")) && len(trim) > 0 { - lines = append(lines, line) + // Remove comment lines (starting with //) + var filteredContent []byte + for _, line := range bytes.Split(content, []byte("\n")) { + trimmed := bytes.TrimSpace(line) + if !bytes.HasPrefix(trimmed, []byte("//")) && len(trimmed) > 0 { + filteredContent = append(filteredContent, line...) + filteredContent = append(filteredContent, '\n') } } - clean := bytes.Join(lines, []byte("\n")) + // Validate JSON var v interface{} - if err := json.Unmarshal(clean, &v); err != nil { - return err + if err := json.Unmarshal(filteredContent, &v); err != nil { + return fmt.Errorf("invalid JSON in %s: %w", path, err) } - return schema.Validate(v) -} -func sanitizeJSONBytes(data []byte) []byte { - data = bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF}) - var out bytes.Buffer - for len(data) > 0 { - r, size := utf8.DecodeRune(data) - if r == utf8.RuneError && size == 1 || r == '\x00' { - data = data[1:] - continue - } - out.WriteRune(r) - data = data[size:] + if err := schema.Validate(v); err != nil { + return fmt.Errorf("schema validation failed for %s: %w", path, err) } - return out.Bytes() + + return nil } func validateEncoding(path string) error { - data, err := ioutil.ReadFile(path) + content, err := ioutil.ReadFile(path) if err != nil { return err } - for i := 0; i < len(data); { - if data[i] > 127 { - // DO NOT declare "size", it is unused and will cause a compiler error - r, _ := utf8.DecodeRune(data[i:]) + + // Check for non-ASCII + for i := 0; i < len(content); { + b := content[i] + if b > 127 { // Non-ASCII + r, _ := utf8.DecodeRune(content[i:]) if r == utf8.RuneError { return fmt.Errorf("invalid UTF-8 sequence at position %d", i) } @@ -180,6 +201,7 @@ func validateEncoding(path string) error { return nil } +// Clean removes all build artifacts func Clean(list ...string) error { fmt.Println("Cleaning up...") for _, f := range list { @@ -190,67 +212,100 @@ func Clean(list ...string) error { return nil } +// createZip creates a zip file from the specified directories func createZip(zipPath string, dirs ...string) error { + // Create zip file zipFile, err := os.Create(zipPath) if err != nil { return fmt.Errorf("creating zip file: %w", err) } defer zipFile.Close() + + // Create zip writer zipWriter := zip.NewWriter(zipFile) defer zipWriter.Close() + + // Add each directory to the zip for _, dir := range dirs { if err := addDirToZip(zipWriter, dir); err != nil { return fmt.Errorf("adding %s to zip: %w", dir, err) } } + return nil } +// addDirToZip adds a directory to the zip, converting text files to DOS line endings func addDirToZip(zipWriter *zip.Writer, dirPath string) error { return filepath.Walk(dirPath, func(filePath string, info os.FileInfo, err error) error { if err != nil { return err } + + // Create relative path for the zip file relPath, err := filepath.Rel(dirPath, filePath) if err != nil { return err } zipPath := filepath.Join(filepath.Base(dirPath), relPath) + + // Create zip file header header, err := zip.FileInfoHeader(info) if err != nil { return err } - header.Name = filepath.ToSlash(zipPath) + header.Name = filepath.ToSlash(zipPath) // Use forward slashes for zip compatibility + + // Use compression for all files header.Method = zip.Deflate + + // Handle directories if info.IsDir() { header.Name += "/" _, err := zipWriter.CreateHeader(header) return err } + + // Open source file file, err := os.Open(filePath) if err != nil { return err } defer file.Close() + + // Create writer in zip writer, err := zipWriter.CreateHeader(header) if err != nil { return err } - if binaryExtensions[strings.ToLower(filepath.Ext(filePath))] { + + // Process based on file type + ext := strings.ToLower(filepath.Ext(filePath)) + if binaryExtensions[ext] { + // Binary file - copy directly _, err = io.Copy(writer, file) return err - } - content, err := ioutil.ReadAll(file) - if err != nil { + } else { + // Text file - read content and convert line endings + content, err := ioutil.ReadAll(file) + if err != nil { + return err + } + + // Convert LF to CRLF + content = convertToDOSLineEndings(content) + + // Write to zip + _, err = writer.Write(content) return err } - content = convertToDOSLineEndings(content) - _, err = writer.Write(content) - return err }) } +// convertToDOSLineEndings converts LF to CRLF while preserving existing CRLF func convertToDOSLineEndings(content []byte) []byte { + // First normalize to LF normalized := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n")) + // Then convert to CRLF return bytes.ReplaceAll(normalized, []byte("\n"), []byte("\r\n")) } diff --git a/makefile b/makefile new file mode 100644 index 0000000..4cf4348 --- /dev/null +++ b/makefile @@ -0,0 +1,9 @@ +ZIP_NAME = DigitalStorageTweaks.zip +FILES = ./ContentLib ./DigitalStorageTweaks.uplugin + +all: + mkdir -p Windows WindowsServer LinuxServer + cp -r $(FILES) Windows/ + cp -r $(FILES) WindowsServer/ + cp -r $(FILES) LinuxServer/ + 7z a -r $(ZIP_NAME) Windows/ LinuxServer/ WindowsServer/ \ No newline at end of file