Lots of changes, too late to describe it all.

This commit is contained in:
wincent
2023-07-15 10:51:25 +02:00
parent c27aa174c0
commit 9599be8db4
60 changed files with 5550 additions and 4258 deletions

View File

@@ -9,6 +9,12 @@ namespace InfectedRose.Core
public static string ReadNiString(this BitReader @this, bool wide = false, bool small = false)
{
var len = small ? @this.Read<byte>() : @this.Read<uint>();
if (len == uint.MaxValue)
{
return string.Empty;
}
var str = new char[len];
for (var i = 0; i < len; i++)

View File

@@ -62,6 +62,27 @@ namespace InfectedRose.Database
}
}
public int GetFieldIndex(string name)
{
var index = -1;
for (var i = 0; i < Table.TableInfo.Count; i++)
{
var column = Table.TableInfo[i];
if (column.Name == name) index = i;
}
if (index == -1)
{
throw new KeyNotFoundException(
$"The given field key of '{name}' does not exist in the {Table.Name} table"
);
}
return index;
}
public IEnumerator<Field> GetEnumerator()
{
int index = default;

View File

@@ -286,6 +286,24 @@ namespace InfectedRose.Database
return Create(max + 1);
}
public Row CreateMin(int minKey)
{
if (TableInfo[0].Type != DataType.Integer)
throw new NotSupportedException("AccessDatabase can only generate primary keys for Int32 types.");
var max = Count > 0 ? this.Max(c => c.Key) : 0;
for (var i = minKey; i < max; i++)
{
if (!ContainsKey(i) && !ClaimedKeys.Contains(i))
{
return Create(i);
}
}
return Create(max + 1);
}
public Row CreateWithFilter(ICollection<int> exclude)
{
if (TableInfo[0].Type != DataType.Integer)

View File

@@ -1,14 +1,13 @@
using System.Collections.Generic;
namespace InfectedRose.Interface
{
/// <summary>
/// A list of items that were generated by the mod and put into the main resource directory
///
/// These items are deleted upon startup.
/// </summary>
public class Artifacts : Dictionary<string, string>
{
namespace InfectedRose.Interface;
/// <summary>
/// A list of items that were generated by the mod and put into the main resource directory
///
/// These items are deleted upon startup.
/// </summary>
public class Artifacts : List<string>
{
}
}

View File

@@ -0,0 +1,151 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace InfectedRose.Interface;
public static class JsonExtensions
{
public static T? ToObject<T>(this JsonElement element, JsonSerializerOptions? options = null)
{
var bufferWriter = new ArrayBufferWriter<byte>();
using (var writer = new Utf8JsonWriter(bufferWriter))
element.WriteTo(writer);
return JsonSerializer.Deserialize<T>(bufferWriter.WrittenSpan, options);
}
public static T? ToObject<T>(this JsonDocument document, JsonSerializerOptions? options = null)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
return document.RootElement.ToObject<T>(options);
}
public static T? ToObject<T>(this JsonValue element, JsonSerializerOptions? options = null)
{
var bufferWriter = new ArrayBufferWriter<byte>();
using (var writer = new Utf8JsonWriter(bufferWriter))
element.WriteTo(writer);
return JsonSerializer.Deserialize<T>(bufferWriter.WrittenSpan, options);
}
public static Dictionary<string, object> ToDictionary(this JsonValue element)
{
var dictionary = new Dictionary<string, object>();
var str = element.ToString();
var json = JsonDocument.Parse(str);
var root = json.RootElement;
foreach (var property in root.EnumerateObject())
{
var value = property.Value;
if (value.ValueKind == JsonValueKind.Array)
{
var array = new List<object>();
foreach (var item in value.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.Object)
{
array.Add(item.ToDictionary());
}
else
{
array.Add(item.ToString());
}
}
dictionary.Add(property.Name, array);
}
else if (value.ValueKind == JsonValueKind.Object)
{
dictionary.Add(property.Name, value.ToDictionary());
}
else if (value.ValueKind == JsonValueKind.String)
{
dictionary.Add(property.Name, value.ToString());
}
else if (value.ValueKind == JsonValueKind.Number)
{
dictionary.Add(property.Name, value.GetDouble());
}
else if (value.ValueKind == JsonValueKind.True)
{
dictionary.Add(property.Name, true);
}
else if (value.ValueKind == JsonValueKind.False)
{
dictionary.Add(property.Name, false);
}
else
{
dictionary.Add(property.Name, value.ToString());
}
}
return dictionary;
}
public static Dictionary<string, object> ToDictionary(this JsonElement element)
{
var dictionary = new Dictionary<string, object>();
foreach (var property in element.EnumerateObject())
{
var value = property.Value;
if (value.ValueKind == JsonValueKind.Array)
{
var array = new List<object>();
foreach (var item in value.EnumerateArray())
{
if (item.ValueKind == JsonValueKind.Object)
{
array.Add(item.ToDictionary());
}
else
{
array.Add(item.ToString());
}
}
dictionary.Add(property.Name, array);
}
else if (value.ValueKind == JsonValueKind.Object)
{
dictionary.Add(property.Name, value.ToDictionary());
}
else if (value.ValueKind == JsonValueKind.String)
{
dictionary.Add(property.Name, value.ToString());
}
else if (value.ValueKind == JsonValueKind.Number)
{
dictionary.Add(property.Name, value.GetDouble());
}
else if (value.ValueKind == JsonValueKind.True)
{
dictionary.Add(property.Name, true);
}
else if (value.ValueKind == JsonValueKind.False)
{
dictionary.Add(property.Name, false);
}
else
{
dictionary.Add(property.Name, value.ToString());
}
}
return dictionary;
}
}

View File

@@ -1,51 +1,51 @@
using System.Collections.Generic;
using System.Xml.Serialization;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
[XmlRoot("locales")]
public class Locales
{
[XmlRoot("locales")]
public class Locales
{
[XmlAttribute("count")]
public int Count { get; set; }
[XmlElement("locale")]
public string[] Locale { get; set; }
}
}
[XmlRoot("translation")]
public class Translation
{
[XmlRoot("translation")]
public class Translation
{
[XmlAttribute("locale")]
public string Locale { get; set; }
[XmlText]
public string Text { get; set; }
}
}
[XmlRoot("phrase")]
public class Phrase
{
[XmlRoot("phrase")]
public class Phrase
{
[XmlAttribute("id")]
public string Id { get; set; }
[XmlElement("translation")]
public List<Translation> Translations { get; set; }
}
}
[XmlRoot("phrases")]
public class Phrases
{
[XmlRoot("phrases")]
public class Phrases
{
[XmlAttribute("count")]
public int Count { get; set; }
[XmlElement("phrase")]
public List<Phrase> Phrase { get; set; }
}
}
[XmlRoot("localization")]
public class Localization
{
[XmlRoot("localization")]
public class Localization
{
[XmlAttribute("version")]
public float Version { get; set; } = 1.200000f;
@@ -54,5 +54,4 @@ namespace InfectedRose.Interface
[XmlElement("phrases")]
public Phrases Phrases { get; set; }
}
}

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public class Lookup : Dictionary<string, int>
{
public class Lookup : Dictionary<string, int>
{
}
}

View File

@@ -1,13 +1,15 @@
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public class Manifest
{
public class Manifest
{
[JsonPropertyName("name")]
public string Name { get; set; } = "mod-name";
[JsonPropertyName("files")]
public string[] Files { get; set; } = {"mod.json"};
}
[JsonPropertyName("resources")]
public string Resources { get; set; } = "resources";
}

View File

@@ -6,10 +6,10 @@ using System.Text.Json.Serialization;
using InfectedRose.Interface.Templates;
using InfectedRose.Interface.Templates.ValueTypes;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public class Mod
{
public class Mod
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
@@ -23,7 +23,7 @@ namespace InfectedRose.Interface
public string Type { get; set; } = "";
[JsonPropertyName("action")]
public string Action { get; set; } = "add";
public string Action { get; set; }
[JsonPropertyName("show-defaults")]
public bool? ShowDefaults { get; set; }
@@ -53,10 +53,7 @@ namespace InfectedRose.Interface
public Zone? Zone { get; set; }
[JsonPropertyName("locale")]
public Dictionary<string, string>? Locale { get; set; } = new Dictionary<string, string>
{
{"en_US", ""}
};
public Dictionary<string, string>? Locale { get; set; }
[JsonPropertyName("values")]
public Dictionary<string, object?> Values { get; set; } = new Dictionary<string, object?>();
@@ -111,5 +108,4 @@ namespace InfectedRose.Interface
{
return (int) Enum.Parse(typeof(ComponentId), Type);
}
}
}

View File

@@ -8,17 +8,17 @@ using InfectedRose.Database;
using InfectedRose.Database.Fdb;
using InfectedRose.Interface.Templates;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public class IdCallback
{
public class IdCallback
{
public string Id { get; set; }
public Action<int> Callback { get; set; }
}
}
public static class ModContext
{
public static class ModContext
{
public static Mods Configuration { get; set; }
public static string Root { get; set; }
@@ -52,30 +52,6 @@ namespace InfectedRose.Interface
return false;
}
public static void AwaitId(JsonValue json, Action<int> callback)
{
if (json.TryGetValue<int>(out var value))
{
callback(value);
return;
}
if (!json.TryGetValue<string>(out var id)) throw new Exception($"Invalid id: {json}");
if (id.StartsWith("lego-universe:"))
{
// Parse ID from legacy format
callback(int.Parse(id[14..]));
return;
}
if (!id.Contains(':')) throw new Exception($"Invalid id: {json}, does not contain any ':' to define scope");
IdCallbacks.Add(new IdCallback { Id = id, Callback = callback });
}
public static bool AwaitIdIfRequired(JsonValue json, Action<int> callback)
{
if (!IsId(json)) return false;
@@ -86,8 +62,13 @@ namespace InfectedRose.Interface
}
public static void AwaitId(string id, Action<int> callback, bool requireMod = false, bool nonDefault = false)
public static void AwaitId(string? id, Action<int> callback, bool requireMod = false, bool nonDefault = false)
{
if (id == null)
{
return;
}
if (int.TryParse(id, out var integer))
{
callback(integer);
@@ -113,6 +94,43 @@ namespace InfectedRose.Interface
IdCallbacks.Add(new IdCallback { Id = id, Callback = callback });
}
public static void AwaitId(JsonValue json, Action<int> callback, bool requireMod = false, bool nonDefault = false)
{
if (json.TryGetValue<int>(out var value))
{
callback(value);
return;
}
if (json.TryGetValue<string>(out var id))
{
AwaitId(id, callback, requireMod, nonDefault);
return;
}
throw new Exception($"Undefined reference to id: {json}");
}
public static void AwaitMultiple(string[] ids, Action callback)
{
var count = 0;
foreach (var id in ids)
{
AwaitId(id, _ =>
{
count++;
if (count == ids.Length)
{
callback();
}
});
}
}
public static int AssertId(JsonValue json)
{
if (json.TryGetValue<int>(out var value))
@@ -162,54 +180,68 @@ namespace InfectedRose.Interface
public static void RegisterId(string id, int value)
{
Lookup[id] = value;
foreach (var idCallback in IdCallbacks.Where(callback => callback.Id == id).ToArray())
{
idCallback.Callback(value);
IdCallbacks.Remove(idCallback);
}
Lookup[id] = value;
}
public static void RegisterArtifact(string source, string destination)
public static void RegisterArtifact(string file)
{
Artifacts[source] = destination;
Artifacts.Add(file);
}
public static void CreateArtifact(string source, string destination)
public static void CreateArtifactFrom(string sourceFile, string destination)
{
if (!File.Exists(source))
var dr = new Uri(Path.Combine(Root, "../", destination));
var src = Path.GetRelativePath(Root, Directory.GetCurrentDirectory());
var resources = Configuration.ResourceFolder;
var relative = sourceFile.Split("mods/").Last();
// Create the path to the resource folder
var actual = new Uri(Path.Combine(Root, resources, src, relative));
var relativePath = dr.MakeRelativeUri(actual).ToString();
if (File.Exists(dr.LocalPath))
{
throw new FileNotFoundException($"Could not create artifact to non-existent file: {source}");
var artifactsRoot = Path.Combine(Root, "artifacts");
if (!Directory.Exists(artifactsRoot))
{
Directory.CreateDirectory(artifactsRoot);
}
File.CreateSymbolicLink(source, destination);
// Copy the directory structure to the artifacts folder
var artifacts = Path.Combine(artifactsRoot, Path.GetDirectoryName(destination) ?? "./");
RegisterArtifact(source, destination);
if (!Directory.Exists(artifacts))
{
Directory.CreateDirectory(artifacts);
}
public static void CreateArtifactFrom(string sourceFile, string destinationDirectory)
var dest = Path.Combine(artifacts, Path.GetFileName(dr.LocalPath));
if (!File.Exists(dest))
{
var dr = Path.Combine(Root, "../", destinationDirectory);
var src = Path.GetRelativePath(dr, Directory.GetCurrentDirectory());
var destination = Path.Combine(dr, Path.GetFileName(sourceFile));
src = Path.Combine(src, sourceFile);
if (File.Exists(destination))
{
File.Delete(destination);
File.Copy(dr.LocalPath, dest, true);
}
File.CreateSymbolicLink(destination, src);
File.Delete(dr.LocalPath);
}
else
{
RegisterArtifact(dr.LocalPath);
}
src = Path.GetRelativePath(Root, src);
destination = Path.GetRelativePath(Root, destination);
RegisterArtifact(src, destination);
File.CreateSymbolicLink(dr.LocalPath, relativePath);
}
public static Mod GetMod(string id)
@@ -351,7 +383,7 @@ namespace InfectedRose.Interface
return false;
}
public static string ParseValue(string value, bool ignoreSpecialRoot = false, string root = "../../res/")
public static string ParseValue(string value, bool ignoreSpecialRoot = false, string root = "../../res/", string columnName = "")
{
if (value.StartsWith("INCLUDE:"))
{
@@ -360,101 +392,85 @@ namespace InfectedRose.Interface
return File.ReadAllText(value);
}
if (!value.StartsWith("ASSET:"))
if (value.StartsWith("ICON:"))
{
value = value.Substring(5);
if (!value.Contains(':'))
{
throw new Exception($"Invalid icon: {value}");
}
var folder = value.Split(':');
var path = Path.Combine(Configuration.ResourceFolder, folder[0], "resources", "icon", folder[1]);
return path;
}
if (string.IsNullOrWhiteSpace(Configuration.ResourceFolder))
{
throw new Exception("Resource folder not set");
}
var resources = Configuration.ResourceFolder;
if (!value.Contains("mods/"))
{
return value;
}
value = value.Substring(6);
if (columnName == "LXFMLFolder")
{
var r = value.Split("mods/").Last();
var a = new Uri(Path.Combine(Root, resources, r));
var s = new Uri(Path.Combine(Root, "../", "res/BrickModels/"));
return s.MakeRelativeUri(a).ToString();
}
var extension = Path.GetExtension(value);
if (string.IsNullOrWhiteSpace(extension) || string.IsNullOrWhiteSpace(Path.GetDirectoryName(value)))
{
return value;
}
var createSymlink = false;
if (value.StartsWith("PHYSICS:"))
if (extension == ".hkx")
{
root = "../../res/physics/";
value = value.Substring(8);
root = "res/physics/";
}
else if (value.StartsWith("MAP:"))
else if (extension == ".luz")
{
root = "../../res/maps/";
value = value.Substring(4);
root = "res/maps/";
}
else if (value.StartsWith("ICON:"))
else if (extension == ".dds")
{
root = "../../res/mesh/bricks/";
value = value.Substring(5);
root = "res/mesh/bricks/";
}
else if (value.StartsWith("LXFML:"))
else if (extension == ".lxfml")
{
createSymlink = true;
root = "res/BrickModels/";
value = value.Substring(6);
}
if (ignoreSpecialRoot)
else if (extension is ".kfm" or ".nif")
{
root = "../../res/";
root = "res/";
}
string final;
// Take all parts after the mods folder
var relative = value.Split("mods/").Last();
// Copy the file to the selected resource folder is specified
if (!string.IsNullOrWhiteSpace(Configuration.ResourceFolder))
{
final = Path.Combine(Root, Configuration.ResourceFolder, Path.GetRelativePath(Root, Directory.GetCurrentDirectory()), value);
// Create the path to the resource folder
var actual = new Uri(Path.Combine(Root, resources, relative));
var directory = Path.GetDirectoryName(final);
var source = new Uri(Path.Combine(Root, "../", root));
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory!);
}
// Get the relative path from the source to the resource folder
var relativePath = source.MakeRelativeUri(actual).ToString();
if (!File.Exists(final))
{
File.Copy(value, final);
}
}
else
{
final = value;
}
if (createSymlink)
{
CreateArtifactFrom(value, root);
/*
var dr = Path.Combine(Root, "../", root);
var src = Path.GetRelativePath(dr, Directory.GetCurrentDirectory());
var destination = Path.Combine(dr, Path.GetFileName(value));
src = Path.Combine(src, value);
Console.WriteLine(destination);
if (File.Exists(destination))
{
File.Delete(destination);
}
File.CreateSymbolicLink(destination, src);
*/
}
root = Root + root;
// Get the relative path from root to asset
var finalRelative = Path.GetRelativePath(root, final);
return finalRelative;
return relativePath;
}
public static int AddIcon(string file)
@@ -475,6 +491,26 @@ namespace InfectedRose.Interface
return icon.Key;
}
public static int AddIcon(string file, out string path)
{
if (file.StartsWith("lego-universe:"))
{
throw new Exception("Cannot add icon with lego-universe ID");
}
file = ParseValue(file);
var table = Database["Icons"]!;
var icon = table.Create()!;
path = "..\\..\\textures/../" + file;
icon["IconPath"].Value = path;
return icon.Key;
}
public static void ApplyValues(Dictionary<string, object> values, Row row, Table table, bool defaultNull = false)
{
for (var index = 1; index < table.TableInfo.Count; index++)
@@ -584,6 +620,10 @@ namespace InfectedRose.Interface
{
var str = objValue.ToString();
var parsed = ParseValue(str);
if (parsed != str) break;
if (str.Contains(':'))
{
AwaitId(str, id =>
@@ -603,7 +643,58 @@ namespace InfectedRose.Interface
break;
case DataType.Varchar:
case DataType.Text:
field.Value = ParseValue(objValue.ToString()!);
{
field.Value = "";
var str = objValue.ToString()!;
if (str.Contains(",") && str.Contains(":"))
{
var parts = str.Split(",");
foreach (var part in parts)
{
var count = 1;
var parts2 = part.Split(":");
if (parts2.Length >= 3)
{
var last = parts2.Last();
if (int.TryParse(last, out var c))
{
count = c;
}
else
{
throw new Exception($"Invalid count {last} in {str}");
}
}
// Value is all parts except the last
var value = string.Join(":", parts2.Take(parts2.Length - 1));
AwaitId(value, id =>
{
if (field.Value == null || field.Value == "")
{
field.Value = "";
}
else
{
field.Value = $"{field.Value},";
}
field.Value = $"{field.Value}{id}:{count}";
});
}
}
else
{
field.Value = ParseValue(objValue.ToString()!, columnName: info.Name);
}
}
break;
case DataType.Boolean:
field.Value = mod.GetValue<bool>(key);
@@ -614,5 +705,4 @@ namespace InfectedRose.Interface
}
}
}
}
}

