From 2c8097f707741a7be50d14010908c3953ae445b5 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Tue, 17 Aug 2021 02:43:52 -0700 Subject: [PATCH] fix(codegen): Fixes various code gen issues. Adds better embedded serialization support (#688) * Adds save flag support (see `ElvenGlasses` for an example) * Updates AOSAttributes so they are code genned * Fixes embedded object support by adding an `IRawSerializable` * Fixes various inconsistencies in serializing with codegen --- .../SerializableEntityGeneration.Class.cs | 62 +++++- ...zableEntityGeneration.DeserializeMethod.cs | 46 ++++- .../SerializableEntityGeneration.Property.cs | 3 +- ...lizableEntityGeneration.SerializeMethod.cs | 50 ++++- .../SerializationGenerator.ContentStruct.cs | 72 ++++++- .../ISerializableMigrationRule.cs | 3 +- .../Rules/ArrayMigrationRule.cs | 11 +- .../Rules/EnumMigrationRule.cs | 2 +- .../Rules/HashSetMigrationRule.cs | 11 +- .../Rules/KeyValuePairMigrationRule.cs | 14 +- .../Rules/ListMigrationRule.cs | 11 +- .../Rules/PrimitiveTypeMigrationRule.cs | 2 +- .../Rules/PrimitiveUOTypeMigrationRule.cs | 2 +- ...ule.cs => RawSerializableMigrationRule.cs} | 14 +- .../SerializableInterfaceMigrationRule.cs | 2 +- ...rializationMethodSignatureMigrationRule.cs | 2 +- .../Rules/TimerMigrationRule.cs | 2 +- .../SerializableFieldFlagComparer.cs | 31 +++ .../SerializableMigrationRulesEngine.cs | 12 +- .../SerializableProperty.cs | 3 + .../SourceGeneration.Class.cs | 18 +- .../SourceGeneration/SourceGeneration.Enum.cs | 49 +++++ .../SymbolMetadata/SymbolMetadata.UO.cs | 24 +-- .../SerializableFieldSaveFlagAttribute.cs | 30 +++ .../Server/Serialization/IRawSerializable.cs | 23 +++ Projects/UOContent/Accounting/Account.cs | 27 ++- .../UOContent/Engines/Bulk Orders/LargeBOD.cs | 11 +- Projects/UOContent/Items/Addons/BaseAddon.cs | 16 +- .../Items/Addons/BaseAddonContainer.cs | 21 +- .../UOContent/Items/Addons/SolenAntHole.cs | 7 +- Projects/UOContent/Items/Aquarium/Aquarium.cs | 41 +++- Projects/UOContent/Items/Armor/BaseArmor.cs | 27 +-- .../Items/Armor/Glasses/ArtsGlasses.cs | 24 +-- .../Items/Armor/Glasses/ElvenGlasses.cs | 108 +++-------- .../Items/Armor/Glasses/LyricalGlasses.cs | 2 +- .../Items/Armor/Glasses/MaceShieldGlasses.cs | 2 +- .../UOContent/Items/Clothing/BaseClothing.cs | 47 +++-- Projects/UOContent/Items/Jewels/BaseJewel.cs | 9 +- .../Minor Artifacts/ML/BrightsightLenses.cs | 2 +- .../UOContent/Items/Quivers/BaseQuiver.cs | 8 +- .../Items/Resources/Blacksmithing/Ore.cs | 4 +- .../Items/Skill Items/Magical/Spellbook.cs | 6 +- .../UOContent/Items/Talismans/BaseTalisman.cs | 162 ++++++++-------- .../UOContent/Items/Weapons/BaseWeapon.cs | 32 ++-- ...json => Server.Accounting.Account.v3.json} | 2 +- .../Migrations/Server.BaseAttributes.v0.json | 24 +++ ...erver.Engines.BulkOrders.LargeBOD.v1.json} | 2 +- ....v3.json => Server.Items.Aquarium.v4.json} | 6 +- .../Server.Items.ArtsGlasses.v0.json | 5 + ...v2.json => Server.Items.BaseAddon.v3.json} | 2 +- ...> Server.Items.BaseAddonContainer.v2.json} | 2 +- .../Server.Items.ElvenGlasses.v0.json | 15 ++ ...json => Server.Items.SolenAntHole.v1.json} | 2 +- Projects/UOContent/Misc/AOS.cs | 179 +++++------------- 54 files changed, 787 insertions(+), 507 deletions(-) rename Projects/SerializationGenerator/SerializableMigration/Rules/{EmbeddedSerializableMigrationRule.cs => RawSerializableMigrationRule.cs} (85%) create mode 100644 Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs create mode 100644 Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Enum.cs create mode 100644 Projects/Server/Serialization/Attributes/SerializableFieldSaveFlagAttribute.cs create mode 100644 Projects/Server/Serialization/IRawSerializable.cs rename Projects/UOContent/Migrations/{Server.Accounting.Account.v2.json => Server.Accounting.Account.v3.json} (99%) create mode 100644 Projects/UOContent/Migrations/Server.BaseAttributes.v0.json rename Projects/UOContent/Migrations/{Server.Engines.BulkOrders.LargeBOD.v0.json => Server.Engines.BulkOrders.LargeBOD.v1.json} (96%) rename Projects/UOContent/Migrations/{Server.Items.Aquarium.v3.json => Server.Items.Aquarium.v4.json} (90%) create mode 100644 Projects/UOContent/Migrations/Server.Items.ArtsGlasses.v0.json rename Projects/UOContent/Migrations/{Server.Items.BaseAddon.v2.json => Server.Items.BaseAddon.v3.json} (96%) rename Projects/UOContent/Migrations/{Server.Items.BaseAddonContainer.v1.json => Server.Items.BaseAddonContainer.v2.json} (97%) create mode 100644 Projects/UOContent/Migrations/Server.Items.ElvenGlasses.v0.json rename Projects/UOContent/Migrations/{Server.Items.SolenAntHole.v0.json => Server.Items.SolenAntHole.v1.json} (95%) diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs index 483ed2ea4..b501c1e18 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs @@ -13,6 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; @@ -113,6 +114,8 @@ namespace SerializationGenerator compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_INTERFACE); var parentSerializableAttribute = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE); + var serializableFieldSaveFlagAttribute = + compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE); // If we have a parent that is or derives from ISerializable, then we are in override var isOverride = classSymbol.BaseType.ContainsInterface(serializableInterface); @@ -122,9 +125,28 @@ namespace SerializationGenerator return null; } + var isRawSerializable = classSymbol.HasRawSerializableInterface(compilation, ImmutableArray.Empty); + var version = (int)serializableAttr.ConstructorArguments[0].Value!; var encodedVersion = (bool)serializableAttr.ConstructorArguments[1].Value!; + // Let's find out if we need to do serialization flags + var serializablePropertyFlagGettersSet = new SortedSet<(IMethodSymbol, int)>(new SerializableFieldFlagComparer()); + foreach (var m in classSymbol.GetMembers().OfType()) + { + var getSaveFlagAttribute = m.GetAttribute(serializableFieldSaveFlagAttribute); + if (getSaveFlagAttribute == null) + { + continue; + } + + var attrCtorArgs = getSaveFlagAttribute.ConstructorArguments; + var order = (int)attrCtorArgs[0].Value!; + + serializablePropertyFlagGettersSet.Add((m, order)); + } + var serializablePropertyFlagGetters = serializablePropertyFlagGettersSet.ToImmutableArray(); + var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; @@ -133,7 +155,11 @@ namespace SerializationGenerator source.AppendLine("#pragma warning disable\n"); source.GenerateNamespaceStart(namespaceName); - source.GenerateClassStart(className, ImmutableArray.Empty); + var interfaces = !embedded || isRawSerializable + ? Array.Empty() + : new ITypeSymbol[] { compilation.GetTypeByMetadataName(SymbolMetadata.RAW_SERIALIZABLE_INTERFACE) }; + + source.GenerateClassStart(className, " ", interfaces.ToImmutableArray()); const string indent = " "; @@ -224,7 +250,8 @@ namespace SerializationGenerator allAttributes, serializableTypes, embeddedSerializableTypes, - classSymbol + classSymbol, + serializablePropertyFlagGetters.FirstOrDefault(m => m.Item2 == order).Item1 ); serializablePropertySet.Add(serializableProperty); @@ -271,7 +298,7 @@ namespace SerializationGenerator var migration = migrations[i]; if (migration.Version < version) { - source.GenerateMigrationContentStruct(migration); + source.GenerateMigrationContentStruct(migration, classSymbol); source.AppendLine(); } } @@ -282,7 +309,8 @@ namespace SerializationGenerator compilation, isOverride, encodedVersion, - serializableProperties + serializableProperties, + serializablePropertyFlagGetters ); source.AppendLine(); @@ -295,10 +323,32 @@ namespace SerializationGenerator encodedVersion, migrations, serializableProperties, - parentFieldOrProperty + parentFieldOrProperty, + serializablePropertyFlagGetters ); - source.GenerateClassEnd(); + // Serialize SaveFlag enum class + if (serializablePropertyFlagGetters.Length > 0) + { + source.AppendLine(); + source.GenerateEnumStart( + "SaveFlag", + " ", + true, + Accessibility.Private + ); + + int index = 0; + source.GenerateEnumValue(" ", true, "None", index++); + foreach (var (_, order) in serializablePropertyFlagGetters) + { + source.GenerateEnumValue(" ", true, serializableProperties[order].Name, index++); + } + + source.GenerateEnumEnd(" "); + } + + source.GenerateClassEnd(" "); source.GenerateNamespaceEnd(); if (migrationPath != null) diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs index a63be123b..c0e540cfb 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs @@ -32,7 +32,8 @@ namespace SerializationGenerator bool encodedVersion, ImmutableArray migrations, ImmutableArray properties, - ISymbol parentFieldOrProperty + ISymbol parentFieldOrProperty, + ImmutableArray<(IMethodSymbol, int)> propertyFlagGetters ) { var genericReaderInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_READER_INTERFACE); @@ -47,6 +48,7 @@ namespace SerializationGenerator ); const string indent = " "; + const string innerIndent = $"{indent} "; if (isOverride) { @@ -89,7 +91,7 @@ namespace SerializationGenerator source.AppendLine(); source.AppendLine($"{indent}if (version == {migrationVersion})"); source.AppendLine($"{indent}{{"); - source.AppendLine($"{indent} MigrateFrom(new V{migrationVersion}Content(reader));"); + source.AppendLine($"{indent} MigrateFrom(new V{migrationVersion}Content(reader, this));"); source.AppendLine($"{indent} {parent}.MarkDirty();"); if (afterDeserialization != null) { @@ -115,17 +117,41 @@ namespace SerializationGenerator } } - foreach (var property in properties) + if (propertyFlagGetters.Length > 0) { source.AppendLine(); - var rule = SerializableMigrationRulesEngine.Rules[property.Rule]; - rule.GenerateDeserializationMethod( - source, - indent, - property - ); + source.AppendLine($"{indent}var saveFlags = reader.ReadEnum();"); + } - (rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, indent, property, compilation, classSymbol); + foreach (var property in properties) + { + var usesSaveFlag = propertyFlagGetters.Any(m => m.Item2 == property.Order); + var rule = SerializableMigrationRulesEngine.Rules[property.Rule]; + + if (usesSaveFlag) + { + source.AppendLine($"\n{indent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{indent}{{"); + rule.GenerateDeserializationMethod( + source, + innerIndent, + property, + "this" + ); + (rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, innerIndent, property, compilation, classSymbol); + + source.AppendLine($"{indent}}}"); + } + else + { + source.AppendLine(); + rule.GenerateDeserializationMethod( + source, + indent, + property, + "this" + ); + (rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, indent, property, compilation, classSymbol); + } } if (afterDeserialization != null) diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs index 870a43375..f2dc08953 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Property.cs @@ -13,6 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -28,7 +29,7 @@ namespace SerializationGenerator Accessibility getter, Accessibility? setter, bool isVirtual, - ISymbol? parentFieldOrProperty = null + ISymbol? parentFieldOrProperty ) { var fieldName = fieldSymbol.Name; diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs index 47dd61dc4..8f23d998c 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs @@ -14,6 +14,7 @@ *************************************************************************/ using System.Collections.Immutable; +using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using SerializableMigration; @@ -27,7 +28,8 @@ namespace SerializationGenerator Compilation compilation, bool isOverride, bool encodedVersion, - ImmutableArray properties + ImmutableArray properties, + ImmutableArray<(IMethodSymbol, int)> propertyFlagGetters ) { var genericWriterInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_WRITER_INTERFACE); @@ -42,6 +44,7 @@ namespace SerializationGenerator ); const string indent = " "; + const string innerIndent = $"{indent} "; if (isOverride) { @@ -52,14 +55,47 @@ namespace SerializationGenerator // Version source.AppendLine($"{indent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);"); + // Let's collect the flags + if (propertyFlagGetters.Length > 0) + { + source.AppendLine($"\n{indent}var saveFlags = SaveFlag.None;"); + + foreach (var (m, order) in propertyFlagGetters) + { + source.AppendLine($"{indent}if ({m.Name}())\n{indent}{{"); + + var propertyName = properties[order].Name; + source.AppendLine($"{innerIndent}saveFlags |= SaveFlag.{propertyName};"); + + source.AppendLine($"{indent}}}"); + } + + source.AppendLine($"{indent}writer.WriteEnum(saveFlags);"); + } + foreach (var property in properties) { - source.AppendLine(); - SerializableMigrationRulesEngine.Rules[property.Rule].GenerateSerializationMethod( - source, - indent, - property - ); + var usesSaveFlag = propertyFlagGetters.Any(m => m.Item2 == property.Order); + + if (usesSaveFlag) + { + source.AppendLine($"\n{indent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{indent}{{"); + SerializableMigrationRulesEngine.Rules[property.Rule].GenerateSerializationMethod( + source, + innerIndent, + property + ); + source.AppendLine($"{indent}}}"); + } + else + { + source.AppendLine(); + SerializableMigrationRulesEngine.Rules[property.Rule].GenerateSerializationMethod( + source, + indent, + property + ); + } } source.GenerateMethodEnd(" "); diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs index a1579ff77..5536f96ce 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs @@ -13,7 +13,9 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Linq; using System.Text; +using Microsoft.CodeAnalysis; using SerializableMigration; namespace SerializationGenerator @@ -22,7 +24,8 @@ namespace SerializationGenerator { public static void GenerateMigrationContentStruct( this StringBuilder source, - SerializableMetadata migration + SerializableMetadata migration, + INamedTypeSymbol classSymbol ) { const string indent = " "; @@ -34,16 +37,69 @@ namespace SerializationGenerator source.AppendLine($"{indent} internal readonly {serializableProperty.Type} {serializableProperty.Name};"); } - source.AppendLine($"{indent} internal V{migration.Version}Content(IGenericReader reader)"); + var innerIndent = $"{indent} "; + + var usesSaveFlags = migration.Properties.Any(p => p.UsesSaveFlag == true); + + if (usesSaveFlags) + { + source.AppendLine(); + source.GenerateEnumStart( + $"V{migration.Version}SaveFlag", + $"{indent} ", + true, + Accessibility.Private + ); + + int index = 0; + source.GenerateEnumValue(innerIndent, true, "None", index++); + foreach (var property in migration.Properties) + { + if (property.UsesSaveFlag == true) + { + source.GenerateEnumValue(innerIndent, true, property.Name, index++); + } + } + + source.GenerateEnumEnd($"{indent} "); + } + + source.AppendLine($"{indent} internal V{migration.Version}Content(IGenericReader reader, {classSymbol.ToDisplayString()} entity)"); source.AppendLine($"{indent} {{"); - foreach (var serializableProperty in migration.Properties) + if (usesSaveFlags) { - SerializableMigrationRulesEngine.Rules[serializableProperty.Rule].GenerateDeserializationMethod( - source, - $"{indent} ", - serializableProperty - ); + source.AppendLine($"{innerIndent}var saveFlags = reader.ReadEnum();"); + } + + if (migration.Properties.Length > 0) + { + source.AppendLine(); + foreach (var property in migration.Properties) + { + if (property.UsesSaveFlag == true) + { + source.AppendLine($"\n{innerIndent}if ((saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0)\n{innerIndent}{{"); + SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod( + source, + $"{innerIndent} ", + property, + "entity" + ); + source.AppendLine($"{innerIndent}}}\n{innerIndent}else\n{innerIndent}{{"); + source.AppendLine($"{innerIndent} {property.Name} = default;"); + source.AppendLine($"{innerIndent}}}"); + } + else + { + SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod( + source, + innerIndent, + property, + "entity" + ); + } + } } source.AppendLine($"{indent} }}"); diff --git a/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs index 064cfed6d..1f944c4fa 100644 --- a/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/ISerializableMigrationRule.cs @@ -36,7 +36,8 @@ namespace SerializableMigration void GenerateDeserializationMethod( StringBuilder source, string indent, - SerializableProperty property + SerializableProperty property, + string? parentReference ); void GenerateSerializationMethod( diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs index 24a41d3eb..f14bf0cd4 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/ArrayMigrationRule.cs @@ -48,7 +48,8 @@ namespace SerializableMigration attributes, serializableTypes, embeddedSerializableTypes, - parentSymbol + parentSymbol, + null ); var length = serializableArrayType.RuleArguments.Length; @@ -60,7 +61,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; @@ -74,7 +75,7 @@ namespace SerializableMigration Array.Copy(ruleArguments, 2, arrayElementRuleArguments, 0, ruleArguments.Length - 2); var propertyIndex = $"{property.Name}Index"; - source.AppendLine($"{indent}{property.Name} = new {ruleArguments[0]}[reader.ReadInt()];"); + source.AppendLine($"{indent}{property.Name} = new {ruleArguments[0]}[reader.ReadEncodedInt()];"); source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {property.Name}.Length; {propertyIndex}++)"); source.AppendLine($"{indent}{{"); @@ -86,7 +87,7 @@ namespace SerializableMigration RuleArguments = arrayElementRuleArguments }; - arrayElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableArrayElement); + arrayElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableArrayElement, parentReference); source.AppendLine($"{indent}}}"); } @@ -110,7 +111,7 @@ namespace SerializableMigration var propertyIndex = $"{propertyVarPrefix}Index"; var propertyLength = $"{propertyVarPrefix}Length"; source.AppendLine($"{indent}var {propertyLength} = {property.Name}?.Length ?? 0;"); - source.AppendLine($"{indent}writer.Write({propertyLength});"); + source.AppendLine($"{indent}writer.WriteEncodedInt({propertyLength});"); source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyLength}; {propertyIndex}++)"); source.AppendLine($"{indent}{{"); diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs index 14d6ac1f5..18ebc3678 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs @@ -45,7 +45,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs index f785b913e..2b56f4d9a 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/HashSetMigrationRule.cs @@ -52,7 +52,8 @@ namespace SerializableMigration attributes, serializableTypes, embeddedSerializableTypes, - parentSymbol + parentSymbol, + null ); var extraOptions = ""; @@ -71,7 +72,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; @@ -95,7 +96,7 @@ namespace SerializableMigration var propertyCount = $"{propertyVarPrefix}Count"; source.AppendLine($"{indent}{ruleArguments[argumentsOffset]} {propertyEntry};"); - source.AppendLine($"{indent}var {propertyCount} = reader.ReadInt();"); + source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();"); source.AppendLine($"{indent}{property.Name} = new System.Collections.Generic.HashSet<{ruleArguments[argumentsOffset]}>({propertyCount});"); source.AppendLine($"{indent}for (var {propertyIndex} = 0; i < {propertyCount}; {propertyIndex}++)"); source.AppendLine($"{indent}{{"); @@ -108,7 +109,7 @@ namespace SerializableMigration RuleArguments = setElementRuleArguments }; - setElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableSetElement); + setElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableSetElement, parentReference); source.AppendLine($"{indent} {property.Name}.Add({propertyEntry});"); source.AppendLine($"{indent}}}"); @@ -142,7 +143,7 @@ namespace SerializableMigration source.AppendLine($"{indent}{property.Name}?.Tidy();"); } source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;"); - source.AppendLine($"{indent}writer.Write({propertyCount});"); + source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});"); source.AppendLine($"{indent}if ({propertyCount} > 0)"); source.AppendLine($"{indent}{{"); source.AppendLine($"{indent} foreach (var {propertyEntry} in {property.Name}!)"); diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs index 29e2c1842..9820296cb 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/KeyValuePairMigrationRule.cs @@ -51,7 +51,8 @@ namespace SerializableMigration attributes, serializableTypes, embeddedSerializableTypes, - parentSymbol + parentSymbol, + null ); var valueSerializedProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty( @@ -62,7 +63,8 @@ namespace SerializableMigration attributes, serializableTypes, embeddedSerializableTypes, - parentSymbol + parentSymbol, + null ); // Key @@ -81,7 +83,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; @@ -107,7 +109,8 @@ namespace SerializableMigration keyRule.GenerateDeserializationMethod( source, indent, - serializableKeyProperty + serializableKeyProperty, + parentReference ); var valueIndex = 3 + keyRuleArguments.Length; @@ -127,7 +130,8 @@ namespace SerializableMigration keyRule.GenerateDeserializationMethod( source, indent, - serializableValueProperty + serializableValueProperty, + parentReference ); source.AppendLine( diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs index 905787aa2..e78b8f27e 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/ListMigrationRule.cs @@ -52,7 +52,8 @@ namespace SerializableMigration attributes, serializableTypes, embeddedSerializableTypes, - parentSymbol + parentSymbol, + null ); var extraOptions = ""; @@ -71,7 +72,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; @@ -96,7 +97,7 @@ namespace SerializableMigration var propertyCount = $"{propertyVarPrefix}Count"; source.AppendLine($"{indent}{ruleArguments[argumentsOffset]} {propertyEntry};"); - source.AppendLine($"{indent}var {propertyCount} = reader.ReadInt();"); + source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();"); source.AppendLine($"{indent}{propertyName} = new System.Collections.Generic.List<{ruleArguments[argumentsOffset]}>({propertyCount});"); source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyCount}; {propertyIndex}++)"); source.AppendLine($"{indent}{{"); @@ -109,7 +110,7 @@ namespace SerializableMigration RuleArguments = listElementRuleArguments }; - listElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableListElement); + listElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableListElement, parentReference); source.AppendLine($"{indent} {propertyName}.Add({propertyEntry});"); source.AppendLine($"{indent}}}"); @@ -143,7 +144,7 @@ namespace SerializableMigration source.AppendLine($"{indent}{property.Name}?.Tidy();"); } source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;"); - source.AppendLine($"{indent}writer.Write({propertyCount});"); + source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});"); source.AppendLine($"{indent}if ({propertyCount} > 0)"); source.AppendLine($"{indent}{{"); source.AppendLine($"{indent} foreach (var {propertyEntry} in {property.Name}!)"); diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs index 1bd6c5e08..5aa6f90e2 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs @@ -80,7 +80,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs index 579c4892d..467d9d432 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveUOTypeMigrationRule.cs @@ -49,7 +49,7 @@ namespace SerializableMigration return ruleArguments != null; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/EmbeddedSerializableMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/RawSerializableMigrationRule.cs similarity index 85% rename from Projects/SerializationGenerator/SerializableMigration/Rules/EmbeddedSerializableMigrationRule.cs rename to Projects/SerializationGenerator/SerializableMigration/Rules/RawSerializableMigrationRule.cs index 504df29e9..e5ee8c725 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/EmbeddedSerializableMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/RawSerializableMigrationRule.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: EmbeddedSerializableMigrationRule.cs * + * File: RawSerializableMigrationRule.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -21,9 +21,9 @@ using SerializationGenerator; namespace SerializableMigration { - public class EmbeddedSerializableMigrationRule : ISerializableMigrationRule + public class RawSerializableMigrationRule : ISerializableMigrationRule { - public string RuleName => nameof(EmbeddedSerializableMigrationRule); + public string RuleName => nameof(RawSerializableMigrationRule); public bool GenerateRuleState( Compilation compilation, @@ -35,13 +35,13 @@ namespace SerializableMigration out string[] ruleArguments ) { - if (symbol is not INamedTypeSymbol namedTypeSymbol) + if (symbol is not ITypeSymbol typeSymbol) { ruleArguments = null; return false; } - if (!embeddedSerializableTypes.Contains(namedTypeSymbol)) + if (!typeSymbol.HasRawSerializableInterface(compilation, embeddedSerializableTypes)) { ruleArguments = null; return false; @@ -51,7 +51,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; @@ -61,7 +61,7 @@ namespace SerializableMigration } var propertyName = property.Name; - source.AppendLine($"{indent}{propertyName} = new {property.Type}(this);"); + source.AppendLine($"{indent}{propertyName} = new {property.Type}({parentReference ?? "this"});"); source.AppendLine($"{indent}{propertyName}.Deserialize(reader);"); } diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs index 5e2effe07..3cc932c80 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializableInterfaceMigrationRule.cs @@ -45,7 +45,7 @@ namespace SerializableMigration return false; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs index 960ce80ee..4bf93d52a 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs @@ -52,7 +52,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/TimerMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/TimerMigrationRule.cs index daa644196..de901432a 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/TimerMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/TimerMigrationRule.cs @@ -49,7 +49,7 @@ namespace SerializableMigration return true; } - public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property, string? parentReference) { var expectedRule = RuleName; var ruleName = property.Rule; diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs new file mode 100644 index 000000000..2b5320f68 --- /dev/null +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace SerializableMigration +{ + public class SerializableFieldFlagComparer : IComparer<(IMethodSymbol, int)> + { + public int Compare((IMethodSymbol, int) x, (IMethodSymbol, int) y) + { + var (methodSymbolX, orderX) = x; + var (methodSymbolY, orderY) = y; + + if (ReferenceEquals(methodSymbolX, methodSymbolY)) + { + return 0; + } + + if (ReferenceEquals(null, methodSymbolY)) + { + return 1; + } + + if (ReferenceEquals(null, methodSymbolX)) + { + return -1; + } + + return orderX.CompareTo(orderY); + } + } +} diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs index 9ec8a60de..c33859b5d 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs @@ -38,7 +38,7 @@ namespace SerializableMigration new PrimitiveUOTypeMigrationRule(), new SerializableInterfaceMigrationRule(), new SerializationMethodSignatureMigrationRule(), - new EmbeddedSerializableMigrationRule(), + new RawSerializableMigrationRule(), new TimerMigrationRule() }; @@ -55,7 +55,8 @@ namespace SerializableMigration ImmutableArray attributes, ImmutableArray serializableTypes, ImmutableArray embeddedSerializableTypes, - ISymbol? parentSymbol = default + ISymbol? parentSymbol, + IMethodSymbol? serializablePropertyFlagGetter ) { string propertyName; @@ -84,7 +85,8 @@ namespace SerializableMigration attributes, serializableTypes, embeddedSerializableTypes, - parentSymbol + parentSymbol, + serializablePropertyFlagGetter ); } @@ -96,7 +98,8 @@ namespace SerializableMigration ImmutableArray attributes, ImmutableArray serializableTypes, ImmutableArray embeddedSerializableTypes, - ISymbol? parentSymbol = default + ISymbol? parentSymbol, + IMethodSymbol? serializablePropertyFlagGetter ) { foreach (var rule in Rules.Values) @@ -116,6 +119,7 @@ namespace SerializableMigration Name = propertyName, Type = propertyType.ToDisplayString(), Order = order, + UsesSaveFlag = serializablePropertyFlagGetter != null ? true : null, Rule = rule.RuleName, RuleArguments = ruleArguments }; diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs index 2255edb46..fc5b56471 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableProperty.cs @@ -25,6 +25,9 @@ namespace SerializableMigration [JsonPropertyName("type")] public string Type { get; init; } + [JsonPropertyName("usesSaveFlag")] + public bool? UsesSaveFlag { get; init; } + [JsonPropertyName("rule")] public string Rule { get; init; } diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs index 50d9d2f99..9da11a1cc 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Class.cs @@ -21,9 +21,16 @@ namespace SerializationGenerator { public static partial class SourceGeneration { - public static void GenerateClassStart(this StringBuilder source, string className, ImmutableArray interfaces) + public static void GenerateClassStart( + this StringBuilder source, + string className, + string indent, + ImmutableArray interfaces, + Accessibility accessor = Accessibility.Public, + bool isPartial = true + ) { - source.Append($" public partial class {className}"); + source.Append($"{indent}{accessor.ToFriendlyString()} {(isPartial ? "partial " : "")}class {className}"); if (!interfaces.IsEmpty) { source.Append(" : "); @@ -37,13 +44,12 @@ namespace SerializationGenerator } } - source.AppendLine(@" - {"); + source.AppendLine($"\n{indent}{{"); } - public static void GenerateClassEnd(this StringBuilder source) + public static void GenerateClassEnd(this StringBuilder source, string indent) { - source.AppendLine(" }"); + source.AppendLine($"{indent}}}"); } // TODO: Generalize this to any field using dynamic indentation diff --git a/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Enum.cs b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Enum.cs new file mode 100644 index 000000000..8d18a2230 --- /dev/null +++ b/Projects/SerializationGenerator/SourceGeneration/SourceGeneration.Enum.cs @@ -0,0 +1,49 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SourceGeneration.Enum.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Text; +using Microsoft.CodeAnalysis; + +namespace SerializationGenerator +{ + public static partial class SourceGeneration + { + public static void GenerateEnumStart( + this StringBuilder source, + string enumName, + string indent, + bool useFlags, + Accessibility accessor = Accessibility.Public + ) + { + if (useFlags) + { + source.AppendLine($"{indent}[System.Flags]"); + } + source.AppendLine($"{indent}{accessor.ToFriendlyString()} enum {enumName}\n{indent}{{"); + } + + public static void GenerateEnumValue(this StringBuilder source, string indent, bool isFlag, string name, int value) + { + var valueStr = isFlag ? $"0x{1 << value:X8}" : value.ToString(); + source.AppendLine($"{indent}{name} = {valueStr},"); + } + + public static void GenerateEnumEnd(this StringBuilder source, string indent) + { + source.AppendLine($"{indent}}}"); + } + } +} diff --git a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs index b346873f3..bae23fa8d 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs @@ -44,6 +44,8 @@ namespace SerializationGenerator public const string TIMER_CLASS = "Server.Timer"; public const string TIMER_DRIFT_ATTRIBUTE = "Server.TimerDriftAttribute"; public const string DESERIALIZE_TIMER_FIELD_ATTRIBUTE = "Server.DeserializeTimerFieldAttribute"; + public const string SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE = "Server.SerializableFieldSaveFlagAttribute"; + public const string RAW_SERIALIZABLE_INTERFACE = "Server.IRawSerializable"; public static bool IsTimerDrift(this AttributeData attr, Compilation compilation) => attr?.IsAttribute(compilation.GetTypeByMetadataName(TIMER_DRIFT_ATTRIBUTE)) == true; @@ -77,9 +79,17 @@ namespace SerializationGenerator symbol.ContainsInterface(compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE)) || serializableTypes.Contains(symbol); - public static bool Contains(this ImmutableArray symbols, ITypeSymbol symbol) => + public static bool HasRawSerializableInterface( + this ITypeSymbol symbol, + Compilation compilation, + ImmutableArray embeddedSerializableTypes + ) => + symbol.ContainsInterface(compilation.GetTypeByMetadataName(RAW_SERIALIZABLE_INTERFACE)) || + embeddedSerializableTypes.Contains(symbol); + + public static bool Contains(this ImmutableArray symbols, ITypeSymbol? symbol) => symbol is INamedTypeSymbol namedSymbol && - symbols.Contains(namedSymbol, SymbolEqualityComparer.Default); + symbols.Contains(namedSymbol, SymbolEqualityComparer.Default) || symbols.Contains(symbol?.BaseType); public static bool HasGenericReaderCtor( this INamedTypeSymbol symbol, @@ -106,11 +116,6 @@ namespace SerializationGenerator ImmutableArray serializableTypes ) { - if (symbol.HasSerializableInterface(compilation, serializableTypes)) - { - return true; - } - var genericWriterInterface = compilation.GetTypeByMetadataName(GENERIC_WRITER_INTERFACE); return symbol.GetAllMethods("Serialize") @@ -129,11 +134,6 @@ namespace SerializationGenerator ImmutableArray serializableTypes ) { - if (symbol.HasSerializableInterface(compilation, serializableTypes)) - { - return true; - } - var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE); return symbol.GetAllMethods("Deserialize") diff --git a/Projects/Server/Serialization/Attributes/SerializableFieldSaveFlagAttribute.cs b/Projects/Server/Serialization/Attributes/SerializableFieldSaveFlagAttribute.cs new file mode 100644 index 000000000..fb9c16184 --- /dev/null +++ b/Projects/Server/Serialization/Attributes/SerializableFieldSaveFlagAttribute.cs @@ -0,0 +1,30 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SerializableFieldSaveFlagAttribute.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + /// + /// Hints to the source generator that the field with the same order value should use a save flag. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SerializableFieldSaveFlagAttribute : Attribute + { + public int Order { get; } + + public SerializableFieldSaveFlagAttribute(int order) => Order = order; + } +} diff --git a/Projects/Server/Serialization/IRawSerializable.cs b/Projects/Server/Serialization/IRawSerializable.cs new file mode 100644 index 000000000..731b56bd0 --- /dev/null +++ b/Projects/Server/Serialization/IRawSerializable.cs @@ -0,0 +1,23 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IRawSerializable.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + public interface IRawSerializable + { + void Deserialize(IGenericReader reader); + void Serialize(IGenericWriter writer); + } +} diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 24d897102..0fd95c522 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -11,7 +11,7 @@ using Server.Network; namespace Server.Accounting { - [Serializable(2)] + [Serializable(3)] public partial class Account : IAccount, IComparable, ISerializable { public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0); @@ -348,16 +348,18 @@ namespace Server.Accounting } } - // Handle old deserialization before codegen private void Deserialize(IGenericReader reader, int version) { - // Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing - reader.Seek(0, SeekOrigin.Begin); + if (version != 2) + { + // Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing + reader.Seek(0, SeekOrigin.Begin); + } _username = reader.ReadString(); - _passwordAlgorithm = (PasswordProtectionAlgorithm)reader.ReadInt(); + _passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum(); _password = reader.ReadString(); - _accessLevel = (AccessLevel)reader.ReadInt(); + _accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum(); _flags = reader.ReadInt(); _created = reader.ReadDateTime(); _lastLogin = reader.ReadDateTime(); @@ -390,9 +392,16 @@ namespace Server.Accounting _loginIPs = new IPAddress[length]; for (int i = 0; i < length; i++) { - if (IPAddress.TryParse(reader.ReadString(), out var address)) + if (version < 2) { - _loginIPs[i] = Utility.Intern(address); + if (IPAddress.TryParse(reader.ReadString(), out var address)) + { + _loginIPs[i] = Utility.Intern(address); + } + } + else + { + _loginIPs[i] = reader.ReadIPAddress(); } } @@ -405,6 +414,8 @@ namespace Server.Accounting _totalGameTime = reader.ReadTimeSpan(); + _email = reader.ReadString(); + Timer.StartTimer(AfterDeserialization); } diff --git a/Projects/UOContent/Engines/Bulk Orders/LargeBOD.cs b/Projects/UOContent/Engines/Bulk Orders/LargeBOD.cs index 25d76644e..b6b8c77b8 100644 --- a/Projects/UOContent/Engines/Bulk Orders/LargeBOD.cs +++ b/Projects/UOContent/Engines/Bulk Orders/LargeBOD.cs @@ -2,7 +2,7 @@ using Server.Mobiles; namespace Server.Engines.BulkOrders { - [Serializable(0)] + [Serializable(1)] public abstract partial class LargeBOD : BaseBOD { [InvalidateProperties] @@ -148,5 +148,14 @@ namespace Server.Engines.BulkOrders } } } + + private void Deserialize(IGenericReader reader, int version) + { + _entries = new LargeBulkEntry[reader.ReadInt()]; + for (var i = 0; i < _entries.Length; i++) + { + _entries[i] = new LargeBulkEntry(reader, this); + } + } } } diff --git a/Projects/UOContent/Items/Addons/BaseAddon.cs b/Projects/UOContent/Items/Addons/BaseAddon.cs index 9bb64a43f..6f328fd8b 100644 --- a/Projects/UOContent/Items/Addons/BaseAddon.cs +++ b/Projects/UOContent/Items/Addons/BaseAddon.cs @@ -20,10 +20,10 @@ namespace Server.Items bool CouldFit(IPoint3D p, Map map); } - [Serializable(2, false)] + [Serializable(3, false)] public abstract partial class BaseAddon : Item, IChoppable, IAddon { - private CraftResource m_Resource; + private CraftResource _resource; public BaseAddon() : base(1) { @@ -67,13 +67,13 @@ namespace Server.Items [SerializableField(1)] public CraftResource Resource { - get => m_Resource; + get => _resource; set { - if (m_Resource != value) + if (_resource != value) { - m_Resource = value; - Hue = CraftResources.GetHue(m_Resource); + _resource = value; + Hue = CraftResources.GetHue(_resource); InvalidateProperties(); this.MarkDirty(); @@ -281,9 +281,9 @@ namespace Server.Items { _components = reader.ReadEntityList(); - if (version < 1 && Weight == 0) + if (version == 2) { - Weight = -1; + _resource = (CraftResource)reader.ReadEncodedInt(); } } } diff --git a/Projects/UOContent/Items/Addons/BaseAddonContainer.cs b/Projects/UOContent/Items/Addons/BaseAddonContainer.cs index 9c8dded1f..abeb3200b 100644 --- a/Projects/UOContent/Items/Addons/BaseAddonContainer.cs +++ b/Projects/UOContent/Items/Addons/BaseAddonContainer.cs @@ -3,12 +3,13 @@ using Server.Multis; namespace Server.Items { - [Serializable(1, false)] + [Serializable(2, false)] public abstract partial class BaseAddonContainer : BaseContainer, IChoppable, IAddon { [SerializableField(0, setter: "private")] private List _components; - private CraftResource m_Resource; + + private CraftResource _resource; public BaseAddonContainer(int itemID) : base(itemID) { @@ -44,13 +45,13 @@ namespace Server.Items [CommandProperty(AccessLevel.GameMaster)] public CraftResource Resource { - get => m_Resource; + get => _resource; set { - if (m_Resource != value) + if (_resource != value) { - m_Resource = value; - Hue = CraftResources.GetHue(m_Resource); + _resource = value; + Hue = CraftResources.GetHue(_resource); InvalidateProperties(); this.MarkDirty(); @@ -169,9 +170,9 @@ namespace Server.Items { base.GetProperties(list); - if (!CraftResources.IsStandard(m_Resource)) + if (!CraftResources.IsStandard(_resource)) { - list.Add(CraftResources.GetLocalizationNumber(m_Resource)); + list.Add(CraftResources.GetLocalizationNumber(_resource)); } } @@ -188,8 +189,8 @@ namespace Server.Items // Handles v0 with old Enum -> Int casting private void Deserialize(IGenericReader reader, int version) { - Components = reader.ReadEntityList(); - m_Resource = (CraftResource)reader.ReadInt(); + _components = reader.ReadEntityList(); + _resource = version == 1 ? reader.ReadEnum() : (CraftResource)reader.ReadInt(); } [AfterDeserialization] diff --git a/Projects/UOContent/Items/Addons/SolenAntHole.cs b/Projects/UOContent/Items/Addons/SolenAntHole.cs index fc545e4d4..00209f6bc 100644 --- a/Projects/UOContent/Items/Addons/SolenAntHole.cs +++ b/Projects/UOContent/Items/Addons/SolenAntHole.cs @@ -36,7 +36,7 @@ namespace Server.Items } } - [Serializable(0)] + [Serializable(1)] public partial class SolenAntHole : BaseAddon { [SerializableField(0, getter: "private", setter: "private")] @@ -153,5 +153,10 @@ namespace Server.Items return _spawned.Count < 2; } + + private void Deserialize(IGenericReader reader, int version) + { + _spawned = reader.ReadEntityList(); + } } } diff --git a/Projects/UOContent/Items/Aquarium/Aquarium.cs b/Projects/UOContent/Items/Aquarium/Aquarium.cs index 8497a3797..c228b2779 100644 --- a/Projects/UOContent/Items/Aquarium/Aquarium.cs +++ b/Projects/UOContent/Items/Aquarium/Aquarium.cs @@ -7,7 +7,7 @@ using Server.Utilities; namespace Server.Items { - [Serializable(3, false)] + [Serializable(4, false)] public partial class Aquarium : BaseAddonContainer { public static readonly TimeSpan EvaluationInterval = TimeSpan.FromDays(1); @@ -479,7 +479,44 @@ namespace Server.Items private void Deserialize(IGenericReader reader, int version) { - // If you are deserializing such an old version, you should validate all of the properties. RunUO had bugs. + switch (version) + { + case 3: + case 2: + case 1: + { + var next = reader.ReadDateTime(); + + if (next < Core.Now) + { + next = Core.Now; + } + + _evaluateTimer = Timer.DelayCall(next - Core.Now, EvaluationInterval, Evaluate); + + goto case 0; + } + case 0: + { + _liveCreatures = reader.ReadInt(); + _vacationLeft = reader.ReadInt(); + + _food = new AquariumState(this); + _food.Deserialize(reader); + _water = new AquariumState(this); + _water.Deserialize(reader); + + _events = new List(); + var count = reader.ReadInt(); + for (var i = 0; i < count; i++) + { + _events.Add(reader.ReadInt()); + } + + _rewardAvailable = reader.ReadBool(); + break; + } + } } public int FoodNumber() => diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index 6f17c26d4..62c48a57e 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.cs @@ -1173,22 +1173,18 @@ namespace Server.Items { var flags = (SaveFlag)reader.ReadEncodedInt(); + Attributes = new AosAttributes(this); + if (GetSaveFlag(flags, SaveFlag.Attributes)) { - Attributes = new AosAttributes(this, reader); - } - else - { - Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); } + ArmorAttributes = new AosArmorAttributes(this); + if (GetSaveFlag(flags, SaveFlag.ArmorAttributes)) { - ArmorAttributes = new AosArmorAttributes(this, reader); - } - else - { - ArmorAttributes = new AosArmorAttributes(this); + ArmorAttributes.Deserialize(reader); } if (GetSaveFlag(flags, SaveFlag.PhysicalBonus)) @@ -1356,9 +1352,11 @@ namespace Server.Items m_Meditate = (AMA)(-1); } + SkillBonuses = new AosSkillBonuses(this); + if (GetSaveFlag(flags, SaveFlag.SkillBonuses)) { - SkillBonuses = new AosSkillBonuses(this, reader); + SkillBonuses.Deserialize(reader); } if (GetSaveFlag(flags, SaveFlag.PlayerConstructed)) @@ -1370,8 +1368,11 @@ namespace Server.Items } case 4: { - Attributes = new AosAttributes(this, reader); - ArmorAttributes = new AosArmorAttributes(this, reader); + Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); + + ArmorAttributes = new AosArmorAttributes(this); + ArmorAttributes.Deserialize(reader); goto case 3; } case 3: diff --git a/Projects/UOContent/Items/Armor/Glasses/ArtsGlasses.cs b/Projects/UOContent/Items/Armor/Glasses/ArtsGlasses.cs index 222ba9804..5982541ad 100644 --- a/Projects/UOContent/Items/Armor/Glasses/ArtsGlasses.cs +++ b/Projects/UOContent/Items/Armor/Glasses/ArtsGlasses.cs @@ -1,6 +1,7 @@ namespace Server.Items { - public class ArtsGlasses : ElvenGlasses + [Serializable(0, false)] + public partial class ArtsGlasses : ElvenGlasses { [Constructible] public ArtsGlasses() @@ -12,10 +13,6 @@ namespace Server.Items Hue = 0x73; } - public ArtsGlasses(Serial serial) : base(serial) - { - } - public override int LabelNumber => 1073363; // Reading Glasses of the Arts public override int BasePhysicalResistance => 10; @@ -26,22 +23,5 @@ namespace Server.Items public override int InitMinHits => 255; public override int InitMaxHits => 255; - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - writer.Write(1); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - var version = reader.ReadInt(); - - if (version == 0 && Hue == 0) - { - Hue = 0x73; - } - } } } diff --git a/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs b/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs index e88ee734b..e61df4770 100644 --- a/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs +++ b/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs @@ -1,18 +1,13 @@ -using System; - namespace Server.Items { - public class ElvenGlasses : BaseArmor + [Serializable(0, false)] + public partial class ElvenGlasses : BaseArmor { [Constructible] public ElvenGlasses() : base(0x2FB8) { Weight = 2; - WeaponAttributes = new AosWeaponAttributes(this); - } - - public ElvenGlasses(Serial serial) : base(serial) - { + _weaponAttributes = new AosWeaponAttributes(this); } public override int LabelNumber => 1032216; // elven glasses @@ -35,8 +30,18 @@ namespace Server.Items public override CraftResource DefaultResource => CraftResource.RegularLeather; public override ArmorMeditationAllowance DefMedAllowance => ArmorMeditationAllowance.All; - [CommandProperty(AccessLevel.GameMaster, canModify: true)] - public AosWeaponAttributes WeaponAttributes { get; private set; } + [SerializableField(0, setter: "private")] + [SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster, canModify: true)]")] + public AosWeaponAttributes _weaponAttributes; + + [SerializableFieldSaveFlag(0)] + private bool ShouldSerializeWeaponAttributes() => !_weaponAttributes.IsEmpty; + + [AfterDeserialization] + private void AfterDeserialization() + { + _weaponAttributes ??= new AosWeaponAttributes(this); + } public override void AppendChildNameProperties(ObjectPropertyList list) { @@ -44,133 +49,80 @@ namespace Server.Items int prop; - if ((prop = WeaponAttributes.HitColdArea) != 0) + if ((prop = _weaponAttributes.HitColdArea) != 0) { list.Add(1060416, prop.ToString()); // hit cold area ~1_val~% } - if ((prop = WeaponAttributes.HitDispel) != 0) + if ((prop = _weaponAttributes.HitDispel) != 0) { list.Add(1060417, prop.ToString()); // hit dispel ~1_val~% } - if ((prop = WeaponAttributes.HitEnergyArea) != 0) + if ((prop = _weaponAttributes.HitEnergyArea) != 0) { list.Add(1060418, prop.ToString()); // hit energy area ~1_val~% } - if ((prop = WeaponAttributes.HitFireArea) != 0) + if ((prop = _weaponAttributes.HitFireArea) != 0) { list.Add(1060419, prop.ToString()); // hit fire area ~1_val~% } - if ((prop = WeaponAttributes.HitFireball) != 0) + if ((prop = _weaponAttributes.HitFireball) != 0) { list.Add(1060420, prop.ToString()); // hit fireball ~1_val~% } - if ((prop = WeaponAttributes.HitHarm) != 0) + if ((prop = _weaponAttributes.HitHarm) != 0) { list.Add(1060421, prop.ToString()); // hit harm ~1_val~% } - if ((prop = WeaponAttributes.HitLeechHits) != 0) + if ((prop = _weaponAttributes.HitLeechHits) != 0) { list.Add(1060422, prop.ToString()); // hit life leech ~1_val~% } - if ((prop = WeaponAttributes.HitLightning) != 0) + if ((prop = _weaponAttributes.HitLightning) != 0) { list.Add(1060423, prop.ToString()); // hit lightning ~1_val~% } - if ((prop = WeaponAttributes.HitLowerAttack) != 0) + if ((prop = _weaponAttributes.HitLowerAttack) != 0) { list.Add(1060424, prop.ToString()); // hit lower attack ~1_val~% } - if ((prop = WeaponAttributes.HitLowerDefend) != 0) + if ((prop = _weaponAttributes.HitLowerDefend) != 0) { list.Add(1060425, prop.ToString()); // hit lower defense ~1_val~% } - if ((prop = WeaponAttributes.HitMagicArrow) != 0) + if ((prop = _weaponAttributes.HitMagicArrow) != 0) { list.Add(1060426, prop.ToString()); // hit magic arrow ~1_val~% } - if ((prop = WeaponAttributes.HitLeechMana) != 0) + if ((prop = _weaponAttributes.HitLeechMana) != 0) { list.Add(1060427, prop.ToString()); // hit mana leech ~1_val~% } - if ((prop = WeaponAttributes.HitPhysicalArea) != 0) + if ((prop = _weaponAttributes.HitPhysicalArea) != 0) { list.Add(1060428, prop.ToString()); // hit physical area ~1_val~% } - if ((prop = WeaponAttributes.HitPoisonArea) != 0) + if ((prop = _weaponAttributes.HitPoisonArea) != 0) { list.Add(1060429, prop.ToString()); // hit poison area ~1_val~% } - if ((prop = WeaponAttributes.HitLeechStam) != 0) + if ((prop = _weaponAttributes.HitLeechStam) != 0) { list.Add(1060430, prop.ToString()); // hit stamina leech ~1_val~% } } - - private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) - { - if (setIf) - { - flags |= toSet; - } - } - - private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - var flags = SaveFlag.None; - - SetSaveFlag(ref flags, SaveFlag.WeaponAttributes, !WeaponAttributes.IsEmpty); - - writer.Write((int)flags); - - if (GetSaveFlag(flags, SaveFlag.WeaponAttributes)) - { - WeaponAttributes.Serialize(writer); - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - var flags = (SaveFlag)reader.ReadInt(); - - if (GetSaveFlag(flags, SaveFlag.WeaponAttributes)) - { - WeaponAttributes = new AosWeaponAttributes(this, reader); - } - else - { - WeaponAttributes = new AosWeaponAttributes(this); - } - } - - [Flags] - private enum SaveFlag - { - None = 0x00000000, - WeaponAttributes = 0x00000001 - } } } diff --git a/Projects/UOContent/Items/Armor/Glasses/LyricalGlasses.cs b/Projects/UOContent/Items/Armor/Glasses/LyricalGlasses.cs index 05075752a..0c1f11a2d 100644 --- a/Projects/UOContent/Items/Armor/Glasses/LyricalGlasses.cs +++ b/Projects/UOContent/Items/Armor/Glasses/LyricalGlasses.cs @@ -5,7 +5,7 @@ namespace Server.Items [Constructible] public LyricalGlasses() { - WeaponAttributes.HitLowerDefend = 20; + _weaponAttributes.HitLowerDefend = 20; Attributes.NightSight = 1; Attributes.ReflectPhysical = 15; diff --git a/Projects/UOContent/Items/Armor/Glasses/MaceShieldGlasses.cs b/Projects/UOContent/Items/Armor/Glasses/MaceShieldGlasses.cs index ccb2cc5d7..c8523927e 100644 --- a/Projects/UOContent/Items/Armor/Glasses/MaceShieldGlasses.cs +++ b/Projects/UOContent/Items/Armor/Glasses/MaceShieldGlasses.cs @@ -5,7 +5,7 @@ namespace Server.Items [Constructible] public MaceShieldGlasses() { - WeaponAttributes.HitLowerDefend = 30; + _weaponAttributes.HitLowerDefend = 30; Attributes.BonusStr = 10; Attributes.BonusDex = 5; diff --git a/Projects/UOContent/Items/Clothing/BaseClothing.cs b/Projects/UOContent/Items/Clothing/BaseClothing.cs index 5939cd356..79d226332 100644 --- a/Projects/UOContent/Items/Clothing/BaseClothing.cs +++ b/Projects/UOContent/Items/Clothing/BaseClothing.cs @@ -1039,40 +1039,32 @@ namespace Server.Items m_Resource = DefaultResource; } + Attributes = new AosAttributes(this); + if (GetSaveFlag(flags, SaveFlag.Attributes)) { - Attributes = new AosAttributes(this, reader); - } - else - { - Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); } + ClothingAttributes = new AosArmorAttributes(this); + if (GetSaveFlag(flags, SaveFlag.ClothingAttributes)) { - ClothingAttributes = new AosArmorAttributes(this, reader); - } - else - { - ClothingAttributes = new AosArmorAttributes(this); + ClothingAttributes.Deserialize(reader); } + SkillBonuses = new AosSkillBonuses(this); + if (GetSaveFlag(flags, SaveFlag.SkillBonuses)) { - SkillBonuses = new AosSkillBonuses(this, reader); - } - else - { - SkillBonuses = new AosSkillBonuses(this); + SkillBonuses.Deserialize(reader); } + Resistances = new AosElementAttributes(this); + if (GetSaveFlag(flags, SaveFlag.Resistances)) { - Resistances = new AosElementAttributes(this, reader); - } - else - { - Resistances = new AosElementAttributes(this); + Resistances.Deserialize(reader); } if (GetSaveFlag(flags, SaveFlag.MaxHitPoints)) @@ -1123,10 +1115,17 @@ namespace Server.Items } case 3: { - Attributes = new AosAttributes(this, reader); - ClothingAttributes = new AosArmorAttributes(this, reader); - SkillBonuses = new AosSkillBonuses(this, reader); - Resistances = new AosElementAttributes(this, reader); + Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); + + ClothingAttributes = new AosArmorAttributes(this); + ClothingAttributes.Deserialize(reader); + + SkillBonuses = new AosSkillBonuses(this); + SkillBonuses.Deserialize(reader); + + Resistances = new AosElementAttributes(this); + Resistances.Deserialize(reader); goto case 2; } diff --git a/Projects/UOContent/Items/Jewels/BaseJewel.cs b/Projects/UOContent/Items/Jewels/BaseJewel.cs index df436491e..b049b8876 100644 --- a/Projects/UOContent/Items/Jewels/BaseJewel.cs +++ b/Projects/UOContent/Items/Jewels/BaseJewel.cs @@ -437,9 +437,12 @@ namespace Server.Items } case 1: { - Attributes = new AosAttributes(this, reader); - Resistances = new AosElementAttributes(this, reader); - SkillBonuses = new AosSkillBonuses(this, reader); + Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); + Resistances = new AosElementAttributes(this); + Resistances.Deserialize(reader); + SkillBonuses = new AosSkillBonuses(this); + SkillBonuses.Deserialize(reader); var m = Parent as Mobile; diff --git a/Projects/UOContent/Items/Minor Artifacts/ML/BrightsightLenses.cs b/Projects/UOContent/Items/Minor Artifacts/ML/BrightsightLenses.cs index 1471e50ee..9cda19b07 100644 --- a/Projects/UOContent/Items/Minor Artifacts/ML/BrightsightLenses.cs +++ b/Projects/UOContent/Items/Minor Artifacts/ML/BrightsightLenses.cs @@ -43,7 +43,7 @@ namespace Server.Items if (version < 1) { - WeaponAttributes.SelfRepair = 0; + _weaponAttributes.SelfRepair = 0; ArmorAttributes.SelfRepair = 3; } } diff --git a/Projects/UOContent/Items/Quivers/BaseQuiver.cs b/Projects/UOContent/Items/Quivers/BaseQuiver.cs index 8ce5c8fc5..e961f39e1 100644 --- a/Projects/UOContent/Items/Quivers/BaseQuiver.cs +++ b/Projects/UOContent/Items/Quivers/BaseQuiver.cs @@ -511,13 +511,11 @@ namespace Server.Items var flags = (SaveFlag)reader.ReadEncodedInt(); + Attributes = new AosAttributes(this); + if (GetSaveFlag(flags, SaveFlag.Attributes)) { - Attributes = new AosAttributes(this, reader); - } - else - { - Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); } if (GetSaveFlag(flags, SaveFlag.LowerAmmoCost)) diff --git a/Projects/UOContent/Items/Resources/Blacksmithing/Ore.cs b/Projects/UOContent/Items/Resources/Blacksmithing/Ore.cs index cbde70d19..8b0849113 100644 --- a/Projects/UOContent/Items/Resources/Blacksmithing/Ore.cs +++ b/Projects/UOContent/Items/Resources/Blacksmithing/Ore.cs @@ -43,8 +43,8 @@ namespace Server.Items case 1: { // Use this line instead if you are getting world loading issues - // _resource = (CraftResource)reader.ReadByte(); - _resource = (CraftResource)reader.ReadInt(); + _resource = (CraftResource)reader.ReadByte(); + // _resource = (CraftResource)reader.ReadInt(); break; } case 0: diff --git a/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs b/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs index da2b722c8..f4d3f636d 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Spellbook.cs @@ -940,8 +940,10 @@ namespace Server.Items } case 1: { - Attributes = new AosAttributes(this, reader); - SkillBonuses = new AosSkillBonuses(this, reader); + Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); + SkillBonuses = new AosSkillBonuses(this); + SkillBonuses.Deserialize(reader); goto case 0; } diff --git a/Projects/UOContent/Items/Talismans/BaseTalisman.cs b/Projects/UOContent/Items/Talismans/BaseTalisman.cs index f429e2e21..920005091 100644 --- a/Projects/UOContent/Items/Talismans/BaseTalisman.cs +++ b/Projects/UOContent/Items/Talismans/BaseTalisman.cs @@ -893,91 +893,89 @@ namespace Server.Items var version = reader.ReadInt(); - switch (version) + var flags = (SaveFlag)reader.ReadEncodedInt(); + + Attributes = new AosAttributes(this); + if (GetSaveFlag(flags, SaveFlag.Attributes)) { - case 0: - { - var flags = (SaveFlag)reader.ReadEncodedInt(); - - Attributes = GetSaveFlag(flags, SaveFlag.Attributes) - ? new AosAttributes(this, reader) - : new AosAttributes(this); - SkillBonuses = GetSaveFlag(flags, SaveFlag.SkillBonuses) - ? new AosSkillBonuses(this, reader) - : new AosSkillBonuses(this); - - // Backward compatibility - if (GetSaveFlag(flags, SaveFlag.Owner)) - { - BlessedFor = reader.ReadEntity(); - } - - m_Protection = GetSaveFlag(flags, SaveFlag.Protection) - ? new TalismanAttribute(reader) - : new TalismanAttribute(); - m_Killer = GetSaveFlag(flags, SaveFlag.Killer) - ? new TalismanAttribute(reader) - : new TalismanAttribute(); - m_Summoner = GetSaveFlag(flags, SaveFlag.Summoner) - ? new TalismanAttribute(reader) - : new TalismanAttribute(); - - if (GetSaveFlag(flags, SaveFlag.Removal)) - { - m_Removal = (TalismanRemoval)reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.OldKarmaLoss)) - { - Attributes.IncreasedKarmaLoss = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.Skill)) - { - m_Skill = (SkillName)reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.SuccessBonus)) - { - m_SuccessBonus = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.ExceptionalBonus)) - { - m_ExceptionalBonus = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.MaxCharges)) - { - m_MaxCharges = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.Charges)) - { - m_Charges = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.MaxChargeTime)) - { - m_MaxChargeTime = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.ChargeTime)) - { - m_ChargeTime = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.Slayer)) - { - m_Slayer = (TalismanSlayerName)reader.ReadEncodedInt(); - } - - m_Blessed = GetSaveFlag(flags, SaveFlag.Blessed); - - break; - } + Attributes.Deserialize(reader); } + SkillBonuses = new AosSkillBonuses(this); + if (GetSaveFlag(flags, SaveFlag.SkillBonuses)) + { + SkillBonuses.Deserialize(reader); + } + + // Backward compatibility + if (GetSaveFlag(flags, SaveFlag.Owner)) + { + BlessedFor = reader.ReadEntity(); + } + + m_Protection = GetSaveFlag(flags, SaveFlag.Protection) + ? new TalismanAttribute(reader) + : new TalismanAttribute(); + m_Killer = GetSaveFlag(flags, SaveFlag.Killer) + ? new TalismanAttribute(reader) + : new TalismanAttribute(); + m_Summoner = GetSaveFlag(flags, SaveFlag.Summoner) + ? new TalismanAttribute(reader) + : new TalismanAttribute(); + + if (GetSaveFlag(flags, SaveFlag.Removal)) + { + m_Removal = (TalismanRemoval)reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.OldKarmaLoss)) + { + Attributes.IncreasedKarmaLoss = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.Skill)) + { + m_Skill = (SkillName)reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.SuccessBonus)) + { + m_SuccessBonus = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.ExceptionalBonus)) + { + m_ExceptionalBonus = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.MaxCharges)) + { + m_MaxCharges = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.Charges)) + { + m_Charges = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.MaxChargeTime)) + { + m_MaxChargeTime = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.ChargeTime)) + { + m_ChargeTime = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.Slayer)) + { + m_Slayer = (TalismanSlayerName)reader.ReadEncodedInt(); + } + + m_Blessed = GetSaveFlag(flags, SaveFlag.Blessed); + + if (Parent is Mobile m) { Attributes.AddStatBonuses(m); diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs index 82d1b3a9d..b23733a3b 100644 --- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs +++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs @@ -4036,22 +4036,18 @@ namespace Server.Items m_Resource = CraftResource.Iron; } + Attributes = new AosAttributes(this); + if (GetSaveFlag(flags, SaveFlag.xAttributes)) { - Attributes = new AosAttributes(this, reader); - } - else - { - Attributes = new AosAttributes(this); + Attributes.Deserialize(reader); } + WeaponAttributes = new AosWeaponAttributes(this); + if (GetSaveFlag(flags, SaveFlag.xWeaponAttributes)) { - WeaponAttributes = new AosWeaponAttributes(this, reader); - } - else - { - WeaponAttributes = new AosWeaponAttributes(this); + WeaponAttributes.Deserialize(reader); } if (UseSkillMod && m_AccuracyLevel != WeaponAccuracyLevel.Regular && parentMobile != null) @@ -4077,13 +4073,11 @@ namespace Server.Items PlayerConstructed = true; } + SkillBonuses = new AosSkillBonuses(this); + if (GetSaveFlag(flags, SaveFlag.SkillBonuses)) { - SkillBonuses = new AosSkillBonuses(this, reader); - } - else - { - SkillBonuses = new AosSkillBonuses(this); + SkillBonuses.Deserialize(reader); } if (GetSaveFlag(flags, SaveFlag.Slayer2)) @@ -4091,13 +4085,11 @@ namespace Server.Items m_Slayer2 = (SlayerName)reader.ReadInt(); } + AosElementDamages = new AosElementAttributes(this); + if (GetSaveFlag(flags, SaveFlag.ElementalDamages)) { - AosElementDamages = new AosElementAttributes(this, reader); - } - else - { - AosElementDamages = new AosElementAttributes(this); + AosElementDamages.Deserialize(reader); } if (GetSaveFlag(flags, SaveFlag.EngravedText)) diff --git a/Projects/UOContent/Migrations/Server.Accounting.Account.v2.json b/Projects/UOContent/Migrations/Server.Accounting.Account.v3.json similarity index 99% rename from Projects/UOContent/Migrations/Server.Accounting.Account.v2.json rename to Projects/UOContent/Migrations/Server.Accounting.Account.v3.json index bc5bd3789..3bca03371 100644 --- a/Projects/UOContent/Migrations/Server.Accounting.Account.v2.json +++ b/Projects/UOContent/Migrations/Server.Accounting.Account.v3.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "type": "Server.Accounting.Account", "properties": [ { diff --git a/Projects/UOContent/Migrations/Server.BaseAttributes.v0.json b/Projects/UOContent/Migrations/Server.BaseAttributes.v0.json new file mode 100644 index 000000000..2ca2e1526 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.BaseAttributes.v0.json @@ -0,0 +1,24 @@ +{ + "version": 0, + "type": "Server.BaseAttributes", + "properties": [ + { + "name": "Names", + "type": "uint", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Values", + "type": "int[]", + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "int", + "PrimitiveTypeMigrationRule", + "EncodedInt" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.BulkOrders.LargeBOD.v0.json b/Projects/UOContent/Migrations/Server.Engines.BulkOrders.LargeBOD.v1.json similarity index 96% rename from Projects/UOContent/Migrations/Server.Engines.BulkOrders.LargeBOD.v0.json rename to Projects/UOContent/Migrations/Server.Engines.BulkOrders.LargeBOD.v1.json index 677d13f20..260cec15d 100644 --- a/Projects/UOContent/Migrations/Server.Engines.BulkOrders.LargeBOD.v0.json +++ b/Projects/UOContent/Migrations/Server.Engines.BulkOrders.LargeBOD.v1.json @@ -1,5 +1,5 @@ { - "version": 0, + "version": 1, "type": "Server.Engines.BulkOrders.LargeBOD", "properties": [ { diff --git a/Projects/UOContent/Migrations/Server.Items.Aquarium.v3.json b/Projects/UOContent/Migrations/Server.Items.Aquarium.v4.json similarity index 90% rename from Projects/UOContent/Migrations/Server.Items.Aquarium.v3.json rename to Projects/UOContent/Migrations/Server.Items.Aquarium.v4.json index 3bb3bc144..6cd8c06de 100644 --- a/Projects/UOContent/Migrations/Server.Items.Aquarium.v3.json +++ b/Projects/UOContent/Migrations/Server.Items.Aquarium.v4.json @@ -1,5 +1,5 @@ { - "version": 3, + "version": 4, "type": "Server.Items.Aquarium", "properties": [ { @@ -29,7 +29,7 @@ { "name": "Food", "type": "Server.Items.AquariumState", - "rule": "EmbeddedSerializableMigrationRule", + "rule": "RawSerializableMigrationRule", "ruleArguments": [ "" ] @@ -37,7 +37,7 @@ { "name": "Water", "type": "Server.Items.AquariumState", - "rule": "EmbeddedSerializableMigrationRule", + "rule": "RawSerializableMigrationRule", "ruleArguments": [ "" ] diff --git a/Projects/UOContent/Migrations/Server.Items.ArtsGlasses.v0.json b/Projects/UOContent/Migrations/Server.Items.ArtsGlasses.v0.json new file mode 100644 index 000000000..3dcc3b28f --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.ArtsGlasses.v0.json @@ -0,0 +1,5 @@ +{ + "version": 0, + "type": "Server.Items.ArtsGlasses", + "properties": [] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.BaseAddon.v2.json b/Projects/UOContent/Migrations/Server.Items.BaseAddon.v3.json similarity index 96% rename from Projects/UOContent/Migrations/Server.Items.BaseAddon.v2.json rename to Projects/UOContent/Migrations/Server.Items.BaseAddon.v3.json index dbf4d351f..58c12dbca 100644 --- a/Projects/UOContent/Migrations/Server.Items.BaseAddon.v2.json +++ b/Projects/UOContent/Migrations/Server.Items.BaseAddon.v3.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 3, "type": "Server.Items.BaseAddon", "properties": [ { diff --git a/Projects/UOContent/Migrations/Server.Items.BaseAddonContainer.v1.json b/Projects/UOContent/Migrations/Server.Items.BaseAddonContainer.v2.json similarity index 97% rename from Projects/UOContent/Migrations/Server.Items.BaseAddonContainer.v1.json rename to Projects/UOContent/Migrations/Server.Items.BaseAddonContainer.v2.json index 3f1f9b3aa..71c045b89 100644 --- a/Projects/UOContent/Migrations/Server.Items.BaseAddonContainer.v1.json +++ b/Projects/UOContent/Migrations/Server.Items.BaseAddonContainer.v2.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "type": "Server.Items.BaseAddonContainer", "properties": [ { diff --git a/Projects/UOContent/Migrations/Server.Items.ElvenGlasses.v0.json b/Projects/UOContent/Migrations/Server.Items.ElvenGlasses.v0.json new file mode 100644 index 000000000..e6524c997 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.ElvenGlasses.v0.json @@ -0,0 +1,15 @@ +{ + "version": 0, + "type": "Server.Items.ElvenGlasses", + "properties": [ + { + "name": "WeaponAttributes", + "type": "Server.AosWeaponAttributes", + "usesSaveFlag": true, + "rule": "RawSerializableMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.SolenAntHole.v0.json b/Projects/UOContent/Migrations/Server.Items.SolenAntHole.v1.json similarity index 95% rename from Projects/UOContent/Migrations/Server.Items.SolenAntHole.v0.json rename to Projects/UOContent/Migrations/Server.Items.SolenAntHole.v1.json index 62ab0f2d1..5517c0a7c 100644 --- a/Projects/UOContent/Migrations/Server.Items.SolenAntHole.v0.json +++ b/Projects/UOContent/Migrations/Server.Items.SolenAntHole.v1.json @@ -1,5 +1,5 @@ { - "version": 0, + "version": 1, "type": "Server.Items.SolenAntHole", "properties": [ { diff --git a/Projects/UOContent/Misc/AOS.cs b/Projects/UOContent/Misc/AOS.cs index b4ceaa3db..025631871 100644 --- a/Projects/UOContent/Misc/AOS.cs +++ b/Projects/UOContent/Misc/AOS.cs @@ -281,18 +281,11 @@ namespace Server public sealed class AosAttributes : BaseAttributes { - public AosAttributes(Item owner) - : base(owner) + public AosAttributes(Item owner) : base(owner) { } - public AosAttributes(Item owner, AosAttributes other) - : base(owner, other) - { - } - - public AosAttributes(Item owner, IGenericReader reader) - : base(owner, reader) + public AosAttributes(Item owner, AosAttributes other) : base(owner, other) { } @@ -637,18 +630,11 @@ namespace Server public sealed class AosWeaponAttributes : BaseAttributes { - public AosWeaponAttributes(Item owner) - : base(owner) + public AosWeaponAttributes(Item owner) : base(owner) { } - public AosWeaponAttributes(Item owner, AosWeaponAttributes other) - : base(owner, other) - { - } - - public AosWeaponAttributes(Item owner, IGenericReader reader) - : base(owner, reader) + public AosWeaponAttributes(Item owner, AosWeaponAttributes other) : base(owner, other) { } @@ -858,7 +844,7 @@ namespace Server } else if (obj is ElvenGlasses glasses) { - var attrs = glasses.WeaponAttributes; + var attrs = glasses._weaponAttributes; if (attrs != null) { @@ -884,18 +870,11 @@ namespace Server public sealed class AosArmorAttributes : BaseAttributes { - public AosArmorAttributes(Item owner) - : base(owner) + public AosArmorAttributes(Item owner) : base(owner) { } - public AosArmorAttributes(Item owner, IGenericReader reader) - : base(owner, reader) - { - } - - public AosArmorAttributes(Item owner, AosArmorAttributes other) - : base(owner, other) + public AosArmorAttributes(Item owner, AosArmorAttributes other) : base(owner, other) { } @@ -977,18 +956,11 @@ namespace Server { private List m_Mods; - public AosSkillBonuses(Item owner) - : base(owner) + public AosSkillBonuses(Item owner) : base(owner) { } - public AosSkillBonuses(Item owner, IGenericReader reader) - : base(owner, reader) - { - } - - public AosSkillBonuses(Item owner, AosSkillBonuses other) - : base(owner, other) + public AosSkillBonuses(Item owner, AosSkillBonuses other) : base(owner, other) { } @@ -1256,18 +1228,11 @@ namespace Server public sealed class AosElementAttributes : BaseAttributes { - public AosElementAttributes(Item owner) - : base(owner) + public AosElementAttributes(Item owner) : base(owner) { } - public AosElementAttributes(Item owner, AosElementAttributes other) - : base(owner, other) - { - } - - public AosElementAttributes(Item owner, IGenericReader reader) - : base(owner, reader) + public AosElementAttributes(Item owner, AosElementAttributes other) : base(owner, other) { } @@ -1330,76 +1295,36 @@ namespace Server } [PropertyObject] - public abstract class BaseAttributes + [EmbeddedSerializable(0)] + public abstract partial class BaseAttributes { - private static readonly int[] m_Empty = Array.Empty(); - private uint m_Names; - private int[] m_Values; + [SerializableField(0, setter: "private")] + private uint _names; + + [EncodedInt] + [SerializableField(1, setter: "private")] + private int[] _values; public BaseAttributes(Item owner) { - Owner = owner; - m_Values = m_Empty; + _owner = owner; + _values = Array.Empty(); } public BaseAttributes(Item owner, BaseAttributes other) { - Owner = owner; - m_Values = new int[other.m_Values.Length]; - other.m_Values.CopyTo(m_Values, 0); - m_Names = other.m_Names; + _owner = owner; + _values = new int[other._values.Length]; + other._values.CopyTo(_values, 0); + _names = other._names; } - public BaseAttributes(Item owner, IGenericReader reader) - { - Owner = owner; + public bool IsEmpty => _names == 0; - int version = reader.ReadByte(); + [SerializableParent] + private readonly Item _owner; - switch (version) - { - case 1: - { - m_Names = reader.ReadUInt(); - m_Values = new int[reader.ReadEncodedInt()]; - - for (var i = 0; i < m_Values.Length; ++i) - { - m_Values[i] = reader.ReadEncodedInt(); - } - - break; - } - case 0: - { - m_Names = reader.ReadUInt(); - m_Values = new int[reader.ReadInt()]; - - for (var i = 0; i < m_Values.Length; ++i) - { - m_Values[i] = reader.ReadInt(); - } - - break; - } - } - } - - public bool IsEmpty => m_Names == 0; - public Item Owner { get; } - - public void Serialize(IGenericWriter writer) - { - writer.Write((byte)1); // version; - - writer.Write(m_Names); - writer.WriteEncodedInt(m_Values.Length); - - for (var i = 0; i < m_Values.Length; ++i) - { - writer.WriteEncodedInt(m_Values[i]); - } - } + public Item Owner => _owner; public int GetValue(int bitmask) { @@ -1410,16 +1335,16 @@ namespace Server var mask = (uint)bitmask; - if ((m_Names & mask) == 0) + if ((_names & mask) == 0) { return 0; } var index = GetIndex(mask); - if (index >= 0 && index < m_Values.Length) + if (index >= 0 && index < _values.Length) { - return m_Values[index]; + return _values[index]; } return 0; @@ -1450,65 +1375,65 @@ namespace Server if (value != 0) { - if ((m_Names & mask) != 0) + if ((_names & mask) != 0) { var index = GetIndex(mask); - if (index >= 0 && index < m_Values.Length) + if (index >= 0 && index < _values.Length) { - m_Values[index] = value; + _values[index] = value; } } else { var index = GetIndex(mask); - if (index >= 0 && index <= m_Values.Length) + if (index >= 0 && index <= _values.Length) { - var old = m_Values; - m_Values = new int[old.Length + 1]; + var old = _values; + _values = new int[old.Length + 1]; for (var i = 0; i < index; ++i) { - m_Values[i] = old[i]; + _values[i] = old[i]; } - m_Values[index] = value; + _values[index] = value; for (var i = index; i < old.Length; ++i) { - m_Values[i + 1] = old[i]; + _values[i + 1] = old[i]; } - m_Names |= mask; + _names |= mask; } } } - else if ((m_Names & mask) != 0) + else if ((_names & mask) != 0) { var index = GetIndex(mask); - if (index >= 0 && index < m_Values.Length) + if (index >= 0 && index < _values.Length) { - m_Names &= ~mask; + _names &= ~mask; - if (m_Values.Length == 1) + if (_values.Length == 1) { - m_Values = m_Empty; + _values = Array.Empty(); } else { - var old = m_Values; - m_Values = new int[old.Length - 1]; + var old = _values; + _values = new int[old.Length - 1]; for (var i = 0; i < index; ++i) { - m_Values[i] = old[i]; + _values[i] = old[i]; } for (var i = index + 1; i < old.Length; ++i) { - m_Values[i - 1] = old[i]; + _values[i - 1] = old[i]; } } } @@ -1555,7 +1480,7 @@ namespace Server private int GetIndex(uint mask) { var index = 0; - var ourNames = m_Names; + var ourNames = _names; uint currentBit = 1; while (currentBit != mask)