Support signed power point affixes
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
@using System.Diagnostics.CodeAnalysis
|
||||
@using RolemasterDb.App.Domain
|
||||
@using RolemasterDb.App.Features
|
||||
@using PowerPointModifierNotation = RolemasterDb.CriticalParsing.PowerPointModifierNotation
|
||||
|
||||
@if (EffectiveEffects.Count > 0)
|
||||
{
|
||||
@@ -61,7 +62,7 @@
|
||||
CriticalEffectCodes.DirectHits => $"+{effect.ValueInteger?.ToString() ?? string.Empty}",
|
||||
CriticalEffectCodes.FoePenalty
|
||||
or CriticalEffectCodes.AttackerBonusNextRound => effect.Modifier?.ToString() ?? string.Empty,
|
||||
CriticalEffectCodes.PowerPointModifier => effect.ValueExpression ?? string.Empty,
|
||||
CriticalEffectCodes.PowerPointModifier => PowerPointModifierNotation.FormatSignedExpression(effect.ValueExpression) ?? string.Empty,
|
||||
_ => effect.ValueInteger?.ToString()
|
||||
?? effect.Modifier?.ToString()
|
||||
?? effect.ValueExpression
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using RolemasterDb.CriticalParsing;
|
||||
|
||||
namespace RolemasterDb.App.Features;
|
||||
|
||||
@@ -72,25 +73,10 @@ public static class CriticalQuickNotationFormatter
|
||||
|
||||
private static string? FormatPowerPointModifier(string? valueExpression, string? sourceText)
|
||||
{
|
||||
var expression = CollapseWhitespace(valueExpression);
|
||||
if (string.IsNullOrWhiteSpace(expression))
|
||||
{
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
expression = expression.Trim();
|
||||
if (expression.StartsWith('+'))
|
||||
{
|
||||
expression = expression[1..];
|
||||
}
|
||||
|
||||
if (expression.StartsWith('(') && expression.EndsWith(')') && expression.Length > 2)
|
||||
{
|
||||
expression = expression[1..^1].Trim();
|
||||
}
|
||||
|
||||
expression = Regex.Replace(expression, @"\s+", string.Empty);
|
||||
return $"+{expression}pp";
|
||||
var expression = PowerPointModifierNotation.FormatSignedExpression(valueExpression);
|
||||
return string.IsNullOrWhiteSpace(expression)
|
||||
? sourceText
|
||||
: $"{expression}pp";
|
||||
}
|
||||
|
||||
private static string CollapseWhitespace(string? value) =>
|
||||
|
||||
@@ -7,7 +7,7 @@ public static class AffixEffectParser
|
||||
private const string FoeTarget = "foe";
|
||||
|
||||
private static readonly Regex DirectHitsRegex = new(@"[+-]\s*\d+\s*H\b", RegexOptions.Compiled);
|
||||
private static readonly Regex PowerPointModifierRegex = new(@"\+\s*\((?<expression>[^)]+)\)\s*P\b", RegexOptions.Compiled);
|
||||
private static readonly Regex PowerPointModifierRegex = new(@"(?<sign>[+-])\s*\((?<expression>[^)]+)\)\s*P\b", RegexOptions.Compiled);
|
||||
private static readonly Regex ModifierRegex = new(@"\((?<noise>[^0-9+\-)]*)(?<sign>[+-])\s*(?<value>\d+)\)", RegexOptions.Compiled);
|
||||
private static readonly Regex QuickDirectHitsRegex = new(@"^\+(?<value>\d+)(?:H)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex QuickStunnedRegex = new(@"^(?<value>\d+)s$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -16,7 +16,7 @@ public static class AffixEffectParser
|
||||
private static readonly Regex QuickBleedRegex = new(@"^(?<value>\d+)hpr$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex QuickFoePenaltyRegex = new(@"^-(?<value>\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex QuickAttackerBonusRegex = new(@"^\+(?<value>\d+)b$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex QuickPowerPointRegex = new(@"^\+(?<expression>.+)pp$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex QuickPowerPointRegex = new(@"^(?<sign>[+-])(?<expression>.+)pp$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static IReadOnlyList<ParsedCriticalEffect> Parse(string? rawAffixText, AffixLegend affixLegend)
|
||||
{
|
||||
@@ -101,7 +101,9 @@ public static class AffixEffectParser
|
||||
CriticalEffectCodes.PowerPointModifier,
|
||||
FoeTarget,
|
||||
null,
|
||||
CriticalCellParserSupport.CollapseWhitespace(match.Groups["expression"].Value),
|
||||
PowerPointModifierNotation.NormalizeExpression(
|
||||
match.Groups["expression"].Value,
|
||||
match.Groups["sign"].Value[0]),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
@@ -161,6 +163,13 @@ public static class AffixEffectParser
|
||||
var symbolClusterRegex = CreateSymbolClusterRegex(affixLegend.EffectSymbols);
|
||||
if (symbolClusterRegex is null)
|
||||
{
|
||||
if (matchedEffects.Count > 0)
|
||||
{
|
||||
effects.AddRange(matchedEffects
|
||||
.OrderBy(item => item.Index)
|
||||
.Select(item => item.Effect));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,18 +332,24 @@ public static class AffixEffectParser
|
||||
}
|
||||
|
||||
if (TryMatchSingle(QuickPowerPointRegex, token, match =>
|
||||
new ParsedCriticalEffect(
|
||||
{
|
||||
var normalizedExpression = PowerPointModifierNotation.NormalizeExpression(
|
||||
match.Groups["expression"].Value,
|
||||
match.Groups["sign"].Value[0])!;
|
||||
|
||||
return new ParsedCriticalEffect(
|
||||
CriticalEffectCodes.PowerPointModifier,
|
||||
FoeTarget,
|
||||
null,
|
||||
NormalizePowerPointExpression(match.Groups["expression"].Value),
|
||||
normalizedExpression,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
"quick",
|
||||
$"+{NormalizePowerPointExpression(match.Groups["expression"].Value)}pp"), out effects, out canonicalToken))
|
||||
$"{normalizedExpression}pp");
|
||||
}, out effects, out canonicalToken))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -477,22 +492,4 @@ public static class AffixEffectParser
|
||||
.Replace(" +", "+", StringComparison.Ordinal)
|
||||
.Replace("( ", "(", StringComparison.Ordinal)
|
||||
.Replace(" )", ")", StringComparison.Ordinal);
|
||||
|
||||
private static string NormalizePowerPointExpression(string value)
|
||||
{
|
||||
var normalized = CriticalCellParserSupport.CollapseWhitespace(value)
|
||||
.Replace(" ", string.Empty, StringComparison.Ordinal);
|
||||
|
||||
if (normalized.StartsWith('+'))
|
||||
{
|
||||
normalized = normalized[1..];
|
||||
}
|
||||
|
||||
if (normalized.StartsWith('(') && normalized.EndsWith(')') && normalized.Length > 2)
|
||||
{
|
||||
normalized = normalized[1..^1];
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace RolemasterDb.CriticalParsing;
|
||||
|
||||
public static class PowerPointModifierNotation
|
||||
{
|
||||
public static string? NormalizeExpression(string? expression, char? forcedSign = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expression))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalized = CriticalCellParserSupport.CollapseWhitespace(expression)
|
||||
.Replace(" ", string.Empty, StringComparison.Ordinal);
|
||||
|
||||
if (normalized.StartsWith('(') && normalized.EndsWith(')') && normalized.Length > 2)
|
||||
{
|
||||
normalized = normalized[1..^1];
|
||||
}
|
||||
|
||||
char? sign = forcedSign;
|
||||
if (normalized.Length > 0 && (normalized[0] == '+' || normalized[0] == '-'))
|
||||
{
|
||||
sign ??= normalized[0];
|
||||
normalized = normalized[1..];
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return sign.HasValue
|
||||
? $"{sign}{normalized}"
|
||||
: normalized;
|
||||
}
|
||||
|
||||
public static string? FormatSignedExpression(string? expression)
|
||||
{
|
||||
var normalized = NormalizeExpression(expression);
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalized[0] is '+' or '-'
|
||||
? normalized
|
||||
: $"+{normalized}";
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public sealed class CriticalCellReparseIntegrationTests
|
||||
Assert.Contains(content.Effects, effect => effect.EffectCode == AppCriticalEffectCodes.BleedPerRound && effect.PerRound == 1);
|
||||
Assert.Contains(content.Effects, effect => effect.EffectCode == AppCriticalEffectCodes.FoePenalty && effect.Modifier == -20);
|
||||
Assert.Contains(content.Effects, effect => effect.EffectCode == AppCriticalEffectCodes.AttackerBonusNextRound && effect.Modifier == 20);
|
||||
Assert.Contains(content.Effects, effect => effect.EffectCode == AppCriticalEffectCodes.PowerPointModifier && effect.ValueExpression == "2d10-3");
|
||||
Assert.Contains(content.Effects, effect => effect.EffectCode == AppCriticalEffectCodes.PowerPointModifier && effect.ValueExpression == "+2d10-3");
|
||||
Assert.Single(content.Branches);
|
||||
Assert.Equal("glancing blow", content.Branches[0].DescriptionText);
|
||||
Assert.Contains(content.Branches[0].Effects, effect => effect.EffectCode == AppCriticalEffectCodes.DirectHits && effect.ValueInteger == 5);
|
||||
@@ -46,6 +46,84 @@ public sealed class CriticalCellReparseIntegrationTests
|
||||
Assert.Empty(content.ValidationErrors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shared_quick_notation_parser_supports_negative_power_point_shorthand()
|
||||
{
|
||||
var legend = new AffixLegend(
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
["P"],
|
||||
supportsFoePenalty: false,
|
||||
supportsAttackerBonus: false,
|
||||
supportsPowerPointModifier: true);
|
||||
|
||||
var content = CriticalQuickNotationParser.Parse(
|
||||
"Void drains the foe.\r\n-2d10+4pp",
|
||||
legend);
|
||||
|
||||
Assert.Contains(content.Effects, effect => effect.EffectCode == AppCriticalEffectCodes.PowerPointModifier && effect.ValueExpression == "-2d10+4");
|
||||
Assert.Equal("-2d10+4pp", content.RawAffixText);
|
||||
Assert.Empty(content.ValidationErrors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shared_symbol_affix_parser_supports_negative_power_point_notation()
|
||||
{
|
||||
var legend = new AffixLegend(
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
["P"],
|
||||
supportsFoePenalty: false,
|
||||
supportsAttackerBonus: false,
|
||||
supportsPowerPointModifier: true);
|
||||
|
||||
var effects = AffixEffectParser.Parse("-(2d10+4)P", legend);
|
||||
|
||||
Assert.Contains(effects, effect => effect.EffectCode == AppCriticalEffectCodes.PowerPointModifier && effect.ValueExpression == "-2d10+4");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Quick_notation_formatter_keeps_power_point_modifier_signs()
|
||||
{
|
||||
var formatted = CriticalQuickNotationFormatter.Format(
|
||||
"Arcane force twists around the foe.",
|
||||
[
|
||||
new CriticalEffectEditorItem(
|
||||
AppCriticalEffectCodes.PowerPointModifier,
|
||||
"foe",
|
||||
null,
|
||||
null,
|
||||
"2d10-3",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
"import",
|
||||
null,
|
||||
null,
|
||||
false),
|
||||
new CriticalEffectEditorItem(
|
||||
AppCriticalEffectCodes.PowerPointModifier,
|
||||
"foe",
|
||||
null,
|
||||
null,
|
||||
"-1d10+4",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
"import",
|
||||
null,
|
||||
null,
|
||||
false)
|
||||
],
|
||||
[]);
|
||||
|
||||
Assert.Equal(
|
||||
"Arcane force twists around the foe.\n+2d10-3pp, -1d10+4pp",
|
||||
formatted.Replace("\r\n", "\n", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shared_cell_parser_extracts_base_effects_and_condition_branches()
|
||||
{
|
||||
|
||||
@@ -546,7 +546,7 @@ public sealed class StandardCriticalTableParserIntegrationTests
|
||||
effect =>
|
||||
{
|
||||
Assert.Equal(CriticalEffectCodes.PowerPointModifier, effect.EffectCode);
|
||||
Assert.Equal("2d10-18", effect.ValueExpression);
|
||||
Assert.Equal("+2d10-18", effect.ValueExpression);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -627,7 +627,7 @@ public sealed class StandardCriticalTableParserIntegrationTests
|
||||
Assert.NotNull(manaResponse);
|
||||
Assert.Contains(slashResponse!.Branches, branch => branch.ConditionKey == "with_leg_greaves" && branch.Effects.Any(effect => effect.EffectCode == CriticalEffectCodes.MustParryRounds));
|
||||
Assert.Contains(slashResponse.Branches, branch => branch.ConditionKey == "without_leg_greaves" && branch.Effects.Any(effect => effect.EffectCode == CriticalEffectCodes.BleedPerRound));
|
||||
Assert.Contains(manaResponse!.Effects, effect => effect.EffectCode == CriticalEffectCodes.PowerPointModifier && effect.ValueExpression == "2d10-18");
|
||||
Assert.Contains(manaResponse!.Effects, effect => effect.EffectCode == CriticalEffectCodes.PowerPointModifier && effect.ValueExpression == "+2d10-18");
|
||||
}
|
||||
|
||||
private static async Task<CriticalTableParseResult> LoadParseResultAsync(CriticalImportManifestEntry entry)
|
||||
|
||||
Reference in New Issue
Block a user