View File

@@ -1,7 +1,6 @@
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public abstract class ModType
{
public abstract class ModType
{
public abstract void Apply(Mod mod);
}
}

View File

@@ -1,15 +1,14 @@
using System;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
[AttributeUsage(AttributeTargets.Class)]
public class ModTypeAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Class)]
public class ModTypeAttribute : Attribute
{
public string Type { get; set; }
public ModTypeAttribute(string type)
{
Type = type;
}
}
}

View File

@@ -1,19 +1,19 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public class ModPriority
{
public class ModPriority
{
[JsonPropertyName("directory")]
public string Directory { get; set; }
[JsonPropertyName("priority")]
public int Priority { get; set; }
}
}
public class Mods
{
public class Mods
{
[JsonPropertyName("version")]
public string Version { get; set; } = "";
@@ -27,9 +27,8 @@ namespace InfectedRose.Interface
public string? Copy { get; set; }
[JsonPropertyName("resource-folder")]
public string ResourceFolder { get; set; }
public string ResourceFolder { get; set; } = "../res/maps/__mods__";
[JsonPropertyName("priorities")]
public List<ModPriority> Priorities { get; set; } = new List<ModPriority>();
}
}

View File

@@ -2,10 +2,10 @@ using System.Collections.Generic;
using System.Linq;
using InfectedRose.Database;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public class PrimaryKeyRegistry
{
public class PrimaryKeyRegistry
{
public Dictionary<string, string[]> PrimaryKeys { get; } = new Dictionary<string, string[]>
{
{"AICombatRoles", new[] {"id"}},
@@ -176,5 +176,4 @@ namespace InfectedRose.Interface
PrimaryKeys.Add(table.Name, primaryKey.ToArray());
}
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -8,31 +7,30 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Xml.Serialization;
using CommandLine;
using InfectedRose.Database;
using InfectedRose.Database.Fdb;
using InfectedRose.Database.Generic;
using InfectedRose.Interface.Templates;
using InfectedRose.Interface.Templates.World;
using InfectedRose.Luz;
using InfectedRose.Lvl;
using InfectedRose.Terrain;
using InfectedRose.Utilities;
using Microsoft.Data.Sqlite;
using RakDotNet.IO;
using SqlExt = InfectedRose.Database.Sql.ColumnExtensions;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public static class Program
{
public static class Program
{
public const string Version = "0.1";
public static Options CommandLineOptions { get; set; }
public static Dictionary<string, ModType> ModTypes { get; set; } = new Dictionary<string, ModType>();
public static List<string> Zones { get; set; } = new List<string>();
public class Options
{
[Option('i', "input", Required = false, HelpText = "Path to mods.json.")]
@@ -98,10 +96,10 @@ namespace InfectedRose.Interface
var table = ModContext.Database[tableName];
if (table != null)
{
Row row;
if (table != null)
{
if (mod.Action == "add")
{
row = table.Create();
@@ -136,6 +134,10 @@ namespace InfectedRose.Interface
throw new InvalidOperationException($"Could not find mod to edit of id '{mod.Id}' in table '{tableName}'.");
}
}
else if (mod.Action == "delete")
{
goto delete;
}
else
{
throw new InvalidOperationException($"Unknown action '{mod.Action}' for mod of id '{mod.Id}'.");
@@ -145,6 +147,46 @@ namespace InfectedRose.Interface
ModContext.ApplyValues(mod, row, table);
}
delete:
if (mod.Action == "delete")
{
if (!Enum.TryParse<ComponentId>(mod.Type, out var componentId))
{
throw new Exception($"Could not parse component ID {mod.Type}!");
}
if (!mod.Id.StartsWith("lego-universe:"))
{
throw new InvalidOperationException($"Can not delete mod of id '{mod.Id}'; only mods starting with a prefix of 'lego-universe:' can be deleted.");
}
var id = int.Parse(mod.Id.Split(':')[1]);
var componentRegistry = ModContext.Database["ComponentsRegistry"];
var componentRow = componentRegistry.SeekMultiple(id).FirstOrDefault(
c => (int)c["component_type"].Value == (int)componentId
);
if (componentRow == null)
{
throw new InvalidOperationException($"Could not find component with ID {id} of type {componentId}!");
}
if (table != null)
{
if (!table.Seek((int)componentRow["component_id"].Value, out row))
{
throw new InvalidOperationException($"Could not find mod to delete of id '{mod.Id}' in table '{tableName}'.");
}
table.Remove(row);
}
componentRegistry.Remove(componentRow);
}
else
{
// For entries without tables, like the MissionNPCComponent
@@ -174,9 +216,9 @@ namespace InfectedRose.Interface
Console.ResetColor();
}
public static void ApplyModFile(Manifest manifest, string file)
public static void ApplyModFile(Manifest manifest, string file, bool applyZone = false)
{
Mod[] mods = ReadJson<Mod[]>(file);
var mods = ReadJson<Mod[]>(file);
var directory = Directory.GetCurrentDirectory();
@@ -186,10 +228,19 @@ namespace InfectedRose.Interface
{
ModContext.Mods[mod.Id] = mod;
if (mod.Type == "zone" && !applyZone)
{
Zones.Add(file);
Directory.SetCurrentDirectory(directory);
return;
}
ApplyMod(mod);
if (!mod.ShowDefaults.HasValue || mod.ShowDefaults.Value == false)
{
if (mod.ShowDefaults.HasValue && mod.ShowDefaults.Value) continue;
foreach (var (key, value) in mod.Defaults)
{
if (mod.HasValue(key) && (mod.Values[key] == null || (value != null && mod.Values[key].ToString() == value.ToString())))
@@ -198,7 +249,6 @@ namespace InfectedRose.Interface
}
}
}
}
Directory.SetCurrentDirectory(directory);
@@ -219,10 +269,39 @@ namespace InfectedRose.Interface
var directory = Path.GetDirectoryName(file)!;
var resources = ModContext.Configuration.ResourceFolder;
if (!string.IsNullOrWhiteSpace(resources))
{
if (!Directory.Exists(resources))
{
Directory.CreateDirectory(resources);
}
// Get the directory name of the mod (last part of the path)
var modName = manifest.Name;
var link = Path.Combine(resources, modName);
if (Directory.Exists(link))
{
Directory.Delete(link);
}
Directory.CreateSymbolicLink(link, Path.GetRelativePath(resources, directory));
}
foreach (var modFile in manifest.Files)
{
ApplyModFile(manifest, Path.Combine(directory, modFile));
}
foreach (var zone in Zones)
{
ApplyModFile(manifest, zone, true);
}
Zones.Clear();
}
public static void SaveDatabase()
@@ -325,7 +404,20 @@ namespace InfectedRose.Interface
table.Recalculate();
}
accessDatabase.Save(Path.Combine(CommandLineOptions.Input, "../../res/cdclient.fdb"));
// Set the permissions of the database to allow writing
var clientDatabase = Path.Combine(CommandLineOptions.Input, "../../res/cdclient.fdb");
if (File.Exists(clientDatabase))
{
var attributes = File.GetAttributes(clientDatabase);
if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
File.SetAttributes(clientDatabase, attributes & ~FileAttributes.ReadOnly);
}
}
accessDatabase.Save(clientDatabase);
connection.Close();
@@ -860,7 +952,7 @@ namespace InfectedRose.Interface
// Load artifacts
ModContext.Artifacts = ReadOrCreateJson<Artifacts>("artifacts.json");
foreach (var (key, value) in ModContext.Artifacts)
foreach (var value in ModContext.Artifacts)
{
if (File.Exists(value))
{
@@ -868,6 +960,29 @@ namespace InfectedRose.Interface
}
}
// Copy the "ModContext.Root/artifacts" folder to "ModContext.Root/../", overwriting any existing files
var artifactsSourcePath = Path.Combine(ModContext.Root, "./artifacts");
var artifactsDestinationPath = Path.Combine(ModContext.Root, "../");
// Get all files and copy them with replacement, keeping folder structure
foreach (var sourcePath in Directory.GetFiles(artifactsSourcePath, "*.*", SearchOption.AllDirectories))
{
var destinationPath = sourcePath.Replace(artifactsSourcePath, artifactsDestinationPath);
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory!);
}
if (File.Exists(destinationPath))
{
File.Delete(destinationPath);
}
File.Copy(sourcePath, destinationPath);
}
ModContext.Artifacts.Clear();
var localeSourcePath = Path.Combine(Path.GetDirectoryName(CommandLineOptions.Input)!, "./locale.xml");
@@ -942,6 +1057,38 @@ namespace InfectedRose.Interface
Console.SetCursorPosition(origin.Left, origin.Top);
var componentsRegistry = ModContext.Database["ComponentsRegistry"];
var renderComponentTable = ModContext.Database["RenderComponent"];
/*
foreach (var itemComponent in ModContext.Database["ItemComponent"])
{
if (!"special_r".Equals(itemComponent[1].Value)) continue;
var componentRow = componentsRegistry.FirstOrDefault(
c => itemComponent.Key.Equals(c[2].Value) && ((int) ComponentId.ItemComponent).Equals(c[1].Value)
);
if (componentRow == null) continue;
var lot = componentRow.Key;
var renderComponentEntry = componentsRegistry.SeekMultiple(lot).FirstOrDefault(c => 2.Equals(c[1].Value));
if (renderComponentEntry == null) continue;
if (!renderComponentTable.Seek((int) renderComponentEntry[2].Value, out var renderComponent)) continue;
var field = renderComponent[5];
if (field.Value is null or 0) continue;
Console.WriteLine($"Found {field.Value} for {lot}");
}
return;
*/
if (CommandLineOptions.Export == "object")
{
Console.Write($"Generating copy of {CommandLineOptions.ExportId} as ");
@@ -992,6 +1139,8 @@ namespace InfectedRose.Interface
ApplyManifest(Path.Combine(priority, "./manifest.json"));
}
WorldInstance.SaveWorldInstances();
// Check for unresolved references and print errors
if (ModContext.IdCallbacks.Count > 0)
{
@@ -1069,5 +1218,4 @@ namespace InfectedRose.Interface
Console.WriteLine("Complete!");
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using InfectedRose.Database;
namespace InfectedRose.Interface
namespace InfectedRose.Interface;
public static class TableExtensions
{
public static class TableExtensions
{
public static Row FromLookup(this Table @this, string id)
{
return !ModContext.Lookup.TryGetValue(id, out var value) ? @this.Create() : @this.Create(value);
@@ -39,5 +39,4 @@ namespace InfectedRose.Interface
throw new KeyNotFoundException($"Could not determine the ID of {mod.Id}, no old ids match lookup.json.");
}
}
}

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public class Behavior
{
public class Behavior
{
[JsonPropertyName("template-id")]
[JsonPropertyName("template")]
public string Template { get; set; }
[JsonPropertyName("effect")]
@@ -17,7 +17,7 @@ namespace InfectedRose.Interface.Templates
public string? EffectHandle { get; set; }
[JsonPropertyName("parameters")]
public Dictionary<string, object> Parameters { get; set; }
public Dictionary<string, object>? Parameters { get; set; }
public int Apply(string id)
{
@@ -53,6 +53,8 @@ namespace InfectedRose.Interface.Templates
behaviorTemplate["effectHandle"].Value = EffectHandle;
if (Parameters == null) return behaviorTemplate.Key;
foreach (var (key, value) in Parameters)
{
var parameter = behaviorParameterTable.Create(behaviorTemplate.Key);
@@ -67,6 +69,21 @@ namespace InfectedRose.Interface.Templates
continue;
}
// Check if true or false
if (value.ToString()!.Equals("true", StringComparison.OrdinalIgnoreCase))
{
valueField.Value = 1;
continue;
}
if (value.ToString()!.Equals("false", StringComparison.OrdinalIgnoreCase))
{
valueField.Value = 0;
continue;
}
ModContext.AwaitId(value.ToString()!, i =>
{
valueField.Value = (float) i;
@@ -75,5 +92,4 @@ namespace InfectedRose.Interface.Templates
return behaviorTemplate.Key;
}
}
}

View File

@@ -1,7 +1,7 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public enum ComponentId
{
public enum ComponentId
{
ControllablePhysicsComponent = 1,
RenderComponent = 2,
SimplePhysicsComponent = 3,
@@ -105,5 +105,4 @@ namespace InfectedRose.Interface.Templates
CombatMediatorComponent = 101,
Component107 = 107,
Possesable = 108
}
}

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public class Effect
{
public class Effect
{
[JsonPropertyName("id")]
public string Id { get; set; }
@@ -56,5 +56,4 @@ namespace InfectedRose.Interface.Templates
return behaviorEffect.Key;
}
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("enemy")]
public class EnemyMod : ModType
{
[ModType("enemy")]
public class EnemyMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
@@ -100,5 +100,4 @@ namespace InfectedRose.Interface.Templates
ObjectMod.AddComponent(mod, obj, ComponentId.MovementAIComponent);
ObjectMod.AddComponent(mod, obj, ComponentId.BaseCombatAIComponent);
}
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("environmental")]
public class EnvironmentalMod : ModType
{
[ModType("environmental")]
public class EnvironmentalMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
@@ -20,5 +20,4 @@ namespace InfectedRose.Interface.Templates
ObjectMod.AddComponent(mod, obj, ComponentId.RenderComponent);
ObjectMod.AddComponent(mod, obj, ComponentId.SimplePhysicsComponent);
}
}
}

View File

@@ -0,0 +1,13 @@
namespace InfectedRose.Interface.Templates;
[ModType("file")]
public class FileMod : ModType
{
public override void Apply(Mod mod)
{
var source = mod.GetValue<string>("source");
var destination = mod.GetValue<string>("destination");
ModContext.CreateArtifactFrom(source, destination);
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("flag")]
public class FlagMod : ModType
{
[ModType("flag")]
public class FlagMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
@@ -22,5 +22,4 @@ namespace InfectedRose.Interface.Templates
ModContext.RegisterId(mod.Id, row.Key);
}
}
}

