From b854565586c58aa14be71fcb427d3d2f535556e8 Mon Sep 17 00:00:00 2001 From: sven-n Date: Tue, 21 Jul 2026 22:09:15 +0200 Subject: [PATCH] Merge pull request #834 from eduardosmaniotto/bugfix/duplicate-rows fix: duplicate rows in deserialized aggregate collections (cherry picked from commit f95498770ea1af30530a164f868f571ec30052b5) --- .../Updates/AddElfSoldierBuffPlugIn.cs | 69 ++++++++++--------- .../VersionSeasonSix/NpcInitialization.cs | 1 + .../Json/ReferenceResolvingConverter.cs | 23 ++++--- 3 files changed, 53 insertions(+), 40 deletions(-) diff --git a/src/Persistence/Initialization/Updates/AddElfSoldierBuffPlugIn.cs b/src/Persistence/Initialization/Updates/AddElfSoldierBuffPlugIn.cs index ef6b7ba..7e47d3b 100644 --- a/src/Persistence/Initialization/Updates/AddElfSoldierBuffPlugIn.cs +++ b/src/Persistence/Initialization/Updates/AddElfSoldierBuffPlugIn.cs @@ -62,41 +62,46 @@ public class AddElfSoldierBuffPlugIn : UpdatePlugInBase return; } - var buffEffect = context.CreateNew(); - gameConfiguration.MagicEffects.Add(buffEffect); - buffEffect.Number = (short)MagicEffectNumber.ElfSoldierBuff; - buffEffect.Name = "Elf Soldier Buff"; - buffEffect.InformObservers = true; - buffEffect.StopByDeath = true; + var buffEffect = gameConfiguration.MagicEffects.FirstOrDefault(e => e.Number == (short)MagicEffectNumber.ElfSoldierBuff); + if (buffEffect is null) + { + buffEffect = context.CreateNew(); + gameConfiguration.MagicEffects.Add(buffEffect); + buffEffect.Number = (short)MagicEffectNumber.ElfSoldierBuff; + buffEffect.Name = "Elf Soldier Buff"; + buffEffect.InformObservers = true; + buffEffect.StopByDeath = true; - // Duration: 60 minutes - buffEffect.Duration = context.CreateNew(); - buffEffect.Duration.ConstantValue.Value = 3600; + // Duration: 60 minutes + buffEffect.Duration = context.CreateNew(); + buffEffect.Duration.ConstantValue.Value = 3600; - // Defense boost: 50 + (Level / 5) - var defensePowerUp = context.CreateNew(); - defensePowerUp.TargetAttribute = Stats.DefenseFinal.GetPersistent(gameConfiguration); - defensePowerUp.Boost = context.CreateNew(); - defensePowerUp.Boost.ConstantValue.Value = 50; - defensePowerUp.Boost.ConstantValue.AggregateType = AggregateType.AddFinal; - var defensePerLevel = context.CreateNew(); - defensePerLevel.InputAttribute = Stats.Level.GetPersistent(gameConfiguration); - defensePerLevel.InputOperand = 1f / 5; - defensePerLevel.InputOperator = InputOperator.Multiply; - defensePowerUp.Boost.RelatedValues.Add(defensePerLevel); - buffEffect.PowerUpDefinitions.Add(defensePowerUp); + // Defense boost: 50 + (Level / 5) + var defensePowerUp = context.CreateNew(); + defensePowerUp.TargetAttribute = Stats.DefenseFinal.GetPersistent(gameConfiguration); + defensePowerUp.Boost = context.CreateNew(); + defensePowerUp.Boost.ConstantValue.Value = 50; + defensePowerUp.Boost.ConstantValue.AggregateType = AggregateType.AddFinal; + var defensePerLevel = context.CreateNew(); + defensePerLevel.InputAttribute = Stats.Level.GetPersistent(gameConfiguration); + defensePerLevel.InputOperand = 1f / 5; + defensePerLevel.InputOperator = InputOperator.Multiply; + defensePowerUp.Boost.RelatedValues.Add(defensePerLevel); + buffEffect.PowerUpDefinitions.Add(defensePowerUp); - // Damage boost: 45 + (Level / 3) - var damagePowerUp = context.CreateNew(); - damagePowerUp.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(gameConfiguration); - damagePowerUp.Boost = context.CreateNew(); - damagePowerUp.Boost.ConstantValue.Value = 45; - var damagePerLevel = context.CreateNew(); - damagePerLevel.InputAttribute = Stats.Level.GetPersistent(gameConfiguration); - damagePerLevel.InputOperand = 1f / 3; - damagePerLevel.InputOperator = InputOperator.Multiply; - damagePowerUp.Boost.RelatedValues.Add(damagePerLevel); - buffEffect.PowerUpDefinitions.Add(damagePowerUp); + // Damage boost: 45 + (Level / 3) + var damagePowerUp = context.CreateNew(); + damagePowerUp.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(gameConfiguration); + damagePowerUp.Boost = context.CreateNew(); + damagePowerUp.Boost.ConstantValue.Value = 45; + damagePowerUp.Boost.ConstantValue.AggregateType = AggregateType.AddRaw; + var damagePerLevel = context.CreateNew(); + damagePerLevel.InputAttribute = Stats.Level.GetPersistent(gameConfiguration); + damagePerLevel.InputOperand = 1f / 3; + damagePerLevel.InputOperator = InputOperator.Multiply; + damagePowerUp.Boost.RelatedValues.Add(damagePerLevel); + buffEffect.PowerUpDefinitions.Add(damagePowerUp); + } var buff = context.CreateNew(); buff.MagicEffectDefinition = buffEffect; diff --git a/src/Persistence/Initialization/VersionSeasonSix/NpcInitialization.cs b/src/Persistence/Initialization/VersionSeasonSix/NpcInitialization.cs index ffbfb00..7ec1a5d 100644 --- a/src/Persistence/Initialization/VersionSeasonSix/NpcInitialization.cs +++ b/src/Persistence/Initialization/VersionSeasonSix/NpcInitialization.cs @@ -151,6 +151,7 @@ internal partial class NpcInitialization : Version095d.NpcInitialization damagePowerUp.TargetAttribute = Stats.GreaterDamageBonus.GetPersistent(this.GameConfiguration); damagePowerUp.Boost = this.Context.CreateNew(); damagePowerUp.Boost.ConstantValue.Value = 45; + damagePowerUp.Boost.ConstantValue.AggregateType = AggregateType.AddRaw; var damagePerLevel = this.Context.CreateNew(); damagePerLevel.InputAttribute = Stats.Level.GetPersistent(this.GameConfiguration); damagePerLevel.InputOperand = 1f / 3; diff --git a/src/Persistence/Json/ReferenceResolvingConverter.cs b/src/Persistence/Json/ReferenceResolvingConverter.cs index 35eda04..9b68976 100644 --- a/src/Persistence/Json/ReferenceResolvingConverter.cs +++ b/src/Persistence/Json/ReferenceResolvingConverter.cs @@ -47,11 +47,14 @@ public class ReferenceResolvingConverter : JsonConverter else if (x.CollectionInterface != null && x.Property.Name.StartsWith("Raw")) { propertyType = x.CollectionInterface.GetGenericArguments()[0]; + + var collectionExpr = Expression.Convert(Expression.Property(tParam, x.Property), x.CollectionInterface); + var itemExpr = Expression.Convert(objParam, propertyType); + var containsCall = Expression.Call(collectionExpr, x.CollectionInterface.GetMethod("Contains")!, itemExpr); + var addCall = Expression.Call(collectionExpr, x.CollectionInterface.GetMethod("Add")!, itemExpr); + adder = Expression.Lambda>( - Expression.Call( - Expression.Property(tParam, x.Property), - x.CollectionInterface.GetMethod("Add")!, - Expression.Convert(objParam, propertyType)), + Expression.IfThen(Expression.Not(containsCall), addCall), tParam, objParam) .Compile(); @@ -66,11 +69,15 @@ public class ReferenceResolvingConverter : JsonConverter propertyType = propertyType.GetProperties().First(p => p.PropertyType.BaseType == baseType).PropertyType; jsonPropertyName = basePropertyName; + + var baseCollectionInterface = DetermineCollectionInterface(baseCollectionProperty)!; + var collectionExpr = Expression.Convert(Expression.Property(tParam, baseCollectionProperty), baseCollectionInterface); + var itemExpr = Expression.Convert(objParam, propertyType); + var containsCall = Expression.Call(collectionExpr, baseCollectionInterface.GetMethod("Contains")!, itemExpr); + var addCall = Expression.Call(collectionExpr, baseCollectionInterface.GetMethod("Add")!, itemExpr); + adder = Expression.Lambda>( - Expression.Call( - Expression.Property(tParam, baseCollectionProperty), - baseCollectionProperty.PropertyType.GetMethod("Add")!, - Expression.Convert(objParam, propertyType)), + Expression.IfThen(Expression.Not(containsCall), addCall), tParam, objParam) .Compile();