Add quick parse notation for critical editor

This commit is contained in:
2026-03-15 12:58:26 +01:00
parent d1ad95e861
commit 7b07477133
12 changed files with 666 additions and 35 deletions

View File

@@ -14,6 +14,7 @@ public sealed record CriticalCellEditorResponse(
string ColumnLabel,
string ColumnRole,
string RawCellText,
string QuickParseInput,
string DescriptionText,
string? RawAffixText,
string ParseStatus,

View File

@@ -9,7 +9,8 @@ public sealed record CriticalCellEditorSnapshot(
bool AreEffectsOverridden,
bool AreBranchesOverridden,
IReadOnlyList<CriticalEffectEditorItem> Effects,
IReadOnlyList<CriticalBranchEditorItem> Branches)
IReadOnlyList<CriticalBranchEditorItem> Branches,
string? QuickParseInput)
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
@@ -21,7 +22,8 @@ public sealed record CriticalCellEditorSnapshot(
request.AreEffectsOverridden,
request.AreBranchesOverridden,
request.Effects,
request.Branches);
request.Branches,
request.QuickParseInput);
public string ToJson() =>
JsonSerializer.Serialize(this, JsonOptions);

View File

@@ -4,6 +4,7 @@ namespace RolemasterDb.App.Features;
public sealed record CriticalCellUpdateRequest(
string RawCellText,
string QuickParseInput,
string DescriptionText,
string? RawAffixText,
string ParseStatus,

View File

@@ -0,0 +1,100 @@
using System.Text.RegularExpressions;
namespace RolemasterDb.App.Features;
public static class CriticalQuickNotationFormatter
{
public static string Format(
string descriptionText,
IReadOnlyList<CriticalEffectEditorItem> effects,
IReadOnlyList<CriticalBranchEditorItem> branches)
{
var lines = new List<string>
{
CollapseWhitespace(descriptionText)
};
var baseAffixLine = FormatEffects(effects);
if (!string.IsNullOrWhiteSpace(baseAffixLine))
{
lines.Add(baseAffixLine);
}
foreach (var branch in branches.OrderBy(item => item.SortOrder))
{
var payloadParts = new List<string>();
var branchDescription = CollapseWhitespace(branch.DescriptionText);
if (!string.IsNullOrWhiteSpace(branchDescription))
{
payloadParts.Add(branchDescription);
}
var branchAffixLine = FormatEffects(branch.Effects);
if (!string.IsNullOrWhiteSpace(branchAffixLine))
{
payloadParts.Add(branchAffixLine);
}
if (payloadParts.Count == 0)
{
continue;
}
lines.Add($"{CollapseWhitespace(branch.ConditionText)}: {string.Join(", ", payloadParts)}");
}
return string.Join(Environment.NewLine, lines.Where(line => !string.IsNullOrWhiteSpace(line)));
}
private static string? FormatEffects(IReadOnlyList<CriticalEffectEditorItem> effects)
{
var tokens = effects
.Select(FormatEffect)
.Where(token => !string.IsNullOrWhiteSpace(token))
.ToList();
return tokens.Count == 0 ? null : string.Join(", ", tokens);
}
private static string? FormatEffect(CriticalEffectEditorItem effect) =>
effect.EffectCode switch
{
Domain.CriticalEffectCodes.DirectHits => effect.ValueInteger is int hits ? $"+{hits}" : effect.SourceText,
Domain.CriticalEffectCodes.StunnedRounds => effect.DurationRounds is int rounds ? $"{rounds}s" : effect.SourceText,
Domain.CriticalEffectCodes.MustParryRounds => effect.DurationRounds is int rounds ? $"{rounds}mp" : effect.SourceText,
Domain.CriticalEffectCodes.NoParryRounds => effect.DurationRounds is int rounds ? $"{rounds}np" : effect.SourceText,
Domain.CriticalEffectCodes.BleedPerRound => effect.PerRound is int bleed ? $"{bleed}hpr" : effect.SourceText,
Domain.CriticalEffectCodes.FoePenalty => effect.Modifier is int penalty ? penalty.ToString(System.Globalization.CultureInfo.InvariantCulture) : effect.SourceText,
Domain.CriticalEffectCodes.AttackerBonusNextRound => effect.Modifier is int bonus ? $"+{bonus}b" : effect.SourceText,
Domain.CriticalEffectCodes.PowerPointModifier => FormatPowerPointModifier(effect.ValueExpression, effect.SourceText),
_ => effect.SourceText ?? effect.EffectCode
};
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";
}
private static string CollapseWhitespace(string? value) =>
string.IsNullOrWhiteSpace(value)
? string.Empty
: Regex.Replace(value.Trim(), @"\s+", " ");
}

View File

@@ -296,7 +296,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
}
var currentState = CreateCurrentEditorState(result);
var generatedContent = await ParseCriticalCellContentAsync(dbContext, result.CriticalTableId, currentState.RawCellText, cancellationToken);
var generatedContent = await ParseCriticalCellContentAsync(dbContext, result.CriticalTableId, currentState.QuickParseInput, cancellationToken);
return CreateCellEditorResponse(result, currentState, generatedContent.ValidationErrors, CreateComparisonState(generatedContent));
}
@@ -325,8 +325,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
return null;
}
var affixLegend = await BuildSharedAffixLegendAsync(dbContext, result.CriticalTableId, cancellationToken);
var content = SharedParsing.CriticalCellTextParser.Parse(currentState.RawCellText, affixLegend);
var content = await ParseCriticalCellContentAsync(dbContext, result.CriticalTableId, currentState.QuickParseInput, cancellationToken);
var generatedState = CreateGeneratedEditorState(content);
var mergedState = MergeGeneratedState(currentState, generatedState);
return CreateCellEditorResponse(result, mergedState, content.ValidationErrors, CreateComparisonState(content));
@@ -370,7 +369,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
await dbContext.SaveChangesAsync(cancellationToken);
var generatedContent = await ParseCriticalCellContentAsync(dbContext, result.CriticalTableId, request.RawCellText, cancellationToken);
var generatedContent = await ParseCriticalCellContentAsync(dbContext, result.CriticalTableId, request.QuickParseInput, cancellationToken);
return CreateCellEditorResponse(result, request, generatedContent.ValidationErrors, CreateComparisonState(generatedContent));
}
@@ -452,6 +451,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
result.CriticalColumn.Label,
result.CriticalColumn.Role,
state.RawCellText,
state.QuickParseInput,
state.DescriptionText,
state.RawAffixText,
state.ParseStatus,
@@ -575,8 +575,13 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
{
if (CriticalCellEditorSnapshot.TryParse(result.ParsedJson, out var snapshot) && snapshot is not null)
{
var snapshotQuickParseInput = string.IsNullOrWhiteSpace(snapshot.QuickParseInput)
? CriticalQuickNotationFormatter.Format(result.DescriptionText, snapshot.Effects, snapshot.Branches)
: snapshot.QuickParseInput;
return new CriticalCellUpdateRequest(
result.RawCellText,
snapshotQuickParseInput,
result.DescriptionText,
result.RawAffixText,
result.ParseStatus,
@@ -600,6 +605,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
return new CriticalCellUpdateRequest(
result.RawCellText,
CriticalQuickNotationFormatter.Format(result.DescriptionText, effects, branches),
result.DescriptionText,
result.RawAffixText,
result.ParseStatus,
@@ -623,17 +629,18 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
.ToList();
return new CriticalCellUpdateRequest(
content.RawCellText,
content.DescriptionText,
content.RawAffixText,
ResolveParseStatus(content.Effects, content.Branches),
SerializeParsedEffects(content.Effects),
false,
false,
false,
false,
effects,
branches);
RawCellText: string.Empty,
QuickParseInput: content.RawCellText,
DescriptionText: content.DescriptionText,
RawAffixText: content.RawAffixText,
ParseStatus: ResolveParseStatus(content.Effects, content.Branches),
ParsedJson: SerializeParsedEffects(content.Effects),
IsDescriptionOverridden: false,
IsRawAffixTextOverridden: false,
AreEffectsOverridden: false,
AreBranchesOverridden: false,
Effects: effects,
Branches: branches);
}
private static CriticalCellUpdateRequest MergeGeneratedState(
@@ -641,6 +648,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
CriticalCellUpdateRequest generatedState) =>
new(
currentState.RawCellText,
currentState.QuickParseInput,
currentState.IsDescriptionOverridden ? currentState.DescriptionText : generatedState.DescriptionText,
currentState.IsRawAffixTextOverridden ? currentState.RawAffixText : generatedState.RawAffixText,
generatedState.ParseStatus,
@@ -922,11 +930,11 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
private static async Task<SharedParsing.CriticalCellParseContent> ParseCriticalCellContentAsync(
RolemasterDbContext dbContext,
int tableId,
string rawCellText,
string quickParseInput,
CancellationToken cancellationToken)
{
var affixLegend = await BuildSharedAffixLegendAsync(dbContext, tableId, cancellationToken);
return SharedParsing.CriticalCellTextParser.Parse(rawCellText, affixLegend);
return SharedParsing.CriticalQuickNotationParser.Parse(quickParseInput, affixLegend);
}
private static bool IsLegendSymbolEffectCode(string effectCode) =>