View File

@@ -1,13 +1,15 @@
namespace InfectedRose.Interface.Templates
using System;
namespace InfectedRose.Interface.Templates;
[ModType("item")]
public class ItemMod : ModType
{
[ModType("item")]
public class ItemMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
{
return;
throw new Exception("ItemMod can only be used with the add action");
}
mod.Default("nametag", false);
@@ -21,12 +23,25 @@ namespace InfectedRose.Interface.Templates
mod.Default("shader_id", 23);
mod.Default("audioEquipMetaEventSet", "Weapon_Hammer_Generic");
var obj = ObjectMod.CreateObject(mod);
if (mod.HasValue("icon"))
{
var iconId = ModContext.AddIcon(mod.GetValue<string>("icon"), out var path);
mod.Values["icon_asset"] = path;
mod.Values["IconID"] = iconId;
}
var obj = ObjectMod.CreateObject(mod, false);
obj["type"].Value = "Loot";
ObjectMod.AddComponent(mod, obj, ComponentId.RenderComponent);
ObjectMod.AddComponent(mod, obj, ComponentId.ItemComponent);
if (mod.HasValue("icon"))
{
mod.Values.Remove("icon_asset");
mod.Values.Remove("IconID");
}
}
}

View File

@@ -0,0 +1,13 @@
namespace InfectedRose.Interface.Templates;
[ModType("locale")]
public class LocaleMod : ModType
{
public override void Apply(Mod mod)
{
foreach (var value in mod.Values)
{
ModContext.AddToLocale(value.Key, value.Key, mod);
}
}
}

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using InfectedRose.Database;
namespace InfectedRose.Interface.Templates;
[ModType("loot-matrix")]
public class LootMatrixMod : ModType
{
class LootMatrix
{
[JsonPropertyName("rarity-table")]
public int? RarityTableIndex { get; set; }
[JsonPropertyName("percent")]
public float? Percent { get; set; }
[JsonPropertyName("min-quantity")]
public int? MinQuantity { get; set; }
[JsonPropertyName("max-quantity")]
public int? MaxQuantity { get; set; }
[JsonPropertyName("id")]
public int? Id { get; set; }
[JsonPropertyName("flag-id")]
public string? FlagId { get; set; }
}
public override void Apply(Mod mod)
{
var lootMatrix = ModContext.Database["LootMatrix"]!;
var lootMatrixIndex = ModContext.Database["LootMatrixIndex"]!;
int modId;
if (mod.ExplicitId.HasValue)
{
modId = mod.ExplicitId.Value;
}
else if (mod.Id.StartsWith("lego-universe:"))
{
modId = int.Parse(mod.Id[14..]);
}
else
{
var indexRow = lootMatrixIndex.Create();
modId = indexRow.Key;
}
ModContext.RegisterId(mod.Id, modId);
foreach (var (key, _) in mod.Values)
{
ModContext.AwaitId(key, lootTableIndex =>
{
var row = lootMatrix.Create(modId);
row["LootTableIndex"].Value = modId;
var value = mod.GetValue<LootMatrix>(key);
row["LootTableIndex"].Value = lootTableIndex;
row["RarityTableIndex"].Value = value.RarityTableIndex ?? 4;
row["percent"].Value = value.Percent ?? 1;
row["minToDrop"].Value = value.MinQuantity ?? 1;
row["maxToDrop"].Value = value.MaxQuantity ?? 1;
row["id"].Value = value.Id ?? lootMatrix.Max(l => (int)l[6].Value);
if (value.FlagId != null)
{
ModContext.AwaitId(value.FlagId, flagId =>
{
row["flagID"].Value = flagId;
});
}
});
}
}
}

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using InfectedRose.Database;
namespace InfectedRose.Interface.Templates;
[ModType("loot-table")]
public class LootTableMod : ModType
{
class LootTable
{
[JsonPropertyName("id")]
public int? Id { get; set; }
[JsonPropertyName("mission-drop")]
public bool? MissionDrop { get; set; }
[JsonPropertyName("sort-priority")]
public int? SortPriority { get; set; }
}
public override void Apply(Mod mod)
{
var lootTable = ModContext.Database["LootTable"]!;
var lootTableIndex = ModContext.Database["LootTableIndex"]!;
int modId;
if (mod.ExplicitId.HasValue)
{
modId = mod.ExplicitId.Value;
}
else if (mod.Id.StartsWith("lego-universe:"))
{
modId = int.Parse(mod.Id[14..]);
}
else
{
var indexRow = lootTableIndex.Create();
modId = indexRow.Key;
}
ModContext.RegisterId(mod.Id, modId);
foreach (var (key, _) in mod.Values)
{
ModContext.AwaitId(key, itemId =>
{
var row = lootTable.Create(itemId);
row["LootTableIndex"].Value = modId;
var value = mod.GetValue<LootTable?>(key);
if (value == null)
{
row["id"].Value = lootTable.Max(l => (int)l[2].Value);
row["MissionDrop"].Value = false;
row["sortPriority"].Value = 0;
}
else
{
row["id"].Value = value.Id ?? lootTable.Max(l => (int)l[2].Value);
row["MissionDrop"].Value = value.MissionDrop ?? false;
row["sortPriority"].Value = value.SortPriority ?? 0;
}
});
}
}
}

