diff --git a/.editorconfig b/.editorconfig index 1faf532e4..c707c9c26 100644 --- a/.editorconfig +++ b/.editorconfig @@ -51,10 +51,10 @@ dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggesti dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion -dotnet_style_qualification_for_event = false:suggestion -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_event = true:suggestion +dotnet_style_qualification_for_field = true:suggestion +dotnet_style_qualification_for_method = true:suggestion +dotnet_style_qualification_for_property = true:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # ReSharper properties @@ -112,9 +112,8 @@ dotnet_diagnostic.rs2008.severity = none # Ignore analyzer release tracking dotnet_diagnostic.sa0001.severity = none # Ignore disabled xml documentation dotnet_diagnostic.sa1000.severity = none # Ignore missing space after "new" dotnet_diagnostic.sa1009.severity = none # Ignore missing space after closing parenthesis -dotnet_diagnostic.sa1028.severity = none # Ignore trailing whitespace dotnet_diagnostic.sa1100.severity = none # Ignore base. prefix if there is no local override -dotnet_diagnostic.sa1101.severity = none # Ignore missing this prefix for local calls +dotnet_diagnostic.sa1101.severity = suggestion # Ignore missing this prefix for local calls dotnet_diagnostic.sa1122.severity = none # Don't force the use of string.Empty dotnet_diagnostic.sa1124.severity = none # Allow the use of regions dotnet_diagnostic.sa1127.severity = none # Generic constraints may share a line with other declarations diff --git a/WoWsShipBuilder.Common/DataContainers/Aircraft/AirstrikeDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Aircraft/AirstrikeDataContainer.cs index ee0fd14dc..646c8bfd1 100644 --- a/WoWsShipBuilder.Common/DataContainers/Aircraft/AirstrikeDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Aircraft/AirstrikeDataContainer.cs @@ -1,5 +1,5 @@ +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.ApplicationData; @@ -7,11 +7,12 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record AirstrikeDataContainer : DataContainerBase { public string Header { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true)] + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.LocalizationKey)] public string Name { get; set; } = default!; [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "HP")] diff --git a/WoWsShipBuilder.Common/DataContainers/Aircraft/CvAircraftDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Aircraft/CvAircraftDataContainer.cs index 28b957236..7dca33344 100644 --- a/WoWsShipBuilder.Common/DataContainers/Aircraft/CvAircraftDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Aircraft/CvAircraftDataContainer.cs @@ -1,6 +1,6 @@ using System.Globalization; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Aircraft; using WoWsShipBuilder.DataStructures.Ship; @@ -8,351 +8,351 @@ using WoWsShipBuilder.Infrastructure.GameData; using WoWsShipBuilder.Infrastructure.Utility; -namespace WoWsShipBuilder.DataContainers +namespace WoWsShipBuilder.DataContainers; + +[DataContainer] +public partial record CvAircraftDataContainer : DataContainerBase { - public partial record CvAircraftDataContainer : DataContainerBase - { - public string Name { get; set; } = default!; + public string Name { get; set; } = default!; + + public string PlaneVariant { get; set; } = default!; + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "AircraftAmount")] + public int NumberInSquad { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "AircraftAmount")] + public int NumberDuringAttack { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "AircraftAmount")] + public int MaxNumberOnDeck { get; set; } + + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal RestorationTime { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Speed", UnitKey = "Knots")] + public decimal CruisingSpeed { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Speed", UnitKey = "Knots")] + public decimal MaxSpeed { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Speed", UnitKey = "Knots")] + public decimal MinSpeed { get; set; } + + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal MaxEngineBoostDuration { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "InitialBoost", UnitKey = "S")] + public decimal JatoDuration { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "InitialBoost", UnitKey = "PerCent")] + public decimal JatoSpeedMultiplier { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "HP", UnitKey = "HP")] + public int PlaneHp { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "HP", UnitKey = "HP")] + public int SquadronHp { get; set; } - public string PlaneVariant { get; set; } = default!; + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "HP", UnitKey = "HP")] + public int AttackGroupHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "AircraftAmount")] - public int NumberInSquad { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] + public int DamageTakenDuringAttack { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "AircraftAmount")] - public int NumberDuringAttack { get; set; } + [DataElementType(DataElementTypes.KeyValue)] + public int AmmoPerAttack { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "AircraftAmount")] - public int MaxNumberOnDeck { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] + public decimal PreparationTime { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal RestorationTime { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] + public decimal AimingTime { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Speed", UnitKey = "Knots")] - public decimal CruisingSpeed { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] + public decimal TimeToFullyAimed { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Speed", UnitKey = "Knots")] - public decimal MaxSpeed { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] + public decimal PostAttackInvulnerabilityDuration { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Speed", UnitKey = "Knots")] - public decimal MinSpeed { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] + public decimal AttackCd { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal MaxEngineBoostDuration { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Concealment", UnitKey = "KM")] + public decimal ConcealmentFromShips { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "InitialBoost", UnitKey = "S")] - public decimal JatoDuration { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Concealment", UnitKey = "KM")] + public decimal ConcealmentFromPlanes { get; set; } + + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] + public decimal MaxViewDistance { get; set; } + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AimPenaltyPlanes", UnitKey = "PerCent")] + public string AimingRateMoving { get; set; } = default!; + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AimPenaltyPlanes", UnitKey = "PerCent")] + public string AimingPreparationRateMoving { get; set; } = default!; + + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] + public int InnerBombPercentage { get; set; } + + public string WeaponType { get; set; } = default!; + + public bool IsLast { get; set; } + + public ProjectileDataContainer? Weapon { get; set; } + + public List PlaneConsumables { get; set; } = default!; + + // TODO + public decimal ArmamentReloadTime { get; set; } + + public decimal BoostDurationTime { get; set; } + + public decimal BoostReloadTime { get; set; } + + public static List? FromShip(Ship ship, List shipConfiguration, List<(string name, float value)> modifiers) + { + if (!ship.CvPlanes.Any()) + { + return null; + } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "InitialBoost", UnitKey = "PerCent")] - public decimal JatoSpeedMultiplier { get; set; } + var list = new List(); + var planes = new List(); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "HP", UnitKey = "HP")] - public int PlaneHp { get; set; } + var rocketConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.Fighter); + if (rocketConfiguration != null) + { + List skipModule = ship.CvPlanes[rocketConfiguration.Components[ComponentType.Fighter].First()]; + planes.AddRange(skipModule); + } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "HP", UnitKey = "HP")] - public int SquadronHp { get; set; } + var torpConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.TorpedoBomber); + if (torpConfiguration != null) + { + List skipModule = ship.CvPlanes[torpConfiguration.Components[ComponentType.TorpedoBomber].First()]; + planes.AddRange(skipModule); + } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "HP", UnitKey = "HP")] - public int AttackGroupHp { get; set; } + var diveConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.DiveBomber); + if (diveConfiguration != null) + { + List diveModule = ship.CvPlanes[diveConfiguration.Components[ComponentType.DiveBomber].First()]; + planes.AddRange(diveModule); + } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] - public int DamageTakenDuringAttack { get; set; } + var skipConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.SkipBomber); + if (skipConfiguration != null) + { + List skipModule = ship.CvPlanes[skipConfiguration.Components[ComponentType.SkipBomber].First()]; + planes.AddRange(skipModule); + } - [DataElementType(DataElementTypes.KeyValue)] - public int AmmoPerAttack { get; set; } + foreach (var value in planes) + { + int index = value.IndexOf("_", StringComparison.InvariantCultureIgnoreCase); + string name = value.Substring(0, index); + var plane = AppData.FindAircraft(name); + var planeDataContainer = ProcessCvPlane(plane, ship.Tier, modifiers); + list.Add(planeDataContainer); + } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] - public decimal PreparationTime { get; set; } + list.Last().IsLast = true; + return list; + } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] - public decimal AimingTime { get; set; } + private static CvAircraftDataContainer ProcessCvPlane(Aircraft plane, int shipTier, List<(string name, float value)> modifiers) + { + var maxOnDeckModifiers = modifiers.FindModifiers("planeExtraHangarSize"); + int maxOnDeck = maxOnDeckModifiers.Aggregate(plane.MaxPlaneInHangar, (current, modifier) => (int)(current + modifier)); + + var restorationTimeModifiers = modifiers.FindModifiers("planeSpawnTime"); + decimal restorationTime = (decimal)restorationTimeModifiers.Aggregate(plane.RestorationTime, (current, modifier) => current * modifier); + + var talentRestorationModifiers = modifiers.FindModifiers("airplaneReloadCoeff"); + restorationTime = talentRestorationModifiers.Aggregate(restorationTime, (current, modifier) => current * (decimal)modifier); + + float planeHp = 0; + float cruisingSpeed = plane.Speed; + float minSpeedMultiplier = plane.SpeedMinModifier; + float maxSpeedMultiplier = plane.SpeedMaxModifier; + var planesConcealmentFromShips = (float)plane.ConcealmentFromShips; + var planesConcealmentFromPlanes = (float)plane.ConcealmentFromPlanes; + decimal aimRateModifier = 1; + decimal aimingTime = 0; + switch (plane.PlaneType) + { + case PlaneType.TacticalFighter: + case PlaneType.Fighter: + var rocketPlaneHpModifiers = modifiers.FindModifiers("fighterHealth"); + planeHp = rocketPlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] - public decimal TimeToFullyAimed { get; set; } + var rocketAimingTimeModifiers = modifiers.FindModifiers("fighterAimingTime"); + aimingTime = rocketAimingTimeModifiers.Aggregate(plane.AimingTime, (current, modifier) => current + (decimal)modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] - public decimal PostAttackInvulnerabilityDuration { get; set; } + var aimModifiersRocket = modifiers.FindModifiers("fighterAccuracyIncRateCoeff"); + aimRateModifier = aimModifiersRocket.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AttackTimings", UnitKey = "S")] - public decimal AttackCd { get; set; } + break; + case PlaneType.TacticalDiveBomber: + case PlaneType.DiveBomber: + var divePlaneHpModifiers = modifiers.FindModifiers("diveBomberHealth"); + planeHp = divePlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Concealment", UnitKey = "KM")] - public decimal ConcealmentFromShips { get; set; } + var divePlaneSpeedModifiers = modifiers.FindModifiers("diveBomberSpeedMultiplier"); + cruisingSpeed = divePlaneSpeedModifiers.Aggregate(cruisingSpeed, (current, modifier) => current * modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Concealment", UnitKey = "KM")] - public decimal ConcealmentFromPlanes { get; set; } + var minSpeedMultiplierModifiers = modifiers.FindModifiers("diveBomberMinSpeedMultiplier"); + minSpeedMultiplier = minSpeedMultiplierModifiers.Aggregate(minSpeedMultiplier, (current, modifier) => current * modifier); - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] - public decimal MaxViewDistance { get; set; } + var maxSpeedMultiplierModifiers = modifiers.FindModifiers("diveBomberMaxSpeedMultiplier"); + maxSpeedMultiplier = maxSpeedMultiplierModifiers.Aggregate(maxSpeedMultiplier, (current, modifier) => current * modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AimPenaltyPlanes", UnitKey = "PerCent")] - public string AimingRateMoving { get; set; } = default!; + var aimModifiersBomber = modifiers.FindModifiers("diveBomberAccuracyIncRateCoeff"); + aimRateModifier = aimModifiersBomber.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AimPenaltyPlanes", UnitKey = "PerCent")] - public string AimingPreparationRateMoving { get; set; } = default!; + break; + case PlaneType.TacticalTorpedoBomber: + case PlaneType.TorpedoBomber: + var torpPlaneHpModifiers = modifiers.FindModifiers("torpedoBomberHealth"); + planeHp = torpPlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] - public int InnerBombPercentage { get; set; } + var torpAimingTimeModifiers = modifiers.FindModifiers("torpedoBomberAimingTime"); + aimingTime = torpAimingTimeModifiers.Aggregate(plane.AimingTime, (current, modifier) => current + (decimal)modifier); - public string WeaponType { get; set; } = default!; + var aimModifiersTorpedo = modifiers.FindModifiers("torpedoBomberAccuracyIncRateCoeff"); + aimRateModifier = aimModifiersTorpedo.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - public bool IsLast { get; set; } + break; + case PlaneType.TacticalSkipBomber: + case PlaneType.SkipBomber: + var skipPlaneHpModifiers = modifiers.FindModifiers("skipBomberHealth"); + planeHp = skipPlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - public ProjectileDataContainer? Weapon { get; set; } + var skipPlaneSpeedModifiers = modifiers.FindModifiers("skipBomberSpeedMultiplier"); + cruisingSpeed = skipPlaneSpeedModifiers.Aggregate(cruisingSpeed, (current, modifier) => current * modifier); - public List PlaneConsumables { get; set; } = default!; + var skipAimingTimeModifiers = modifiers.FindModifiers("skipBomberAimingTime"); + aimingTime = skipAimingTimeModifiers.Aggregate(plane.AimingTime, (current, modifier) => current * (decimal)modifier); - // TODO - public decimal ArmamentReloadTime { get; set; } + var aimModifiersSkip = modifiers.FindModifiers("skipBomberAccuracyIncRateCoeff"); + aimRateModifier = aimModifiersSkip.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - public decimal BoostDurationTime { get; set; } + break; + } - public decimal BoostReloadTime { get; set; } + var allPlaneHpModifiers = modifiers.FindModifiers("planeHealthCoeff", true); + var finalPlaneHp = (int)Math.Round(allPlaneHpModifiers.Aggregate(planeHp, (current, modifier) => current * modifier), 0); - public static List? FromShip(Ship ship, List shipConfiguration, List<(string name, float value)> modifiers) + int planeHpPerTierIndex = modifiers.FindModifierIndex("planeHealthPerLevel"); + if (planeHpPerTierIndex > 0) { - if (!ship.CvPlanes.Any()) - { - return null; - } - - var list = new List(); - var planes = new List(); - - var rocketConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.Fighter); - if (rocketConfiguration != null) - { - List skipModule = ship.CvPlanes[rocketConfiguration.Components[ComponentType.Fighter].First()]; - planes.AddRange(skipModule); - } - - var torpConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.TorpedoBomber); - if (torpConfiguration != null) - { - List skipModule = ship.CvPlanes[torpConfiguration.Components[ComponentType.TorpedoBomber].First()]; - planes.AddRange(skipModule); - } - - var diveConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.DiveBomber); - if (diveConfiguration != null) - { - List diveModule = ship.CvPlanes[diveConfiguration.Components[ComponentType.DiveBomber].First()]; - planes.AddRange(diveModule); - } - - var skipConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.SkipBomber); - if (skipConfiguration != null) - { - List skipModule = ship.CvPlanes[skipConfiguration.Components[ComponentType.SkipBomber].First()]; - planes.AddRange(skipModule); - } - - foreach (var value in planes) - { - int index = value.IndexOf("_", StringComparison.InvariantCultureIgnoreCase); - string name = value.Substring(0, index); - var plane = AppData.FindAircraft(name); - var planeDataContainer = ProcessCvPlane(plane, ship.Tier, modifiers); - list.Add(planeDataContainer); - } - - list.Last().IsLast = true; - return list; + int additionalHp = (int)modifiers[planeHpPerTierIndex].value * shipTier; + finalPlaneHp += additionalHp; } - private static CvAircraftDataContainer ProcessCvPlane(Aircraft plane, int shipTier, List<(string name, float value)> modifiers) + var cruisingSpeedModifiers = modifiers.FindModifiers("planeSpeed"); + decimal finalCruisingSpeed = (decimal)cruisingSpeedModifiers.Aggregate(cruisingSpeed, (current, modifier) => current * modifier); + + var talentCruisingSpeedModifiers = modifiers.FindModifiers("squadronSpeedCoeff"); + finalCruisingSpeed = talentCruisingSpeedModifiers.Aggregate(finalCruisingSpeed, (current, modifier) => current * (decimal)modifier); + + var maxSpeedModifiers = modifiers.FindModifiers("planeMaxSpeedMultiplier"); + maxSpeedMultiplier = maxSpeedModifiers.Aggregate(maxSpeedMultiplier, (current, modifier) => current * modifier); + + var maxEngineBoostDurationModifiers = modifiers.FindModifiers("planeForsageTimeCoeff"); + var maxEngineBoostDuration = maxEngineBoostDurationModifiers.Aggregate(plane.MaxEngineBoostDuration, (current, modifier) => current * modifier); + + var planesConcealmentModifiers = modifiers.FindModifiers("planeVisibilityFactor").ToList(); + planesConcealmentFromShips = planesConcealmentModifiers.Aggregate(planesConcealmentFromShips, (current, modifier) => current * modifier); + planesConcealmentFromPlanes = planesConcealmentModifiers.Aggregate(planesConcealmentFromPlanes, (current, modifier) => current * modifier); + + var jatoDuration = (decimal)plane.JatoData.JatoDuration; + var jatoMultiplier = (decimal)plane.JatoData.JatoSpeedMultiplier; + if (jatoDuration == 0) { - var maxOnDeckModifiers = modifiers.FindModifiers("planeExtraHangarSize"); - int maxOnDeck = maxOnDeckModifiers.Aggregate(plane.MaxPlaneInHangar, (current, modifier) => (int)(current + modifier)); + jatoMultiplier = 0; + } - var restorationTimeModifiers = modifiers.FindModifiers("planeSpawnTime"); - decimal restorationTime = (decimal)restorationTimeModifiers.Aggregate(plane.RestorationTime, (current, modifier) => current * modifier); + var weaponType = AppData.FindProjectile(plane.BombName).ProjectileType; + var bombInnerEllipse = 0; + ProjectileDataContainer weapon = null!; + switch (weaponType) + { + case ProjectileType.Bomb: + weapon = BombDataContainer.FromBombName(plane.BombName, modifiers); + bombInnerEllipse = (int)plane.InnerBombsPercentage; + break; + case ProjectileType.SkipBomb: + weapon = BombDataContainer.FromBombName(plane.BombName, modifiers); + break; + case ProjectileType.Torpedo: + var torpList = new List + { + plane.BombName, + }; + weapon = TorpedoDataContainer.FromTorpedoName(torpList, modifiers, true).First(); + break; + case ProjectileType.Rocket: + weapon = RocketDataContainer.FromRocketName(plane.BombName, modifiers); + break; + } - var talentRestorationModifiers = modifiers.FindModifiers("airplaneReloadCoeff"); - restorationTime = talentRestorationModifiers.Aggregate(restorationTime, (current, modifier) => current * (decimal)modifier); - - float planeHp = 0; - float cruisingSpeed = plane.Speed; - float minSpeedMultiplier = plane.SpeedMinModifier; - float maxSpeedMultiplier = plane.SpeedMaxModifier; - var planesConcealmentFromShips = (float)plane.ConcealmentFromShips; - var planesConcealmentFromPlanes = (float)plane.ConcealmentFromPlanes; - decimal aimRateModifier = 1; - decimal aimingTime = 0; - switch (plane.PlaneType) - { - case PlaneType.TacticalFighter: - case PlaneType.Fighter: - var rocketPlaneHpModifiers = modifiers.FindModifiers("fighterHealth"); - planeHp = rocketPlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - - var rocketAimingTimeModifiers = modifiers.FindModifiers("fighterAimingTime"); - aimingTime = rocketAimingTimeModifiers.Aggregate(plane.AimingTime, (current, modifier) => current + (decimal)modifier); - - var aimModifiersRocket = modifiers.FindModifiers("fighterAccuracyIncRateCoeff"); - aimRateModifier = aimModifiersRocket.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - - break; - case PlaneType.TacticalDiveBomber: - case PlaneType.DiveBomber: - var divePlaneHpModifiers = modifiers.FindModifiers("diveBomberHealth"); - planeHp = divePlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - - var divePlaneSpeedModifiers = modifiers.FindModifiers("diveBomberSpeedMultiplier"); - cruisingSpeed = divePlaneSpeedModifiers.Aggregate(cruisingSpeed, (current, modifier) => current * modifier); - - var minSpeedMultiplierModifiers = modifiers.FindModifiers("diveBomberMinSpeedMultiplier"); - minSpeedMultiplier = minSpeedMultiplierModifiers.Aggregate(minSpeedMultiplier, (current, modifier) => current * modifier); - - var maxSpeedMultiplierModifiers = modifiers.FindModifiers("diveBomberMaxSpeedMultiplier"); - maxSpeedMultiplier = maxSpeedMultiplierModifiers.Aggregate(maxSpeedMultiplier, (current, modifier) => current * modifier); - - var aimModifiersBomber = modifiers.FindModifiers("diveBomberAccuracyIncRateCoeff"); - aimRateModifier = aimModifiersBomber.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - - break; - case PlaneType.TacticalTorpedoBomber: - case PlaneType.TorpedoBomber: - var torpPlaneHpModifiers = modifiers.FindModifiers("torpedoBomberHealth"); - planeHp = torpPlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - - var torpAimingTimeModifiers = modifiers.FindModifiers("torpedoBomberAimingTime"); - aimingTime = torpAimingTimeModifiers.Aggregate(plane.AimingTime, (current, modifier) => current + (decimal)modifier); - - var aimModifiersTorpedo = modifiers.FindModifiers("torpedoBomberAccuracyIncRateCoeff"); - aimRateModifier = aimModifiersTorpedo.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - - break; - case PlaneType.TacticalSkipBomber: - case PlaneType.SkipBomber: - var skipPlaneHpModifiers = modifiers.FindModifiers("skipBomberHealth"); - planeHp = skipPlaneHpModifiers.Aggregate(plane.MaxHealth, (current, modifier) => current * modifier); - - var skipPlaneSpeedModifiers = modifiers.FindModifiers("skipBomberSpeedMultiplier"); - cruisingSpeed = skipPlaneSpeedModifiers.Aggregate(cruisingSpeed, (current, modifier) => current * modifier); - - var skipAimingTimeModifiers = modifiers.FindModifiers("skipBomberAimingTime"); - aimingTime = skipAimingTimeModifiers.Aggregate(plane.AimingTime, (current, modifier) => current * (decimal)modifier); - - var aimModifiersSkip = modifiers.FindModifiers("skipBomberAccuracyIncRateCoeff"); - aimRateModifier = aimModifiersSkip.Aggregate(aimRateModifier, (current, modifier) => current * (decimal)modifier); - - break; - } - - var allPlaneHpModifiers = modifiers.FindModifiers("planeHealthCoeff", true); - var finalPlaneHp = (int)Math.Round(allPlaneHpModifiers.Aggregate(planeHp, (current, modifier) => current * modifier), 0); - - int planeHpPerTierIndex = modifiers.FindModifierIndex("planeHealthPerLevel"); - if (planeHpPerTierIndex > 0) - { - int additionalHp = (int)modifiers[planeHpPerTierIndex].value * shipTier; - finalPlaneHp += additionalHp; - } - - var cruisingSpeedModifiers = modifiers.FindModifiers("planeSpeed"); - decimal finalCruisingSpeed = (decimal)cruisingSpeedModifiers.Aggregate(cruisingSpeed, (current, modifier) => current * modifier); - - var talentCruisingSpeedModifiers = modifiers.FindModifiers("squadronSpeedCoeff"); - finalCruisingSpeed = talentCruisingSpeedModifiers.Aggregate(finalCruisingSpeed, (current, modifier) => current * (decimal)modifier); - - var maxSpeedModifiers = modifiers.FindModifiers("planeMaxSpeedMultiplier"); - maxSpeedMultiplier = maxSpeedModifiers.Aggregate(maxSpeedMultiplier, (current, modifier) => current * modifier); - - var maxEngineBoostDurationModifiers = modifiers.FindModifiers("planeForsageTimeCoeff"); - var maxEngineBoostDuration = maxEngineBoostDurationModifiers.Aggregate(plane.MaxEngineBoostDuration, (current, modifier) => current * modifier); - - var planesConcealmentModifiers = modifiers.FindModifiers("planeVisibilityFactor").ToList(); - planesConcealmentFromShips = planesConcealmentModifiers.Aggregate(planesConcealmentFromShips, (current, modifier) => current * modifier); - planesConcealmentFromPlanes = planesConcealmentModifiers.Aggregate(planesConcealmentFromPlanes, (current, modifier) => current * modifier); - - var jatoDuration = (decimal)plane.JatoData.JatoDuration; - var jatoMultiplier = (decimal)plane.JatoData.JatoSpeedMultiplier; - if (jatoDuration == 0) - { - jatoMultiplier = 0; - } - - var weaponType = AppData.FindProjectile(plane.BombName).ProjectileType; - var bombInnerEllipse = 0; - ProjectileDataContainer weapon = null!; - switch (weaponType) - { - case ProjectileType.Bomb: - weapon = BombDataContainer.FromBombName(plane.BombName, modifiers); - bombInnerEllipse = (int)plane.InnerBombsPercentage; - break; - case ProjectileType.SkipBomb: - weapon = BombDataContainer.FromBombName(plane.BombName, modifiers); - break; - case ProjectileType.Torpedo: - var torpList = new List - { - plane.BombName, - }; - weapon = TorpedoDataContainer.FromTorpedoName(torpList, modifiers, true).First(); - break; - case ProjectileType.Rocket: - weapon = RocketDataContainer.FromRocketName(plane.BombName, modifiers); - break; - } - - List consumables = new(); - foreach (var consumable in plane.AircraftConsumable) - { - var consumableDataContainer = ConsumableDataContainer.FromTypeAndVariant(consumable, modifiers, true, 0, ShipClass.AirCarrier); - consumables.Add(consumableDataContainer); - } - - consumables = consumables.OrderBy(x => x.Slot).ToList(); - - var aimingRateMoving = plane.AimingAccuracyIncreaseRate + plane.AimingAccuracyDecreaseRate; - var preparationAimingRateMoving = plane.PreparationAccuracyIncreaseRate + plane.PreparationAccuracyDecreaseRate; - - var fullAimTime = plane.PreparationTime + ((1 - (plane.PreparationTime * plane.PreparationAccuracyIncreaseRate * aimRateModifier)) / (plane.AimingAccuracyIncreaseRate * aimRateModifier)); - - var jatoSpeedMultiplier = jatoMultiplier > 1 ? (jatoMultiplier - 1) * 100 : 0; - - const string stringFormat = "+#0.0;-#0.0;0"; - - var cvAircraft = new CvAircraftDataContainer - { - Name = plane.Name, - PlaneVariant = plane.PlaneType.PlaneTypeToString(), - PlaneHp = finalPlaneHp, - SquadronHp = finalPlaneHp * plane.NumPlanesInSquadron, - AttackGroupHp = finalPlaneHp * plane.AttackData.AttackerSize, - NumberInSquad = plane.NumPlanesInSquadron, - MaxNumberOnDeck = maxOnDeck, - RestorationTime = Math.Round(restorationTime, 2), - CruisingSpeed = finalCruisingSpeed, - MaxSpeed = Math.Round(finalCruisingSpeed * (decimal)maxSpeedMultiplier, 0), - MinSpeed = Math.Round(finalCruisingSpeed * (decimal)minSpeedMultiplier, 0), - MaxEngineBoostDuration = (decimal)maxEngineBoostDuration, - InnerBombPercentage = bombInnerEllipse, - NumberDuringAttack = plane.AttackData.AttackerSize, - AmmoPerAttack = plane.AttackData.AttackCount, - AttackCd = Math.Round((decimal)plane.AttackData.AttackCooldown, 1), - JatoDuration = jatoDuration, - JatoSpeedMultiplier = Math.Round(jatoSpeedMultiplier, 0), - WeaponType = weaponType.ProjectileTypeToString(), - Weapon = weapon, - PlaneConsumables = consumables, - AimingTime = Math.Round(aimingTime, 1), - PreparationTime = plane.PreparationTime, - PostAttackInvulnerabilityDuration = plane.PostAttackInvulnerabilityDuration, - DamageTakenDuringAttack = (int)Math.Round(plane.DamageTakenMultiplier * 100), - AimingRateMoving = (aimingRateMoving * 100).ToString(stringFormat, CultureInfo.InvariantCulture), - AimingPreparationRateMoving = (preparationAimingRateMoving * 100).ToString(stringFormat, CultureInfo.InvariantCulture), - TimeToFullyAimed = Math.Round(fullAimTime, 1), - ConcealmentFromShips = (decimal)planesConcealmentFromShips, - ConcealmentFromPlanes = (decimal)planesConcealmentFromPlanes, - MaxViewDistance = (decimal)plane.SpottingOnShips, - }; - - cvAircraft.UpdateDataElements(); - - return cvAircraft; + List consumables = new(); + foreach (var consumable in plane.AircraftConsumable) + { + var consumableDataContainer = ConsumableDataContainer.FromTypeAndVariant(consumable, modifiers, true, 0, ShipClass.AirCarrier); + consumables.Add(consumableDataContainer); } + + consumables = consumables.OrderBy(x => x.Slot).ToList(); + + var aimingRateMoving = plane.AimingAccuracyIncreaseRate + plane.AimingAccuracyDecreaseRate; + var preparationAimingRateMoving = plane.PreparationAccuracyIncreaseRate + plane.PreparationAccuracyDecreaseRate; + + var fullAimTime = plane.PreparationTime + ((1 - (plane.PreparationTime * plane.PreparationAccuracyIncreaseRate * aimRateModifier)) / (plane.AimingAccuracyIncreaseRate * aimRateModifier)); + + var jatoSpeedMultiplier = jatoMultiplier > 1 ? (jatoMultiplier - 1) * 100 : 0; + + const string stringFormat = "+#0.0;-#0.0;0"; + + var cvAircraft = new CvAircraftDataContainer + { + Name = plane.Name, + PlaneVariant = plane.PlaneType.PlaneTypeToString(), + PlaneHp = finalPlaneHp, + SquadronHp = finalPlaneHp * plane.NumPlanesInSquadron, + AttackGroupHp = finalPlaneHp * plane.AttackData.AttackerSize, + NumberInSquad = plane.NumPlanesInSquadron, + MaxNumberOnDeck = maxOnDeck, + RestorationTime = Math.Round(restorationTime, 2), + CruisingSpeed = finalCruisingSpeed, + MaxSpeed = Math.Round(finalCruisingSpeed * (decimal)maxSpeedMultiplier, 0), + MinSpeed = Math.Round(finalCruisingSpeed * (decimal)minSpeedMultiplier, 0), + MaxEngineBoostDuration = (decimal)maxEngineBoostDuration, + InnerBombPercentage = bombInnerEllipse, + NumberDuringAttack = plane.AttackData.AttackerSize, + AmmoPerAttack = plane.AttackData.AttackCount, + AttackCd = Math.Round((decimal)plane.AttackData.AttackCooldown, 1), + JatoDuration = jatoDuration, + JatoSpeedMultiplier = Math.Round(jatoSpeedMultiplier, 0), + WeaponType = weaponType.ProjectileTypeToString(), + Weapon = weapon, + PlaneConsumables = consumables, + AimingTime = Math.Round(aimingTime, 1), + PreparationTime = plane.PreparationTime, + PostAttackInvulnerabilityDuration = plane.PostAttackInvulnerabilityDuration, + DamageTakenDuringAttack = (int)Math.Round(plane.DamageTakenMultiplier * 100), + AimingRateMoving = (aimingRateMoving * 100).ToString(stringFormat, CultureInfo.InvariantCulture), + AimingPreparationRateMoving = (preparationAimingRateMoving * 100).ToString(stringFormat, CultureInfo.InvariantCulture), + TimeToFullyAimed = Math.Round(fullAimTime, 1), + ConcealmentFromShips = (decimal)planesConcealmentFromShips, + ConcealmentFromPlanes = (decimal)planesConcealmentFromPlanes, + MaxViewDistance = (decimal)plane.SpottingOnShips, + }; + + cvAircraft.UpdateDataElements(); + + return cvAircraft; } } diff --git a/WoWsShipBuilder.Common/DataContainers/Armament/DepthChargesLauncherDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Armament/DepthChargesLauncherDataContainer.cs index 75c72d99d..17d36e9ef 100644 --- a/WoWsShipBuilder.Common/DataContainers/Armament/DepthChargesLauncherDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Armament/DepthChargesLauncherDataContainer.cs @@ -1,11 +1,12 @@ +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.Utility; namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record DepthChargesLauncherDataContainer : DataContainerBase { [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] diff --git a/WoWsShipBuilder.Common/DataContainers/Armament/MainBatteryDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Armament/MainBatteryDataContainer.cs index ea15bdb30..d5d3dbe3e 100644 --- a/WoWsShipBuilder.Common/DataContainers/Armament/MainBatteryDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Armament/MainBatteryDataContainer.cs @@ -1,7 +1,7 @@ using System.Globalization; using System.Text; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.GameData; @@ -10,9 +10,10 @@ // ReSharper disable UnusedAutoPropertyAccessor.Global namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record MainBatteryDataContainer : DataContainerBase { - [DataElementType(DataElementTypes.FormattedText, ValuesPropertyName = "TurretNames", ArePropertyNameValuesKeys = true)] + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "TurretNames", ArgumentsTextKind = TextKind.LocalizationKey)] public string Name { get; set; } = default!; public List TurretNames { get; set; } = new(); diff --git a/WoWsShipBuilder.Common/DataContainers/Armament/PingerGunDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Armament/PingerGunDataContainer.cs index eabd36ce2..540be959f 100644 --- a/WoWsShipBuilder.Common/DataContainers/Armament/PingerGunDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Armament/PingerGunDataContainer.cs @@ -1,103 +1,103 @@ using Microsoft.Extensions.Logging; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.Utility; -namespace WoWsShipBuilder.DataContainers +namespace WoWsShipBuilder.DataContainers; + +[DataContainer] +public partial record PingerGunDataContainer : DataContainerBase { - public partial record PingerGunDataContainer : DataContainerBase - { - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal Reload { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal Reload { get; set; } + + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "DegreePerSecond")] + public decimal TraverseSpeed { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "DegreePerSecond")] - public decimal TraverseSpeed { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal TurnTime { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal TurnTime { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] + public decimal Range { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] - public decimal Range { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "PingDuration", UnitKey = "S", LocalizationKeyOverride = "First")] + public decimal FirstPingDuration { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "PingDuration", UnitKey = "S", NameLocalizationKey = "First")] - public decimal FirstPingDuration { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "PingDuration", UnitKey = "S", LocalizationKeyOverride = "Second")] + public decimal SecondPingDuration { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "PingDuration", UnitKey = "S", NameLocalizationKey = "Second")] - public decimal SecondPingDuration { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "M")] + public decimal PingWidth { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "M")] - public decimal PingWidth { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MPS")] + public decimal PingSpeed { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MPS")] - public decimal PingSpeed { get; set; } + public static PingerGunDataContainer? FromShip(Ship ship, IEnumerable shipConfiguration, List<(string name, float value)> modifiers) + { + if (!ship.PingerGunList.Any()) + { + return null; + } - public static PingerGunDataContainer? FromShip(Ship ship, IEnumerable shipConfiguration, List<(string name, float value)> modifiers) + PingerGun pingerGun; + var pingerUpgrade = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.Sonar); + if (pingerUpgrade is null && ship.PingerGunList.Count is 1) { - if (!ship.PingerGunList.Any()) - { - return null; - } - - PingerGun pingerGun; - var pingerUpgrade = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.Sonar); - if (pingerUpgrade is null && ship.PingerGunList.Count is 1) - { - Logging.Logger.LogWarning("No sonar upgrade information found for ship {ShipName} even though there is one sonar module available", ship.Name); - return null; - } - - if (pingerUpgrade is null) - { - throw new InvalidOperationException($"No sonar upgrade information found for ship {ship.Name} but there is more than one sonar module."); - } - - // Safe approach is necessary because data up until 0.11.9#1 does not include this data due to an issue in the data converter - if (pingerUpgrade.Components.TryGetValue(ComponentType.Sonar, out string[]? pingerGunInfo)) - { - pingerGun = ship.PingerGunList[pingerGunInfo.First()]; - } - else - { - Logging.Logger.LogWarning("Unable to retrieve sonar component from upgrade info for ship {} and ship upgrade {}", ship.Index, pingerUpgrade.Name); - pingerGun = ship.PingerGunList.First().Value; - } - - var pingSpeed = pingerGun.WaveParams.First().WaveSpeed.First(); - var pingSpeedModifiers = modifiers.FindModifiers("pingerWaveSpeedCoeff"); - pingSpeed = pingSpeedModifiers.Aggregate(pingSpeed, (current, pingSpeedModifier) => current * (decimal)pingSpeedModifier); - - var firstPingDuration = pingerGun.SectorParams[0].Lifetime; - var firstPingDurationModifiers = modifiers.FindModifiers("firstSectorTimeCoeff"); - firstPingDuration = firstPingDurationModifiers.Aggregate(firstPingDuration, (current, firstPingDurationModifier) => current * (decimal)firstPingDurationModifier); - - var secondPingDuration = pingerGun.SectorParams[1].Lifetime; - var secondPingDurationModifiers = modifiers.FindModifiers("secondSectorTimeCoeff"); - secondPingDuration = secondPingDurationModifiers.Aggregate(secondPingDuration, (current, secondPingDurationModifiersModifier) => current * (decimal)secondPingDurationModifiersModifier); - - var traverseSpeed = pingerGun.RotationSpeed[0]; - - var arModifiers = modifiers.FindModifiers("lastChanceReloadCoefficient"); - var reload = arModifiers.Aggregate(pingerGun.WaveReloadTime, (current, arModifier) => current * (1 - ((decimal)arModifier / 100))); - var pingReloadModifiers = modifiers.FindModifiers("pingerReloadCoeff"); - reload = pingReloadModifiers.Aggregate(reload, (current, pingReloadModifier) => current * (decimal)pingReloadModifier); - - var pingerGunDataContainer = new PingerGunDataContainer - { - TurnTime = Math.Round(180 / traverseSpeed, 1), - TraverseSpeed = traverseSpeed, - Reload = Math.Round(reload, 2), - Range = pingerGun.WaveDistance / 1000, - FirstPingDuration = Math.Round(firstPingDuration, 1), - SecondPingDuration = Math.Round(secondPingDuration, 1), - PingWidth = pingerGun.WaveParams.First().StartWaveWidth, - PingSpeed = Math.Round(pingSpeed, 0), - }; - - pingerGunDataContainer.UpdateDataElements(); - - return pingerGunDataContainer; + Logging.Logger.LogWarning("No sonar upgrade information found for ship {ShipName} even though there is one sonar module available", ship.Name); + return null; } + + if (pingerUpgrade is null) + { + throw new InvalidOperationException($"No sonar upgrade information found for ship {ship.Name} but there is more than one sonar module."); + } + + // Safe approach is necessary because data up until 0.11.9#1 does not include this data due to an issue in the data converter + if (pingerUpgrade.Components.TryGetValue(ComponentType.Sonar, out string[]? pingerGunInfo)) + { + pingerGun = ship.PingerGunList[pingerGunInfo.First()]; + } + else + { + Logging.Logger.LogWarning("Unable to retrieve sonar component from upgrade info for ship {} and ship upgrade {}", ship.Index, pingerUpgrade.Name); + pingerGun = ship.PingerGunList.First().Value; + } + + var pingSpeed = pingerGun.WaveParams.First().WaveSpeed.First(); + var pingSpeedModifiers = modifiers.FindModifiers("pingerWaveSpeedCoeff"); + pingSpeed = pingSpeedModifiers.Aggregate(pingSpeed, (current, pingSpeedModifier) => current * (decimal)pingSpeedModifier); + + var firstPingDuration = pingerGun.SectorParams[0].Lifetime; + var firstPingDurationModifiers = modifiers.FindModifiers("firstSectorTimeCoeff"); + firstPingDuration = firstPingDurationModifiers.Aggregate(firstPingDuration, (current, firstPingDurationModifier) => current * (decimal)firstPingDurationModifier); + + var secondPingDuration = pingerGun.SectorParams[1].Lifetime; + var secondPingDurationModifiers = modifiers.FindModifiers("secondSectorTimeCoeff"); + secondPingDuration = secondPingDurationModifiers.Aggregate(secondPingDuration, (current, secondPingDurationModifiersModifier) => current * (decimal)secondPingDurationModifiersModifier); + + var traverseSpeed = pingerGun.RotationSpeed[0]; + + var arModifiers = modifiers.FindModifiers("lastChanceReloadCoefficient"); + var reload = arModifiers.Aggregate(pingerGun.WaveReloadTime, (current, arModifier) => current * (1 - ((decimal)arModifier / 100))); + var pingReloadModifiers = modifiers.FindModifiers("pingerReloadCoeff"); + reload = pingReloadModifiers.Aggregate(reload, (current, pingReloadModifier) => current * (decimal)pingReloadModifier); + + var pingerGunDataContainer = new PingerGunDataContainer + { + TurnTime = Math.Round(180 / traverseSpeed, 1), + TraverseSpeed = traverseSpeed, + Reload = Math.Round(reload, 2), + Range = pingerGun.WaveDistance / 1000, + FirstPingDuration = Math.Round(firstPingDuration, 1), + SecondPingDuration = Math.Round(secondPingDuration, 1), + PingWidth = pingerGun.WaveParams.First().StartWaveWidth, + PingSpeed = Math.Round(pingSpeed, 0), + }; + + pingerGunDataContainer.UpdateDataElements(); + + return pingerGunDataContainer; } } diff --git a/WoWsShipBuilder.Common/DataContainers/Armament/SecondaryBatteryDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Armament/SecondaryBatteryDataContainer.cs index ef10cb9c3..4939a0a93 100644 --- a/WoWsShipBuilder.Common/DataContainers/Armament/SecondaryBatteryDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Armament/SecondaryBatteryDataContainer.cs @@ -1,7 +1,7 @@ using System.Globalization; using Microsoft.Extensions.Logging; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.GameData; @@ -9,6 +9,7 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record SecondaryBatteryDataContainer : DataContainerBase { public string Name { get; set; } = default!; @@ -92,7 +93,7 @@ public partial record SecondaryBatteryDataContainer : DataContainerBase { Name = arrangementString, TurretName = turretName, - TurretSetup = new(arrangementString, turretName, AreValuesKeys: true), + TurretSetup = new(arrangementString, turretName, ArgumentsTextKind: DataElementTextKind.LocalizationKey), BarrelsLayout = $"{secondaryGroup.Count} x {secondaryGun.NumBarrels}", BarrelsCount = secondaryGroup.Count * secondaryGun.NumBarrels, GunCaliber = Math.Round(secondaryGun.BarrelDiameter * 1000), diff --git a/WoWsShipBuilder.Common/DataContainers/Armament/TorpedoArmamentDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Armament/TorpedoArmamentDataContainer.cs index 0617fbb60..bc9990430 100644 --- a/WoWsShipBuilder.Common/DataContainers/Armament/TorpedoArmamentDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Armament/TorpedoArmamentDataContainer.cs @@ -1,16 +1,17 @@ using System.Globalization; using System.Text; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.Utility; namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record TorpedoArmamentDataContainer : DataContainerBase { - [DataElementType(DataElementTypes.FormattedText, ValuesPropertyName = "LauncherNames", ArePropertyNameValuesKeys = true)] + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "LauncherNames", ArgumentsTextKind = TextKind.LocalizationKey)] public string Name { get; set; } = default!; public List LauncherNames { get; set; } = new(); @@ -39,10 +40,10 @@ public partial record TorpedoArmamentDataContainer : DataContainerBase [DataElementType(DataElementTypes.KeyValue)] public string FullSalvoDamage { get; set; } = default!; - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "FullSalvoDamage", NameLocalizationKey = "FirstOption")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "FullSalvoDamage", LocalizationKeyOverride = "FirstOption")] public string TorpFullSalvoDmg { get; set; } = default!; - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "FullSalvoDamage", NameLocalizationKey = "SecondOption")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "FullSalvoDamage", LocalizationKeyOverride = "SecondOption")] public string AltTorpFullSalvoDmg { get; set; } = default!; public int LoadersCount { get; set; } diff --git a/WoWsShipBuilder.Common/DataContainers/Projectiles/BombDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Projectiles/BombDataContainer.cs index b8898ad76..5ff69265f 100644 --- a/WoWsShipBuilder.Common/DataContainers/Projectiles/BombDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Projectiles/BombDataContainer.cs @@ -7,12 +7,13 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record BombDataContainer : ProjectileDataContainer { - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true, IsValueAppLocalization = true)] + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.AppLocalizationKey)] public string BombType { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true)] + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.LocalizationKey)] public string Name { get; set; } = default!; [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MM")] diff --git a/WoWsShipBuilder.Common/DataContainers/Projectiles/DepthChargeDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Projectiles/DepthChargeDataContainer.cs index d73c1e699..027b37542 100644 --- a/WoWsShipBuilder.Common/DataContainers/Projectiles/DepthChargeDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Projectiles/DepthChargeDataContainer.cs @@ -5,6 +5,7 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record DepthChargeDataContainer : ProjectileDataContainer { [DataElementType(DataElementTypes.KeyValue)] diff --git a/WoWsShipBuilder.Common/DataContainers/Projectiles/ProjectileDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Projectiles/ProjectileDataContainer.cs index 0696e8c69..fe19a2a02 100644 --- a/WoWsShipBuilder.Common/DataContainers/Projectiles/ProjectileDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Projectiles/ProjectileDataContainer.cs @@ -1,4 +1,4 @@ -using WoWsShipBuilder.DataElements.DataElements; +using WoWsShipBuilder.DataElements; namespace WoWsShipBuilder.DataContainers; diff --git a/WoWsShipBuilder.Common/DataContainers/Projectiles/RocketDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Projectiles/RocketDataContainer.cs index 69140fbf6..e432d1a77 100644 --- a/WoWsShipBuilder.Common/DataContainers/Projectiles/RocketDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Projectiles/RocketDataContainer.cs @@ -5,113 +5,113 @@ using WoWsShipBuilder.Infrastructure.GameData; using WoWsShipBuilder.Infrastructure.Utility; -namespace WoWsShipBuilder.DataContainers +namespace WoWsShipBuilder.DataContainers; + +[DataContainer] +public partial record RocketDataContainer : ProjectileDataContainer { - public partial record RocketDataContainer : ProjectileDataContainer - { - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true, IsValueAppLocalization = true)] - public string RocketType { get; set; } = default!; + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.AppLocalizationKey)] + public string RocketType { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true)] - public string Name { get; set; } = default!; + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.LocalizationKey)] + public string Name { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValue)] - public decimal Damage { get; set; } + [DataElementType(DataElementTypes.KeyValue)] + public decimal Damage { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "Splash", TooltipKey = "SplashExplanation", UnitKey = "M")] - public decimal SplashRadius { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "Splash", TooltipKey = "SplashExplanation", UnitKey = "M")] + public decimal SplashRadius { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "Splash", TooltipKey = "SplashExplanation")] - public decimal SplashDmg { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "Splash", TooltipKey = "SplashExplanation")] + public decimal SplashDmg { get; set; } - [DataElementType(DataElementTypes.Tooltip, TooltipKey = "KruppExplanation")] - public decimal Krupp { get; set; } + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "KruppExplanation")] + public decimal Krupp { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MM")] - public int Penetration { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MM")] + public int Penetration { get; set; } - [DataElementType(DataElementTypes.Tooltip, TooltipKey = "ApPenetrationFormula", UnitKey = "MM")] - public int PenetrationAp { get; set; } + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "ApPenetrationFormula", UnitKey = "MM")] + public int PenetrationAp { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal FuseTimer { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal FuseTimer { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MM")] - public int ArmingThreshold { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MM")] + public int ArmingThreshold { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Degree")] - public string RicochetAngles { get; set; } = default!; + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Degree")] + public string RicochetAngles { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] - public decimal FireChance { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] + public decimal FireChance { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Blast", UnitKey = "M")] - public decimal ExplosionRadius { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Blast", UnitKey = "M")] + public decimal ExplosionRadius { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "Blast", TooltipKey = "BlastExplanation")] - [DataElementFiltering(true, "ShouldDisplayBlastPenetration")] - public decimal SplashCoeff { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "Blast", TooltipKey = "BlastExplanation")] + [DataElementFiltering(true, "ShouldDisplayBlastPenetration")] + public decimal SplashCoeff { get; set; } - public bool ShowBlastPenetration { get; private set; } + public bool ShowBlastPenetration { get; private set; } - public static RocketDataContainer FromRocketName(string name, List<(string name, float value)> modifiers) + public static RocketDataContainer FromRocketName(string name, List<(string name, float value)> modifiers) + { + var rocket = AppData.FindProjectile(name); + + var rocketDamage = (decimal)rocket.Damage; + var showBlastPenetration = true; + var ricochetAngle = ""; + decimal fuseTimer = 0; + var armingThreshold = 0; + decimal fireChance = 0; + int penetrationHe = 0; + int penetrationAp = 0; + if (rocket.RocketType.Equals(DataStructures.RocketType.AP)) { - var rocket = AppData.FindProjectile(name); - - var rocketDamage = (decimal)rocket.Damage; - var showBlastPenetration = true; - var ricochetAngle = ""; - decimal fuseTimer = 0; - var armingThreshold = 0; - decimal fireChance = 0; - int penetrationHe = 0; - int penetrationAp = 0; - if (rocket.RocketType.Equals(DataStructures.RocketType.AP)) - { - List rocketDamageModifiers = modifiers.FindModifiers("rocketApAlphaDamageMultiplier").ToList(); - rocketDamage = rocketDamageModifiers.Aggregate(rocketDamage, (current, modifier) => current * (decimal)modifier); - ricochetAngle = $"{rocket.RicochetAngle}-{rocket.AlwaysRicochetAngle}"; - fuseTimer = (decimal)rocket.FuseTimer; - armingThreshold = (int)rocket.ArmingThreshold; - showBlastPenetration = false; - penetrationAp = (int)Math.Round(BallisticHelper.CalculatePen(rocket.MuzzleVelocity, rocket.Caliber, rocket.Mass, rocket.Krupp)); - } - else - { - var fireChanceModifiers = modifiers.FindModifiers("rocketBurnChanceBonus"); - fireChance = (decimal)fireChanceModifiers.Aggregate(rocket.FireChance, (current, modifier) => current + modifier); - var fireChanceModifiersRockets = modifiers.FindModifiers("burnChanceFactorSmall"); - fireChance = fireChanceModifiersRockets.Aggregate(fireChance, (current, modifier) => current + (decimal)modifier); - penetrationHe = (int)Math.Truncate(rocket.Penetration); - } - - var rocketDataContainer = new RocketDataContainer - { - Name = rocket.Name, - RocketType = $"ArmamentType_{rocket.RocketType.RocketTypeToString()}", - Damage = Math.Round(rocketDamage, 2), - Penetration = penetrationHe, - PenetrationAp = penetrationAp, - FuseTimer = fuseTimer, - ArmingThreshold = armingThreshold, - RicochetAngles = ricochetAngle, - FireChance = Math.Round(fireChance * 100, 1), - ExplosionRadius = (decimal)rocket.ExplosionRadius, - SplashCoeff = (decimal)rocket.SplashCoeff, - ShowBlastPenetration = showBlastPenetration, - SplashRadius = Math.Round((decimal)rocket.DepthSplashRadius, 1), - SplashDmg = Math.Round(rocketDamage * (decimal)rocket.SplashDamageCoefficient), - Krupp = (decimal)rocket.Krupp, - }; - - rocketDataContainer.UpdateDataElements(); - - return rocketDataContainer; + List rocketDamageModifiers = modifiers.FindModifiers("rocketApAlphaDamageMultiplier").ToList(); + rocketDamage = rocketDamageModifiers.Aggregate(rocketDamage, (current, modifier) => current * (decimal)modifier); + ricochetAngle = $"{rocket.RicochetAngle}-{rocket.AlwaysRicochetAngle}"; + fuseTimer = (decimal)rocket.FuseTimer; + armingThreshold = (int)rocket.ArmingThreshold; + showBlastPenetration = false; + penetrationAp = (int)Math.Round(BallisticHelper.CalculatePen(rocket.MuzzleVelocity, rocket.Caliber, rocket.Mass, rocket.Krupp)); } - - private bool ShouldDisplayBlastPenetration(object obj) + else { - return ShowBlastPenetration; + var fireChanceModifiers = modifiers.FindModifiers("rocketBurnChanceBonus"); + fireChance = (decimal)fireChanceModifiers.Aggregate(rocket.FireChance, (current, modifier) => current + modifier); + var fireChanceModifiersRockets = modifiers.FindModifiers("burnChanceFactorSmall"); + fireChance = fireChanceModifiersRockets.Aggregate(fireChance, (current, modifier) => current + (decimal)modifier); + penetrationHe = (int)Math.Truncate(rocket.Penetration); } + + var rocketDataContainer = new RocketDataContainer + { + Name = rocket.Name, + RocketType = $"ArmamentType_{rocket.RocketType.RocketTypeToString()}", + Damage = Math.Round(rocketDamage, 2), + Penetration = penetrationHe, + PenetrationAp = penetrationAp, + FuseTimer = fuseTimer, + ArmingThreshold = armingThreshold, + RicochetAngles = ricochetAngle, + FireChance = Math.Round(fireChance * 100, 1), + ExplosionRadius = (decimal)rocket.ExplosionRadius, + SplashCoeff = (decimal)rocket.SplashCoeff, + ShowBlastPenetration = showBlastPenetration, + SplashRadius = Math.Round((decimal)rocket.DepthSplashRadius, 1), + SplashDmg = Math.Round(rocketDamage * (decimal)rocket.SplashDamageCoefficient), + Krupp = (decimal)rocket.Krupp, + }; + + rocketDataContainer.UpdateDataElements(); + + return rocketDataContainer; + } + + private bool ShouldDisplayBlastPenetration(object obj) + { + return ShowBlastPenetration; } } diff --git a/WoWsShipBuilder.Common/DataContainers/Projectiles/ShellDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Projectiles/ShellDataContainer.cs index 837116d77..65d4b55f5 100644 --- a/WoWsShipBuilder.Common/DataContainers/Projectiles/ShellDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Projectiles/ShellDataContainer.cs @@ -1,7 +1,7 @@ // ReSharper disable UnusedAutoPropertyAccessor.Global +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Projectile; using WoWsShipBuilder.Infrastructure.ApplicationData; @@ -10,11 +10,12 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record ShellDataContainer : DataContainerBase { public string Name { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true, IsValueAppLocalization = true)] + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.AppLocalizationKey)] public string Type { get; set; } = default!; [DataElementType(DataElementTypes.KeyValue)] diff --git a/WoWsShipBuilder.Common/DataContainers/Projectiles/TorpedoDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Projectiles/TorpedoDataContainer.cs index d6b68cf3c..1753f4ab1 100644 --- a/WoWsShipBuilder.Common/DataContainers/Projectiles/TorpedoDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Projectiles/TorpedoDataContainer.cs @@ -7,12 +7,13 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record TorpedoDataContainer : ProjectileDataContainer { - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true, IsValueAppLocalization = true)] + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.AppLocalizationKey)] public string TorpedoType { get; set; } = default!; - [DataElementType(DataElementTypes.KeyValue, IsValueLocalizationKey = true)] + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.LocalizationKey)] [DataElementFiltering(true, "ShouldDisplayName")] public string Name { get; set; } = default!; @@ -44,70 +45,70 @@ public partial record TorpedoDataContainer : ProjectileDataContainer [DataElementFiltering(false)] public decimal SplashCoeff { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxTurningSpeed", UnitKey = "DegreePerSecond", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxTurningSpeed", UnitKey = "DegreePerSecond", LocalizationKeyOverride = "FirstPing")] public decimal MaxTurningSpeedFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxTurningSpeed", UnitKey = "DegreePerSecond", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxTurningSpeed", UnitKey = "DegreePerSecond", LocalizationKeyOverride = "SecondPing")] public decimal MaxTurningSpeedSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "TurningAcceleration", UnitKey = "DegreePerSecond2", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "TurningAcceleration", UnitKey = "DegreePerSecond2", LocalizationKeyOverride = "FirstPing")] public decimal TurningAccelerationFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "TurningAcceleration", UnitKey = "DegreePerSecond2", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "TurningAcceleration", UnitKey = "DegreePerSecond2", LocalizationKeyOverride = "SecondPing")] public decimal TurningAccelerationSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxVerticalSpeed", UnitKey = "MPS", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxVerticalSpeed", UnitKey = "MPS", LocalizationKeyOverride = "FirstPing")] public decimal MaxVerticalSpeedFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxVerticalSpeed", UnitKey = "MPS", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxVerticalSpeed", UnitKey = "MPS", LocalizationKeyOverride = "SecondPing")] public decimal MaxVerticalSpeedSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "VerticalAcceleration", UnitKey = "MPS2", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "VerticalAcceleration", UnitKey = "MPS2", LocalizationKeyOverride = "FirstPing")] public decimal VerticalAccelerationFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "VerticalAcceleration", UnitKey = "MPS2", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "VerticalAcceleration", UnitKey = "MPS2", LocalizationKeyOverride = "SecondPing")] public decimal VerticalAccelerationSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchRadius", UnitKey = "KM", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchRadius", UnitKey = "KM", LocalizationKeyOverride = "FirstPing")] public decimal SearchRadiusFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchRadius", UnitKey = "KM", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchRadius", UnitKey = "KM", LocalizationKeyOverride = "SecondPing")] public decimal SearchRadiusSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchAngle", UnitKey = "Degree", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchAngle", UnitKey = "Degree", LocalizationKeyOverride = "FirstPing")] public decimal SearchAngleFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchAngle", UnitKey = "Degree", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "SearchAngle", UnitKey = "Degree", LocalizationKeyOverride = "SecondPing")] public decimal SearchAngleSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Destroyer", UnitKey = "M", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Destroyer", UnitKey = "M", LocalizationKeyOverride = "FirstPing")] public decimal DestroyerCutOffFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Destroyer", UnitKey = "M", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Destroyer", UnitKey = "M", LocalizationKeyOverride = "SecondPing")] public decimal DestroyerCutOffSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battleship", UnitKey = "M", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battleship", UnitKey = "M", LocalizationKeyOverride = "FirstPing")] public decimal BattleshipCutOffFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battleship", UnitKey = "M", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battleship", UnitKey = "M", LocalizationKeyOverride = "SecondPing")] public decimal BattleshipCutOffSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Cruiser", UnitKey = "M", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Cruiser", UnitKey = "M", LocalizationKeyOverride = "FirstPing")] public decimal CruiserCutOffFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Cruiser", UnitKey = "M", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Cruiser", UnitKey = "M", LocalizationKeyOverride = "SecondPing")] public decimal CruiserCutOffSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Submarine", UnitKey = "M", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Submarine", UnitKey = "M", LocalizationKeyOverride = "FirstPing")] public decimal SubCutOffFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Submarine", UnitKey = "M", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Submarine", UnitKey = "M", LocalizationKeyOverride = "SecondPing")] public decimal SubCutOffSecondPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AirCarrier", UnitKey = "M", NameLocalizationKey = "FirstPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AirCarrier", UnitKey = "M", LocalizationKeyOverride = "FirstPing")] public decimal CvCutOffFirstPing { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AirCarrier", UnitKey = "M", NameLocalizationKey = "SecondPing")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AirCarrier", UnitKey = "M", LocalizationKeyOverride = "SecondPing")] public decimal CvCutOffSecondPing { get; set; } public List? CanHitClasses { get; set; } diff --git a/WoWsShipBuilder.Common/DataContainers/Ship/AuraDataDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Ship/AuraDataDataContainer.cs index c8d002729..5cc0e0d54 100644 --- a/WoWsShipBuilder.Common/DataContainers/Ship/AuraDataDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Ship/AuraDataDataContainer.cs @@ -1,8 +1,9 @@ -using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; +using WoWsShipBuilder.DataElements; +using WoWsShipBuilder.DataElements.DataElementAttributes; namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record AuraDataDataContainer : DataContainerBase { [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] @@ -22,6 +23,6 @@ public partial record AuraDataDataContainer : DataContainerBase public void UpdateData() { - UpdateDataElements(); + this.UpdateDataElements(); } } diff --git a/WoWsShipBuilder.Common/DataContainers/Ship/ConcealmentDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Ship/ConcealmentDataContainer.cs index 75428aef2..67f40078d 100644 --- a/WoWsShipBuilder.Common/DataContainers/Ship/ConcealmentDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Ship/ConcealmentDataContainer.cs @@ -1,119 +1,119 @@ +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.Utility; -namespace WoWsShipBuilder.DataContainers +namespace WoWsShipBuilder.DataContainers; + +[DataContainer] +public partial record ConcealmentDataContainer : DataContainerBase { - public partial record ConcealmentDataContainer : DataContainerBase - { - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Sea", UnitKey = "KM")] - public decimal ConcealmentBySea { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Sea", UnitKey = "KM")] + public decimal ConcealmentBySea { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Sea", UnitKey = "KM")] - public decimal ConcealmentBySeaFire { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Sea", UnitKey = "KM")] + public decimal ConcealmentBySeaFire { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Sea", UnitKey = "KM")] - public decimal ConcealmentBySeaFiringSmoke { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Sea", UnitKey = "KM")] + public decimal ConcealmentBySeaFiringSmoke { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Air", UnitKey = "KM")] - public decimal ConcealmentByAir { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Air", UnitKey = "KM")] + public decimal ConcealmentByAir { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Air", UnitKey = "KM")] - public decimal ConcealmentByAirFire { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Air", UnitKey = "KM")] + public decimal ConcealmentByAirFire { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "FromSubs", UnitKey = "KM")] - public decimal ConcealmentBySubPeriscope { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "FromSubs", UnitKey = "KM")] + public decimal ConcealmentBySubPeriscope { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "FromSubs", UnitKey = "KM")] - [DataElementFiltering(false)] - public decimal ConcealmentBySubOperating { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "FromSubs", UnitKey = "KM")] + [DataElementFiltering(false)] + public decimal ConcealmentBySubOperating { get; set; } - public static ConcealmentDataContainer FromShip(Ship ship, List shipConfiguration, List<(string Key, float Value)> modifiers) - { - var hull = ship.Hulls[shipConfiguration.First(upgrade => upgrade.UcType == ComponentType.Hull).Components[ComponentType.Hull].First()]; + public static ConcealmentDataContainer FromShip(Ship ship, List shipConfiguration, List<(string Key, float Value)> modifiers) + { + var hull = ship.Hulls[shipConfiguration.First(upgrade => upgrade.UcType == ComponentType.Hull).Components[ComponentType.Hull].First()]; - // Sea Detection - decimal concealmentBySea = hull.SurfaceDetection; - decimal concealmentBySeaFiringSmoke = hull.SmokeFiringDetection; + // Sea Detection + decimal concealmentBySea = hull.SurfaceDetection; + decimal concealmentBySeaFiringSmoke = hull.SmokeFiringDetection; - // AA Detection - decimal concealmentByAir = hull.AirDetection; + // AA Detection + decimal concealmentByAir = hull.AirDetection; - decimal concealmentBySubPeriscope = hull.DetectionBySubPeriscope; - decimal concealmentBySubOperating = hull.DetectionBySubOperating; + decimal concealmentBySubPeriscope = hull.DetectionBySubPeriscope; + decimal concealmentBySubOperating = hull.DetectionBySubOperating; - int concealmentExpertIndex = modifiers.FindModifierIndex("visibilityDistCoeff"); - if (concealmentExpertIndex > -1) + int concealmentExpertIndex = modifiers.FindModifierIndex("visibilityDistCoeff"); + if (concealmentExpertIndex > -1) + { + List modifiersValues = modifiers.FindModifiers("visibilityDistCoeff").ToList(); + foreach (decimal value in modifiersValues.Select(f => (decimal)f)) { - List modifiersValues = modifiers.FindModifiers("visibilityDistCoeff").ToList(); - foreach (decimal value in modifiersValues.Select(f => (decimal)f)) - { - concealmentBySea *= value; - concealmentByAir *= value; - concealmentBySubPeriscope *= value; - concealmentBySubOperating *= value; - } + concealmentBySea *= value; + concealmentByAir *= value; + concealmentBySubPeriscope *= value; + concealmentBySubOperating *= value; } + } + + int visibilityFactorIndex = modifiers.FindModifierIndex("visibilityFactor", true); + if (visibilityFactorIndex > -1) + { + var visibilityFactorModifier = (decimal)modifiers[visibilityFactorIndex].Value; + concealmentBySea *= visibilityFactorModifier; + } - int visibilityFactorIndex = modifiers.FindModifierIndex("visibilityFactor", true); - if (visibilityFactorIndex > -1) + // Checks for Heavy He + var artilleryConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.Artillery); + if (artilleryConfiguration != null) + { + string[]? artilleryOptions = artilleryConfiguration.Components[ComponentType.Artillery]; + TurretModule? mainBattery; + if (artilleryOptions.Length == 1) { - var visibilityFactorModifier = (decimal)modifiers[visibilityFactorIndex].Value; - concealmentBySea *= visibilityFactorModifier; + mainBattery = ship.MainBatteryModuleList[artilleryConfiguration.Components[ComponentType.Artillery].First()]; } - - // Checks for Heavy He - var artilleryConfiguration = shipConfiguration.FirstOrDefault(c => c.UcType == ComponentType.Artillery); - if (artilleryConfiguration != null) + else { - string[]? artilleryOptions = artilleryConfiguration.Components[ComponentType.Artillery]; - TurretModule? mainBattery; - if (artilleryOptions.Length == 1) - { - mainBattery = ship.MainBatteryModuleList[artilleryConfiguration.Components[ComponentType.Artillery].First()]; - } - else - { - string? hullArtilleryName = shipConfiguration.First(c => c.UcType == ComponentType.Hull).Components[ComponentType.Artillery].First(); - mainBattery = ship.MainBatteryModuleList[hullArtilleryName]; - } + string? hullArtilleryName = shipConfiguration.First(c => c.UcType == ComponentType.Hull).Components[ComponentType.Artillery].First(); + mainBattery = ship.MainBatteryModuleList[hullArtilleryName]; + } - var gun = mainBattery.Guns.First(); + var gun = mainBattery.Guns.First(); - // GMBigGunVisibilityCoeff - if (gun.BarrelDiameter >= 0.149M) + // GMBigGunVisibilityCoeff + if (gun.BarrelDiameter >= 0.149M) + { + int bigGunVisibilityFactorIndex = modifiers.FindModifierIndex("GMBigGunVisibilityCoeff", true); + if (bigGunVisibilityFactorIndex > -1) { - int bigGunVisibilityFactorIndex = modifiers.FindModifierIndex("GMBigGunVisibilityCoeff", true); - if (bigGunVisibilityFactorIndex > -1) - { - var bigGunVisibilityFactorModifier = (decimal)modifiers[bigGunVisibilityFactorIndex].Value; - concealmentBySea *= bigGunVisibilityFactorModifier; - concealmentByAir *= bigGunVisibilityFactorModifier; - concealmentBySubOperating *= bigGunVisibilityFactorModifier; - concealmentBySubPeriscope *= bigGunVisibilityFactorModifier; - } + var bigGunVisibilityFactorModifier = (decimal)modifiers[bigGunVisibilityFactorIndex].Value; + concealmentBySea *= bigGunVisibilityFactorModifier; + concealmentByAir *= bigGunVisibilityFactorModifier; + concealmentBySubOperating *= bigGunVisibilityFactorModifier; + concealmentBySubPeriscope *= bigGunVisibilityFactorModifier; } } + } - decimal concealmentBySeaFire = concealmentBySea + 2.0m; - decimal concealmentByAirFire = concealmentByAir + 3.0m; + decimal concealmentBySeaFire = concealmentBySea + 2.0m; + decimal concealmentByAirFire = concealmentByAir + 3.0m; - var concealment = new ConcealmentDataContainer - { - ConcealmentBySea = Math.Round(concealmentBySea, 2), - ConcealmentBySeaFiringSmoke = Math.Round(concealmentBySeaFiringSmoke, 2), - ConcealmentBySeaFire = Math.Round(concealmentBySeaFire, 2), - ConcealmentByAir = Math.Round(concealmentByAir, 2), - ConcealmentByAirFire = Math.Round(concealmentByAirFire, 2), - ConcealmentBySubOperating = Math.Round(concealmentBySubOperating, 2), - ConcealmentBySubPeriscope = Math.Round(concealmentBySubPeriscope, 2), - }; - - concealment.UpdateDataElements(); - - return concealment; - } + var concealment = new ConcealmentDataContainer + { + ConcealmentBySea = Math.Round(concealmentBySea, 2), + ConcealmentBySeaFiringSmoke = Math.Round(concealmentBySeaFiringSmoke, 2), + ConcealmentBySeaFire = Math.Round(concealmentBySeaFire, 2), + ConcealmentByAir = Math.Round(concealmentByAir, 2), + ConcealmentByAirFire = Math.Round(concealmentByAirFire, 2), + ConcealmentBySubOperating = Math.Round(concealmentBySubOperating, 2), + ConcealmentBySubPeriscope = Math.Round(concealmentBySubPeriscope, 2), + }; + + concealment.UpdateDataElements(); + + return concealment; } } diff --git a/WoWsShipBuilder.Common/DataContainers/Ship/ConsumableDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Ship/ConsumableDataContainer.cs index 8d0406405..60f0a156b 100644 --- a/WoWsShipBuilder.Common/DataContainers/Ship/ConsumableDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Ship/ConsumableDataContainer.cs @@ -1,7 +1,7 @@ using System.Globalization; using Microsoft.Extensions.Logging; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Aircraft; using WoWsShipBuilder.DataStructures.Ship; @@ -11,6 +11,7 @@ namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record ConsumableDataContainer : DataContainerBase { public string Name { get; set; } = default!; diff --git a/WoWsShipBuilder.Common/DataContainers/Ship/ManeuverabilityDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Ship/ManeuverabilityDataContainer.cs index 45420638e..42ce84f70 100644 --- a/WoWsShipBuilder.Common/DataContainers/Ship/ManeuverabilityDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Ship/ManeuverabilityDataContainer.cs @@ -1,17 +1,18 @@ +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.Utility; namespace WoWsShipBuilder.DataContainers; +[DataContainer] public partial record ManeuverabilityDataContainer : DataContainerBase { [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots")] public decimal ManeuverabilityMaxSpeed { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots", NameLocalizationKey = "MaxReverseSpeed")] + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots", LocalizationKeyOverride = "MaxReverseSpeed")] public decimal ManeuverabilityMaxReverseSpeed { get; set; } [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeed", UnitKey = "Knots")] @@ -23,13 +24,13 @@ public partial record ManeuverabilityDataContainer : DataContainerBase [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeed", UnitKey = "Knots")] public decimal ManeuverabilitySubsMaxSpeedAtMaxDepth { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", NameLocalizationKey = "ManeuverabilitySubsMaxSpeedOnSurface")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", LocalizationKeyOverride = "ManeuverabilitySubsMaxSpeedOnSurface")] public decimal ManeuverabilitySubsMaxReverseSpeedOnSurface { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", NameLocalizationKey = "ManeuverabilitySubsMaxSpeedAtPeriscope")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", LocalizationKeyOverride = "ManeuverabilitySubsMaxSpeedAtPeriscope")] public decimal ManeuverabilitySubsMaxReverseSpeedAtPeriscope { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", NameLocalizationKey = "ManeuverabilitySubsMaxSpeedAtMaxDepth")] + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", LocalizationKeyOverride = "ManeuverabilitySubsMaxSpeedAtMaxDepth")] public decimal ManeuverabilitySubsMaxReverseSpeedAtMaxDepth { get; set; } [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MPS")] diff --git a/WoWsShipBuilder.Common/DataContainers/Ship/SpecialAbilityDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Ship/SpecialAbilityDataContainer.cs index ee9b2f062..2ae848071 100644 --- a/WoWsShipBuilder.Common/DataContainers/Ship/SpecialAbilityDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Ship/SpecialAbilityDataContainer.cs @@ -1,119 +1,119 @@ using System.Text.Json.Serialization; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; // ReSharper disable InconsistentNaming -namespace WoWsShipBuilder.DataContainers +namespace WoWsShipBuilder.DataContainers; + +[DataContainer] +public partial record SpecialAbilityDataContainer : DataContainerBase { - public partial record SpecialAbilityDataContainer : DataContainerBase - { - public string Name { get; set; } = default!; + public string Name { get; set; } = default!; - public string Description { get; set; } = default!; + public string Description { get; set; } = default!; - // These are for the special ability of ships like satsuma etc. - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal Duration { get; set; } + // These are for the special ability of ships like satsuma etc. + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal Duration { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] - public decimal TargetAreaRadius { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "KM")] + public decimal TargetAreaRadius { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] - public decimal ProgressPerAction { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] + public decimal ProgressPerAction { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal InactivityDelay { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal InactivityDelay { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal ProgressLossInterval { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal ProgressLossInterval { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] - public decimal ProgressLossPerInterval { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "PerCent")] + public decimal ProgressLossPerInterval { get; set; } - // These are for Burst mode - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal ReloadDuringBurst { get; set; } + // These are for Burst mode + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal ReloadDuringBurst { get; set; } - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal ReloadAfterBurst { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] + public decimal ReloadAfterBurst { get; set; } - [DataElementType(DataElementTypes.KeyValue)] - public int ShotInBurst { get; set; } + [DataElementType(DataElementTypes.KeyValue)] + public int ShotInBurst { get; set; } - // This is in common - [JsonIgnore] - public Dictionary Modifiers { get; set; } = null!; + // This is in common + [JsonIgnore] + public Dictionary Modifiers { get; set; } = null!; - public bool IsBurstMode { get; set; } + public bool IsBurstMode { get; set; } - public static SpecialAbilityDataContainer? FromShip(Ship ship, List shipConfiguration, List<(string name, float value)> modifiers) + public static SpecialAbilityDataContainer? FromShip(Ship ship, List shipConfiguration, List<(string name, float value)> modifiers) + { + SpecialAbilityDataContainer specialDataContainer; + + if (ship.SpecialAbility is not null) { - SpecialAbilityDataContainer specialDataContainer; + var specialAbility = ship.SpecialAbility; - if (ship.SpecialAbility is not null) + specialDataContainer = new() { - var specialAbility = ship.SpecialAbility; - - specialDataContainer = new() - { - Name = $"DOCK_RAGE_MODE_TITLE_{specialAbility.Name}", - Description = specialAbility.ActivatorName.Equals("RibbonActivator") ? $"RAGE_MODE_TRIGGER_DESCRIPTION_{specialAbility.ActivatorName}" : $"RAGE_MODE_DESCRIPTION_{specialAbility.ActivatorName}", - Duration = Math.Round((decimal)specialAbility.Duration, 1), - TargetAreaRadius = Math.Round((decimal)(specialAbility.ActivatorRadius / 1000), 1), - ProgressPerAction = (decimal)specialAbility.ProgressPerAction, - InactivityDelay = (decimal)specialAbility.DecrementDelay, - ProgressLossInterval = (decimal)specialAbility.DecrementPeriod, - ProgressLossPerInterval = (decimal)specialAbility.DecrementCount, - Modifiers = specialAbility.Modifiers, - }; - - specialDataContainer.UpdateDataElements(); + Name = $"DOCK_RAGE_MODE_TITLE_{specialAbility.Name}", + Description = specialAbility.ActivatorName.Equals("RibbonActivator") ? $"RAGE_MODE_TRIGGER_DESCRIPTION_{specialAbility.ActivatorName}" : $"RAGE_MODE_DESCRIPTION_{specialAbility.ActivatorName}", + Duration = Math.Round((decimal)specialAbility.Duration, 1), + TargetAreaRadius = Math.Round((decimal)(specialAbility.ActivatorRadius / 1000), 1), + ProgressPerAction = (decimal)specialAbility.ProgressPerAction, + InactivityDelay = (decimal)specialAbility.DecrementDelay, + ProgressLossInterval = (decimal)specialAbility.DecrementPeriod, + ProgressLossPerInterval = (decimal)specialAbility.DecrementCount, + Modifiers = specialAbility.Modifiers, + }; + + specialDataContainer.UpdateDataElements(); + } + else + { + var artilleryConfiguration = shipConfiguration.Find(c => c.UcType == ComponentType.Artillery); + if (artilleryConfiguration == null) + { + return null; + } + + string[] artilleryOptions = artilleryConfiguration.Components[ComponentType.Artillery]; + string[] supportedModules = artilleryConfiguration.Components[ComponentType.Artillery]; + + TurretModule? mainBattery; + if (artilleryOptions.Length == 1) + { + mainBattery = ship.MainBatteryModuleList[supportedModules[0]]; } else { - var artilleryConfiguration = shipConfiguration.Find(c => c.UcType == ComponentType.Artillery); - if (artilleryConfiguration == null) - { - return null; - } - - string[] artilleryOptions = artilleryConfiguration.Components[ComponentType.Artillery]; - string[] supportedModules = artilleryConfiguration.Components[ComponentType.Artillery]; - - TurretModule? mainBattery; - if (artilleryOptions.Length == 1) - { - mainBattery = ship.MainBatteryModuleList[supportedModules[0]]; - } - else - { - string hullArtilleryName = shipConfiguration.First(c => c.UcType == ComponentType.Hull).Components[ComponentType.Artillery].First(artilleryName => supportedModules.Contains(artilleryName)); - mainBattery = ship.MainBatteryModuleList[hullArtilleryName]; - } - - var burstMode = mainBattery.BurstModeAbility; - - if (burstMode is null) - { - return null; - } - - specialDataContainer = new() - { - Name = "ShipStats_BurstMode", - ReloadDuringBurst = burstMode.ReloadDuringBurst, - ReloadAfterBurst = burstMode.ReloadAfterBurst, - ShotInBurst = burstMode.ShotInBurst, - Modifiers = burstMode.Modifiers, - IsBurstMode = true, - }; - - specialDataContainer.UpdateDataElements(); + string hullArtilleryName = shipConfiguration.First(c => c.UcType == ComponentType.Hull).Components[ComponentType.Artillery].First(artilleryName => supportedModules.Contains(artilleryName)); + mainBattery = ship.MainBatteryModuleList[hullArtilleryName]; + } + + var burstMode = mainBattery.BurstModeAbility; + + if (burstMode is null) + { + return null; } - return specialDataContainer; + specialDataContainer = new() + { + Name = "ShipStats_BurstMode", + ReloadDuringBurst = burstMode.ReloadDuringBurst, + ReloadAfterBurst = burstMode.ReloadAfterBurst, + ShotInBurst = burstMode.ShotInBurst, + Modifiers = burstMode.Modifiers, + IsBurstMode = true, + }; + + specialDataContainer.UpdateDataElements(); } + + return specialDataContainer; } } diff --git a/WoWsShipBuilder.Common/DataContainers/Ship/SurvivabilityDataContainer.cs b/WoWsShipBuilder.Common/DataContainers/Ship/SurvivabilityDataContainer.cs index 009a2534c..9127b08fc 100644 --- a/WoWsShipBuilder.Common/DataContainers/Ship/SurvivabilityDataContainer.cs +++ b/WoWsShipBuilder.Common/DataContainers/Ship/SurvivabilityDataContainer.cs @@ -1,198 +1,198 @@ // ReSharper disable InconsistentNaming +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.Infrastructure.Utility; -namespace WoWsShipBuilder.DataContainers +namespace WoWsShipBuilder.DataContainers; + +[DataContainer] +public partial record SurvivabilityDataContainer : DataContainerBase { - public partial record SurvivabilityDataContainer : DataContainerBase - { - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "HP", NameLocalizationKey = "ShipHp")] - public int HitPoints { get; set; } + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "HP", LocalizationKeyOverride = "ShipHp")] + public int HitPoints { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Citadel", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int CitadelHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Citadel", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int CitadelHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Citadel", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal CitadelRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Citadel", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal CitadelRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Casemate", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int CasemateHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Casemate", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int CasemateHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Casemate", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal CasemateRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Casemate", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal CasemateRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Bow", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int BowHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Bow", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int BowHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Bow", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal BowRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Bow", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal BowRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Stern", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int SternHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Stern", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int SternHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Stern", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal SternRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Stern", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal SternRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Superstructure", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int SuperstructureHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Superstructure", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int SuperstructureHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Superstructure", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal SuperstructureRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Superstructure", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal SuperstructureRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AuxiliaryRooms", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int AuxiliaryRoomsHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AuxiliaryRooms", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int AuxiliaryRoomsHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AuxiliaryRooms", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal AuxiliaryRoomsRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "AuxiliaryRooms", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal AuxiliaryRoomsRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Hull", UnitKey = "HP", NameLocalizationKey = "HitPoints")] - public int HullHp { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Hull", UnitKey = "HP", LocalizationKeyOverride = "HitPoints")] + public int HullHp { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Hull", UnitKey = "PerCent", NameLocalizationKey = "RegenRatio")] - public decimal HullRegenRatio { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Hull", UnitKey = "PerCent", LocalizationKeyOverride = "RegenRatio")] + public decimal HullRegenRatio { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battery", UnitKey = "U")] - public decimal DiveCapacity { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battery", UnitKey = "U")] + public decimal DiveCapacity { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battery", UnitKey = "UPS")] - public decimal DiveCapacityRechargeRate { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Battery", UnitKey = "UPS")] + public decimal DiveCapacityRechargeRate { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Fire")] - public decimal FireAmount { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Fire")] + public decimal FireAmount { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "S")] - public decimal FireDuration { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "S")] + public decimal FireDuration { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "DPS")] - public decimal FireDPS { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "DPS")] + public decimal FireDPS { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "HP")] - public decimal FireTotalDamage { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "HP")] + public decimal FireTotalDamage { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "PerCent")] - public decimal FireReduction { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Fire", UnitKey = "PerCent")] + public decimal FireReduction { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Flooding")] - public decimal FloodAmount { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Flooding")] + public decimal FloodAmount { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "S")] - public decimal FloodDuration { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "S")] + public decimal FloodDuration { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "DPS")] - public decimal FloodDPS { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "DPS")] + public decimal FloodDPS { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "HP")] - public decimal FloodTotalDamage { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "HP")] + public decimal FloodTotalDamage { get; set; } - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "PerCent")] - public decimal FloodTorpedoProtection { get; set; } + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "Flooding", UnitKey = "PerCent")] + public decimal FloodTorpedoProtection { get; set; } - public static SurvivabilityDataContainer FromShip(Ship ship, List shipConfiguration, List<(string Key, float Value)> modifiers) - { - Hull shipHull = ship.Hulls[shipConfiguration.First(upgrade => upgrade.UcType == ComponentType.Hull).Components[ComponentType.Hull].First()]; + public static SurvivabilityDataContainer FromShip(Ship ship, List shipConfiguration, List<(string Key, float Value)> modifiers) + { + Hull shipHull = ship.Hulls[shipConfiguration.First(upgrade => upgrade.UcType == ComponentType.Hull).Components[ComponentType.Hull].First()]; - // Survivability expert - decimal hitPoints = shipHull.Health; - int survivabilityExpertIndex = modifiers.FindModifierIndex("healthPerLevel"); - if (survivabilityExpertIndex > -1) - { - hitPoints += (decimal)modifiers[survivabilityExpertIndex].Value * ship.Tier; - } + // Survivability expert + decimal hitPoints = shipHull.Health; + int survivabilityExpertIndex = modifiers.FindModifierIndex("healthPerLevel"); + if (survivabilityExpertIndex > -1) + { + hitPoints += (decimal)modifiers[survivabilityExpertIndex].Value * ship.Tier; + } - int fireSpots = shipHull.FireSpots; - if (modifiers.FindModifierIndex("fireResistanceEnabled") > -1) - { - fireSpots--; - } + int fireSpots = shipHull.FireSpots; + if (modifiers.FindModifierIndex("fireResistanceEnabled") > -1) + { + fireSpots--; + } - decimal fireDuration = shipHull.FireDuration; - decimal floodDuration = shipHull.FloodingDuration; - foreach (float modifier in modifiers.FindModifiers("hlCritTimeCoeff")) - { - fireDuration *= (decimal)modifier; - floodDuration *= (decimal)modifier; - } + decimal fireDuration = shipHull.FireDuration; + decimal floodDuration = shipHull.FloodingDuration; + foreach (float modifier in modifiers.FindModifiers("hlCritTimeCoeff")) + { + fireDuration *= (decimal)modifier; + floodDuration *= (decimal)modifier; + } - fireDuration = modifiers.FindModifiers("burnTime").Aggregate(fireDuration, (current, modifier) => current * (decimal)modifier); + fireDuration = modifiers.FindModifiers("burnTime").Aggregate(fireDuration, (current, modifier) => current * (decimal)modifier); - floodDuration = modifiers.FindModifiers("floodTime").Aggregate(floodDuration, (current, modifier) => current * (decimal)modifier); + floodDuration = modifiers.FindModifiers("floodTime").Aggregate(floodDuration, (current, modifier) => current * (decimal)modifier); - // fire chance reduction = base fire resistance +(100 - base fire resistance) *(1 - burnProb) - decimal baseFireResistance = 1 - shipHull.FireResistance; - decimal fireResistanceModifiers = modifiers.FindModifiers("burnProb").Aggregate(1M, (current, modifier) => current * (decimal)modifier); - decimal fireResistance = baseFireResistance + ((1 - baseFireResistance) * (1 - fireResistanceModifiers)); + // fire chance reduction = base fire resistance +(100 - base fire resistance) *(1 - burnProb) + decimal baseFireResistance = 1 - shipHull.FireResistance; + decimal fireResistanceModifiers = modifiers.FindModifiers("burnProb").Aggregate(1M, (current, modifier) => current * (decimal)modifier); + decimal fireResistance = baseFireResistance + ((1 - baseFireResistance) * (1 - fireResistanceModifiers)); - decimal modifiedFloodingCoeff = modifiers.FindModifiers("uwCoeffBonus").Aggregate(shipHull.FloodingResistance * 3, (current, modifier) => current - ((decimal)modifier / 100)) * 100; - decimal fireDps = hitPoints * shipHull.FireTickDamage / 100; - decimal fireTotalDamage = fireDuration * fireDps; + decimal modifiedFloodingCoeff = modifiers.FindModifiers("uwCoeffBonus").Aggregate(shipHull.FloodingResistance * 3, (current, modifier) => current - ((decimal)modifier / 100)) * 100; + decimal fireDps = hitPoints * shipHull.FireTickDamage / 100; + decimal fireTotalDamage = fireDuration * fireDps; - decimal floodDps = hitPoints * shipHull.FloodingTickDamage / 100; - decimal floodTotalDamage = floodDuration * floodDps; + decimal floodDps = hitPoints * shipHull.FloodingTickDamage / 100; + decimal floodTotalDamage = floodDuration * floodDps; - decimal diveCapacityRechargeRateModifier = modifiers.FindModifiers("batteryRegenCoeff").Aggregate(1M, (current, modifier) => current * (decimal)modifier); - decimal diveCapacityModifier = modifiers.FindModifiers("batteryCapacityCoeff").Aggregate(1M, (current, modifier) => current * (decimal)modifier); + decimal diveCapacityRechargeRateModifier = modifiers.FindModifiers("batteryRegenCoeff").Aggregate(1M, (current, modifier) => current * (decimal)modifier); + decimal diveCapacityModifier = modifiers.FindModifiers("batteryCapacityCoeff").Aggregate(1M, (current, modifier) => current * (decimal)modifier); - float repairableDamageModifier = modifiers.FindModifiers("regeneratedHPPartCoef", true).Aggregate(0f, (current, modifier) => current + modifier); + float repairableDamageModifier = modifiers.FindModifiers("regeneratedHPPartCoef", true).Aggregate(0f, (current, modifier) => current + modifier); - var survivability = new SurvivabilityDataContainer - { - HitPoints = (int)hitPoints, - FireDuration = Math.Round(fireDuration, 1), - FireAmount = fireSpots, - FireReduction = Math.Round(fireResistance * 100, 1), - FireDPS = Math.Round(fireDps), - FireTotalDamage = Math.Round(fireTotalDamage), - FloodDuration = Math.Round(floodDuration, 1), - FloodAmount = shipHull.FloodingSpots, - FloodTorpedoProtection = Math.Round(100 - modifiedFloodingCoeff, 1), - FloodDPS = Math.Round(floodDps), - FloodTotalDamage = Math.Round(floodTotalDamage), - DiveCapacity = Math.Round(shipHull.SubBatteryCapacity * diveCapacityModifier, 1), - DiveCapacityRechargeRate = Math.Round(shipHull.SubBatteryRegenRate * diveCapacityRechargeRateModifier, 1), - }; - - foreach (var location in shipHull.HitLocations) + var survivability = new SurvivabilityDataContainer + { + HitPoints = (int)hitPoints, + FireDuration = Math.Round(fireDuration, 1), + FireAmount = fireSpots, + FireReduction = Math.Round(fireResistance * 100, 1), + FireDPS = Math.Round(fireDps), + FireTotalDamage = Math.Round(fireTotalDamage), + FloodDuration = Math.Round(floodDuration, 1), + FloodAmount = shipHull.FloodingSpots, + FloodTorpedoProtection = Math.Round(100 - modifiedFloodingCoeff, 1), + FloodDPS = Math.Round(floodDps), + FloodTotalDamage = Math.Round(floodTotalDamage), + DiveCapacity = Math.Round(shipHull.SubBatteryCapacity * diveCapacityModifier, 1), + DiveCapacityRechargeRate = Math.Round(shipHull.SubBatteryRegenRate * diveCapacityRechargeRateModifier, 1), + }; + + foreach (var location in shipHull.HitLocations) + { + switch (location.Name) { - switch (location.Name) - { - case ShipHitLocation.Citadel: - survivability.CitadelHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.CitadelRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - case ShipHitLocation.Bow: - survivability.BowHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.BowRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - case ShipHitLocation.Stern: - survivability.SternHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.SternRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - case ShipHitLocation.Superstructure: - survivability.SuperstructureHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.SuperstructureRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - case ShipHitLocation.AuxiliaryRooms: - survivability.AuxiliaryRoomsHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.AuxiliaryRoomsRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - case ShipHitLocation.Casemate: - survivability.CasemateHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.CasemateRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - default: - survivability.HullHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; - survivability.HullRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); - break; - } + case ShipHitLocation.Citadel: + survivability.CitadelHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.CitadelRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; + case ShipHitLocation.Bow: + survivability.BowHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.BowRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; + case ShipHitLocation.Stern: + survivability.SternHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.SternRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; + case ShipHitLocation.Superstructure: + survivability.SuperstructureHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.SuperstructureRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; + case ShipHitLocation.AuxiliaryRooms: + survivability.AuxiliaryRoomsHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.AuxiliaryRoomsRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; + case ShipHitLocation.Casemate: + survivability.CasemateHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.CasemateRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; + default: + survivability.HullHp = (int)Math.Ceiling(location.Hp * (survivability.HitPoints / shipHull.Health) / 50) * 50; + survivability.HullRegenRatio = Math.Round((decimal)(location.RepairableDamage + repairableDamageModifier) * 100); + break; } + } - survivability.UpdateDataElements(); + survivability.UpdateDataElements(); - return survivability; - } + return survivability; } } diff --git a/WoWsShipBuilder.Common/Features/ShipStats/Components/ConsumableSelector.razor b/WoWsShipBuilder.Common/Features/ShipStats/Components/ConsumableSelector.razor index 2557340c3..fe33ce149 100644 --- a/WoWsShipBuilder.Common/Features/ShipStats/Components/ConsumableSelector.razor +++ b/WoWsShipBuilder.Common/Features/ShipStats/Components/ConsumableSelector.razor @@ -1,6 +1,6 @@ @using WoWsShipBuilder.DataStructures -@using WoWsShipBuilder.DataElements.DataElements @using WoWsShipBuilder.DataContainers +@using WoWsShipBuilder.DataElements @using WoWsShipBuilder.Features.ShipStats.ViewModels @using WoWsShipBuilder.Infrastructure.Localization.Resources @using WoWsShipBuilder.Infrastructure.Metrics diff --git a/WoWsShipBuilder.Common/Features/ShipStats/Components/SharedFragments.razor b/WoWsShipBuilder.Common/Features/ShipStats/Components/SharedFragments.razor index 3a5e69e9b..00d8beeac 100644 --- a/WoWsShipBuilder.Common/Features/ShipStats/Components/SharedFragments.razor +++ b/WoWsShipBuilder.Common/Features/ShipStats/Components/SharedFragments.razor @@ -1,5 +1,4 @@ -@using WoWsShipBuilder.DataElements.DataElements - +@using WoWsShipBuilder.DataElements @code { // Parameter needs to be named __builder, otherwise compilation will fail. @@ -22,7 +21,7 @@ case KeyValueDataElement element:
@group.localizer.GetAppLocalization(element.Key).Localization - @ConvertValue(element.Value, element.IsValueKey, element.IsValueAppLocalization, group.localizer) + @ConvertValue(element.Value, element.ValueTextKind, group.localizer)
break; case KeyValueUnitDataElement element: @@ -52,7 +51,7 @@ break; case ValueDataElement element: - @ConvertValue(element.Value, element.IsValueKey, element.IsValueAppLocalization, group.localizer) + @ConvertValue(element.Value, element.ValueTextKind, group.localizer) break; case FormattedTextDataElement element: @@ -74,9 +73,14 @@ } } - private static string ConvertValue(string value, bool isValueKey, bool isAppLocalizationKey, ILocalizer localizer) + private static string ConvertValue(string value, DataElementTextKind valueTextKind, ILocalizer localizer) { - return !isValueKey ? value : (isAppLocalizationKey ? localizer.GetAppLocalization(value) : localizer.GetGameLocalization(value)).Localization; + return valueTextKind switch { + DataElementTextKind.Plain => value, + DataElementTextKind.LocalizationKey => localizer.SimpleGameLocalization(value), + DataElementTextKind.AppLocalizationKey => localizer.SimpleAppLocalization(value), + _ => throw new ArgumentOutOfRangeException(nameof(valueTextKind), valueTextKind, "Unknown DataElementTextKind"), + }; } -} \ No newline at end of file +} diff --git a/WoWsShipBuilder.Common/Features/ShipStats/Components/ShipStatsPanel.razor b/WoWsShipBuilder.Common/Features/ShipStats/Components/ShipStatsPanel.razor index 7ea600172..9eea18651 100644 --- a/WoWsShipBuilder.Common/Features/ShipStats/Components/ShipStatsPanel.razor +++ b/WoWsShipBuilder.Common/Features/ShipStats/Components/ShipStatsPanel.razor @@ -1,5 +1,4 @@ -@using WoWsShipBuilder.DataElements.DataElements -@using WoWsShipBuilder.DataStructures +@using WoWsShipBuilder.DataStructures @using static WoWsShipBuilder.Features.ShipStats.Components.SharedFragments @using Microsoft.Extensions.Logging.Console @using System.Text diff --git a/WoWsShipBuilder.Common/Features/ShipStats/FormattedTextHelper.cs b/WoWsShipBuilder.Common/Features/ShipStats/FormattedTextHelper.cs index 6ae93b43b..2a3960796 100644 --- a/WoWsShipBuilder.Common/Features/ShipStats/FormattedTextHelper.cs +++ b/WoWsShipBuilder.Common/Features/ShipStats/FormattedTextHelper.cs @@ -1,5 +1,5 @@ using System.Globalization; -using WoWsShipBuilder.DataElements.DataElements; +using WoWsShipBuilder.DataElements; using WoWsShipBuilder.Infrastructure.Localization; namespace WoWsShipBuilder.Features.ShipStats; @@ -8,17 +8,21 @@ public static class FormattedTextHelper { public static string ConvertFormattedText(FormattedTextDataElement formattedTextDataElement, ILocalizer localizer) { - string text = formattedTextDataElement.Text; - if (formattedTextDataElement.IsTextKey) + string text = formattedTextDataElement.ValueTextKind switch { - text = formattedTextDataElement.IsTextAppLocalization ? localizer.GetAppLocalization(text).Localization : localizer.GetGameLocalization(text).Localization; - } + DataElementTextKind.Plain => formattedTextDataElement.Text, + DataElementTextKind.LocalizationKey => localizer.SimpleGameLocalization(formattedTextDataElement.Text), + DataElementTextKind.AppLocalizationKey => localizer.SimpleAppLocalization(formattedTextDataElement.Text), + _ => throw new NotSupportedException("Invalid value for ValueTextKind"), + }; - IEnumerable values = formattedTextDataElement.Values; - if (formattedTextDataElement.AreValuesKeys) + IEnumerable values = formattedTextDataElement.ArgumentsTextKind switch { - values = formattedTextDataElement.AreValuesAppLocalization ? values.Select(x => localizer.GetAppLocalization(x).Localization) : values.Select(x => localizer.GetGameLocalization(x).Localization); - } + DataElementTextKind.Plain => formattedTextDataElement.Arguments, + DataElementTextKind.LocalizationKey => formattedTextDataElement.Arguments.Select(localizer.SimpleGameLocalization), + DataElementTextKind.AppLocalizationKey => formattedTextDataElement.Arguments.Select(localizer.SimpleAppLocalization), + _ => throw new NotSupportedException("Invalid value for ArgumentsTextKind"), + }; return string.Format(CultureInfo.InvariantCulture, text, values.Cast().ToArray()); } diff --git a/WoWsShipBuilder.Data.Generator.Test/AssertionMethodAttribute.cs b/WoWsShipBuilder.Data.Generator.Test/AssertionMethodAttribute.cs new file mode 100644 index 000000000..c3e23e5dc --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/AssertionMethodAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace WoWsShipBuilder.Data.Generator.Test; + +[AttributeUsage(AttributeTargets.Method)] +public class AssertionMethodAttribute : Attribute +{ +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/DataElementAnalyzerTest.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/DataElementAnalyzerTest.cs new file mode 100644 index 000000000..6ca8d4bd4 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/DataElementAnalyzerTest.cs @@ -0,0 +1,79 @@ +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.CSharp.Testing.NUnit; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using WoWsShipBuilder.Data.Generator.DataElementGenerator; +using WoWsShipBuilder.DataElements; + +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +using Verify = AnalyzerVerifier; + +[TestFixture] +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task Analyze_InvalidDataElementType_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType((DataElementTypes)128)] + public decimal {|SB0001:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [AssertionMethod] + private static CSharpAnalyzerTest CreateTest(string source) + { + const string baseClass = """ + namespace WoWsShipBuilder.DataElements; + + public abstract record DataContainerBase + { + public global::System.Collections.Generic.List DataElements { get; } = new(); + + protected static bool ShouldAdd(object? value) + { + return value switch + { + string strValue => !string.IsNullOrEmpty(strValue), + decimal decValue => decValue != 0, + (decimal min, decimal max) => min > 0 || max > 0, + int intValue => intValue != 0, + _ => false, + }; + } + } + """; + return new() + { + TestCode = source, + TestState = + { + Sources = + { + AttributeHelper.DataElementTypesEnum, + AttributeHelper.DataElementTextKindEnum, + AttributeHelper.DataContainerAttribute, + AttributeHelper.DataElementTypeAttribute, + AttributeHelper.DataElementFilteringAttribute, + baseClass, + }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net70, + AdditionalReferences = { MetadataReference.CreateFromFile(typeof(IDataElement).GetTypeInfo().Assembly.Location) }, + }, + }; + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/FormattedText.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/FormattedText.cs new file mode 100644 index 000000000..72162ebf1 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/FormattedText.cs @@ -0,0 +1,144 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task AnalyzeFormattedText_AllRequiredParams_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName="Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeFormattedText_ArgumentCollectionNameMissing_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText)] + public decimal {|SB1002:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeFormattedText_TreatArgumentsAsLocalizationSpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "Test", ArgumentsTextKind = TextKind.LocalizationKey)] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeFormattedText_TreatArgumentsAsAppLocalizationSpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "Test", ArgumentsTextKind = TextKind.AppLocalizationKey)] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeFormattedText_LocalizationKeyOverrideSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "Test", LocalizationKeyOverride = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeFormattedText_UnitKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "Test", UnitKey = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeFormattedText_TooltipKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = "Test", TooltipKey = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Grouped.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Grouped.cs new file mode 100644 index 000000000..bdb277cae --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Grouped.cs @@ -0,0 +1,84 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task AnalyzeGrouped_SecondaryTypeSpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord: DataContainerBase + { + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Loaders")] + public string Prop1 { get; set; } = default!; + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeGrouped_SecondaryTypeMissing_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord: DataContainerBase + { + [DataElementType(DataElementTypes.Grouped, GroupKey = "Loaders")] + public string {|SB0002:Prop1|} { get; set; } = default!; + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeGrouped_GroupKeyMissing_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord: DataContainerBase + { + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue)] + public string {|SB1001:Prop1|} { get; set; } = default!; + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeGrouped_NoGroupGroupKeyExists_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord: DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, GroupKey="Test")] + public string {|SB1003:Prop1|} { get; set; } = default!; + } + """; + + await CreateTest(source).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/KeyValue.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/KeyValue.cs new file mode 100644 index 000000000..972720cf7 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/KeyValue.cs @@ -0,0 +1,144 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task AnalyzeKeyValue_AllRequiredParams_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue)] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValue_LocalizationKeyOverrideSpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, LocalizationKeyOverride = "Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValue_CollectionNameSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, ArgumentsCollectionName = "Values")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValue_TreatArgumentsAsLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, ArgumentsTextKind = TextKind.LocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValue_TreatArgumentsAsAppLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, ArgumentsTextKind = TextKind.AppLocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValue_UnitKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, UnitKey="Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValue_TooltipKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, TooltipKey="Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/KeyValueUnit.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/KeyValueUnit.cs new file mode 100644 index 000000000..76fc965fc --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/KeyValueUnit.cs @@ -0,0 +1,184 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task AnalyzeKeyValueUnit_AllRequiredParams_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_LocalizationKeyOverrideSpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", LocalizationKeyOverride = "Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_UnitKeyMissing_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit)] + public decimal {|SB1002:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_CollectionNameSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", ArgumentsCollectionName = "Values")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_TreatArgumentsAsLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", ArgumentsTextKind = TextKind.LocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_TreatArgumentsAsAppLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", ArgumentsTextKind = TextKind.AppLocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_TooltipKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", TooltipKey = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_TreatValueAsLocalizationKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", ValueTextKind = TextKind.LocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeKeyValueUnit_TreatValueAsAppLocalizationKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Test", ValueTextKind = TextKind.AppLocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Tooltip.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Tooltip.cs new file mode 100644 index 000000000..de80eff14 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Tooltip.cs @@ -0,0 +1,184 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task AnalyzeTooltip_AllRequiredParams_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_TooltipKeyMissing_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip)] + public decimal {|SB1002:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_UnitKeySpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", UnitKey = "Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_LocalizationKeySpecified_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", LocalizationKeyOverride = "Test")] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_CollectionNameSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", ArgumentsCollectionName = "Values")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_TreatArgumentsAsLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", ArgumentsTextKind = TextKind.LocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_TreatArgumentsAsAppLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", ArgumentsTextKind = TextKind.AppLocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_TreatValueAsLocalizationKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", ValueTextKind = TextKind.LocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeTooltip_TreatValueAsAppLocalizationKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "Test", ValueTextKind = TextKind.AppLocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Value.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Value.cs new file mode 100644 index 000000000..f6c5d1a13 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementAnalyzerTests/Value.cs @@ -0,0 +1,144 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementAnalyzerTests; + +public partial class DataElementAnalyzerTest +{ + [Test] + public async Task AnalyzeValue_AllRequiredParams_NoDiagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value)] + public decimal Prop1 { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeValue_CollectionNameSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, ArgumentsCollectionName = "Values")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeValue_TreatArgumentsAsLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, ArgumentsTextKind = TextKind.LocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeValue_TreatArgumentsAsAppLocalizationSpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, ArgumentsTextKind = TextKind.AppLocalizationKey)] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeValue_UnitKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, UnitKey = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeValue_TooltipKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, TooltipKey = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } + + [Test] + public async Task AnalyzeValue_LocalizationKeySpecified_Diagnostics() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, LocalizationKeyOverride = "Test")] + public decimal {|SB1003:Prop1|} { get; set; } + } + """; + + await CreateTest(source).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/DataElementGeneratorTest.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/DataElementGeneratorTest.cs new file mode 100644 index 000000000..45f6ae3c7 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/DataElementGeneratorTest.cs @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using WoWsShipBuilder.Data.Generator.DataElementGenerator; +using WoWsShipBuilder.DataElements; + +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +[TestFixture] +[SuppressMessage("Maintainability", "S2699", Justification = "false-positive since sonarlint does not recognize custom CreateTest method")] +public partial class DataElementGeneratorTest +{ + [AssertionMethod] + private static CSharpSourceGeneratorTest CreateTest(string source, string expected) + { + const string baseClass = """ + namespace WoWsShipBuilder.DataElements; + + public abstract record DataContainerBase + { + public global::System.Collections.Generic.List DataElements { get; } = new(); + + protected static bool ShouldAdd(object? value) + { + return value switch + { + string strValue => !string.IsNullOrEmpty(strValue), + decimal decValue => decValue != 0, + (decimal min, decimal max) => min > 0 || max > 0, + int intValue => intValue != 0, + _ => false, + }; + } + } + """; + return new() + { + TestState = + { + Sources = { baseClass, source }, + GeneratedSources = + { + (typeof(DataElementGenerator.DataElementGenerator), "DataElementTypes.g.cs", AttributeHelper.DataElementTypesEnum), + (typeof(DataElementGenerator.DataElementGenerator), "TextKind.g.cs", AttributeHelper.DataElementTextKindEnum), + (typeof(DataElementGenerator.DataElementGenerator), "DataContainerAttribute.g.cs", AttributeHelper.DataContainerAttribute), + (typeof(DataElementGenerator.DataElementGenerator), "DataElementTypeAttribute.g.cs", AttributeHelper.DataElementTypeAttribute), + (typeof(DataElementGenerator.DataElementGenerator), "DataElementFilteringAttribute.g.cs", AttributeHelper.DataElementFilteringAttribute), + (typeof(DataElementGenerator.DataElementGenerator), "TestRecord.g.cs", expected), + }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net70, + AdditionalReferences = { MetadataReference.CreateFromFile(typeof(IDataElement).GetTypeInfo().Assembly.Location) }, + }, + }; + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/FormattedText.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/FormattedText.cs new file mode 100644 index 000000000..5b538c2a6 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/FormattedText.cs @@ -0,0 +1,319 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextNoLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues))] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextValuesLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues), ArgumentsTextKind = TextKind.LocalizationKey)] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain, global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextKeyLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues), ValueTextKind = TextKind.LocalizationKey)] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextKeyAndValueLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues), ValueTextKind = TextKind.LocalizationKey, ArgumentsTextKind = TextKind.LocalizationKey)] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey, global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextValuesAppLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues), ArgumentsTextKind = TextKind.AppLocalizationKey)] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain, global::WoWsShipBuilder.DataElements.DataElementTextKind.AppLocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextKeyAppLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues), ValueTextKind = TextKind.AppLocalizationKey)] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.AppLocalizationKey, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("FormattedText")] + public async Task GenerateCode_FormattedTextKeyAndValueAppLocalization_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues), ValueTextKind = TextKind.AppLocalizationKey, ArgumentsTextKind = TextKind.AppLocalizationKey)] + public string Test { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Test)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Test, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.AppLocalizationKey, global::WoWsShipBuilder.DataElements.DataElementTextKind.AppLocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Grouped.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Grouped.cs new file mode 100644 index 000000000..a81aed6a1 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Grouped.cs @@ -0,0 +1,136 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + [Category("Grouped")] + public async Task GenerateCode_OneGroupTwoElements_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Loaders")] + public string BowLoaders { get; set; } = default!; + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Loaders")] + public string SternLoaders { get; set; } = default!; + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + var LoadersList = new global::System.Collections.Generic.List(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.BowLoaders)) + { + LoadersList.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_BowLoaders", this.BowLoaders, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.SternLoaders)) + { + LoadersList.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_SternLoaders", this.SternLoaders, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + + if (LoadersList.Count > 0) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.GroupedDataElement("ShipStats_Loaders", LoadersList)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("Grouped")] + public async Task GenerateCode_TwoGroupsMixedOrder_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Group1")] + public string Prop1 { get; set; } = default!; + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Group2")] + public string Prop2 { get; set; } = default!; + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Group1")] + public string Prop3 { get; set; } = default!; + + [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "Group2")] + public string Prop4 { get; set; } = default!; + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + var Group1List = new global::System.Collections.Generic.List(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + Group1List.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop1", this.Prop1, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop3)) + { + Group1List.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop3", this.Prop3, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + + if (Group1List.Count > 0) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.GroupedDataElement("ShipStats_Group1", Group1List)); + } + + var Group2List = new global::System.Collections.Generic.List(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop2)) + { + Group2List.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop2", this.Prop2, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop4)) + { + Group2List.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop4", this.Prop4, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + + if (Group2List.Count > 0) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.GroupedDataElement("ShipStats_Group2", Group2List)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/KeyValue.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/KeyValue.cs new file mode 100644 index 000000000..c5fd55bcb --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/KeyValue.cs @@ -0,0 +1,127 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + [Category("KeyValue")] + public async Task GenerateCode_KeyValueNoLocalization_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue)] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop1", this.Prop1.ToString(), global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("KeyValue")] + public async Task GenerateCode_KeyValueGameLocalization_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.LocalizationKey)] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop1", this.Prop1.ToString(), global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("KeyValue")] + public async Task GenerateCode_KeyValueAppLocalization_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.AppLocalizationKey)] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop1", this.Prop1.ToString(), global::WoWsShipBuilder.DataElements.DataElementTextKind.AppLocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/KeyValueUnit.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/KeyValueUnit.cs new file mode 100644 index 000000000..7c1210bce --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/KeyValueUnit.cs @@ -0,0 +1,45 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + [Category("KeyValueUnit")] + public async Task GenerateCode_SingleKeyValueUnit_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots")] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueUnitDataElement("ShipStats_Prop1", this.Prop1.ToString(), "Unit_Knots")); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Mixed.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Mixed.cs new file mode 100644 index 000000000..8b32892a7 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Mixed.cs @@ -0,0 +1,152 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + public async Task GenerateCode_EmptyRecord_EmptyMethod() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + public async Task GenerateCode_ElementWithCustomFilter_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "FPM")] + [DataElementFiltering(true, nameof(this.ShouldDisplayTest))] + public decimal TestProperty { get; set; } + + private bool ShouldDisplayTest(object obj) + { + return true; + } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (ShouldDisplayTest(this.TestProperty)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueUnitDataElement("ShipStats_TestProperty", this.TestProperty.ToString(), "Unit_FPM")); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + public async Task GenerateCode_MixedElements_Success() + { + var source = """ + using System.Collections.Generic; + + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.KeyValue, ValueTextKind = TextKind.LocalizationKey)] + public string Prop1 { get; set; } = default!; + + [DataElementType(DataElementTypes.KeyValueUnit, UnitKey="Knots")] + public decimal Prop2 { get; set; } = default!; + + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "TestTooltip")] + public string Prop3 { get; set; } = default!; + + [DataElementType(DataElementTypes.FormattedText, ArgumentsCollectionName = nameof(TestValues))] + public string Prop4 { get; set; } = default!; + + public List TestValues { get; set; } = new(); + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueDataElement("ShipStats_Prop1", this.Prop1, global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey)); + } + + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop2)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.KeyValueUnitDataElement("ShipStats_Prop2", this.Prop2.ToString(), "Unit_Knots")); + } + + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop3)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.TooltipDataElement("ShipStats_Prop3", this.Prop3, "ShipStats_TestTooltip", "")); + } + + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop4)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.FormattedTextDataElement(this.Prop4, this.TestValues, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain, global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Tooltip.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Tooltip.cs new file mode 100644 index 000000000..e101fd47c --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Tooltip.cs @@ -0,0 +1,86 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + [Category("KeyValueUnit")] + public async Task GenerateCode_Tooltip_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "TestTooltip")] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.TooltipDataElement("ShipStats_Prop1", this.Prop1.ToString(), "ShipStats_TestTooltip", "")); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("KeyValueUnit")] + public async Task GenerateCode_TooltipWithUnit_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Tooltip, TooltipKey = "TestTooltip", UnitKey="Knots")] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.TooltipDataElement("ShipStats_Prop1", this.Prop1.ToString(), "ShipStats_TestTooltip", "Unit_Knots")); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Value.cs b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Value.cs new file mode 100644 index 000000000..70ef9f202 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/DataElementGeneratorTests/Value.cs @@ -0,0 +1,127 @@ +namespace WoWsShipBuilder.Data.Generator.Test.DataElementGeneratorTests; + +public partial class DataElementGeneratorTest +{ + [Test] + [Category("Value")] + public async Task GenerateCode_ValueNoLocalization_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value)] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.ValueDataElement(this.Prop1.ToString(), global::WoWsShipBuilder.DataElements.DataElementTextKind.Plain)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("Value")] + public async Task GenerateCode_ValueGameLocalization_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, ValueTextKind = TextKind.LocalizationKey)] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.ValueDataElement(this.Prop1.ToString(), global::WoWsShipBuilder.DataElements.DataElementTextKind.LocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } + + [Test] + [Category("Value")] + public async Task GenerateCode_ValueAppLocalization_Success() + { + var source = """ + using WoWsShipBuilder.DataElements.DataElementAttributes; + using WoWsShipBuilder.DataElements; + + namespace Test; + + [DataContainer] + public partial record TestRecord : DataContainerBase + { + [DataElementType(DataElementTypes.Value, ValueTextKind = TextKind.AppLocalizationKey)] + public decimal Prop1 { get; set; } + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial record TestRecord + { + private void UpdateDataElements() + { + this.DataElements.Clear(); + if (global::WoWsShipBuilder.DataElements.DataContainerBase.ShouldAdd(this.Prop1)) + { + this.DataElements.Add(new global::WoWsShipBuilder.DataElements.ValueDataElement(this.Prop1.ToString(), global::WoWsShipBuilder.DataElements.DataElementTextKind.AppLocalizationKey)); + } + } + } + } + + """; + + await CreateTest(source, expected).RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/PropertyChangedGeneratorTests/PropertyChangedGeneratorTest.cs b/WoWsShipBuilder.Data.Generator.Test/PropertyChangedGeneratorTests/PropertyChangedGeneratorTest.cs new file mode 100644 index 000000000..d802ddf98 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator.Test/PropertyChangedGeneratorTests/PropertyChangedGeneratorTest.cs @@ -0,0 +1,224 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using WoWsShipBuilder.Data.Generator.PropertyChangedGenerator; + +namespace WoWsShipBuilder.Data.Generator.Test.PropertyChangedGeneratorTests; + +[TestFixture] +[SuppressMessage("Maintainability", "S2699", Justification = "false-positive since sonarlint does not recognize RunAsync method from generator test framework")] +public class PropertyChangedGeneratorTest +{ + private const string AttributeClass = """ + namespace WoWsShipBuilder.Infrastructure.Utility; + + [global::System.AttributeUsage(global::System.AttributeTargets.Field)] + public class ObservableAttribute : global::System.Attribute + { + public enum Visibility + { + Private, Protected, Internal, Public, + } + + public Visibility SetterVisibility { get; set; } = Visibility.Public; + + public string[]? Dependants { get; set; } + } + """; + + [Test] + public async Task GenerateCode_NoFields_NoCode() + { + var source = """ + using WoWsShipBuilder.Infrastructure.Utility; + + namespace Test; + + public partial class TestViewModel + { + } + + """; + + await new CSharpSourceGeneratorTest + { + TestState = + { + Sources = { source, AttributeClass }, + }, + }.RunAsync(); + } + + [Test] + public async Task GenerateCode_OneField_Success() + { + var source = """ + #nullable enable + using WoWsShipBuilder.Infrastructure.Utility; + + namespace Test; + + public partial class TestViewModel + { + [Observable] + private string test = default!; + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial class TestViewModel + { + public string Test + { + get => this.test; + set => global::{|CS0400:ReactiveUI|}.IReactiveObjectExtensions.RaiseAndSetIfChanged(this, ref this.test, value); + } + } + } + + """; + + await new CSharpSourceGeneratorTest + { + TestState = + { + Sources = { source, AttributeClass }, + GeneratedSources = { (typeof(PropertyChangedSourceGenerator), "Test.TestViewModel_test.g.cs", expected) }, + }, + }.RunAsync(); + } + + [Test] + public async Task GenerateCode_OneNullableField_Success() + { + var source = """ + #nullable enable + using WoWsShipBuilder.Infrastructure.Utility; + + namespace Test; + + public partial class TestViewModel + { + [Observable] + private string? test; + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial class TestViewModel + { + public string? Test + { + get => this.test; + set => global::{|CS0400:ReactiveUI|}.IReactiveObjectExtensions.RaiseAndSetIfChanged(this, ref this.test, value); + } + } + } + + """; + + await new CSharpSourceGeneratorTest + { + TestState = + { + Sources = { source, AttributeClass }, + GeneratedSources = { (typeof(PropertyChangedSourceGenerator), "Test.TestViewModel_test.g.cs", expected) }, + }, + }.RunAsync(); + } + + [Test] + public async Task GenerateCode_OneNullableFieldValueType_Success() + { + var source = """ + #nullable enable + using WoWsShipBuilder.Infrastructure.Utility; + + namespace Test; + + public partial class TestViewModel + { + [Observable] + private int? test; + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial class TestViewModel + { + public int? Test + { + get => this.test; + set => global::{|CS0400:ReactiveUI|}.IReactiveObjectExtensions.RaiseAndSetIfChanged(this, ref this.test, value); + } + } + } + + """; + + await new CSharpSourceGeneratorTest + { + TestState = + { + Sources = { source, AttributeClass }, + GeneratedSources = { (typeof(PropertyChangedSourceGenerator), "Test.TestViewModel_test.g.cs", expected) }, + }, + }.RunAsync(); + } + + [Test] + public async Task GenerateCode_ListWithNullableReferenceType_Success() + { + var source = """ + #nullable enable + using System.Collections.Generic; + using WoWsShipBuilder.Infrastructure.Utility; + + namespace Test; + + public partial class TestViewModel + { + [Observable] + private List test = default!; + } + """; + + var expected = """ + // + #nullable enable + namespace Test + { + public partial class TestViewModel + { + public global::System.Collections.Generic.List Test + { + get => this.test; + set => global::{|CS0400:ReactiveUI|}.IReactiveObjectExtensions.RaiseAndSetIfChanged(this, ref this.test, value); + } + } + } + + """; + + await new CSharpSourceGeneratorTest + { + TestState = + { + Sources = { source, AttributeClass }, + GeneratedSources = { (typeof(PropertyChangedSourceGenerator), "Test.TestViewModel_test.g.cs", expected) }, + }, + }.RunAsync(); + } +} diff --git a/WoWsShipBuilder.Data.Generator.Test/SourceGeneratorResultTest.cs b/WoWsShipBuilder.Data.Generator.Test/SourceGeneratorResultTest.cs deleted file mode 100644 index 6a2aa998f..000000000 --- a/WoWsShipBuilder.Data.Generator.Test/SourceGeneratorResultTest.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using FluentAssertions; -using NUnit.Framework; -using WoWsShipBuilder.Data.Generator.Test.TestStructures; -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.Data.Generator.Test; - -public class SourceGeneratorResultTest -{ - [Test] - public void SingleDataValue_DataElementsNotEmpty() - { - const string testString = "1234test"; - var testRecord = new TestDataUi1 - { - TestValue = testString, - }; - - testRecord.UpdateData(); - - testRecord.DataElements.Should().NotBeEmpty(); - } - - [Test] - public void GroupedValuesSet_DataElementHasGroup() - { - const string testString = "1234test"; - var testRecord = new TestDataUi1 - { - TestGroup1 = testString, - Test2Group1 = testString, - }; - - testRecord.UpdateData(); - - testRecord.DataElements.Should().NotBeEmpty(); - testRecord.DataElements.OfType().Should().HaveCount(1); - var groupedData = testRecord.DataElements.OfType().Single(); - groupedData.Key.Should().BeEquivalentTo("ShipStats_test1"); - groupedData.Children.Should().HaveCount(2); - } -} diff --git a/WoWsShipBuilder.Data.Generator.Test/SourceGeneratorTest.cs b/WoWsShipBuilder.Data.Generator.Test/SourceGeneratorTest.cs deleted file mode 100644 index dc4e36000..000000000 --- a/WoWsShipBuilder.Data.Generator.Test/SourceGeneratorTest.cs +++ /dev/null @@ -1,327 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Reflection; -using FluentAssertions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using NUnit.Framework; -using WoWsShipBuilder.DataElements.DataElementAttributes; -using Binder = Microsoft.CSharp.RuntimeBinder.Binder; - -namespace WoWsShipBuilder.Data.Generator.Test; - -[TestFixture] -public class SourceGeneratorTest -{ - [Test] - public void AllTest() - { - var code = @"using WoWsShipBuilder.DataElements.DataElements; -using WoWsShipBuilder.DataElements.DataElementAttributes; - -namespace WoWsShipBuilder.Data.Generator.Test.TestStructures; - -public partial record TestDataUi1 : ProjectileDataContainer -{ - [DataElementType(DataElementTypes.Value)] - public string TestValue { get; init; } = default!; - - [DataElementType(DataElementTypes.KeyValue)] - [DataElementVisibility(false)] - public decimal TestKeyValue { get; init; } - - [DataElementType(DataElementTypes.KeyValue)] - [DataElementVisibility(true, ""TestVisibility"")] - public decimal TestVisibilityCustom { get; init; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = ""mm"")] - public string TestKeyUnitValue { get; init; } = default!; - - [DataElementType(DataElementTypes.Tooltip, TooltipKey = ""testTooltip"")] - public decimal TestTooltipValue { get; init; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = ""test1"")] - public string TestGroup1 { get; init; } = default!; - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = ""test1"")] - public string Test2Group1 { get; init; } = default!; - - public bool TestVisibility(object value) - { - return true; - } -} -"; - _ = VerifyGenerator(code); - } - - [Test] - public void ManeuverabilityDataContainer_NoErrors() - { - var code = """ -using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; -using WoWsShipBuilder.DataStructures; -using WoWsShipBuilder.DataStructures.Ship; -using WoWsShipBuilder.Infrastructure.Utility; - -namespace WoWsShipBuilder.DataContainers; - -public partial record ManeuverabilityDataContainer : DataContainerBase -{ - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots")] - public decimal ManeuverabilityMaxSpeed { get; set; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots", NameLocalizationKey = "MaxReverseSpeed")] - public decimal ManeuverabilityMaxReverseSpeed { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeed", UnitKey = "Knots")] - public decimal ManeuverabilitySubsMaxSpeedOnSurface { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeed", UnitKey = "Knots")] - public decimal ManeuverabilitySubsMaxSpeedAtPeriscope { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeed", UnitKey = "Knots")] - public decimal ManeuverabilitySubsMaxSpeedAtMaxDepth { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", NameLocalizationKey = "ManeuverabilitySubsMaxSpeedOnSurface")] - public decimal ManeuverabilitySubsMaxReverseSpeedOnSurface { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", NameLocalizationKey = "ManeuverabilitySubsMaxSpeedAtPeriscope")] - public decimal ManeuverabilitySubsMaxReverseSpeedAtPeriscope { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxReverseSpeed", UnitKey = "Knots", NameLocalizationKey = "ManeuverabilitySubsMaxSpeedAtMaxDepth")] - public decimal ManeuverabilitySubsMaxReverseSpeedAtMaxDepth { get; set; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "MPS")] - public decimal ManeuverabilitySubsMaxDiveSpeed { get; set; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal ManeuverabilitySubsDivingPlaneShiftTime { get; set; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "S")] - public decimal ManeuverabilityRudderShiftTime { get; set; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "M")] - public decimal ManeuverabilityTurningCircle { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeedTime", UnitKey = "S")] - public decimal ForwardMaxSpeedTime { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "MaxSpeedTime", UnitKey = "S")] - public decimal ReverseMaxSpeedTime { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "BlastProtection", TooltipKey = "BlastExplanation")] - [DataElementFiltering(false)] - public decimal RudderBlastProtection { get; set; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.Tooltip, GroupKey = "BlastProtection", TooltipKey = "BlastExplanation")] - [DataElementFiltering(false)] - public decimal EngineBlastProtection { get; set; } -} -"""; - var expected = """ -using System; -using System.Collections.Generic; -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.DataContainers; - -#nullable enable -public partial record ManeuverabilityDataContainer -{ - private void UpdateDataElements() - { - DataElements.Clear(); - if (DataContainerBase.ShouldAdd(ManeuverabilityMaxSpeed)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilityMaxSpeed", ManeuverabilityMaxSpeed.ToString(), "Unit_Knots")); - - if (DataContainerBase.ShouldAdd(ManeuverabilityMaxReverseSpeed)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_MaxReverseSpeed", ManeuverabilityMaxReverseSpeed.ToString(), "Unit_Knots")); - - var MaxSpeedList = new List(); - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxSpeedOnSurface)) - MaxSpeedList.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxSpeedOnSurface", ManeuverabilitySubsMaxSpeedOnSurface.ToString(), "Unit_Knots")); - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxSpeedAtPeriscope)) - MaxSpeedList.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxSpeedAtPeriscope", ManeuverabilitySubsMaxSpeedAtPeriscope.ToString(), "Unit_Knots")); - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxSpeedAtMaxDepth)) - MaxSpeedList.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxSpeedAtMaxDepth", ManeuverabilitySubsMaxSpeedAtMaxDepth.ToString(), "Unit_Knots")); - if (MaxSpeedList.Count > 0) - DataElements.Add(new GroupedDataElement("ShipStats_MaxSpeed", MaxSpeedList)); - - var MaxReverseSpeedList = new List(); - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxReverseSpeedOnSurface)) - MaxReverseSpeedList.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxSpeedOnSurface", ManeuverabilitySubsMaxReverseSpeedOnSurface.ToString(), "Unit_Knots")); - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxReverseSpeedAtPeriscope)) - MaxReverseSpeedList.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxSpeedAtPeriscope", ManeuverabilitySubsMaxReverseSpeedAtPeriscope.ToString(), "Unit_Knots")); - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxReverseSpeedAtMaxDepth)) - MaxReverseSpeedList.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxSpeedAtMaxDepth", ManeuverabilitySubsMaxReverseSpeedAtMaxDepth.ToString(), "Unit_Knots")); - if (MaxReverseSpeedList.Count > 0) - DataElements.Add(new GroupedDataElement("ShipStats_MaxReverseSpeed", MaxReverseSpeedList)); - - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsMaxDiveSpeed)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsMaxDiveSpeed", ManeuverabilitySubsMaxDiveSpeed.ToString(), "Unit_MPS")); - - if (DataContainerBase.ShouldAdd(ManeuverabilitySubsDivingPlaneShiftTime)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilitySubsDivingPlaneShiftTime", ManeuverabilitySubsDivingPlaneShiftTime.ToString(), "Unit_S")); - - if (DataContainerBase.ShouldAdd(ManeuverabilityRudderShiftTime)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilityRudderShiftTime", ManeuverabilityRudderShiftTime.ToString(), "Unit_S")); - - if (DataContainerBase.ShouldAdd(ManeuverabilityTurningCircle)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_ManeuverabilityTurningCircle", ManeuverabilityTurningCircle.ToString(), "Unit_M")); - - var MaxSpeedTimeList = new List(); - if (DataContainerBase.ShouldAdd(ForwardMaxSpeedTime)) - MaxSpeedTimeList.Add(new KeyValueUnitDataElement("ShipStats_ForwardMaxSpeedTime", ForwardMaxSpeedTime.ToString(), "Unit_S")); - if (DataContainerBase.ShouldAdd(ReverseMaxSpeedTime)) - MaxSpeedTimeList.Add(new KeyValueUnitDataElement("ShipStats_ReverseMaxSpeedTime", ReverseMaxSpeedTime.ToString(), "Unit_S")); - if (MaxSpeedTimeList.Count > 0) - DataElements.Add(new GroupedDataElement("ShipStats_MaxSpeedTime", MaxSpeedTimeList)); - - var BlastProtectionList = new List(); - - BlastProtectionList.Add(new TooltipDataElement("ShipStats_RudderBlastProtection", RudderBlastProtection.ToString(), "ShipStats_BlastExplanation", "")); - - BlastProtectionList.Add(new TooltipDataElement("ShipStats_EngineBlastProtection", EngineBlastProtection.ToString(), "ShipStats_BlastExplanation", "")); - if (BlastProtectionList.Count > 0) - DataElements.Add(new GroupedDataElement("ShipStats_BlastProtection", BlastProtectionList)); - } -} -#nullable restore -"""; - - _ = VerifyGenerator(code, expected); - } - - [Test] - public void SingleKeyValueUnitElement_NoErrors() - { - var code = """ -using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.DataContainers; - -public partial record TestContainer : DataContainerBase -{ - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "Knots")] - public decimal TestProperty { get; set; } -} -"""; - var expected = """ -using System; -using System.Collections.Generic; -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.DataContainers; - -#nullable enable -public partial record TestContainer -{ - private void UpdateDataElements() - { - DataElements.Clear(); - if (DataContainerBase.ShouldAdd(TestProperty)) - DataElements.Add(new KeyValueUnitDataElement("ShipStats_TestProperty", TestProperty.ToString(), "Unit_Knots")); - } -} -#nullable restore -"""; - - _ = VerifyGenerator(code, expected); - } - - [Test] - public void GroupedKeyValueUnitElement_NoErrors() - { - var code = """ -using WoWsShipBuilder.DataElements.DataElementAttributes; -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.DataContainers; - -public partial record TestContainer : DataContainerBase -{ - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValueUnit, GroupKey = "TestGroup", UnitKey = "Knots")] - public decimal TestProperty { get; set; } -} -"""; - var expected = """ -using System; -using System.Collections.Generic; -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.DataContainers; - -#nullable enable -public partial record TestContainer -{ - private void UpdateDataElements() - { - DataElements.Clear(); - - var TestGroupList = new List(); - if (DataContainerBase.ShouldAdd(TestProperty)) - TestGroupList.Add(new KeyValueUnitDataElement("ShipStats_TestProperty", TestProperty.ToString(), "Unit_Knots")); - if (TestGroupList.Count > 0) - DataElements.Add(new GroupedDataElement("ShipStats_TestGroup", TestGroupList)); - - - } -} -#nullable restore -"""; - - _ = VerifyGenerator(code, expected); - } - - private static GeneratorDriverRunResult VerifyGenerator(string source, string generated = "") - { - var baseInput = """ -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.Data.Generator.Test.TestStructures; - -public record ProjectileDataContainer : DataContainerBase; -"""; - - var compilation = CreateCompilation(baseInput, source); - GeneratorDriver driver = CSharpGeneratorDriver.Create(new DataElementSourceGenerator()); - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out ImmutableArray _); - - var result = driver.GetRunResult(); - result.Diagnostics.Should().BeEmpty(); - - if (!string.IsNullOrEmpty(generated)) - { - var expectedTree = CSharpSyntaxTree.ParseText(generated); - result.GeneratedTrees.Should().Contain(syntaxTree => syntaxTree.IsEquivalentTo(expectedTree, false)); - } - - return result; - } - - private static Compilation CreateCompilation(params string[] source) - { - List syntaxTrees = source.Select(sourceFile => CSharpSyntaxTree.ParseText(sourceFile)).ToList(); - - var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location)!; - return CSharpCompilation.Create("compilation", - syntaxTrees, - new[] - { - MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")), - MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")), - MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")), - MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")), - MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")), - MetadataReference.CreateFromFile(typeof(DataElementTypeAttribute).GetTypeInfo().Assembly.Location), - }, - new(OutputKind.ConsoleApplication)); - } - -} diff --git a/WoWsShipBuilder.Data.Generator.Test/TestStructures/DataElements.cs b/WoWsShipBuilder.Data.Generator.Test/TestStructures/DataElements.cs deleted file mode 100644 index 1852d99a7..000000000 --- a/WoWsShipBuilder.Data.Generator.Test/TestStructures/DataElements.cs +++ /dev/null @@ -1,5 +0,0 @@ -using WoWsShipBuilder.DataElements.DataElements; - -namespace WoWsShipBuilder.Data.Generator.Test.TestStructures; - -public record ProjectileDataContainer : DataContainerBase; diff --git a/WoWsShipBuilder.Data.Generator.Test/TestStructures/TestDataUi1.cs b/WoWsShipBuilder.Data.Generator.Test/TestStructures/TestDataUi1.cs deleted file mode 100644 index 636b17a96..000000000 --- a/WoWsShipBuilder.Data.Generator.Test/TestStructures/TestDataUi1.cs +++ /dev/null @@ -1,39 +0,0 @@ -using WoWsShipBuilder.DataElements.DataElementAttributes; - -namespace WoWsShipBuilder.Data.Generator.Test.TestStructures; - -public partial record TestDataUi1 : ProjectileDataContainer -{ - [DataElementType(DataElementTypes.Value)] - public string TestValue { get; init; } = default!; - - [DataElementType(DataElementTypes.KeyValue)] - [DataElementFiltering(false)] - public decimal TestKeyValue { get; init; } - - [DataElementType(DataElementTypes.KeyValue)] - [DataElementFiltering(true, "TestVisibility")] - public decimal TestVisibilityCustom { get; init; } - - [DataElementType(DataElementTypes.KeyValueUnit, UnitKey = "mm")] - public string TestKeyUnitValue { get; init; } = default!; - - [DataElementType(DataElementTypes.Tooltip, TooltipKey = "testTooltip")] - public decimal TestTooltipValue { get; init; } - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "test1")] - public string TestGroup1 { get; init; } = default!; - - [DataElementType(DataElementTypes.Grouped | DataElementTypes.KeyValue, GroupKey = "test1")] - public string Test2Group1 { get; init; } = default!; - - public void UpdateData() - { - UpdateDataElements(); - } - - public bool TestVisibility(object value) - { - return true; - } -} diff --git a/WoWsShipBuilder.Data.Generator.Test/WoWsShipBuilder.Data.Generator.Test.csproj b/WoWsShipBuilder.Data.Generator.Test/WoWsShipBuilder.Data.Generator.Test.csproj index 3c170182e..4e9b42c04 100644 --- a/WoWsShipBuilder.Data.Generator.Test/WoWsShipBuilder.Data.Generator.Test.csproj +++ b/WoWsShipBuilder.Data.Generator.Test/WoWsShipBuilder.Data.Generator.Test.csproj @@ -5,13 +5,21 @@ false + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + diff --git a/WoWsShipBuilder.Data.Generator/Attributes/AttributeGenerator.cs b/WoWsShipBuilder.Data.Generator/Attributes/AttributeGenerator.cs deleted file mode 100644 index a608d75a3..000000000 --- a/WoWsShipBuilder.Data.Generator/Attributes/AttributeGenerator.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace WoWsShipBuilder.Data.Generator.Attributes; - -public static class AttributeGenerator -{ - - public const string DataElementTypesEnum = @" -namespace WoWsShipBuilder.DataElements.DataElementAttributes; - -[Flags] -public enum DataElementTypes -{ - KeyValue = 1, - KeyValueUnit = 2, - Value = 4, - Grouped = 8, - Tooltip = 16, -} -"; - - public const string DataElementTypeAttribute = @" -using System; - -namespace WoWsShipBuilder.DataElements.DataElementAttributes; - -[AttributeUsage(AttributeTargets.Property)] -public class DataElementTypeAttribute : Attribute -{ - public DataElementTypeAttribute(DataElementTypes type) - { - Type = type; - } - - public DataElementTypes Type { get; } - - public string? UnitKey { get; set; } - - public string? TooltipKey { get; set; } - - public string? GroupKey { get; set; } - - public string[] LocalizationArguments { get; set; } = Array.Empty(); -} -"; - - public const string DataElementVisibilityAttribute = @"using System; - -namespace WoWsShipBuilder.DataElements.DataElementAttributes; - -[AttributeUsage(AttributeTargets.Property)] -public class DataElementVisibilityAttribute : Attribute -{ - public DataElementVisibilityAttribute(bool enableFilterVisibility, string filterMethodName = "") - { - EnableFilterVisibility = enableFilterVisibility; - FilterMethodName = filterMethodName; - } - - public bool EnableFilterVisibility { get; } - - public string FilterMethodName { get; } -}"; -} diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/AttributeHelper.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/AttributeHelper.cs new file mode 100644 index 000000000..1c80e0d8a --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/AttributeHelper.cs @@ -0,0 +1,169 @@ +using Microsoft.CodeAnalysis; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; + +public static class AttributeHelper +{ + public const string AttributeNamespace = "WoWsShipBuilder.DataElements.DataElementAttributes"; + + public const string DataElementTypesEnumName = "DataElementTypes"; + + // language=csharp + public const string DataElementTypesEnum = $$""" + // + #nullable enable + namespace {{AttributeNamespace}}; + + [global::System.Flags] + internal enum {{DataElementTypesEnumName}} + { + KeyValue = 1, + KeyValueUnit = 2, + Value = 4, + Grouped = 8, + Tooltip = 16, + FormattedText = 32, + } + """; + + public const string DataElementTextKindEnumName = "TextKind"; + + // language=csharp + public const string DataElementTextKindEnum = $$""" + // + #nullable enable + namespace {{AttributeNamespace}}; + + /// + /// Specifies how a text value of a property marked by will be interpreted. + /// + internal enum {{DataElementTextKindEnumName}} + { + /// + /// Identifies a plain text value that will be displayed as is. + /// + Plain, + + /// + /// Identifies a text value that will be used as game localization key. + /// + LocalizationKey, + + /// + /// Identifies a text value that will be used as app localization key. + /// + AppLocalizationKey, + } + """; + + public const string DataContainerAttributeName = "DataContainerAttribute"; + + // language=csharp + public const string DataContainerAttribute = $$""" + // + #nullable enable + namespace {{AttributeNamespace}}; + + [global::System.AttributeUsage(global::System.AttributeTargets.Class, Inherited = false)] + internal class {{DataContainerAttributeName}} : global::System.Attribute + { + } + """; + + public const string DataElementTypeAttributeName = "DataElementTypeAttribute"; + + // language=csharp + public const string DataElementTypeAttribute = $$""" + // + #nullable enable + namespace {{AttributeNamespace}}; + + [global::System.AttributeUsage(global::System.AttributeTargets.Property)] + internal class {{DataElementTypeAttributeName}} : global::System.Attribute + { + public {{DataElementTypeAttributeName}}(global::{{AttributeNamespace}}.DataElementTypes type) + { + this.Type = type; + } + + /// + /// Gets the type of the DataElement for the property marked by this attribute. /> + /// + public global::{{AttributeNamespace}}.DataElementTypes Type { get; } + + /// + /// Gets or sets the property name localization key override for the property marked by this attribute.
+ /// Only valid for , , and . + ///
+ public string? LocalizationKeyOverride { get; set; } + + /// + /// Gets or sets the unit localization key for the property marked by this attribute.
+ /// Only valid for and . + ///
+ public string? UnitKey { get; set; } + + /// + /// Gets or sets the tooltip localization key for the property marked by this attribute.
+ /// Only valid for . + ///
+ public string? TooltipKey { get; set; } + + /// + /// Gets or sets the group localization key and identifier for the property marked by this attribute.
+ /// Only valid for . + ///
+ public string? GroupKey { get; set; } + + /// + /// Gets or sets the of the value of the property marked by this attribute.
+ /// Only valid for , and + ///
+ public global::{{AttributeNamespace}}.{{DataElementTextKindEnumName}} ValueTextKind { get; set; } + + /// + /// Gets or set the name of the property containing the list of values that will replace the placeholder. Requires the value of the property marked by this attribute to follow the specifications.
+ /// Only valid for . + ///
+ public string? ArgumentsCollectionName { get; set; } + + /// + /// Gets or sets the of the argument values of the property marked by this attribute.
+ /// Only valid for + ///
+ public global::{{AttributeNamespace}}.{{DataElementTextKindEnumName}} ArgumentsTextKind { get; set; } + } + """; + + public const string DataElementFilteringAttributeName = "DataElementFilteringAttribute"; + + // language=csharp + public const string DataElementFilteringAttribute = $$""" + // + #nullable enable + namespace {{AttributeNamespace}}; + + [global::System.AttributeUsage(global::System.AttributeTargets.Property)] + internal class {{DataElementFilteringAttributeName}} : global::System.Attribute + { + public {{DataElementFilteringAttributeName}}(bool enableFilterVisibility, string filterMethodName = "") + { + this.EnableFilterVisibility = enableFilterVisibility; + this.FilterMethodName = filterMethodName; + } + + public bool EnableFilterVisibility { get; } + + public string FilterMethodName { get; } + } + """; + + public static void GenerateAttributes(IncrementalGeneratorPostInitializationContext context) + { + context.AddSource("DataElementTypes.g.cs", DataElementTypesEnum); + context.AddSource("TextKind.g.cs", DataElementTextKindEnum); + context.AddSource("DataContainerAttribute.g.cs", DataContainerAttribute); + context.AddSource("DataElementTypeAttribute.g.cs", DataElementTypeAttribute); + context.AddSource("DataElementFilteringAttribute.g.cs", DataElementFilteringAttribute); + } +} diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementAnalyzer.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementAnalyzer.cs new file mode 100644 index 000000000..571ac26cb --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementAnalyzer.cs @@ -0,0 +1,256 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class DataElementAnalyzer : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + Rules.InvalidDataElementTypeRule, + Rules.MissingSecondaryDataElementTypeRule, + Rules.GroupKeyMissingRule, + Rules.MissingAttributeParametersRule, + Rules.IncompatibleAttributeParametersRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterSymbolAction(AnalyzeDataElementProperty, SymbolKind.Property); + } + + private static void AnalyzeDataElementProperty(SymbolAnalysisContext context) + { + var propertySymbol = (IPropertySymbol)context.Symbol; + var dataElementAttribute = propertySymbol.FindAttributeOrDefault($"{AttributeHelper.AttributeNamespace}.{AttributeHelper.DataElementTypeAttributeName}"); + if (dataElementAttribute is null) + { + return; + } + + var dataElementType = (DataElementTypes)dataElementAttribute.ConstructorArguments[0].Value!; + var propertyData = ExtractPropertyData(propertySymbol, dataElementType, dataElementAttribute); + context.CancellationToken.ThrowIfCancellationRequested(); + + if ((dataElementType & DataElementTypes.Grouped) == DataElementTypes.Grouped) + { + CheckGroupedDataElement(context, propertySymbol, dataElementType, dataElementAttribute); + } + else if (propertyData.DisplayOptions.GroupKey is not null) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.IncompatibleAttributeParametersRule, propertySymbol.Locations[0], "GroupKey")); + } + + context.CancellationToken.ThrowIfCancellationRequested(); + switch (dataElementType & ~DataElementTypes.Grouped) + { + case DataElementTypes.KeyValue: + CheckKeyValueDataElement(context, propertySymbol, propertyData); + break; + case DataElementTypes.KeyValueUnit: + CheckKeyValueUnitDataElement(context, propertySymbol, propertyData); + break; + case DataElementTypes.Value: + CheckValueDataElement(context, propertySymbol, propertyData); + break; + case DataElementTypes.Tooltip: + CheckTooltipDataElement(context, propertySymbol, propertyData); + break; + case DataElementTypes.FormattedText: + CheckFormattedTextDataElement(context, propertySymbol, propertyData); + break; + default: + if ((dataElementType & DataElementTypes.Grouped) != DataElementTypes.Grouped) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.InvalidDataElementTypeRule, propertySymbol.Locations[0], dataElementType)); + } + + break; + } + } + + private static void CheckGroupedDataElement(SymbolAnalysisContext context, ISymbol propertySymbol, DataElementTypes dataElementType, AttributeData dataElementAttribute) + { + if ((dataElementType & ~DataElementTypes.Grouped) == 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.MissingSecondaryDataElementTypeRule, propertySymbol.Locations[0])); + } + + if (dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "GroupKey").Value.Value is null) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.GroupKeyMissingRule, propertySymbol.Locations[0])); + } + } + + private static void CheckKeyValueDataElement(SymbolAnalysisContext context, ISymbol propertySymbol, SinglePropertyData propertyData) + { + var incompatibleParameters = new List(); + + if (propertyData.FormattedTextData.ArgumentsCollectionName is not null) + { + incompatibleParameters.Add("ArgumentsCollectionName"); + } + + if (propertyData.FormattedTextData.ArgumentsTextKind != TextKind.Plain) + { + incompatibleParameters.Add("ArgumentsTextKind"); + } + + if (propertyData.DisplayOptions.UnitKey is not null) + { + incompatibleParameters.Add("UnitKey"); + } + + if (propertyData.DisplayOptions.TooltipKey is not null) + { + incompatibleParameters.Add("TooltipKey"); + } + + if (incompatibleParameters.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.IncompatibleAttributeParametersRule, propertySymbol.Locations[0], string.Join(", ", incompatibleParameters))); + } + } + + private static void CheckKeyValueUnitDataElement(SymbolAnalysisContext context, ISymbol propertySymbol, SinglePropertyData propertyData) + { + var errors = new List(); + + if (propertyData.FormattedTextData.ArgumentsCollectionName is not null) + { + errors.Add("ArgumentsCollectionName"); + } + + if (propertyData.FormattedTextData.ArgumentsTextKind != TextKind.Plain) + { + errors.Add("ArgumentsTextKind"); + } + + if (propertyData.DisplayOptions.TooltipKey is not null) + { + errors.Add("TooltipKey"); + } + + if (propertyData.DisplayOptions.ValueTextKind != TextKind.Plain) + { + errors.Add("ValueTextKind"); + } + + if (errors.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.IncompatibleAttributeParametersRule, propertySymbol.Locations[0], string.Join(", ", errors))); + } + + if (string.IsNullOrEmpty(propertyData.DisplayOptions.UnitKey)) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.MissingAttributeParametersRule, propertySymbol.Locations[0], "UnitKey")); + } + } + + private static void CheckValueDataElement(SymbolAnalysisContext context, ISymbol propertySymbol, SinglePropertyData propertyData) + { + var incompatibleParameters = new List(); + + if (propertyData.FormattedTextData.ArgumentsCollectionName is not null) + { + incompatibleParameters.Add("ArgumentsCollectionName"); + } + + if (propertyData.FormattedTextData.ArgumentsTextKind != TextKind.Plain) + { + incompatibleParameters.Add("ArgumentsTextKind"); + } + + if (propertyData.DisplayOptions.UnitKey is not null) + { + incompatibleParameters.Add("UnitKey"); + } + + if (propertyData.DisplayOptions.TooltipKey is not null) + { + incompatibleParameters.Add("TooltipKey"); + } + + if (!propertyData.DisplayOptions.LocalizationKey.Equals(propertySymbol.Name)) + { + incompatibleParameters.Add("LocalizationKeyOverride"); + } + + if (incompatibleParameters.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.IncompatibleAttributeParametersRule, propertySymbol.Locations[0], string.Join(", ", incompatibleParameters))); + } + } + + private static void CheckTooltipDataElement(SymbolAnalysisContext context, ISymbol propertySymbol, SinglePropertyData propertyData) + { + var errors = new List(); + + if (propertyData.FormattedTextData.ArgumentsCollectionName is not null) + { + errors.Add("ArgumentsCollectionName"); + } + + if (propertyData.FormattedTextData.ArgumentsTextKind != TextKind.Plain) + { + errors.Add("ArgumentsTextKind"); + } + + if (propertyData.DisplayOptions.ValueTextKind != TextKind.Plain) + { + errors.Add("ValueTextKind"); + } + + if (errors.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.IncompatibleAttributeParametersRule, propertySymbol.Locations[0], string.Join(", ", errors))); + } + + if (string.IsNullOrEmpty(propertyData.DisplayOptions.TooltipKey)) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.MissingAttributeParametersRule, propertySymbol.Locations[0], "TooltipKey")); + } + } + + private static void CheckFormattedTextDataElement(SymbolAnalysisContext context, ISymbol propertySymbol, SinglePropertyData propertyData) + { + var errors = new List(); + + if (propertyData.DisplayOptions.UnitKey is not null) + { + errors.Add("UnitKey"); + } + + if (propertyData.DisplayOptions.TooltipKey is not null) + { + errors.Add("TooltipKey"); + } + + if (!propertyData.DisplayOptions.LocalizationKey.Equals(propertySymbol.Name)) + { + errors.Add("LocalizationKeyOverride"); + } + + if (errors.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.IncompatibleAttributeParametersRule, propertySymbol.Locations[0], string.Join(", ", errors))); + } + + if (string.IsNullOrEmpty(propertyData.FormattedTextData.ArgumentsCollectionName)) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.MissingAttributeParametersRule, propertySymbol.Locations[0], "ArgumentsCollectionName")); + } + } + + private static SinglePropertyData ExtractPropertyData(IPropertySymbol propertySymbol, DataElementTypes dataElementType, AttributeData dataElementAttribute) + { + return new(propertySymbol.Name, propertySymbol.Type.SpecialType == SpecialType.System_String, propertySymbol.NullableAnnotation == NullableAnnotation.Annotated, dataElementType, PropertyHelper.ExtractDisplayOptions(propertySymbol, dataElementAttribute), PropertyHelper.ExtractFilterOptions(propertySymbol), PropertyHelper.ExtractFormattedTextOptions(dataElementAttribute), 0); + } +} diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementGenerator.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementGenerator.cs new file mode 100644 index 000000000..00c6556a1 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementGenerator.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; + +[Generator(LanguageNames.CSharp)] +public class DataElementGenerator : IIncrementalGenerator +{ + private const string DataContainerAttributeFullName = $"{AttributeHelper.AttributeNamespace}.{AttributeHelper.DataContainerAttributeName}"; + private const string DataElementAttributeFullName = $"{AttributeHelper.AttributeNamespace}.{AttributeHelper.DataElementTypeAttributeName}"; + private const string DataElementNamespace = "global::WoWsShipBuilder.DataElements"; + private const string DataElementsCollectionName = "this.DataElements"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(AttributeHelper.GenerateAttributes); + var model = context.SyntaxProvider + .ForAttributeWithMetadataName(DataContainerAttributeFullName, CouldBeDataContainer, ComputeRawContainerData) + .Select(ExtractPropertyGroups); + + context.RegisterSourceOutput(model, GenerateSourceCode); + } + + private static bool CouldBeDataContainer(SyntaxNode syntaxNode, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + return syntaxNode is RecordDeclarationSyntax typeDeclarationSyntax && typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)); + } + + private static RawContainerData ComputeRawContainerData(GeneratorAttributeSyntaxContext context, CancellationToken token) + { + var recordSymbol = (INamedTypeSymbol)context.TargetSymbol; + token.ThrowIfCancellationRequested(); + var name = recordSymbol.Name; + var dataNamespace = recordSymbol.ContainingNamespace.ToDisplayString(); + var properties = recordSymbol.GetMembers() + .OfType() + .Where(prop => prop.HasAttributeWithFullName(DataElementAttributeFullName)) + .Select(RefineProperty) + .ToEquatableArray(); + + token.ThrowIfCancellationRequested(); + return new(name, dataNamespace, properties); + } + + private static SinglePropertyData RefineProperty(IPropertySymbol propertySymbol, int index) + { + var dataElementAttribute = propertySymbol.FindAttribute(DataElementAttributeFullName); + var dataElementType = (DataElementTypes)dataElementAttribute.ConstructorArguments[0].Value!; + + return new(propertySymbol.Name, propertySymbol.Type.SpecialType == SpecialType.System_String, propertySymbol.NullableAnnotation == NullableAnnotation.Annotated, dataElementType, PropertyHelper.ExtractDisplayOptions(propertySymbol, dataElementAttribute), PropertyHelper.ExtractFilterOptions(propertySymbol), PropertyHelper.ExtractFormattedTextOptions(dataElementAttribute), index); + } + + private static ContainerData ExtractPropertyGroups(RawContainerData rawContainerData, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + IEnumerable propertyGroups = rawContainerData.Properties + .Where(prop => (prop.DataElementType & DataElementTypes.Grouped) == DataElementTypes.Grouped) + .GroupBy(prop => prop.DisplayOptions.GroupKey!) + .Select(CreatePropertyGroup) + .Select(groupData => new PropertyData(null, groupData, groupData.DeclarationIndex)); + token.ThrowIfCancellationRequested(); + + var newProperties = rawContainerData.Properties + .Where(prop => prop.DisplayOptions.GroupKey is null) + .Select(p => new PropertyData(p, null, p.DeclarationIndex)) + .Concat(propertyGroups) + .OrderBy(p => p.DeclarationIndex) + .ToEquatableArray(); + + token.ThrowIfCancellationRequested(); + return new(rawContainerData.ContainerName, rawContainerData.Namespace, newProperties.ToEquatableArray()); + } + + private static GroupPropertyData CreatePropertyGroup(IGrouping grouping) + { + var children = grouping.Select(prop => prop with { DataElementType = prop.DataElementType & ~DataElementTypes.Grouped }).ToEquatableArray(); + return new(grouping.Key, children, children[0].DeclarationIndex); + } + + private static void GenerateSourceCode(SourceProductionContext context, ContainerData containerData) + { + var builder = new SourceBuilder(); + builder.Line("// "); + builder.Line("#nullable enable"); + using (builder.Namespace(containerData.Namespace)) + { + using (builder.Record(containerData.ContainerName)) + { + using (builder.Block("private void UpdateDataElements()")) + { + GenerateUpdateMethodContent(builder, containerData); + } + } + } + + context.AddSource($"{containerData.ContainerName}.g.cs", builder.ToString()); + } + + private static void GenerateUpdateMethodContent(SourceBuilder builder, ContainerData containerData) + { + builder.Line("this.DataElements.Clear();"); + + foreach (var property in containerData.Properties) + { + if (property.SinglePropertyData is { } singleProperty) + { + GeneratePropertyCode(builder, singleProperty); + } + else if (property.GroupPropertyData is { } propertyGroup) + { + var listName = $"{propertyGroup.GroupName}List"; + builder.Line($"var {listName} = new global::System.Collections.Generic.List<{DataElementNamespace}.IDataElement>();"); + foreach (var childProperty in propertyGroup.Properties) + { + GenerateGroupedPropertyCode(builder, childProperty, listName); + } + + using (builder.Block($"if ({listName}.Count > 0)")) + { + builder.Line($"{DataElementsCollectionName}.Add(new {DataElementNamespace}.GroupedDataElement(\"ShipStats_{propertyGroup.GroupName}\", {listName}));"); + } + } + } + } + + private static void GenerateGroupedPropertyCode(SourceBuilder builder, SinglePropertyData property, string listName) + { + if (property.PropertyFilter.IsEnabled) + { + var filterMethodName = property.PropertyFilter.FilterMethodName; + using (builder.Block($"if ({filterMethodName}(this.{property.Name}))")) + { + builder.Line($"{listName}.Add({GenerateDataElementCreationCode(property)});"); + } + } + else + { + builder.Line($"{listName}.Add({GenerateDataElementCreationCode(property)});"); + } + } + + private static void GeneratePropertyCode(SourceBuilder builder, SinglePropertyData property) + { + if (property.PropertyFilter.IsEnabled) + { + var filterMethodName = property.PropertyFilter.FilterMethodName; + using (builder.Block($"if ({filterMethodName}(this.{property.Name}))")) + { + builder.Line($"{DataElementsCollectionName}.Add({GenerateDataElementCreationCode(property)});"); + } + } + else + { + builder.Line($"{DataElementsCollectionName}.Add({GenerateDataElementCreationCode(property)});"); + } + } + + private static string GenerateDataElementCreationCode(SinglePropertyData propertyData) + { + return propertyData.DataElementType switch + { + DataElementTypes.Value => $"new {DataElementNamespace}.ValueDataElement({GeneratePropertyAccess(propertyData)}, {DataElementNamespace}.DataElementTextKind.{propertyData.DisplayOptions.ValueTextKind})", + DataElementTypes.KeyValue => $$"""new {{DataElementNamespace}}.KeyValueDataElement("ShipStats_{{propertyData.DisplayOptions.LocalizationKey}}", {{GeneratePropertyAccess(propertyData)}}, {{DataElementNamespace}}.DataElementTextKind.{{propertyData.DisplayOptions.ValueTextKind}})""", + DataElementTypes.KeyValueUnit => $"""new {DataElementNamespace}.KeyValueUnitDataElement("ShipStats_{propertyData.DisplayOptions.LocalizationKey}", {GeneratePropertyAccess(propertyData)}, "Unit_{propertyData.DisplayOptions.UnitKey}")""", + DataElementTypes.FormattedText => $"new {DataElementNamespace}.FormattedTextDataElement({GeneratePropertyAccess(propertyData)}, this.{propertyData.FormattedTextData.ArgumentsCollectionName}, {DataElementNamespace}.DataElementTextKind.{propertyData.DisplayOptions.ValueTextKind}, {DataElementNamespace}.DataElementTextKind.{propertyData.FormattedTextData.ArgumentsTextKind})", + DataElementTypes.Tooltip => $"""new {DataElementNamespace}.TooltipDataElement("ShipStats_{propertyData.DisplayOptions.LocalizationKey}", {GeneratePropertyAccess(propertyData)}, "ShipStats_{propertyData.DisplayOptions.TooltipKey}", "{ComputeNullableUnitValue(propertyData.DisplayOptions)}")""", + _ => string.Empty, + }; + } + + private static string ComputeNullableUnitValue(PropertyDisplayOptions displayOptions) => displayOptions.UnitKey is not null ? $"Unit_{displayOptions.UnitKey}" : string.Empty; + + private static string GeneratePropertyAccess(SinglePropertyData propertyData) + { + return propertyData switch + { + { IsString: true, IsNullable: false } => $"this.{propertyData.Name}", + { IsString: true, IsNullable: true } => $"this.{propertyData.Name} ?? \"null\"", + { IsString: false, IsNullable: false } => $"this.{propertyData.Name}.ToString()", + { IsString: false, IsNullable: true } => $"this.{propertyData.Name}?.ToString() ?? \"null\"", + }; + } +} diff --git a/WoWsShipBuilder.Data.Generator/Internals/DataElementTypes.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementTypes.cs similarity index 73% rename from WoWsShipBuilder.Data.Generator/Internals/DataElementTypes.cs rename to WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementTypes.cs index b9fbd147b..46b15036b 100644 --- a/WoWsShipBuilder.Data.Generator/Internals/DataElementTypes.cs +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/DataElementTypes.cs @@ -1,6 +1,6 @@ using System; -namespace WoWsShipBuilder.Data.Generator.Internals; +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; [Flags] internal enum DataElementTypes diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/ContainerData.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/ContainerData.cs new file mode 100644 index 000000000..f296d55b5 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/ContainerData.cs @@ -0,0 +1,5 @@ +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record ContainerData(string ContainerName, string Namespace, EquatableArray Properties); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/FormattedTextData.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/FormattedTextData.cs new file mode 100644 index 000000000..9d9c804d3 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/FormattedTextData.cs @@ -0,0 +1,3 @@ +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record FormattedTextData(string? ArgumentsCollectionName, TextKind ArgumentsTextKind); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/GroupPropertyData.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/GroupPropertyData.cs new file mode 100644 index 000000000..4c8f596d9 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/GroupPropertyData.cs @@ -0,0 +1,6 @@ +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record GroupPropertyData(string GroupName, EquatableArray Properties, int DeclarationIndex); + diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyData.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyData.cs new file mode 100644 index 000000000..554d07f42 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyData.cs @@ -0,0 +1,3 @@ +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record PropertyData(SinglePropertyData? SinglePropertyData, GroupPropertyData? GroupPropertyData, int DeclarationIndex); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyDisplayOptions.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyDisplayOptions.cs new file mode 100644 index 000000000..4659f9e49 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyDisplayOptions.cs @@ -0,0 +1,3 @@ +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record PropertyDisplayOptions(string? UnitKey, string LocalizationKey, string? TooltipKey, string? GroupKey, TextKind ValueTextKind); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyFilter.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyFilter.cs new file mode 100644 index 000000000..880ce1c34 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/PropertyFilter.cs @@ -0,0 +1,3 @@ +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record PropertyFilter(bool IsEnabled, string FilterMethodName); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/RawContainerData.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/RawContainerData.cs new file mode 100644 index 000000000..b41a93b6c --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/RawContainerData.cs @@ -0,0 +1,5 @@ +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record RawContainerData(string ContainerName, string Namespace, EquatableArray Properties); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/SinglePropertyData.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/SinglePropertyData.cs new file mode 100644 index 000000000..a418b9e28 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Model/SinglePropertyData.cs @@ -0,0 +1,3 @@ +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; + +internal sealed record SinglePropertyData(string Name, bool IsString, bool IsNullable, DataElementTypes DataElementType, PropertyDisplayOptions DisplayOptions, PropertyFilter PropertyFilter, FormattedTextData FormattedTextData, int DeclarationIndex); diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/PropertyHelper.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/PropertyHelper.cs new file mode 100644 index 000000000..d15c2a3dd --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/PropertyHelper.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using WoWsShipBuilder.Data.Generator.DataElementGenerator.Model; +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; + +internal static class PropertyHelper +{ + private const string DataElementNamespace = "global::WoWsShipBuilder.DataElements"; + + public static FormattedTextData ExtractFormattedTextOptions(AttributeData dataElementAttribute) + { + return new( + dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "ArgumentsCollectionName").Value.Value?.ToString(), + (TextKind)(dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "ArgumentsTextKind").Value.Value ?? TextKind.Plain)); + } + + public static PropertyDisplayOptions ExtractDisplayOptions(ISymbol propertySymbol, AttributeData dataElementAttribute) + { + return new( + dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "UnitKey").Value.Value?.ToString(), + dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "LocalizationKeyOverride").Value.Value?.ToString() ?? propertySymbol.Name, + dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "TooltipKey").Value.Value?.ToString(), + dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "GroupKey").Value.Value?.ToString(), + (TextKind)(dataElementAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "ValueTextKind").Value.Value ?? TextKind.Plain)); + } + + public static PropertyFilter ExtractFilterOptions(IPropertySymbol propertySymbol) + { + var filterAttribute = propertySymbol.FindAttributeOrDefault("WoWsShipBuilder.DataElements.DataElementAttributes.DataElementFilteringAttribute"); + if (filterAttribute is null) + { + return new(true, $"{DataElementNamespace}.DataContainerBase.ShouldAdd"); + } + + var isEnabled = (bool)filterAttribute.ConstructorArguments[0].Value!; + var filterMethodName = filterAttribute.ConstructorArguments[1].Value?.ToString() ?? $"{DataElementNamespace}.DataContainerBase.ShouldAdd"; + return new(isEnabled, filterMethodName); + } +} diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/Rules.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Rules.cs new file mode 100644 index 000000000..e6590f6be --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/Rules.cs @@ -0,0 +1,58 @@ +using Microsoft.CodeAnalysis; + +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; + +internal static class Rules +{ + public const string DataElementCategory = "DataElement"; + + public const string InvalidDataElementTypeId = "SB0001"; + + public const string MissingSecondaryDataElementTypeId = "SB0002"; + + public const string GroupKeyMissingId = "SB1001"; + + public const string MissingAttributeParametersId = "SB1002"; + + public const string IncompatibleAttributeParametersId = "SB1003"; + + public static readonly DiagnosticDescriptor InvalidDataElementTypeRule = new( + id: InvalidDataElementTypeId, + title: "Invalid DataElementType", + messageFormat: "The specified DataElementType is not valid: {0}", + category: DataElementCategory, + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MissingSecondaryDataElementTypeRule = new( + id: MissingSecondaryDataElementTypeId, + title: "Missing DataElementType specification", + messageFormat: "When using DataElementTypes.Grouped, a second type must be specified", + category: DataElementCategory, + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor GroupKeyMissingRule = new( + id: GroupKeyMissingId, + title: "Missing GroupKey", + messageFormat: "When using DataElementTypes.Grouped, a GroupKey must be specified", + category: DataElementCategory, + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MissingAttributeParametersRule = new( + id: MissingAttributeParametersId, + title: "Missing attribute parameters", + messageFormat: "The following attribute parameters are required with the specified DataElementType but not specified: {0}", + category: DataElementCategory, + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor IncompatibleAttributeParametersRule = new( + id: IncompatibleAttributeParametersId, + title: "Incompatible attribute parameters", + messageFormat: "The following attribute parameters are incompatible with the specified DataElementType: {0}", + category: DataElementCategory, + DiagnosticSeverity.Warning, + isEnabledByDefault: true); +} diff --git a/WoWsShipBuilder.Data.Generator/DataElementGenerator/TextKind.cs b/WoWsShipBuilder.Data.Generator/DataElementGenerator/TextKind.cs new file mode 100644 index 000000000..7433c5d63 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/DataElementGenerator/TextKind.cs @@ -0,0 +1,8 @@ +namespace WoWsShipBuilder.Data.Generator.DataElementGenerator; + +internal enum TextKind +{ + Plain, + LocalizationKey, + AppLocalizationKey, +} diff --git a/WoWsShipBuilder.Data.Generator/DataElementSourceGenerator.cs b/WoWsShipBuilder.Data.Generator/DataElementSourceGenerator.cs deleted file mode 100644 index fbbed4346..000000000 --- a/WoWsShipBuilder.Data.Generator/DataElementSourceGenerator.cs +++ /dev/null @@ -1,445 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -using WoWsShipBuilder.Data.Generator.Attributes; -using WoWsShipBuilder.Data.Generator.Internals; - -namespace WoWsShipBuilder.Data.Generator; - -[Generator] -public class DataElementSourceGenerator : IIncrementalGenerator -{ - private const string DataContainerBaseName = "DataContainerBase"; - private const string ProjectileDataContainer = "ProjectileDataContainer"; - private const string GeneratedMethodName = "UpdateDataElements"; - private const string DataElementCollectionName = "DataElements"; - private const string Indentation = " "; - private const string IfIndentation = " "; - - private static readonly DiagnosticDescriptor MissingAttributeError = new(id: "SB001", - title: "A required secondary attribute is missing", - messageFormat: "Couldn't find the required attribute property {0} for the DataElement type {1}", - category: "Generator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static readonly DiagnosticDescriptor InvalidTypeEnumError = new(id: "SB002", - title: "The enum type is invalid", - messageFormat: "The enum type for the property {0} doesn't exist", - category: "Generator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static readonly DiagnosticDescriptor GenerationError = new(id: "SB003", - title: "Error during source generation", - messageFormat: "There was an exception during source generation: {0}", - category: "Generator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static readonly DiagnosticDescriptor TooManyIterationsError = new(id: "SB004", - title: "Too many iterations", - messageFormat: "Too many iteration for the grouped data element", - category: "Generator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - private static readonly DiagnosticDescriptor GroupedMissingDefinitionError = new(id: "SB004", - title: "Grouped require secondary type definition", - messageFormat: "The property {0} is missing its own type definition. Add a type definition with the use of |.", - category: "Generator", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var dataClasses = context.SyntaxProvider.CreateSyntaxProvider(IsDataContainerRecord, GetRecordTypeOrNull) - .Where(type => type is not null) - .Select(GetPropertiesWithAttributes) - .Where(x => x.properties.Count > 0) - .Collect(); - - context.RegisterSourceOutput(dataClasses, GenerateCode); - } - - private static bool IsDataContainerRecord(SyntaxNode syntaxNode, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - if (syntaxNode is not RecordDeclarationSyntax recordSyntax) - { - return false; - } - - bool baseName = recordSyntax.BaseList?.Types.ToString().Contains(DataContainerBaseName) ?? false; - bool projectileDataContainerName = recordSyntax.BaseList?.Types.ToString().Contains(ProjectileDataContainer) ?? false; - return recordSyntax.Modifiers.ToString().Contains("partial") && (baseName || projectileDataContainerName); - } - - private static ITypeSymbol? GetRecordTypeOrNull(GeneratorSyntaxContext context, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var recordSyntax = (RecordDeclarationSyntax)context.Node; - return context.SemanticModel.GetDeclaredSymbol(recordSyntax); - } - - private static (string className, string classNamespace, List properties) GetPropertiesWithAttributes(ITypeSymbol? symbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var name = symbol!.Name; - var dataNamespace = symbol.ContainingNamespace.ToDisplayString(); - var properties = symbol.GetMembers().OfType().Where(prop => prop.GetAttributes().Any(attr => attr.AttributeClass!.Name == nameof(AttributeGenerator.DataElementTypeAttribute))).ToList(); - return (name, dataNamespace, properties); - } - - private static void GenerateCode(SourceProductionContext context, ImmutableArray<(string className, string classNamespace, List properties)> dataRecords) - { - // context.AddSource("test.g.cs", SourceText.From("//" + string.Join(" - ",dataRecords.Select(x => x.Name)), Encoding.UTF8)); - foreach (var dataRecord in dataRecords) - { - var properties = dataRecord.properties; - var classStart = $@" -using System; -using System.Collections.Generic; -using WoWsShipBuilder.DataElements.DataElements; - -namespace {dataRecord.classNamespace}; - -#nullable enable -public partial record {dataRecord.className} -{{ - private void {GeneratedMethodName}() - {{ - {DataElementCollectionName}.Clear(); -"; - const string classEnd = @" - } -} -#nullable restore -"; - var builder = new StringBuilder(classStart); - - while (properties.Any()) - { - var prop = properties.First(); - var propertyAttributes = prop.GetAttributes(); - - var typeAttribute = propertyAttributes.FirstOrDefault(); - if (typeAttribute is not null) - { - if (typeAttribute.ConstructorArguments.IsEmpty) - { - context.ReportDiagnostic(Diagnostic.Create(InvalidTypeEnumError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), prop.Name)); - properties.RemoveAt(0); - continue; - } - - try - { - var (code, additionalIndexes) = GenerateCode(context, typeAttribute, prop, propertyAttributes, properties, DataElementCollectionName, 0); - builder.Append(code); - builder.AppendLine(); - foreach (var index in additionalIndexes.OrderByDescending(x => x)) - { - properties.RemoveAt(index); - } - } - catch (Exception e) - { - context.ReportDiagnostic(Diagnostic.Create(GenerationError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), e.Message + " - " + e.TargetSite)); - break; - } - } - } - - builder.Append(classEnd); - context.AddSource($"{dataRecord.className}.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8)); - } - } - - private static (string code, List additionalIndexes) GenerateCode(SourceProductionContext context, AttributeData typeAttribute, IPropertySymbol currentProp, ImmutableArray propertyAttributes, List properties, string collectionName, int iterationCounter, bool isGroup = false) - { - var additionalPropIndexes = new List(); - if (iterationCounter > 10) - { - context.ReportDiagnostic(Diagnostic.Create(TooManyIterationsError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation())); - additionalPropIndexes.Add(0); - return("", additionalPropIndexes); - } - - var builder = new StringBuilder(); - - var type = (DataElementTypes)typeAttribute.ConstructorArguments[0].Value!; - - if (isGroup) - { - type &= ~DataElementTypes.Grouped; - // Grouped element is missing the element own type. Return and add error diagnostic - if (type == 0) - { - context.ReportDiagnostic(Diagnostic.Create(GroupedMissingDefinitionError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), currentProp.Name)); - additionalPropIndexes.Add(0); - return("", additionalPropIndexes); - } - } - - switch (type) - { - case DataElementTypes.Value: - builder.Append(GenerateValueRecord(currentProp, typeAttribute, propertyAttributes, collectionName)); - builder.AppendLine(); - additionalPropIndexes.Add(0); - break; - case DataElementTypes.KeyValue: - builder.Append(GenerateKeyValueRecord(currentProp, typeAttribute, propertyAttributes, collectionName)); - builder.AppendLine(); - additionalPropIndexes.Add(0); - break; - case DataElementTypes.KeyValueUnit: - builder.Append(GenerateKeyValueUnitRecord(context, currentProp, typeAttribute, propertyAttributes, collectionName)); - builder.AppendLine(); - additionalPropIndexes.Add(0); - break; - case DataElementTypes.Tooltip: - builder.Append(GenerateTooltipRecord(context, currentProp, typeAttribute, propertyAttributes, collectionName)); - builder.AppendLine(); - additionalPropIndexes.Add(0); - break; - case DataElementTypes.FormattedText: - builder.Append(GenerateFormattedTextRecord(context, currentProp, typeAttribute, propertyAttributes, collectionName)); - builder.AppendLine(); - additionalPropIndexes.Add(0); - break; - case { } when (type & DataElementTypes.Grouped) == DataElementTypes.Grouped: - var (code, additionalIndexes) = GenerateGroupedRecord(context, typeAttribute, properties, collectionName, iterationCounter); - builder.Append(code); - builder.AppendLine(); - additionalPropIndexes.AddRange(additionalIndexes); - break; - default: - context.ReportDiagnostic(Diagnostic.Create(InvalidTypeEnumError, Location.None, currentProp.Name)); - break; - } - - return (builder.ToString(), additionalPropIndexes); - } - - private static (string code, List additionalIndexes) GenerateGroupedRecord(SourceProductionContext context, AttributeData typeAttr, List properties, string collectionName, int iterationCounter) - { - - var groupName = (string?)typeAttr.NamedArguments.First(arg => arg.Key == "GroupKey").Value.Value; - - if (string.IsNullOrWhiteSpace(groupName)) - { - context.ReportDiagnostic(Diagnostic.Create(MissingAttributeError, typeAttr.ApplicationSyntaxReference!.GetSyntax().GetLocation(), "GroupKey", "GroupedDataElement")); - return (string.Empty, new List() { 0 }); - } - - var builder = new StringBuilder(); - - builder.Append($@"{Indentation}var {groupName}List = new List();"); - builder.AppendLine(); - - var groupProperties = properties.Where(prop => prop.GetAttributes().Any(attribute => attribute.AttributeClass!.Name.Contains("DataElementType") && ((DataElementTypes)attribute.ConstructorArguments[0].Value!).HasFlag(DataElementTypes.Grouped) && attribute.NamedArguments.Any(arg => arg.Key == "GroupKey" && (arg.Value.Value?.Equals(groupName) ?? false)))).ToList(); - - var indexList = groupProperties.Select(singleProperty => properties.IndexOf(singleProperty)).ToList(); - - while (groupProperties.Any()) - { - var currentGroupProp = groupProperties.First(); - var currentGroupPropertyAttributes = currentGroupProp.GetAttributes(); - - //exclude the group attribute, to avoid infinite recursion. Need to find a better way to skip the Group type. - var typeAttribute = currentGroupPropertyAttributes.FirstOrDefault(); - if (typeAttribute is not null) - { - if (typeAttribute.ConstructorArguments.IsEmpty) - { - context.ReportDiagnostic(Diagnostic.Create(InvalidTypeEnumError, Location.None, currentGroupProp.Name)); - groupProperties.RemoveAt(0); - continue; - } - - var (code, additionalIndexes) = GenerateCode(context, typeAttribute, currentGroupProp, currentGroupPropertyAttributes, groupProperties, $"{groupName}List", iterationCounter++, true); - builder.Append(code); - foreach (var index in additionalIndexes.OrderByDescending(x => x)) - { - groupProperties.RemoveAt(index); - } - } - } - - builder.Append($@"{IfIndentation}if ({groupName}List.Count > 0)"); - builder.AppendLine(); - builder.Append(@$"{Indentation}{collectionName}.Add(new GroupedDataElement(""ShipStats_{groupName}"", {groupName}List));"); - - return (builder.ToString(), indexList); - } - - private static string GenerateFormattedTextRecord(SourceProductionContext context, IPropertySymbol property, AttributeData typeAttribute, ImmutableArray propertyAttributes, string collectionName) - { - var name = property.Name; - var valuesProperty = (string?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "ValuesPropertyName").Value.Value; - if (string.IsNullOrWhiteSpace(valuesProperty)) - { - context.ReportDiagnostic(Diagnostic.Create(MissingAttributeError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), "ValuesPropertyName", "TooltipDataElement")); - return string.Empty; - } - var isKeyLocalization = (bool?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsValueLocalizationKey").Value.Value ?? false; - var isListLocalization = (bool?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "ArePropertyNameValuesKeys").Value.Value ?? false; - - var isKeyAppLocalization = (bool?) typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsValueAppLocalization").Value.Value ?? false; - var isListAppLocalization = (bool?) typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsPropertyNameValuesAppLocalization").Value.Value ?? false; - - - var filter = GetFilterAttributeData(property.Name, propertyAttributes); - var builder = new StringBuilder(); - builder.Append(filter); - builder.AppendLine(); - builder.Append($@"{Indentation}{collectionName}.Add(new FormattedTextDataElement({name}, {valuesProperty}, {isKeyLocalization.ToString().ToLower()}, {isKeyAppLocalization.ToString().ToLower()}, {isListLocalization.ToString().ToLower()}, {isListAppLocalization.ToString().ToLower()}));"); - return builder.ToString(); - } - - private static string GenerateTooltipRecord(SourceProductionContext context, IPropertySymbol property, AttributeData typeAttribute, ImmutableArray propertyAttributes, string collectionName) - { - var name = property.Name; - var propertyProcessingAddition = GetPropertyAddition(property); - - var tooltip = (string?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "TooltipKey").Value.Value; - if (string.IsNullOrWhiteSpace(tooltip)) - { - context.ReportDiagnostic(Diagnostic.Create(MissingAttributeError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), "TooltipKey", "TooltipDataElement")); - return string.Empty; - } - - var unit = (string?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "UnitKey").Value.Value ?? ""; - if (!string.IsNullOrWhiteSpace(unit)) - { - unit = "Unit_" + unit; - } - - var localizationKey = (string?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "NameLocalizationKey").Value.Value ?? name; - - var filter = GetFilterAttributeData(property.Name, propertyAttributes); - - var builder = new StringBuilder(); - builder.Append(filter); - builder.AppendLine(); - builder.Append($@"{Indentation}{collectionName}.Add(new TooltipDataElement(""ShipStats_{localizationKey}"", {name}{propertyProcessingAddition}, ""ShipStats_{tooltip}"", ""{unit}""));"); - return builder.ToString(); - } - - private static string GenerateKeyValueUnitRecord(SourceProductionContext context, IPropertySymbol property, AttributeData typeAttribute, ImmutableArray propertyAttributes, string collectionName) - { - var name = property.Name; - var propertyProcessingAddition = GetPropertyAddition(property); - - var unit = (string?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "UnitKey").Value.Value; - if (string.IsNullOrWhiteSpace(unit)) - { - context.ReportDiagnostic(Diagnostic.Create(MissingAttributeError, typeAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), "UnitKey", "KeyValueUnitDataElement")); - return string.Empty; - } - - var localizationKey = (string?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "NameLocalizationKey").Value.Value ?? name; - - var filter = GetFilterAttributeData(property.Name, propertyAttributes); - - var builder = new StringBuilder(); - builder.Append(filter); - builder.AppendLine(); - builder.Append($@"{Indentation}{collectionName}.Add(new KeyValueUnitDataElement(""ShipStats_{localizationKey}"", {name}{propertyProcessingAddition}, ""Unit_{unit}""));"); - return builder.ToString(); - } - - private static string GenerateKeyValueRecord(IPropertySymbol property, AttributeData typeAttribute, ImmutableArray propertyAttributes, string collectionName) - { - var name = property.Name; - var propertyProcessingAddition = GetPropertyAddition(property); - var filter = GetFilterAttributeData(property.Name, propertyAttributes); - - var isKeyLocalization = (bool?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsValueLocalizationKey").Value.Value ?? false; - var isKeyAppLocalization = (bool?) typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsValueAppLocalization").Value.Value ?? false; - - var localizationKey = (string?) typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "NameLocalizationKey").Value.Value ?? name; - - var builder = new StringBuilder(); - builder.Append(filter); - builder.AppendLine(); - builder.Append($@"{Indentation}{collectionName}.Add(new KeyValueDataElement(""ShipStats_{localizationKey}"", {name}{propertyProcessingAddition}, {isKeyLocalization.ToString().ToLower()}, {isKeyAppLocalization.ToString().ToLower()}));"); - return builder.ToString(); - } - - private static string GenerateValueRecord(IPropertySymbol property, AttributeData typeAttribute, ImmutableArray propertyAttributes, string collectionName) - { - var propertyProcessingAddition = GetPropertyAddition(property); - var filter = GetFilterAttributeData(property.Name, propertyAttributes); - - var isKeyLocalization = (bool?)typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsValueLocalizationKey").Value.Value ?? false; - var isKeyAppLocalization = (bool?) typeAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "IsValueAppLocalization").Value.Value ?? false; - - var builder = new StringBuilder(); - builder.Append(filter); - builder.AppendLine(); - builder.Append($@"{Indentation}{collectionName}.Add(new ValueDataElement({property.Name}{propertyProcessingAddition}, {isKeyLocalization.ToString().ToLower()}, {isKeyAppLocalization.ToString().ToLower()}));"); - return builder.ToString(); - } - - private static string GetPropertyAddition(IPropertySymbol property) - { - var propertyProcessingAddition = string.Empty; - if (!property.Type.Name.Equals("string", StringComparison.InvariantCultureIgnoreCase)) - { - propertyProcessingAddition = ".ToString()"; - } - else if (property.NullableAnnotation == NullableAnnotation.Annotated) - { - propertyProcessingAddition = " ?? \"null\""; - } - - return propertyProcessingAddition; - } - - private static string GetFilterString(string propertyName, bool filterEnabled, string filterName) - { - var filter = string.Empty; - if (!filterEnabled) - { - return filter; - } - - if (string.IsNullOrWhiteSpace(filterName)) - { - return @$"{IfIndentation}if ({DataContainerBaseName}.ShouldAdd({propertyName}))"; - } - - filter = @$"{IfIndentation}if ({filterName}({propertyName}))"; - return filter; - } - - private static string GetFilterAttributeData(string propertyName, ImmutableArray propertyAttributes) - { - var attribute = propertyAttributes.FirstOrDefault(attribute => attribute.AttributeClass!.Name.Contains("DataElementFilteringAttribute")); - - // if there is no attribute, returns active filter, no name for custom filter. - if (attribute is null) - { - return GetFilterString(propertyName, true, ""); - } - - var filterEnabled = (bool)attribute.ConstructorArguments[0].Value!; - string filterName = string.Empty; - if (attribute.ConstructorArguments.Length > 1) - { - filterName = attribute.ConstructorArguments[1].Value?.ToString() ?? string.Empty; - } - - return GetFilterString(propertyName, filterEnabled, filterName); - } -} diff --git a/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/FieldData.cs b/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/FieldData.cs new file mode 100644 index 000000000..a73d71e5c --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/FieldData.cs @@ -0,0 +1,3 @@ +namespace WoWsShipBuilder.Data.Generator.PropertyChangedGenerator; + +internal sealed record FieldData(string FieldName, string PropertyName, string Type, string ClassName, string Namespace, string VisibilityModifier); diff --git a/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/PropertyChangedSourceGenerator.cs b/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/PropertyChangedSourceGenerator.cs new file mode 100644 index 000000000..428e32087 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/PropertyChangedSourceGenerator.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using WoWsShipBuilder.Data.Generator.Utilities; + +namespace WoWsShipBuilder.Data.Generator.PropertyChangedGenerator; + +[Generator(LanguageNames.CSharp)] +public class PropertyChangedSourceGenerator : IIncrementalGenerator +{ + private static readonly SymbolDisplayFormat FullyQualifiedWithNullableFormat = SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var properties = context.SyntaxProvider.ForAttributeWithMetadataName("WoWsShipBuilder.Infrastructure.Utility.ObservableAttribute", CouldBeObservableField, ProcessField); + context.RegisterSourceOutput(properties, GeneratePropertyCode); + } + + private static bool CouldBeObservableField(SyntaxNode node, CancellationToken _) + { + return node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } }; + } + + private static FieldData ProcessField(GeneratorAttributeSyntaxContext context, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + var fieldSymbol = (IFieldSymbol)context.TargetSymbol; + var propertyName = char.ToUpper(fieldSymbol.Name[0]) + fieldSymbol.Name[1..]; + var type = fieldSymbol.Type.ToDisplayString(FullyQualifiedWithNullableFormat); + var className = fieldSymbol.ContainingType.Name; + var containingNamespace = fieldSymbol.ContainingType.ContainingNamespace.ToFullyQualifiedMetadataName(); + var visibility = (Visibility)(context.Attributes[0].NamedArguments.FirstOrDefault(arg => arg.Key == "SetterVisibility").Value.Value ?? Visibility.Public); + var visibilityModifier = visibility switch + { + Visibility.Public => string.Empty, + Visibility.Protected => "protected ", + Visibility.Internal => "internal ", + Visibility.Private => "private ", + _ => throw new InvalidOperationException(), + }; + + token.ThrowIfCancellationRequested(); + return new(fieldSymbol.Name, propertyName, type, className, containingNamespace, visibilityModifier); + } + + private static void GeneratePropertyCode(SourceProductionContext context, FieldData fieldData) + { + var builder = new SourceBuilder(); + builder.Line("// ").Line("#nullable enable"); + using (builder.Namespace(fieldData.Namespace)) + { + using (builder.Class(fieldData.ClassName)) + { + using (builder.Block($"public {fieldData.Type} {fieldData.PropertyName}")) + { + builder.Line($"get => this.{fieldData.FieldName};"); + builder.Line($"{fieldData.VisibilityModifier}set => global::ReactiveUI.IReactiveObjectExtensions.RaiseAndSetIfChanged(this, ref this.{fieldData.FieldName}, value);"); + } + } + } + + context.AddSource($"{fieldData.Namespace}.{fieldData.ClassName}_{fieldData.FieldName}.g.cs", builder.ToString()); + } +} diff --git a/WoWsShipBuilder.Data.Generator/Internals/Visibility.cs b/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/Visibility.cs similarity index 50% rename from WoWsShipBuilder.Data.Generator/Internals/Visibility.cs rename to WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/Visibility.cs index ce0640084..2463334c4 100644 --- a/WoWsShipBuilder.Data.Generator/Internals/Visibility.cs +++ b/WoWsShipBuilder.Data.Generator/PropertyChangedGenerator/Visibility.cs @@ -1,4 +1,4 @@ -namespace WoWsShipBuilder.Data.Generator.Internals; +namespace WoWsShipBuilder.Data.Generator.PropertyChangedGenerator; internal enum Visibility { diff --git a/WoWsShipBuilder.Data.Generator/PropertyChangedSourceGenerator.cs b/WoWsShipBuilder.Data.Generator/PropertyChangedSourceGenerator.cs deleted file mode 100644 index cf8094544..000000000 --- a/WoWsShipBuilder.Data.Generator/PropertyChangedSourceGenerator.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -using WoWsShipBuilder.Data.Generator.Internals; - -namespace WoWsShipBuilder.Data.Generator; - -[Generator] -public class PropertyChangedSourceGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var viewModels = context.SyntaxProvider.CreateSyntaxProvider(ViewModelFilter, GetViewModelOrNull) - .Where(type => type is not null) - .Select(GetObservableFields!) - .Where(result => result.Items.Count > 0) - .Collect(); - context.RegisterSourceOutput(viewModels, GenerateCode); - } - - private static bool ViewModelFilter(SyntaxNode syntaxNode, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - if (syntaxNode is not ClassDeclarationSyntax classDeclaration) - { - return false; - } - - return classDeclaration.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword)); - } - - private static ITypeSymbol? GetViewModelOrNull(GeneratorSyntaxContext context, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - var typeDeclaration = (TypeDeclarationSyntax)context.Node; - return context.SemanticModel.GetDeclaredSymbol(typeDeclaration); - } - - private static SourceGenFilterResult GetObservableFields(ITypeSymbol symbol, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - string className = symbol.Name; - string classNamespace = symbol.ContainingNamespace.ToDisplayString(); - List fields = symbol.GetMembers().OfType().Where(field => field.GetAttributes().Any(attr => attr.AttributeClass!.Name == "ObservableAttribute")).ToList(); - return new(className, classNamespace, fields); - } - - private static void GenerateCode(SourceProductionContext context, ImmutableArray viewmodels) - { - var logMessages = new StringBuilder(); - foreach (var viewmodel in viewmodels) - { - var classStart = $@" -using ReactiveUI; - -namespace {viewmodel.ElementNamespace}; - -#nullable enable -public partial class {viewmodel.ElementName} -{{ -"; - const string classEnd = @" -} -#nullable restore -"; - - var classBuilder = new StringBuilder(classStart); - foreach (var field in viewmodel.Items) - { - var propertyName = char.ToUpper(field.Name.First()) + field.Name.Substring(1); - var fieldAttribute = field.GetAttributes().First(a => a.AttributeClass!.Name == "ObservableAttribute"); - var setterVisibility = (Visibility)(fieldAttribute.NamedArguments.FirstOrDefault(arg => arg.Key == "SetterVisibility").Value.Value ?? Visibility.Public); - var setterVisibilityString = setterVisibility switch - { - Visibility.Public => string.Empty, - Visibility.Protected => "protected ", - Visibility.Internal => "internal ", - Visibility.Private => "private ", - _ => throw new InvalidOperationException(), - }; - logMessages.AppendLine("#4"); - classBuilder.AppendLine($@" - public {field.Type} {propertyName} - {{ - get => this.{field.Name}; - {setterVisibilityString}set => this.RaiseAndSetIfChanged(ref this.{field.Name}, value); - }}"); - } - - classBuilder.AppendLine(classEnd); - context.AddSource($"{viewmodel.ElementName}.g.cs", SourceText.From(classBuilder.ToString(), Encoding.UTF8)); - } - } - - private record struct SourceGenFilterResult(string ElementName, string ElementNamespace, List Items); -} diff --git a/WoWsShipBuilder.Data.Generator/Utilities/EquatableArray.cs b/WoWsShipBuilder.Data.Generator/Utilities/EquatableArray.cs new file mode 100644 index 000000000..b262dcc28 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/Utilities/EquatableArray.cs @@ -0,0 +1,196 @@ +// Based on the EquatableArray{T} implementation from CommunityToolkit/dotnet +// see https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs for the original implementation + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace WoWsShipBuilder.Data.Generator.Utilities; + +/// +/// Extensions for . +/// +internal static class EquatableArray +{ + public static EquatableArray ToEquatableArray(this ImmutableArray array) + where T : IEquatable + { + return new(array); + } + + public static EquatableArray ToEquatableArray(this IEnumerable enumerable) + where T : IEquatable + { + return new(enumerable.ToImmutableArray()); + } +} + +/// +/// An immutable, equatable array. This is equivalent to but with value equality support. +/// +/// The type of values in the array. +internal readonly struct EquatableArray : IEquatable>, IEnumerable + where T : IEquatable +{ + /// + /// The underlying array. + /// + private readonly T[]? array; + + public EquatableArray(ImmutableArray array) + { + this.array = Unsafe.As, T[]?>(ref array); + } + + public static readonly EquatableArray Empty = new(); + + /// + /// Gets a reference to an item at a specified position within the array. + /// + /// The index of the item to retrieve a reference to. + /// A reference to an item at a specified position within the array. + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this.AsImmutableArray().ItemRef(index); + } + + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.AsImmutableArray().IsEmpty; + } + + /// + public bool Equals(EquatableArray array) + { + return this.AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + /// + public override int GetHashCode() + { + if (this.array is not { } array) + { + return 0; + } + + HashCode hashCode = default; + + foreach (var item in array) + { + hashCode.Add(item); + } + + return hashCode.ToHashCode(); + } + + /// + /// Gets an instance from the current . + /// + /// The from the current . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return Unsafe.As>(ref Unsafe.AsRef(in this.array)); + } + + /// + /// Creates an instance from a given . + /// + /// The input instance. + /// An instance from a given . + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return this.AsImmutableArray().AsSpan(); + } + + /// + /// Copies the contents of this instance to a mutable array. + /// + /// The newly instantiated array. + public T[] ToArray() + { + return this.AsImmutableArray().ToArray(); + } + + /// + /// Gets an value to traverse items in the current array. + /// + /// An value to traverse items in the current array. + public ImmutableArray.Enumerator GetEnumerator() + { + return this.AsImmutableArray().GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this.AsImmutableArray()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this.AsImmutableArray()).GetEnumerator(); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator EquatableArray(ImmutableArray array) + { + return FromImmutableArray(array); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator ImmutableArray(EquatableArray array) + { + return array.AsImmutableArray(); + } + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) + { + return left.Equals(right); + } + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) + { + return !left.Equals(right); + } +} diff --git a/WoWsShipBuilder.Data.Generator/Utilities/HashCode.cs b/WoWsShipBuilder.Data.Generator/Utilities/HashCode.cs new file mode 100644 index 000000000..3169f4b3e --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/Utilities/HashCode.cs @@ -0,0 +1,180 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +#pragma warning disable CS0809 + +namespace WoWsShipBuilder.Data.Generator.Utilities; + +/// +/// A polyfill for the HashCode struct from .NET 6 with the methods required for . +/// +internal struct HashCode +{ + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private static readonly uint Seed = GenerateGlobalSeed(); + + private uint v1, v2, v3, v4; + private uint queue1, queue2, queue3; + private uint length; + + /// + /// Adds a single value to the current hash. + /// + /// The type of the value to add into the hash code. + /// The value to add into the hash code. + public void Add(T value) + { + this.Add(value?.GetHashCode() ?? 0); + } + + /// + /// Gets the resulting hashcode from the current instance. + /// + /// The resulting hashcode from the current instance. + public int ToHashCode() + { + uint length = this.length; + uint position = length % 4; + uint hash = length < 4 ? MixEmptyState() : MixState(this.v1, this.v2, this.v3, this.v4); + + hash += length * 4; + + if (position > 0) + { + hash = QueueRound(hash, this.queue1); + + if (position > 1) + { + hash = QueueRound(hash, this.queue2); + + if (position > 2) + { + hash = QueueRound(hash, this.queue3); + } + } + } + + hash = MixFinal(hash); + + return (int)hash; + } + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => throw new NotSupportedException(); + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => throw new NotSupportedException(); + + private static uint GenerateGlobalSeed() + { + var bytes = new byte[4]; + using (var generator = RandomNumberGenerator.Create()) + { + generator.GetBytes(bytes); + } + + return BitConverter.ToUInt32(bytes, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = Seed + Prime1 + Prime2; + v2 = Seed + Prime2; + v3 = Seed; + v4 = Seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + (input * Prime2), 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + (queuedValue * Prime3), 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixEmptyState() + { + return Seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + + return hash; + } + + private void Add(int value) + { + uint val = (uint)value; + uint previousLength = this.length++; + uint position = previousLength % 4; + + if (position == 0) + { + this.queue1 = val; + } + else if (position == 1) + { + this.queue2 = val; + } + else if (position == 2) + { + this.queue3 = val; + } + else + { + if (previousLength == 3) + { + Initialize(out this.v1, out this.v2, out this.v3, out this.v4); + } + + this.v1 = Round(this.v1, this.queue1); + this.v2 = Round(this.v2, this.queue2); + this.v3 = Round(this.v3, this.queue3); + this.v4 = Round(this.v4, val); + } + } + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + { + return (value << offset) | (value >> (32 - offset)); + } +} diff --git a/WoWsShipBuilder.Data.Generator/Utilities/SourceBuilder.cs b/WoWsShipBuilder.Data.Generator/Utilities/SourceBuilder.cs new file mode 100644 index 000000000..23f400564 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/Utilities/SourceBuilder.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace WoWsShipBuilder.Data.Generator.Utilities; + +internal sealed class SourceBuilder +{ + private const int IndentSize = 4; + + private readonly StringBuilder sb; + + private int currentIndent; + + private LastAction lastAction; + + public SourceBuilder(StringBuilder? sb = null) + { + this.sb = sb ?? new StringBuilder(); + } + + private enum LastAction + { + None, + + BlockClose, + } + + public IDisposable Namespace(string ns) + { + return this.Block($"namespace {ns}"); + } + + public IDisposable Class(string className) + { + return this.Block($"public partial class {className}"); + } + + public IDisposable Record(string recordName) + { + return this.Block($"public partial record {recordName}"); + } + + public IDisposable Block(string? line = null) + { + this.BlockOpen(line); + return this.BlockCloseAction(); + } + + public IDisposable PragmaWarning(string warning) + { + this.AddBlankLineIfNeeded(); + this.Append($"#pragma warning disable {warning}").AppendNewLine(); + return new DisposableAction(() => this.Append($"#pragma warning restore {warning}").AppendNewLine()); + } + + public SourceBuilder DelimitedLines(string delimiter, params string[] lines) => this.DelimitedLines(delimiter, lines as IReadOnlyList); + + public SourceBuilder DelimitedLines(string delimiter, IEnumerable lines) => this.DelimitedLines(delimiter, lines.ToList()); + + public SourceBuilder DelimitedLines(string delimiter, IReadOnlyList lines) + { + _ = lines ?? throw new ArgumentNullException(nameof(lines)); + + this.AddBlankLineIfNeeded(); + + for (var i = 0; i < lines.Count; ++i) + { + this.AppendIndented(lines[i]); + + if (i < lines.Count - 1) + { + this.Append(delimiter); + } + + this.AppendNewLine(); + } + + return this; + } + + public SourceBuilder Line() + { + this.AddBlankLineIfNeeded(); + return this.AppendNewLine(); + } + + public SourceBuilder Line(string line) + { + this.AddBlankLineIfNeeded(); + return this.AppendIndentedLine(line); + } + + public SourceBuilder SpacedLine(string line) + { + this.AddBlankLineIfNeeded(); + this.AppendIndentedLine(line); + this.lastAction = LastAction.BlockClose; + return this; + } + + public SourceBuilder Lines(params string[] lines) + { + this.AddBlankLineIfNeeded(); + return this.DelimitedLines("", lines); + } + + public SourceBuilder Lines(IEnumerable lines) + { + this.AddBlankLineIfNeeded(); + return this.DelimitedLines("", lines.ToList()); + } + + public IDisposable Parens(string line, string? postfix = null) + { + this.AddBlankLineIfNeeded(); + this.AppendIndented(line).Append("(").AppendNewLine().IncreaseIndent(); + return new DisposableAction(() => this.DecreaseIndent().AppendIndented(")").Append(postfix).AppendNewLine()); + } + + public override string ToString() => this.sb.ToString(); + + private void BlockClose() + { + this.DecreaseIndent().AppendIndentedLine("}"); + this.lastAction = LastAction.BlockClose; + } + + private void BlockOpen(string? line = null) + { + this.AddBlankLineIfNeeded(); + + if (line is not null) + { + this.AppendIndentedLine(line); + } + + this.AppendIndentedLine("{").IncreaseIndent(); + } + + private SourceBuilder DecreaseIndent() + { + --this.currentIndent; + return this; + } + + private SourceBuilder IncreaseIndent() + { + ++this.currentIndent; + return this; + } + + private void AddBlankLineIfNeeded() + { + if (this.lastAction != LastAction.BlockClose) + { + return; + } + + this.lastAction = LastAction.None; + this.AppendNewLine(); + } + + private SourceBuilder Append(string? text) + { + if (text is not null) + { + this.sb.Append(text); + } + + return this; + } + + private SourceBuilder AppendIndent() + { + this.sb.Append(' ', this.currentIndent * IndentSize); + return this; + } + + private SourceBuilder AppendIndented(string text) => this.AppendIndent().Append(text); + + private SourceBuilder AppendIndentedLine(string text) => this.AppendIndent().Append(text).AppendNewLine(); + + private SourceBuilder AppendNewLine() + { + this.sb.AppendLine(); + return this; + } + + private IDisposable BlockCloseAction() => new DisposableAction(this.BlockClose); + + private sealed class DisposableAction : IDisposable + { + private readonly Action action; + + public DisposableAction(Action action) => this.action = action; + + public void Dispose() => this.action(); + } +} diff --git a/WoWsShipBuilder.Data.Generator/Utilities/SymbolExtensions.cs b/WoWsShipBuilder.Data.Generator/Utilities/SymbolExtensions.cs new file mode 100644 index 000000000..815b1c1d2 --- /dev/null +++ b/WoWsShipBuilder.Data.Generator/Utilities/SymbolExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace WoWsShipBuilder.Data.Generator.Utilities; + +internal static class SymbolExtensions +{ + private static readonly SymbolDisplayFormat FullyQualifiedWithoutGlobalFormat = SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted).AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + public static AttributeData? FindAttributeOrDefault(this ISymbol symbol, string fullAttributeName) + { + return symbol.GetAttributes().FirstOrDefault(attribute => attribute.AttributeClass?.ToDisplayString(FullyQualifiedWithoutGlobalFormat).Equals(fullAttributeName, StringComparison.Ordinal) == true); + } + + public static AttributeData FindAttribute(this ISymbol symbol, string fullAttributeName) + { + return symbol.FindAttributeOrDefault(fullAttributeName) ?? throw new KeyNotFoundException($"No attribute found with name {fullAttributeName}."); + } + + public static bool HasAttributeWithFullName(this ISymbol symbol, string fullAttributeName) + { + return symbol.FindAttributeOrDefault(fullAttributeName) != null; + } + + public static bool HasInterface(this INamedTypeSymbol symbol, INamedTypeSymbol interfaceType) + { + return symbol.AllInterfaces.Contains(interfaceType, SymbolEqualityComparer.Default); + } + + public static bool NamespaceContains(this INamedTypeSymbol symbol, string search) + { + return symbol.ContainingNamespace.ToDisplayString(FullyQualifiedWithoutGlobalFormat).IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; + } + + public static string ToLowerString(this bool value) => value.ToString().ToLowerInvariant(); + + public static string ToFullyQualifiedMetadataName(this ISymbol symbol) => symbol.ToDisplayString(FullyQualifiedWithoutGlobalFormat); +} diff --git a/WoWsShipBuilder.Data.Generator/WoWsShipBuilder.Data.Generator.csproj b/WoWsShipBuilder.Data.Generator/WoWsShipBuilder.Data.Generator.csproj index 339dbe19a..5d0f90414 100644 --- a/WoWsShipBuilder.Data.Generator/WoWsShipBuilder.Data.Generator.csproj +++ b/WoWsShipBuilder.Data.Generator/WoWsShipBuilder.Data.Generator.csproj @@ -7,11 +7,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/WoWsShipBuilder.DataElements/DataElements/DataContainerBase.cs b/WoWsShipBuilder.DataElements/DataContainerBase.cs similarity index 90% rename from WoWsShipBuilder.DataElements/DataElements/DataContainerBase.cs rename to WoWsShipBuilder.DataElements/DataContainerBase.cs index 18b77f1bb..f5248f1c1 100644 --- a/WoWsShipBuilder.DataElements/DataElements/DataContainerBase.cs +++ b/WoWsShipBuilder.DataElements/DataContainerBase.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace WoWsShipBuilder.DataElements.DataElements; +namespace WoWsShipBuilder.DataElements; public abstract record DataContainerBase { diff --git a/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementFilteringAttribute.cs b/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementFilteringAttribute.cs deleted file mode 100644 index a2aefd790..000000000 --- a/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementFilteringAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace WoWsShipBuilder.DataElements.DataElementAttributes; - -[AttributeUsage(AttributeTargets.Property)] -public class DataElementFilteringAttribute : Attribute -{ - public DataElementFilteringAttribute(bool enableFilterVisibility, string filterMethodName = "") - { - EnableFilterVisibility = enableFilterVisibility; - FilterMethodName = filterMethodName; - } - - public bool EnableFilterVisibility { get; } - - public string FilterMethodName { get; } -} diff --git a/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementTypeAttribute.cs b/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementTypeAttribute.cs deleted file mode 100644 index b7a8b5f6c..000000000 --- a/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementTypeAttribute.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; - -namespace WoWsShipBuilder.DataElements.DataElementAttributes; - -[AttributeUsage(AttributeTargets.Property)] -public class DataElementTypeAttribute : Attribute -{ - public DataElementTypeAttribute(DataElementTypes type) - { - Type = type; - } - - /// - /// Gets the type of the DataElement for the property marked by this attribute. /> - /// - public DataElementTypes Type { get; } - - /// - /// Gets or sets the unit localization key for the property marked by this attribute.
- /// Only valid for and . - ///
- public string? UnitKey { get; set; } - - /// - /// Gets or sets the property name localization key for the property marked by this attribute.
- /// Only valid for , , and . - ///
- public string? NameLocalizationKey { get; set; } - - /// - /// Gets or sets the tooltip localization key for the property marked by this attribute.
- /// Only valid for . - ///
- public string? TooltipKey { get; set; } - - /// - /// Gets or sets the group localization key and identifier for the property marked by this attribute.
- /// Only valid for . - ///
- public string? GroupKey { get; set; } - - /// - /// Gets or set the name of the property containing the list of values that will replace the placeholder. Requires the value of the property marked by this attribute to follow the specifications.
- /// Only valid for . - ///
- public string? ValuesPropertyName { get; set; } - - /// - /// Gets or sets if the value of the property marked by this attribute is a localization key.
- /// Only valid for , and - ///
- public bool IsValueLocalizationKey { get; set; } - - /// - /// Gets or sets if the values indicated by are localization keys.
- /// Only valid for - ///
- public bool ArePropertyNameValuesKeys { get; set; } - - /// - /// Gets or sets if the value of the property marked by this attribute is an app localization key.
- /// Only valid for , and - ///
- public bool IsValueAppLocalization { get; set; } - - /// - /// Gets or sets if the values indicated by are app localization keys.
- /// Only valid for - ///
- public bool IsPropertyNameValuesAppLocalization { get; set; } - -} diff --git a/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementTypes.cs b/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementTypes.cs deleted file mode 100644 index a7b83d491..000000000 --- a/WoWsShipBuilder.DataElements/DataElementAttributes/DataElementTypes.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace WoWsShipBuilder.DataElements.DataElementAttributes; - -[Flags] -public enum DataElementTypes -{ - KeyValue = 1, - KeyValueUnit = 2, - Value = 4, - Grouped = 8, - Tooltip = 16, - FormattedText = 32, -} diff --git a/WoWsShipBuilder.DataElements/DataElementTextKind.cs b/WoWsShipBuilder.DataElements/DataElementTextKind.cs new file mode 100644 index 000000000..eec521dc3 --- /dev/null +++ b/WoWsShipBuilder.DataElements/DataElementTextKind.cs @@ -0,0 +1,8 @@ +namespace WoWsShipBuilder.DataElements; + +public enum DataElementTextKind +{ + Plain, + LocalizationKey, + AppLocalizationKey, +} diff --git a/WoWsShipBuilder.DataElements/DataElements/FormattedTextDataElement.cs b/WoWsShipBuilder.DataElements/DataElements/FormattedTextDataElement.cs deleted file mode 100644 index 17ddeba30..000000000 --- a/WoWsShipBuilder.DataElements/DataElements/FormattedTextDataElement.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace WoWsShipBuilder.DataElements.DataElements; - -/// -/// A record that represent a formatted text data element. -/// -/// The text to format. Need to respect specifications. -/// The enumerable that will be used by . -/// If is a localization key and not actual text. -/// If are localization keys and not actual values. -public sealed record FormattedTextDataElement(string Text, IEnumerable Values, bool IsTextKey = false, bool IsTextAppLocalization = false, bool AreValuesKeys = false, bool AreValuesAppLocalization = false) : IDataElement; diff --git a/WoWsShipBuilder.DataElements/DataElements/KeyValueDataElement.cs b/WoWsShipBuilder.DataElements/DataElements/KeyValueDataElement.cs deleted file mode 100644 index 8e7f5eef7..000000000 --- a/WoWsShipBuilder.DataElements/DataElements/KeyValueDataElement.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace WoWsShipBuilder.DataElements.DataElements; - -/// -/// Record that represent a key value pair. Localization is left to the UI. -/// -/// The key of the element. -/// The value of the element. -/// If the value is a localizer key. -/// If the value is an app localization key. -public readonly record struct KeyValueDataElement(string Key, string Value, bool IsValueKey, bool IsValueAppLocalization) : IDataElement; diff --git a/WoWsShipBuilder.DataElements/DataElements/ValueDataElement.cs b/WoWsShipBuilder.DataElements/DataElements/ValueDataElement.cs deleted file mode 100644 index 74d3332b0..000000000 --- a/WoWsShipBuilder.DataElements/DataElements/ValueDataElement.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace WoWsShipBuilder.DataElements.DataElements; - -/// -/// A record that represent a single value. -/// -/// The value of the element. -/// If the value is a localizer key. -/// If the value is an app localization key. -public readonly record struct ValueDataElement(string Value, bool IsValueKey, bool IsValueAppLocalization) : IDataElement; diff --git a/WoWsShipBuilder.DataElements/FormattedTextDataElement.cs b/WoWsShipBuilder.DataElements/FormattedTextDataElement.cs new file mode 100644 index 000000000..795f89db5 --- /dev/null +++ b/WoWsShipBuilder.DataElements/FormattedTextDataElement.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace WoWsShipBuilder.DataElements; + +/// +/// A record that represent a formatted text data element. +/// +/// The text to format. Need to respect specifications. +/// The enumerable that will be used by . +/// The of the text. +/// The of the arguments. +public sealed record FormattedTextDataElement(string Text, IEnumerable Arguments, DataElementTextKind ValueTextKind = DataElementTextKind.Plain, DataElementTextKind ArgumentsTextKind = DataElementTextKind.Plain) : IDataElement; diff --git a/WoWsShipBuilder.DataElements/DataElements/GroupedDataElement.cs b/WoWsShipBuilder.DataElements/GroupedDataElement.cs similarity index 88% rename from WoWsShipBuilder.DataElements/DataElements/GroupedDataElement.cs rename to WoWsShipBuilder.DataElements/GroupedDataElement.cs index c9bf6245b..3d4048a66 100644 --- a/WoWsShipBuilder.DataElements/DataElements/GroupedDataElement.cs +++ b/WoWsShipBuilder.DataElements/GroupedDataElement.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace WoWsShipBuilder.DataElements.DataElements; +namespace WoWsShipBuilder.DataElements; /// /// A record that represent a group of that share a common group. diff --git a/WoWsShipBuilder.DataElements/DataElements/IDataElement.cs b/WoWsShipBuilder.DataElements/IDataElement.cs similarity index 76% rename from WoWsShipBuilder.DataElements/DataElements/IDataElement.cs rename to WoWsShipBuilder.DataElements/IDataElement.cs index eb73de959..7ed750277 100644 --- a/WoWsShipBuilder.DataElements/DataElements/IDataElement.cs +++ b/WoWsShipBuilder.DataElements/IDataElement.cs @@ -1,4 +1,4 @@ -namespace WoWsShipBuilder.DataElements.DataElements; +namespace WoWsShipBuilder.DataElements; /// /// Base interface that marks a type as DataElement that can represent data that is supposed to be automatically rendered. diff --git a/WoWsShipBuilder.DataElements/KeyValueDataElement.cs b/WoWsShipBuilder.DataElements/KeyValueDataElement.cs new file mode 100644 index 000000000..e144512cb --- /dev/null +++ b/WoWsShipBuilder.DataElements/KeyValueDataElement.cs @@ -0,0 +1,9 @@ +namespace WoWsShipBuilder.DataElements; + +/// +/// Record that represent a key value pair. Localization is left to the UI. +/// +/// The key of the element. +/// The value of the element. +/// The of the . +public readonly record struct KeyValueDataElement(string Key, string Value, DataElementTextKind ValueTextKind) : IDataElement; diff --git a/WoWsShipBuilder.DataElements/DataElements/KeyValueUnitDataElement.cs b/WoWsShipBuilder.DataElements/KeyValueUnitDataElement.cs similarity index 87% rename from WoWsShipBuilder.DataElements/DataElements/KeyValueUnitDataElement.cs rename to WoWsShipBuilder.DataElements/KeyValueUnitDataElement.cs index fc58db8c3..f97fc79c3 100644 --- a/WoWsShipBuilder.DataElements/DataElements/KeyValueUnitDataElement.cs +++ b/WoWsShipBuilder.DataElements/KeyValueUnitDataElement.cs @@ -1,4 +1,4 @@ -namespace WoWsShipBuilder.DataElements.DataElements; +namespace WoWsShipBuilder.DataElements; /// /// Record that represent a key value pair, with a measurement unit to be applied to the value. diff --git a/WoWsShipBuilder.DataElements/DataElements/TooltipDataElement.cs b/WoWsShipBuilder.DataElements/TooltipDataElement.cs similarity index 89% rename from WoWsShipBuilder.DataElements/DataElements/TooltipDataElement.cs rename to WoWsShipBuilder.DataElements/TooltipDataElement.cs index 95ba692ce..120001fb7 100644 --- a/WoWsShipBuilder.DataElements/DataElements/TooltipDataElement.cs +++ b/WoWsShipBuilder.DataElements/TooltipDataElement.cs @@ -1,4 +1,4 @@ -namespace WoWsShipBuilder.DataElements.DataElements; +namespace WoWsShipBuilder.DataElements; /// /// A record that represent a key value pair with a tooltip text. diff --git a/WoWsShipBuilder.DataElements/ValueDataElement.cs b/WoWsShipBuilder.DataElements/ValueDataElement.cs new file mode 100644 index 000000000..d57e59978 --- /dev/null +++ b/WoWsShipBuilder.DataElements/ValueDataElement.cs @@ -0,0 +1,8 @@ +namespace WoWsShipBuilder.DataElements; + +/// +/// A record that represent a single value. +/// +/// The value of the element. +/// The of the value. +public readonly record struct ValueDataElement(string Value, DataElementTextKind ValueTextKind) : IDataElement; diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..b52765b30 --- /dev/null +++ b/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + +