diff --git a/InfectedRose.Interface/EnvironmentalMod.cs b/InfectedRose.Interface/EnvironmentalMod.cs new file mode 100644 index 0000000..a449b26 --- /dev/null +++ b/InfectedRose.Interface/EnvironmentalMod.cs @@ -0,0 +1,24 @@ +namespace InfectedRose.Interface +{ + [ModType("environmental")] + public class EnvironmentalMod : ModType + { + public override void Apply(Mod mod) + { + if (mod.Action != "add") + { + return; + } + + mod.Default("static", 1); + mod.Default("shader_id", 1); + + var obj = ObjectMod.CreateObject(mod); + + obj["type"].Value = "Environmental"; + + ObjectMod.AddComponent(mod, obj, ComponentId.RenderComponent); + ObjectMod.AddComponent(mod, obj, ComponentId.SimplePhysicsComponent); + } + } +} \ No newline at end of file diff --git a/InfectedRose.Interface/ItemMod.cs b/InfectedRose.Interface/ItemMod.cs new file mode 100644 index 0000000..1f6d2dc --- /dev/null +++ b/InfectedRose.Interface/ItemMod.cs @@ -0,0 +1,55 @@ +namespace InfectedRose.Interface +{ + [ModType("item")] + public class ItemMod : ModType + { + public override void Apply(Mod mod) + { + if (mod.Action != "add") + { + return; + } + + mod.Default("nametag", false); + mod.Default("placeable", false); + mod.Default("localize", true); + mod.Default("locStatus", 2); + mod.Default("offsetGroupID", 78); + mod.Default("itemInfo", 0); + mod.Default("fade", true); + mod.Default("fadeInTime", 1); + mod.Default("shader_id", 23); + mod.Default("audioEquipMetaEventSet", "Weapon_Hammer_Generic"); + + var obj = ObjectMod.CreateObject(mod); + + obj["type"].Value = "Loot"; + + ObjectMod.AddComponent(mod, obj, ComponentId.RenderComponent); + ObjectMod.AddComponent(mod, obj, ComponentId.ItemComponent); + ObjectMod.AddComponent(mod, obj, ComponentId.SkillComponent); + + if (mod.Skills != null) + { + var objectSkillsTable = ModContext.Database["ObjectSkills"]; + + foreach (var skill in mod.Skills) + { + var objectSkill = objectSkillsTable.Create(obj.Key); + + objectSkill["castOnType"].Value = mod.HasValue("castOnType") ? mod.GetValue("castOnType") : 0; + objectSkill["AICombatWeight"].Value = 0; + + if (skill.TryGetValue(out int? value)) + { + objectSkill["skillID"].Value = value; + } + else if (skill.TryGetValue(out string? itemId)) + { + ModContext.AwaitId(itemId, lot => objectSkill["skillID"].Value = lot); + } + } + } + } + } +} \ No newline at end of file diff --git a/InfectedRose.Interface/Locale.cs b/InfectedRose.Interface/Locale.cs new file mode 100644 index 0000000..4f02eab --- /dev/null +++ b/InfectedRose.Interface/Locale.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace InfectedRose.Interface +{ + [XmlRoot("locales")] + public class Locales + { + [XmlAttribute("count")] + public int Count { get; set; } + + [XmlElement("locale")] + public string[] Locale { get; set; } + } + + [XmlRoot("translation")] + public class Translation + { + [XmlAttribute("locale")] + public string Locale { get; set; } + + [XmlText] + public string Text { get; set; } + } + + [XmlRoot("phrase")] + public class Phrase + { + [XmlAttribute("id")] + public string Id { get; set; } + + [XmlElement("translation")] + public List Translations { get; set; } + } + + [XmlRoot("phrases")] + public class Phrases + { + [XmlAttribute("count")] + public int Count { get; set; } + + [XmlElement("phrase")] + public List Phrase { get; set; } + } + + [XmlRoot("localization")] + public class Localization + { + [XmlAttribute("version")] + public float Version { get; set; } = 1.200000f; + + [XmlElement("locales")] + public Locales Locales { get; set; } + + [XmlElement("phrases")] + public Phrases Phrases { get; set; } + } +} \ No newline at end of file diff --git a/InfectedRose.Interface/Mod.cs b/InfectedRose.Interface/Mod.cs index 7ea2e90..db3add5 100644 --- a/InfectedRose.Interface/Mod.cs +++ b/InfectedRose.Interface/Mod.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace InfectedRose.Interface @@ -8,26 +9,68 @@ namespace InfectedRose.Interface public class Mod { [JsonPropertyName("id")] - public string Id { get; set; } + public string Id { get; set; } = ""; [JsonPropertyName("type")] - public string Type { get; set; } + public string Type { get; set; } = ""; [JsonPropertyName("action")] public string Action { get; set; } = "add"; + + [JsonPropertyName("show-defaults")] + public bool? ShowDefaults { get; set; } [JsonPropertyName("components")] - public string[] Components { get; set; } + public string[]? Components { get; set; } [JsonPropertyName("table")] - public string Table { get; set; } + public string? Table { get; set; } + + [JsonPropertyName("items")] + public JsonValue[]? Items { get; set; } + [JsonPropertyName("skills")] + public JsonValue[]? Skills { get; set; } + + [JsonPropertyName("locale")] + public Dictionary? Locale { get; set; } = new Dictionary + { + {"en_US", ""} + }; + [JsonPropertyName("values")] public Dictionary Values { get; set; } = new Dictionary(); + + [JsonIgnore] + public Dictionary Defaults { get; set; } = new Dictionary(); public T GetValue(string id) { - return ((JsonElement) Values[id]).Deserialize()!; + if (Values[id] is JsonElement jsonElement) + { + return jsonElement.Deserialize()!; + } + + if (Values[id] is T value) + { + return value; + } + + return (T) Convert.ChangeType(Values[id], typeof(T)); + } + + public bool HasValue(string id) + { + return Values.ContainsKey(id); + } + + public void Default(string id, T value) + { + Defaults[id] = value; + + if (HasValue(id)) return; + + Values[id] = value; } public int GetComponentType() diff --git a/InfectedRose.Interface/ModContext.cs b/InfectedRose.Interface/ModContext.cs index 9994313..bcd569f 100644 --- a/InfectedRose.Interface/ModContext.cs +++ b/InfectedRose.Interface/ModContext.cs @@ -23,6 +23,8 @@ namespace InfectedRose.Interface public static AccessDatabase Database { get; set; } + public static Localization Localization { get; set; } + public static Dictionary Ids { get; set; } = new Dictionary(); public static Dictionary Mods { get; set; } = new Dictionary(); @@ -59,6 +61,60 @@ namespace InfectedRose.Interface { return Mods[id]; } + + public static Table? GetComponentTable(ComponentId component) + { + return Database[GetComponentTableName(component)]; + } + + public static Table? GetComponentTable(string component) + { + return Database[GetComponentTableName(component)]; + } + + public static string GetComponentTableName(ComponentId component) + { + return GetComponentTableName(component.ToString()); + } + + public static string GetComponentTableName(string component) + { + if (component.Contains("PhysicsComponent")) + { + return "PhysicsComponent"; + } + + return component; + } + + public static void AddToLocale(string id, string text, string locale) + { + var phrase = Localization.Phrases.Phrase.FirstOrDefault(p => p.Id == id); + + if (phrase == null) + { + phrase = new Phrase(); + + phrase.Id = id; + + phrase.Translations = new List(); + + Localization.Phrases.Phrase.Add(phrase); + } + + var translation = phrase.Translations.FirstOrDefault(t => t.Locale == locale); + + if (translation == null) + { + translation = new Translation(); + + translation.Locale = locale; + + phrase.Translations.Add(translation); + } + + translation.Text = text; + } public static string ParseValue(string value) { @@ -84,6 +140,12 @@ namespace InfectedRose.Interface value = value.Substring(8); } + else if (value.StartsWith("ICON:")) + { + root = "../../res/mesh/bricks/"; + + value = value.Substring(5); + } root = Root + root; @@ -97,16 +159,20 @@ namespace InfectedRose.Interface { foreach (var (key, objValue) in mod.Values) { - var value = (JsonElement) objValue; + var info = table.TableInfo.FirstOrDefault(column => column.Name == key); + + if (info == null) + { + continue; + } var field = row[key]; - var info = table.TableInfo.First(column => column.Name == key); switch (info.Type) { case DataType.Integer: { - var str = value.ToString(); + var str = objValue.ToString(); if (str.Contains(':')) { @@ -117,23 +183,23 @@ namespace InfectedRose.Interface } else { - field.Value = value.GetInt32(); + field.Value = mod.GetValue(key); } break; } case DataType.Float: - field.Value = value.GetSingle(); + field.Value = mod.GetValue(key); break; case DataType.Varchar: case DataType.Text: - field.Value = ParseValue(value.GetString()!); + field.Value = ParseValue(objValue.ToString()!); break; case DataType.Boolean: - field.Value = value.GetBoolean(); + field.Value = mod.GetValue(key); break; case DataType.Bigint: - field.Value = value.GetInt64(); + field.Value = mod.GetValue(key); break; } } diff --git a/InfectedRose.Interface/NpcMod.cs b/InfectedRose.Interface/NpcMod.cs new file mode 100644 index 0000000..d3ce793 --- /dev/null +++ b/InfectedRose.Interface/NpcMod.cs @@ -0,0 +1,79 @@ +namespace InfectedRose.Interface +{ + [ModType("npc")] + public class NpcMod : ModType + { + public override void Apply(Mod mod) + { + if (mod.Action != "add") + { + return; + } + + mod.Default("render_asset", @"animations\\minifig\\mf_ambient.kfm"); + mod.Default("animationGroupIDs", "93"); + mod.Default("shader_id", 14); + mod.Default("static", 1); + mod.Default("jump", 0); + mod.Default("doublejump", 0); + mod.Default("speed", 5); + mod.Default("rotSpeed", 360); + mod.Default("playerHeight", 4.4f); + mod.Default("playerRadius", 1); + mod.Default("pcShapeType", 2); + mod.Default("collisionGroup", 3); + mod.Default("airSpeed", 5); + mod.Default("jumpAirSpeed", 25); + + mod.Default("chatBubbleOffset", 6); + mod.Default("fade", true); + mod.Default("fadeInTime", 1); + mod.Default("billboardHeight", 6); + mod.Default("AudioMetaEventSet", "Emotes_Non_Player"); + mod.Default("usedropshadow", false); + mod.Default("preloadAnimations", false); + mod.Default("ignoreCameraCollision", false); + mod.Default("gradualSnap", false); + mod.Default("staticBillboard", false); + mod.Default("attachIndicatorsToNode", false); + + mod.Default("npcTemplateID", 17); + mod.Default("nametag", true); + mod.Default("placeable", true); + mod.Default("localize", true); + mod.Default("locStatus", 2); + + var obj = ObjectMod.CreateObject(mod); + + obj["type"].Value = "UserGeneratedNPCs"; + + ObjectMod.AddComponent(mod, obj, ComponentId.SimplePhysicsComponent); + ObjectMod.AddComponent(mod, obj, ComponentId.RenderComponent); + ObjectMod.AddComponent(mod, obj, ComponentId.MinifigComponent); + + if (mod.Items != null) + { + var id = 0; + + foreach (var item in mod.Items) + { + var itemComponent = ObjectMod.AddComponent(mod, obj, ComponentId.InventoryComponent, id)!; + + itemComponent["count"].Value = 1; + itemComponent["equip"].Value = true; + + id = itemComponent.Key; + + if (item.TryGetValue(out int? value)) + { + itemComponent["itemid"].Value = value; + } + else if (item.TryGetValue(out string? itemId)) + { + ModContext.AwaitId(itemId, lot => itemComponent["itemid"].Value = lot); + } + } + } + } + } +} \ No newline at end of file diff --git a/InfectedRose.Interface/ObjectMod.cs b/InfectedRose.Interface/ObjectMod.cs index 524987b..8808833 100644 --- a/InfectedRose.Interface/ObjectMod.cs +++ b/InfectedRose.Interface/ObjectMod.cs @@ -1,8 +1,90 @@ +using InfectedRose.Database; + namespace InfectedRose.Interface { [ModType("object")] public class ObjectMod : ModType { + public static Row CreateObject(Mod mod) + { + var table = ModContext.Database["Objects"]; + + var row = table.Create(); + + row["name"].Value = mod.Id; + row["description"].Value = mod.Id; + + ModContext.ApplyValues(mod, row, table); + + ModContext.RegisterId(mod.Id, row.Key); + + if (mod.Components != null) + { + foreach (var component in mod.Components) + { + AwaitComponent(row, component); + } + } + + if (mod.Locale != null) + { + foreach (var (locale, text) in mod.Locale) + { + ModContext.AddToLocale($"Objects_{row.Key}_name", text, locale); + } + } + + return row; + } + + public static void AwaitComponent(Row obj, string component) + { + var row = ModContext.Database["ComponentsRegistry"].Create(obj.Key); + + ModContext.AwaitId(component, id => + { + row["component_id"].Value = id; + row["component_type"].Value = ModContext.GetMod(component).GetComponentType(); + }); + } + + public static Row? AddComponent(Mod mod, Row obj, ComponentId componentId, int id = 0) + { + var table = ModContext.GetComponentTable(componentId); + + Row component; + + if (id == 0) + { + var row = ModContext.Database["ComponentsRegistry"].Create(obj.Key); + + row["component_type"].Value = (int) componentId; + + if (table == null) + { + row["component_id"].Value = 0; + + return null; + } + + component = table.Create(); + + row["component_id"].Value = component.Key; + + ModContext.ApplyValues(mod, component, table); + + return component; + } + + if (table == null) return null; + + component = table.Create(id); + + ModContext.ApplyValues(mod, component, table); + + return component; + } + public override void Apply(Mod mod) { if (mod.Action != "add") @@ -10,24 +92,7 @@ namespace InfectedRose.Interface return; } - var table = ModContext.Database["Objects"]; - - var obj = table.Create(); - - ModContext.ApplyValues(mod, obj, table); - - ModContext.RegisterId(mod.Id, obj.Key); - - foreach (var component in mod.Components) - { - var row = ModContext.Database["ComponentsRegistry"].Create(obj.Key); - - ModContext.AwaitId(component, id => - { - row["component_id"].Value = id; - row["component_type"].Value = ModContext.GetMod(component).GetComponentType(); - }); - } + CreateObject(mod); } } } \ No newline at end of file diff --git a/InfectedRose.Interface/Program.cs b/InfectedRose.Interface/Program.cs index b5e53f3..33b9808 100644 --- a/InfectedRose.Interface/Program.cs +++ b/InfectedRose.Interface/Program.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; +using System.Xml.Serialization; using CommandLine; using InfectedRose.Database; using InfectedRose.Database.Fdb; @@ -71,16 +72,6 @@ namespace InfectedRose.Interface return JsonSerializer.Deserialize(stream) ?? new T(); } - public static string GetComponentTable(string component) - { - if (component.Contains("PhysicsComponent")) - { - return "PhysicsComponent"; - } - - return component; - } - public static void ApplyRow(Mod mod) { if (mod.Action != "add") @@ -88,7 +79,7 @@ namespace InfectedRose.Interface return; } - var tableName = GetComponentTable(mod.Type); + var tableName = ModContext.GetComponentTableName(mod.Type); if (!string.IsNullOrWhiteSpace(mod.Table)) { @@ -147,6 +138,17 @@ namespace InfectedRose.Interface ModContext.Mods[mod.Id] = mod; ApplyMod(mod); + + if (!mod.ShowDefaults.HasValue || mod.ShowDefaults.Value == false) + { + foreach (var (key, value) in mod.Defaults) + { + if (mod.HasValue(key) && mod.Values[key].ToString() == value.ToString()) + { + mod.Values.Remove(key); + } + } + } } Directory.SetCurrentDirectory(directory); @@ -259,7 +261,7 @@ namespace InfectedRose.Interface components.Add(componentMod.Id); - if (!ModContext.Database[GetComponentTable(componentMod.Type)].Seek(componentId, out var componentRow)) + if (!ModContext.Database[ModContext.GetComponentTableName(componentMod.Type)].Seek(componentId, out var componentRow)) { continue; } @@ -308,6 +310,22 @@ namespace InfectedRose.Interface ModContext.Configuration = ReadOrCreateJson(CommandLineOptions.Input); + // Load locale + XmlSerializer localeSerializer = new XmlSerializer(typeof(Localization)); + + var localeSourcePath = Path.Combine(Path.GetDirectoryName(CommandLineOptions.Input)!, "./locale.xml"); + var localeDestinationPath = Path.Combine(Path.GetDirectoryName(CommandLineOptions.Input)!, "../locale/locale.xml"); + + if (!File.Exists(localeSourcePath)) + { + File.Copy(localeDestinationPath, localeSourcePath); + } + + using (var stream = File.OpenRead(localeSourcePath)) + { + ModContext.Localization = (Localization) localeSerializer.Deserialize(stream)!; + } + // Check version if (string.IsNullOrWhiteSpace(ModContext.Configuration.Version)) { @@ -324,9 +342,10 @@ namespace InfectedRose.Interface // Open database if (!File.Exists(ModContext.Configuration.Database)) { - Console.WriteLine("Please specify a valid path to the base fdb database in mods.json."); + var databaseSourcePath = Path.Combine(Path.GetDirectoryName(CommandLineOptions.Input)!, "./cdclient.fdb"); + var databaseDestinationPath = Path.Combine(Path.GetDirectoryName(CommandLineOptions.Input)!, "../res/cdclient.fdb"); - return; + File.Copy(databaseDestinationPath, databaseSourcePath); } var origin = Console.GetCursorPosition(); @@ -426,6 +445,14 @@ namespace InfectedRose.Interface Console.SetCursorPosition(origin.Left, origin.Top); + using (var stream = File.Create(localeDestinationPath)) + { + ModContext.Localization.Locales.Count = ModContext.Localization.Locales.Locale.Length; + ModContext.Localization.Phrases.Count = ModContext.Localization.Phrases.Phrase.Count; + + localeSerializer.Serialize(stream, ModContext.Localization); + } + Console.WriteLine("Complete!"); } }