View File

@@ -2,11 +2,11 @@ using System;
using System.Linq;
using System.Text;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("mission")]
public class MissionMod : ModType
{
[ModType("mission")]
public class MissionMod : ModType
{
// Proud of this one :D
public static void ParsePrerequisites(StringBuilder builder, StringBuilder missionId, string str, int i, Action<string> done)
{
@@ -170,7 +170,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item1"), id =>
{
mission["reward_item1"].Value = id;
mission["reward_item1"].Value = id == 0 ? -1 : id;
});
}
@@ -178,7 +178,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item2"), id =>
{
mission["reward_item2"].Value = id;
mission["reward_item2"].Value = id == 0 ? -1 : id;
});
}
@@ -186,7 +186,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item3"), id =>
{
mission["reward_item3"].Value = id;
mission["reward_item3"].Value = id == 0 ? -1 : id;
});
}
@@ -194,7 +194,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item4"), id =>
{
mission["reward_item4"].Value = id;
mission["reward_item4"].Value = id == 0 ? -1 : id;
});
}
@@ -202,7 +202,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item1_repeatable"), id =>
{
mission["reward_item1_repeatable"].Value = id;
mission["reward_item1_repeatable"].Value = id == 0 ? -1 : id;
});
}
@@ -210,7 +210,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item2_repeatable"), id =>
{
mission["reward_item2_repeatable"].Value = id;
mission["reward_item2_repeatable"].Value = id == 0 ? -1 : id;
});
}
@@ -218,7 +218,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item3_repeatable"), id =>
{
mission["reward_item3_repeatable"].Value = id;
mission["reward_item3_repeatable"].Value = id == 0 ? -1 : id;
});
}
@@ -226,7 +226,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_item4_repeatable"), id =>
{
mission["reward_item4_repeatable"].Value = id;
mission["reward_item4_repeatable"].Value = id == 0 ? -1 : id;
});
}
@@ -234,7 +234,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_emote"), id =>
{
mission["reward_emote"].Value = id;
mission["reward_emote"].Value = id == 0 ? -1 : id;
});
}
@@ -242,7 +242,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_emote2"), id =>
{
mission["reward_emote2"].Value = id;
mission["reward_emote2"].Value = id == 0 ? -1 : id;
});
}
@@ -250,7 +250,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_emote3"), id =>
{
mission["reward_emote3"].Value = id;
mission["reward_emote3"].Value = id == 0 ? -1 : id;
});
}
@@ -258,7 +258,7 @@ namespace InfectedRose.Interface.Templates
{
ModContext.AwaitId(mod.GetValue<string>("reward_emote4"), id =>
{
mission["reward_emote4"].Value = id;
mission["reward_emote4"].Value = id == 0 ? -1 : id;
});
}
@@ -401,5 +401,4 @@ namespace InfectedRose.Interface.Templates
}
}
}
}
}

View File

@@ -2,10 +2,10 @@ using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public class MissionModTask
{
public class MissionModTask
{
[JsonPropertyName("type")]
public string Type { get; set; }
@@ -35,5 +35,4 @@ namespace InfectedRose.Interface.Templates
{
{"en_US", ""}
};
}
}

View File

@@ -1,10 +1,10 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public class MissionOffer
{
public class MissionOffer
{
[JsonPropertyName("mission")]
public JsonValue Mission { get; set; }
@@ -13,5 +13,4 @@ namespace InfectedRose.Interface.Templates
[JsonPropertyName("offer")]
public bool Offer { get; set; }
}
}

View File

@@ -1,7 +1,7 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public enum MissionTaskType
{
public enum MissionTaskType
{
Smash = 0,
Script = 1,
Activity = 2,
@@ -20,5 +20,4 @@ namespace InfectedRose.Interface.Templates
Racing = 23,
PlayerFlag = 24,
VisitProperty = 30
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("npc")]
public class NpcMod : ModType
{
[ModType("npc")]
public class NpcMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
@@ -52,5 +52,4 @@ namespace InfectedRose.Interface.Templates
ObjectMod.AddComponent(mod, obj, ComponentId.RenderComponent);
ObjectMod.AddComponent(mod, obj, ComponentId.MinifigComponent);
}
}
}

View File

@@ -1,12 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using InfectedRose.Database;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("object")]
public class ObjectMod : ModType
{
[ModType("object")]
public class ObjectMod : ModType
{
public static Row CreateObject(Mod mod)
public static Row CreateObject(Mod mod, bool checkIcon = true)
{
var table = ModContext.Database["Objects"];
@@ -19,6 +20,19 @@ namespace InfectedRose.Interface.Templates
ModContext.RegisterId(mod.Id, row.Key);
if (!mod.HasValue("interactionDistance") || mod.GetValue<float>("interactionDistance") == 0.0f)
{
row["interactionDistance"].Value = 10.0f;
}
if (checkIcon && mod.HasValue("icon"))
{
var iconId = ModContext.AddIcon(mod.GetValue<string>("icon"), out var path);
mod.Values["icon_asset"] = path;
mod.Values["IconID"] = iconId;
}
if (mod.Components != null)
{
foreach (var component in mod.Components)
@@ -27,6 +41,12 @@ namespace InfectedRose.Interface.Templates
}
}
if (checkIcon && mod.HasValue("icon"))
{
mod.Values.Remove("icon_asset");
mod.Values.Remove("IconID");
}
if (mod.Locale != null)
{
foreach (var (locale, text) in mod.Locale)
@@ -35,6 +55,16 @@ namespace InfectedRose.Interface.Templates
}
}
if (mod.HasValue("description-locale"))
{
var description = mod.GetValue<Dictionary<string, string>>("description-locale");
foreach (var (locale, text) in description)
{
ModContext.AddToLocale($"Objects_{row.Key}_description", text, locale);
}
}
if (mod.Skills != null && mod.Skills.Length > 0)
{
AddComponent(mod, row, ComponentId.SkillComponent);
@@ -264,6 +294,18 @@ namespace InfectedRose.Interface.Templates
Row component;
foreach (var r in ModContext.Database["ComponentsRegistry"].SeekMultiple(obj.Key))
{
if ((int)r["component_type"].Value != (int)componentId) continue;
if (table != null && table.Seek((int) r["component_id"].Value, out var row))
{
return row;
}
return null;
}
if (id == 0)
{
var row = ModContext.Database["ComponentsRegistry"].Create(obj.Key);
@@ -308,5 +350,4 @@ namespace InfectedRose.Interface.Templates
EditObject(mod);
}
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
public class ObjectSkillEntry
{
public class ObjectSkillEntry
{
[JsonPropertyName("skill-id")]
public JsonValue SkillId { get; set; }
@@ -13,5 +13,4 @@ namespace InfectedRose.Interface.Templates
[JsonPropertyName("combat-ai-weight")]
public int CombatAiWeight { get; set; }
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("quickbuild")]
public class QuickBuildMod : ModType
{
[ModType("quickbuild")]
public class QuickBuildMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
@@ -75,5 +75,4 @@ namespace InfectedRose.Interface.Templates
ModContext.ParseValue(mod.GetValue<string>("lxfml"));
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using InfectedRose.Database.Generic;
namespace InfectedRose.Interface.Templates;
[ModType("recipe")]
public class RecipeMod : ModType
{
class RecipeList : Dictionary<string, int>
{
}
public override void Apply(Mod mod)
{
foreach (var (key, _) in mod.Values)
{
ModContext.AwaitId(key, objectId =>
{
var itemTable = ModContext.GetComponentTable(ComponentId.ItemComponent)!;
var componentRegistry = ModContext.Database["ComponentsRegistry"]!;
var itemComponentEntry = componentRegistry.SeekMultiple(objectId).FirstOrDefault(
c => c.Value<int>("component_type") == (int) ComponentId.ItemComponent
);
if (itemComponentEntry == null)
{
throw new KeyNotFoundException($"Item component entry for {key} not found.");
}
if (!itemTable.Seek(itemComponentEntry.Value<int>("component_id"), out var itemComponent))
{
throw new KeyNotFoundException($"Item component for {key} not found.");
}
var field = itemComponent["currencyCosts"];
field.Value = "";
foreach (var (itemId, quantity) in mod.GetValue<RecipeList>(key))
{
ModContext.AwaitId(itemId, id =>
{
var fieldValue = (string) field.Value;
if (fieldValue != "")
{
fieldValue += ",";
}
fieldValue = $"{fieldValue}{id}:{quantity}";
field.Value = fieldValue;
});
}
});
}
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("skill")]
public class SkillMod : ModType
{
[ModType("skill")]
public class SkillMod : ModType
{
public override void Apply(Mod mod)
{
if (mod.Action != "add")
@@ -14,6 +14,11 @@ namespace InfectedRose.Interface.Templates
var skillBehavior = skillBehaviorTable.FromLookup(mod);
if (mod.Locale != null)
{
ModContext.AddToLocale($"SkillBehavior_{skillBehavior.Key}_name", mod.Locale);
}
ModContext.RegisterId(mod.Id, skillBehavior.Key);
foreach (var (key, behavior) in mod.Behaviors)
@@ -21,8 +26,20 @@ namespace InfectedRose.Interface.Templates
behavior.Apply(key);
}
if (mod.HasValue("icon"))
{
var iconId = ModContext.AddIcon(mod.GetValue<string>("icon"), out var path);
mod.Values["skillIcon"] = iconId;
}
ModContext.ApplyValues(mod, skillBehavior, skillBehaviorTable);
if (mod.HasValue("icon"))
{
mod.Values.Remove("skillIcon");
}
if (mod.HasValue("root-behavior"))
{
ModContext.AwaitId(mod.GetValue<string>("root-behavior"), i =>
@@ -31,5 +48,4 @@ namespace InfectedRose.Interface.Templates
});
}
}
}
}

View File

@@ -1,8 +1,8 @@
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("sql")]
public class SqlMod : ModType
{
[ModType("sql")]
public class SqlMod : ModType
{
public override void Apply(Mod mod)
{
switch (mod.Action)
@@ -15,5 +15,4 @@ namespace InfectedRose.Interface.Templates
break;
}
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Drawing;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public struct Color3
{
public struct Color3
{
[JsonPropertyName("r")]
public float R { get; set; }
@@ -13,5 +13,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("b")]
public float B { get; set; }
}
}

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
namespace InfectedRose.Interface.Templates.ValueTypes
{
public class DataDictionary : Dictionary<string, DataValue>
{
namespace InfectedRose.Interface.Templates.ValueTypes;
public class DataDictionary : Dictionary<string, DataValue>
{
}
}

View File

@@ -1,29 +1,103 @@
using System;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class DataValue
{
public class DataValue
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("value")]
public object Value { get; set; }
public JsonValue Value { get; set; }
public static int ParseTypeString(string type)
[JsonPropertyName("type")]
public string? TypeInternal { get; set; }
[JsonIgnore]
public FieldType Type
{
get
{
if (TypeInternal == null)
{
var str = Value.ToString();
if (int.TryParse(str, out var i))
{
return FieldType.Integer;
}
if (float.TryParse(str, out var f))
{
return FieldType.Float;
}
// Count instances of \u001f
var instances = str.Split("\u001f").Length;
if (instances == 3)
{
return FieldType.Position;
}
if (instances == 4)
{
return FieldType.Rotation;
}
return FieldType.String;
}
if (Enum.TryParse<FieldType>(TypeInternal, out var fieldType)) return fieldType;
fieldType = ParseFieldType(TypeInternal);
TypeInternal = fieldType.ToString();
return fieldType;
}
set => TypeInternal = value.ToString();
}
public static string ParseFieldType(FieldType type)
{
return type switch
{
"int" => 1,
"float" => 3,
"double" => 4,
"uint" => 5,
"bool" => 7,
"long" => 8,
"blob" => 13,
"string" => 0,
"id" => 100,
_ => 0
FieldType.Id => "id",
FieldType.Object => "id",
FieldType.WorldObject => "id",
FieldType.Integer => "int",
FieldType.UnsignedInteger => "uint",
FieldType.String => "string",
FieldType.Buffer => "blob",
FieldType.Skill => "id",
FieldType.Flag => "id",
FieldType.Mission => "id",
FieldType.Float => "float",
FieldType.Double => "double",
FieldType.Boolean => "bool",
FieldType.Model => "id",
FieldType.Physics => "id",
FieldType.Icon => "id",
FieldType.None => "string",
FieldType.Position => "string",
FieldType.Rotation => "string",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
public static FieldType ParseFieldType(string type)
{
return type switch
{
"id" => FieldType.Object,
"int" => FieldType.Integer,
"uint" => FieldType.UnsignedInteger,
"string" => FieldType.String,
"blob" => FieldType.Buffer,
"float" => FieldType.Float,
"double" => FieldType.Double,
"bool" => FieldType.Boolean,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, $"Unknown type: {type}")
};
}
@@ -44,38 +118,71 @@ namespace InfectedRose.Interface.Templates.ValueTypes
};
}
public static int ParseType(string type)
{
return type switch
{
"int" => 1,
"float" => 3,
"double" => 4,
"uint" => 5,
"bool" => 7,
"long" => 8,
"blob" => 13,
"string" => 0,
"id" => 0,
_ => 0
};
}
[JsonIgnore]
public int TypeId
{
get => ParseTypeString(Type);
set => Type = ParseType(value);
set => ParseFieldType(ParseType(value));
get => ParseType(ParseFieldType(Type));
}
[JsonIgnore]
public object? ObjectValue
{
get
{
switch (Type)
{
case FieldType.None:
return null;
case FieldType.Id:
case FieldType.Object:
case FieldType.WorldObject:
case FieldType.String:
case FieldType.Buffer:
case FieldType.Rotation:
case FieldType.Position:
case FieldType.Skill:
case FieldType.Flag:
case FieldType.Mission:
case FieldType.Model:
case FieldType.Physics:
case FieldType.Icon:
return Value.ToString();
case FieldType.Integer:
return Value.GetValue<int>();
case FieldType.UnsignedInteger:
return Value.GetValue<uint>();
case FieldType.Float:
return Value.GetValue<float>();
case FieldType.Double:
return Value.GetValue<double>();
case FieldType.Boolean:
return Value.GetValue<bool>();
default:
throw new ArgumentOutOfRangeException();
}
}
}
public override string ToString()
{
var type = TypeId;
var value = Value;
if (value is false) value = 0;
else if (value is true) value = 1;
else if (type == 0 || type == 13) value = value.ToString();
if (type == 100)
{
value = ModContext.AssertId((string)value!);
type = 1;
}
if (value != null)
{
value = value.ToString();
if (value == "True") value = "1";
else if (value == "False") value = "0";
}
return $"{type}:{value}";
}
return $"{TypeId}:{Value}";
}
}

View File

@@ -0,0 +1,24 @@
namespace InfectedRose.Interface.Templates.ValueTypes;
public enum FieldType
{
None,
Id,
Object,
WorldObject,
Integer,
UnsignedInteger,
String,
Buffer,
Position,
Rotation,
Skill,
Flag,
Mission,
Float,
Double,
Boolean,
Model,
Physics,
Icon,
}

View File

@@ -1,15 +1,15 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class LevelTemplate
{
public class LevelTemplate
{
[JsonPropertyName("id")]
public JsonValue Id { get; set; }
[JsonPropertyName("type")]
public int Type { get; set; }
public int? Type { get; set; }
[JsonPropertyName("object-id")]
public ulong? ObjectId { get; set; }
@@ -21,9 +21,8 @@ namespace InfectedRose.Interface.Templates.ValueTypes
public Point4 Rotation { get; set; }
[JsonPropertyName("scale")]
public float Scale { get; set; }
public float? Scale { get; set; }
[JsonPropertyName("data")]
public DataDictionary Data { get; set; }
}
}

View File

@@ -1,9 +1,9 @@
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class Particle
{
public class Particle
{
[JsonPropertyName("position")]
public Point3 Position { get; set; }
@@ -12,5 +12,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("name")]
public string Name { get; set; }
}
}

View File

@@ -1,9 +1,9 @@
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class PathData
{
public class PathData
{
public class Waypoint
{
public class Config
@@ -131,5 +131,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("activate-on-load")]
public bool? ActivateOnLoad { get; set; }
}
}

View File

@@ -1,10 +1,10 @@
using System.Numerics;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public struct Point3
{
public struct Point3
{
[JsonPropertyName("x")]
public float X { get; set; }
@@ -19,5 +19,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
{
return new Vector3(point.X, point.Y, point.Z);
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Numerics;
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public struct Point4
{
public struct Point4
{
[JsonPropertyName("x")]
public float X { get; set; }
@@ -22,5 +22,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
{
return new Quaternion(point.X, point.Y, point.Z, point.W);
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Text.Json.Serialization;
using InfectedRose.Triggers;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class Scene
{
public class Scene
{
[JsonPropertyName("id")]
public int Id { get; set; }
@@ -73,5 +73,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("scene-audio")]
public SceneAudio SceneAudio { get; set; }
}
}

View File

@@ -1,11 +1,11 @@
using System.Text.Json.Serialization;
using System.Xml.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
[XmlRoot("SceneAudioAttributes")]
public class SceneAudio
{
[XmlRoot("SceneAudioAttributes")]
public class SceneAudio
{
[JsonPropertyName("music-cue")]
[XmlAttribute("musicCue")]
public string MusicCue { get; set; }
@@ -37,5 +37,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("boredom-time")]
[XmlAttribute("boredomTime")]
public string BoredomTime { get; set; }
}
}

View File

@@ -1,9 +1,9 @@
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class Transition
{
public class Transition
{
public class Point
{
[JsonPropertyName("scene")]
@@ -18,5 +18,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("points")]
public Point[] Points { get; set; }
}
}

View File

@@ -1,9 +1,9 @@
using System.Text.Json.Serialization;
namespace InfectedRose.Interface.Templates.ValueTypes
namespace InfectedRose.Interface.Templates.ValueTypes;
public class Zone
{
public class Zone
{
[JsonPropertyName("spawn-point")]
public Point3 SpawnPoint { get; set; }
@@ -21,5 +21,4 @@ namespace InfectedRose.Interface.Templates.ValueTypes
[JsonPropertyName("paths")]
public PathData[] Paths { get; set; }
}
}

View File

@@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using InfectedRose.Database.Generic;
using InfectedRose.Luz;
using InfectedRose.Lvl;
using RakDotNet.IO;
namespace InfectedRose.Interface.Templates.World;
public class WorldInstance
{
public static Dictionary<string, WorldInstance> WorldInstances { get; } = new();
public string Name { get; set; }
public LuzFile Zone { get; set; }
public Dictionary<string, LvlFile> Scenes { get; set; }
public HashSet<ulong> ClaimedIds { get; }
public WorldInstance(string name, LuzFile zone, Dictionary<string, LvlFile> scenes, HashSet<ulong> claimedIds)
{
Name = name;
Zone = zone;
Scenes = scenes;
ClaimedIds = claimedIds;
}
public ulong ClaimId()
{
var lowest = 0x38B40AUL;
while (ClaimedIds.Contains(lowest))
{
lowest++;
}
ClaimedIds.Add(lowest);
return lowest;
}
public static void Await(string id, Action<WorldInstance> callback)
{
if (WorldInstances.TryGetValue(id, out var instance))
{
callback(instance);
}
ModContext.AwaitId(id, zoneId =>
{
var zoneTable = ModContext.Database["ZoneTable"]!;
if (!zoneTable.Seek(zoneId, out var zoneEntry))
{
throw new Exception($"Zone {zoneId} not found");
}
var zoneName = zoneEntry.Value<string>("zoneName").ToLower();
var path = Path.Combine(ModContext.Root, "../res/maps/", zoneName);
if (!File.Exists(path))
{
throw new Exception($"Zone file for {zoneName} not found: {path}");
}
var zone = new LuzFile();
{
using var zoneStream = File.OpenRead(path);
using var zoneReader = new ByteReader(zoneStream);
zone.Deserialize(zoneReader);
}
var scenes = new Dictionary<string, LvlFile>();
var claimedIds = new HashSet<ulong>();
var zoneRoot = Path.GetDirectoryName(path)!;
foreach (var scene in zone.Scenes)
{
var scenePath = Path.Combine(zoneRoot, scene.FileName.ToLower());
if (!File.Exists(scenePath))
{
throw new Exception($"Scene file for {scene.SceneName} not found: {scenePath}");
}
var lvl = new LvlFile();
using var lvlStream = File.OpenRead(scenePath);
using var lvlReader = new ByteReader(lvlStream);
lvl.Deserialize(lvlReader);
if (lvl.LevelObjects != null)
{
foreach (var template in lvl.LevelObjects.Templates)
{
claimedIds.Add(template.ObjectId);
}
}
scenes.Add(scene.FileName.ToLower(), lvl);
}
instance = new WorldInstance(zoneName, zone, scenes, claimedIds);
WorldInstances.Add(id, instance);
callback(instance);
});
}
public static WorldInstance Get(string id)
{
if (!WorldInstances.TryGetValue(id, out var instance))
{
throw new Exception($"World instance {id} not found");
}
return instance;
}
public static void AwaitZone(string id, Action<LuzFile> callback)
{
Await(id, instance => callback(instance.Zone));
}
public static void AwaitScene(string id, string sceneName, Action<LvlFile> callback)
{
Await(id, instance =>
{
var sceneId = -1;
var layerId = -1;
if (sceneName.Contains(":"))
{
var split = sceneName.Split(':');
if (split.Length != 3)
{
throw new Exception($"Invalid scene id {id}");
}
sceneName = split[0];
sceneId = int.Parse(split[1]);
layerId = int.Parse(split[2]);
}
foreach (var scene in instance.Zone.Scenes)
{
if (sceneId == -1 && layerId == -1 && sceneName == scene.SceneName)
{
callback(instance.Scenes[scene.FileName.ToLower()]);
return;
}
if (sceneId != scene.SceneId || layerId != scene.LayerId || sceneName != scene.SceneName) continue;
callback(instance.Scenes[scene.FileName.ToLower()]);
return;
}
throw new Exception($"Scene {sceneName} not found");
});
}
public static void SaveWorldInstances()
{
foreach (var (name, instance) in WorldInstances)
{
SaveWorldInstance(name, instance.Zone, instance.Scenes);
}
}
private static void SaveWorldInstance(string name, LuzFile luzFile, Dictionary<string, LvlFile> scenes)
{
var zoneTable = ModContext.Database["ZoneTable"]!;
var zoneId = ModContext.AssertId(name);
if (!zoneTable.Seek(zoneId, out var zoneEntry))
{
throw new Exception($"Zone {name} not found");
}
var zoneName = zoneEntry.Value<string>("zoneName").ToLower();
var root = Path.Combine(
ModContext.Root,
ModContext.Configuration.ResourceFolder,
$"./compiled/{zoneId}/",
Path.GetDirectoryName(zoneName)!
);
if (!Directory.Exists(root))
{
Directory.CreateDirectory(root);
}
var path = Path.Combine(root, Path.GetFileName(zoneName));
{
using var zoneStream = File.Create(path);
using var zoneWriter = new ByteWriter(zoneStream);
luzFile.Serialize(zoneWriter);
}
foreach (var (fileName, scene) in scenes)
{
var scenePath = Path.Combine(root, fileName);
using var sceneStream = File.Create(scenePath);
using var sceneWriter = new ByteWriter(sceneStream);
scene.Serialize(sceneWriter);
}
var originalPath = Path.Combine(ModContext.Root, "../res/maps/", zoneName);
// Copy all ".raw", ".lutriggers", ".evc", and ".ast" files
foreach (var file in Directory.GetFiles(Path.GetDirectoryName(originalPath)!))
{
var extension = Path.GetExtension(file);
if (extension == ".raw" || extension == ".lutriggers" || extension == ".evc" || extension == ".ast")
{
File.Copy(file, Path.Combine(root, Path.GetFileName(file)), true);
// Remove read-only flag
var attributes = File.GetAttributes(Path.Combine(root, Path.GetFileName(file)));
attributes &= ~FileAttributes.ReadOnly;
File.SetAttributes(Path.Combine(root, Path.GetFileName(file)), attributes);
}
}
var link = new Uri(Path.Combine("../", ModContext.Configuration.ResourceFolder, $"./compiled/{zoneId}/", zoneName), UriKind.Relative);
zoneEntry["zoneName"].Value = link.ToString();
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Text;
using InfectedRose.Interface.Templates.ValueTypes;
using InfectedRose.Lvl;
namespace InfectedRose.Interface.Templates.World;
[ModType("world-object")]
public class WorldObjectMod : ModType
{
public override void Apply(Mod mod)
{
var zoneId = mod.GetValue<string>("zone");
var sceneId = mod.GetValue<string>("scene");
WorldInstance.AwaitScene(zoneId, sceneId, scene => ApplyToZone(mod, scene));
}
private void ApplyToZone(Mod mod, LvlFile scene)
{
if (scene.LevelObjects == null)
{
scene.LevelObjects = new LevelObjects(scene.LvlVersion);
}
var zoneId = mod.GetValue<string>("zone");
var instance = WorldInstance.Get(zoneId);
LevelObjectTemplate levelObjectTemplate;
switch (mod.Action)
{
case "add":
{
levelObjectTemplate = new LevelObjectTemplate(scene.LvlVersion);
levelObjectTemplate.ObjectId = instance.ClaimId();
} break;
default:
{
throw new Exception($"Unknown action {mod.Action} for world-object");
} break;
}
var template = mod.GetValue<LevelTemplate>("template");
ModContext.AwaitId(template.Id, lot =>
{
levelObjectTemplate.Lot = lot;
});
levelObjectTemplate.Position = template.Position;
levelObjectTemplate.Rotation = template.Rotation;
levelObjectTemplate.Scale = template.Scale ?? 1;
levelObjectTemplate.AssetType = (uint) (template.Type ?? 1);
levelObjectTemplate.GlomId = 1;
var dataString = new StringBuilder();
var ids = new List<string>();
foreach (var (_, dataValue) in template.Data)
{
var value = dataValue.ObjectValue ?? "";
if (dataValue.Type == FieldType.Object)
{
ids.Add(value.ToString()!);
}
}
ModContext.AwaitMultiple(ids.ToArray(), () =>
{
foreach (var (dataKey, dataValue) in template.Data)
{
var type = dataValue.TypeId;
var value = dataValue.ObjectValue ?? "";
if (value is true) value = 1;
if (value is false) value = 0;
if (dataValue.Type == FieldType.Object)
{
value = ModContext.AssertId(value.ToString()!);
type = 1;
}
var strValue = value.ToString()!;
if (dataValue.Type == FieldType.Position)
{
if (dataValue.Value.TryGetValue(out string? _))
{
strValue = strValue.Replace("<", "").Replace(">", "").Replace(" ", "");
var split = strValue.Split(',');
strValue = $"{split[0]}\u001F{split[1]}\u001F{split[2]}";
}
else
{
var obj = dataValue.Value.ToDictionary();
strValue = $"{obj["X"]}\u001F{obj["Y"]}\u001F{obj["Z"]}";
}
}
else if (dataValue.Type == FieldType.Rotation)
{
if (dataValue.Value.TryGetValue(out string? _))
{
strValue = strValue.Replace("<", "").Replace(">", "").Replace(" ", "");
var split = strValue.Split(',');
strValue = $"{split[0]}\u001F{split[1]}\u001F{split[2]}\u001F{split[3]}";
}
else
{
var obj = dataValue.Value.ToDictionary();
strValue = $"{obj["X"]}\u001F{obj["Y"]}\u001F{obj["Z"]}\u001F{obj["W"]}";
}
}
if (strValue == "True") strValue = "1";
else if (strValue == "False") strValue = "0";
dataString.Append($"{dataKey}={type}:{strValue}\n");
}
if (dataString.Length > 0)
{
dataString.Length -= 1;
levelObjectTemplate.LegoInfo = LegoDataDictionary.FromString(dataString.ToString());
}
else
{
levelObjectTemplate.LegoInfo = new LegoDataDictionary();
}
// Resize and add the object
var levelObjectsTemplates = scene.LevelObjects.Templates;
Array.Resize(ref levelObjectsTemplates, levelObjectsTemplates.Length + 1);
levelObjectsTemplates[^1] = levelObjectTemplate;
scene.LevelObjects.Templates = levelObjectsTemplates;
});
}
}

View File

@@ -4,6 +4,8 @@ using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Xml;
using System.Xml.Serialization;
using InfectedRose.Core;
@@ -14,11 +16,11 @@ using InfectedRose.Lvl;
using InfectedRose.Triggers;
using RakDotNet.IO;
namespace InfectedRose.Interface.Templates
namespace InfectedRose.Interface.Templates;
[ModType("zone")]
public class ZoneMod : ModType
{
[ModType("zone")]
public class ZoneMod : ModType
{
public void InsertZone(Mod mod)
{
var zoneTable = ModContext.Database["ZoneTable"];
@@ -129,7 +131,9 @@ namespace InfectedRose.Interface.Templates
var fileName = mod.Id.Replace("-", "_").Replace(":", "_");
var root = $"./compiled/{fileName}/";
var root = Path.Combine(ModContext.Root, ModContext.Configuration.ResourceFolder, $"./compiled/{fileName}/");
root = new Uri(root).LocalPath;
if (!Directory.Exists(root))
{
@@ -145,7 +149,7 @@ namespace InfectedRose.Interface.Templates
var terrainFile = $"{fileName}.raw";
File.CreateSymbolicLink(Path.Combine(root, terrainFile), Path.Combine("../../", zone.TerrainFile));
File.Copy(zone.TerrainFile, Path.Combine(root, terrainFile));
var luzFile = new LuzFile();
@@ -414,8 +418,8 @@ namespace InfectedRose.Interface.Templates
levelObjectTemplate.Lot = ModContext.AssertId(template.Id);
levelObjectTemplate.Position = template.Position;
levelObjectTemplate.Rotation = template.Rotation;
levelObjectTemplate.Scale = template.Scale;
levelObjectTemplate.AssetType = (uint)template.Type;
levelObjectTemplate.Scale = template.Scale ?? 1;
levelObjectTemplate.AssetType = (uint) (template.Type ?? 1);
levelObjectTemplate.GlomId = 1;
var objectId = template.ObjectId ?? objId++;
@@ -433,25 +437,51 @@ namespace InfectedRose.Interface.Templates
foreach (var (dataKey, dataValue) in template.Data)
{
var type = dataValue.TypeId;
var value = dataValue.Value;
var value = dataValue.ObjectValue ?? "";
if (value is true) value = 1;
if (value is false) value = 0;
if (dataValue.Type == "id")
if (dataValue.Type == FieldType.Object)
{
value = ModContext.AssertId(value.ToString()!);
type = 1;
}
if (value != null)
{
value = value.ToString();
var strValue = value.ToString()!;
if (value == "True") value = "1";
else if (value == "False") value = "0";
if (dataValue.Type == FieldType.Position)
{
if (dataValue.Value.TryGetValue(out string? _))
{
strValue = strValue.Replace("<", "").Replace(">", "").Replace(" ", "");
var split = strValue.Split(',');
strValue = $"{split[0]}\u001F{split[1]}\u001F{split[2]}";
}
else
{
var obj = dataValue.Value.ToDictionary();
strValue = $"{obj["X"]}\u001F{obj["Y"]}\u001F{obj["Z"]}";
}
}
else if (dataValue.Type == FieldType.Rotation)
{
if (dataValue.Value.TryGetValue(out string? _))
{
strValue = strValue.Replace("<", "").Replace(">", "").Replace(" ", "");
var split = strValue.Split(',');
strValue = $"{split[0]}\u001F{split[1]}\u001F{split[2]}\u001F{split[3]}";
}
else
{
var obj = dataValue.Value.ToDictionary();
strValue = $"{obj["X"]}\u001F{obj["Y"]}\u001F{obj["Z"]}\u001F{obj["W"]}";
}
}
dataString.Append($"{dataKey}={type}:{value}\n");
if (strValue == "True") strValue = "1";
else if (strValue == "False") strValue = "0";
dataString.Append($"{dataKey}={type}:{strValue}\n");
}
if (dataString.Length > 0)
@@ -583,7 +613,13 @@ namespace InfectedRose.Interface.Templates
ModContext.AddToLocale($"ZoneTable_{entry.Key}_DisplayDescription", mod.Locale);
}
entry["zoneName"].Value = ModContext.ParseValue($"ASSET:MAP:{luzFilename}");
var relativeTo = Path.Combine(ModContext.Root, "../res", "maps/");
var uri = new Uri(relativeTo);
var relativePath = uri.MakeRelativeUri(new Uri(luzFilename)).ToString();
entry["zoneName"].Value = relativePath;
}
public override void Apply(Mod mod)
@@ -606,5 +642,4 @@ namespace InfectedRose.Interface.Templates
"Action " + mod.Action + " is not supported for zone mods"
);
}
}
}

View File

@@ -172,8 +172,8 @@ namespace InfectedRose.Lvl
var str = value switch
{
Vector2 vec2 => $"{vec2.X}{InfoSeparator}{vec2.Y}",
Vector3 vec3 => $"{vec3.X}{InfoSeparator}{vec3.Z}{InfoSeparator}{vec3.Y}",
Vector4 vec4 => $"{vec4.X}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.W}",
Vector3 vec3 => $"{vec3.X}{InfoSeparator}{vec3.Y}{InfoSeparator}{vec3.Z}",
Vector4 vec4 => $"{vec4.X}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.W}",
LegoDataList list => list.ToString(),
_ => value.ToString()
};
@@ -198,8 +198,8 @@ namespace InfectedRose.Lvl
var val = v switch
{
Vector2 vec2 => $"{vec2.X}{InfoSeparator}{vec2.Y}",
Vector3 vec3 => $"{vec3.X}{InfoSeparator}{vec3.Z}{InfoSeparator}{vec3.Y}",
Vector4 vec4 => $"{vec4.X}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.W}",
Vector3 vec3 => $"{vec3.X}{InfoSeparator}{vec3.Y}{InfoSeparator}{vec3.Z}",
Vector4 vec4 => $"{vec4.X}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.W}",
LegoDataList list => list.ToString(),
false => "0",
true => "1",

View File

@@ -113,11 +113,11 @@ namespace InfectedRose.Lvl
break;
case Vector3 vec3:
val = $"{vec3.X}{InfoSeparator}{vec3.Z}{InfoSeparator}{vec3.Y}";
val = $"{vec3.X}{InfoSeparator}{vec3.Y}{InfoSeparator}{vec3.Z}";
break;
case Vector4 vec4:
val = $"{vec4.X}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.W}";
val = $"{vec4.X}{InfoSeparator}{vec4.Y}{InfoSeparator}{vec4.Z}{InfoSeparator}{vec4.W}";
break;
case LegoDataList list:

View File

@@ -40,6 +40,8 @@ namespace InfectedRose.Lvl
{
writer.Write<ushort>(0);
}
writer.WriteNiString(Config, true);
}
public void Deserialize(BitReader reader)
@@ -59,6 +61,8 @@ namespace InfectedRose.Lvl
{
reader.Read<ushort>();
}
Config = reader.ReadNiString(true);
}
}
}

View File

@@ -4,10 +4,10 @@ using InfectedRose.Luz;
using InfectedRose.Lvl;
using RakDotNet.IO;
namespace InfectedRose.Utilities
namespace InfectedRose.Utilities;
public static class Checksum
{
public static class Checksum
{
/// <summary>
/// Calculate the checksum for a LEGO Universe zone
/// </summary>
@@ -220,6 +220,4 @@ namespace InfectedRose.Utilities
return (uint) (upper << 16 | lower);
}
}
}

View File

@@ -1,7 +1,7 @@
namespace InfectedRose.Utilities
namespace InfectedRose.Utilities;
internal struct ChecksumLayer
{
internal struct ChecksumLayer
{
internal uint Id { get; set; }
internal uint Layer { get; set; }
@@ -21,5 +21,4 @@ namespace InfectedRose.Utilities
total += value; // Add to total
}
}
}
}

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using InfectedRose.Luz;
using InfectedRose.Lvl;
namespace InfectedRose.Utilities
namespace InfectedRose.Utilities;
public static class LuzFileExtensions
{
public static class LuzFileExtensions
{
public static uint GenerateChecksum(this LuzFile @this, List<LvlFile> scenes)
{
if (@this.Scenes.Length != scenes.Count)
@@ -64,5 +64,4 @@ namespace InfectedRose.Utilities
return (uint) (upper << 16 | lower);
}
}
}