|
|
|
|
@@ -296,7 +296,7 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
public async Task<CriticalCellEditorResponse?> ReparseCriticalCellAsync(
|
|
|
|
|
string slug,
|
|
|
|
|
int resultId,
|
|
|
|
|
string rawCellText,
|
|
|
|
|
CriticalCellUpdateRequest currentState,
|
|
|
|
|
CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
|
@@ -319,8 +319,10 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var affixLegend = await BuildSharedAffixLegendAsync(dbContext, result.CriticalTableId, cancellationToken);
|
|
|
|
|
var content = SharedParsing.CriticalCellTextParser.Parse(rawCellText, affixLegend);
|
|
|
|
|
return CreateCellEditorResponse(result, content);
|
|
|
|
|
var content = SharedParsing.CriticalCellTextParser.Parse(currentState.RawCellText, affixLegend);
|
|
|
|
|
var generatedState = CreateGeneratedEditorState(content);
|
|
|
|
|
var mergedState = MergeGeneratedState(currentState, generatedState);
|
|
|
|
|
return CreateCellEditorResponse(result, mergedState, content.ValidationErrors);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<CriticalCellEditorResponse?> UpdateCriticalCellAsync(
|
|
|
|
|
@@ -354,14 +356,14 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
result.DescriptionText = request.DescriptionText.Trim();
|
|
|
|
|
result.RawAffixText = NormalizeOptionalText(request.RawAffixText);
|
|
|
|
|
result.ParseStatus = request.ParseStatus.Trim();
|
|
|
|
|
result.ParsedJson = string.IsNullOrWhiteSpace(request.ParsedJson) ? "{}" : request.ParsedJson.Trim();
|
|
|
|
|
result.ParsedJson = CriticalCellEditorSnapshot.FromRequest(request).ToJson();
|
|
|
|
|
|
|
|
|
|
ReplaceBaseEffects(dbContext, result, request.Effects);
|
|
|
|
|
ReplaceBranches(dbContext, result, request.Branches);
|
|
|
|
|
|
|
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
|
|
|
|
|
|
return CreateCellEditorResponse(result);
|
|
|
|
|
return CreateCellEditorResponse(result, request, []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IReadOnlyList<CriticalTableLegendEntry> BuildLegend(IReadOnlyList<CriticalTableCellDetail> cells)
|
|
|
|
|
@@ -422,37 +424,20 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
effect.SourceType,
|
|
|
|
|
effect.SourceText);
|
|
|
|
|
|
|
|
|
|
private static CriticalCellEditorResponse CreateCellEditorResponse(CriticalResult result) =>
|
|
|
|
|
new(
|
|
|
|
|
result.Id,
|
|
|
|
|
result.CriticalTable.Slug,
|
|
|
|
|
result.CriticalTable.DisplayName,
|
|
|
|
|
result.CriticalTable.SourceDocument,
|
|
|
|
|
result.CriticalRollBand.Label,
|
|
|
|
|
result.CriticalGroup?.GroupKey,
|
|
|
|
|
result.CriticalGroup?.Label,
|
|
|
|
|
result.CriticalColumn.ColumnKey,
|
|
|
|
|
result.CriticalColumn.Label,
|
|
|
|
|
result.CriticalColumn.Role,
|
|
|
|
|
result.RawCellText,
|
|
|
|
|
result.DescriptionText,
|
|
|
|
|
result.RawAffixText,
|
|
|
|
|
result.ParseStatus,
|
|
|
|
|
result.ParsedJson,
|
|
|
|
|
[],
|
|
|
|
|
result.Effects
|
|
|
|
|
.OrderBy(effect => effect.Id)
|
|
|
|
|
.Select(CreateEffectEditorItem)
|
|
|
|
|
.ToList(),
|
|
|
|
|
result.Branches
|
|
|
|
|
.OrderBy(branch => branch.SortOrder)
|
|
|
|
|
.Select(CreateBranchEditorItem)
|
|
|
|
|
.ToList());
|
|
|
|
|
private static CriticalCellEditorResponse CreateCellEditorResponse(CriticalResult result)
|
|
|
|
|
{
|
|
|
|
|
var state = CreateCurrentEditorState(result);
|
|
|
|
|
return CreateCellEditorResponse(result, state, []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CriticalCellEditorResponse CreateCellEditorResponse(
|
|
|
|
|
CriticalResult result,
|
|
|
|
|
SharedParsing.CriticalCellParseContent content) =>
|
|
|
|
|
new(
|
|
|
|
|
CriticalCellUpdateRequest state,
|
|
|
|
|
IReadOnlyList<string> validationMessages)
|
|
|
|
|
{
|
|
|
|
|
var snapshotJson = CriticalCellEditorSnapshot.FromRequest(state).ToJson();
|
|
|
|
|
|
|
|
|
|
return new(
|
|
|
|
|
result.Id,
|
|
|
|
|
result.CriticalTable.Slug,
|
|
|
|
|
result.CriticalTable.DisplayName,
|
|
|
|
|
@@ -463,19 +448,19 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
result.CriticalColumn.ColumnKey,
|
|
|
|
|
result.CriticalColumn.Label,
|
|
|
|
|
result.CriticalColumn.Role,
|
|
|
|
|
content.RawCellText,
|
|
|
|
|
content.DescriptionText,
|
|
|
|
|
content.RawAffixText,
|
|
|
|
|
ResolveParseStatus(content.Effects, content.Branches),
|
|
|
|
|
SerializeParsedEffects(content.Effects),
|
|
|
|
|
content.ValidationErrors.ToList(),
|
|
|
|
|
content.Effects
|
|
|
|
|
.Select(CreateEffectEditorItem)
|
|
|
|
|
.ToList(),
|
|
|
|
|
content.Branches
|
|
|
|
|
.OrderBy(branch => branch.SortOrder)
|
|
|
|
|
.Select(CreateBranchEditorItem)
|
|
|
|
|
.ToList());
|
|
|
|
|
state.RawCellText,
|
|
|
|
|
state.DescriptionText,
|
|
|
|
|
state.RawAffixText,
|
|
|
|
|
state.ParseStatus,
|
|
|
|
|
snapshotJson,
|
|
|
|
|
state.IsDescriptionOverridden,
|
|
|
|
|
state.IsRawAffixTextOverridden,
|
|
|
|
|
state.AreEffectsOverridden,
|
|
|
|
|
state.AreBranchesOverridden,
|
|
|
|
|
validationMessages.ToList(),
|
|
|
|
|
state.Effects.ToList(),
|
|
|
|
|
state.Branches.ToList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CriticalBranchLookupResponse CreateBranchLookupResponse(CriticalBranch branch) =>
|
|
|
|
|
new(
|
|
|
|
|
@@ -491,8 +476,11 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
branch.RawText,
|
|
|
|
|
branch.SortOrder);
|
|
|
|
|
|
|
|
|
|
private static CriticalBranchEditorItem CreateBranchEditorItem(CriticalBranch branch) =>
|
|
|
|
|
new(
|
|
|
|
|
private static CriticalBranchEditorItem CreateBranchEditorItem(CriticalBranch branch, int branchIndex)
|
|
|
|
|
{
|
|
|
|
|
var originKey = CreateBranchOriginKey(branchIndex, branch.BranchKind, branch.ConditionKey, branch.ConditionText);
|
|
|
|
|
|
|
|
|
|
return new(
|
|
|
|
|
branch.BranchKind,
|
|
|
|
|
branch.ConditionKey,
|
|
|
|
|
branch.ConditionText,
|
|
|
|
|
@@ -502,13 +490,20 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
branch.RawAffixText,
|
|
|
|
|
branch.ParsedJson,
|
|
|
|
|
branch.SortOrder,
|
|
|
|
|
originKey,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
(branch.Effects ?? Enumerable.Empty<CriticalEffect>())
|
|
|
|
|
.OrderBy(effect => effect.Id)
|
|
|
|
|
.Select(CreateEffectEditorItem)
|
|
|
|
|
.Select((effect, effectIndex) => CreateEffectEditorItem(effect, CreateBranchEffectOriginKey(originKey, effectIndex, effect.EffectCode)))
|
|
|
|
|
.ToList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CriticalBranchEditorItem CreateBranchEditorItem(SharedParsing.ParsedCriticalBranch branch) =>
|
|
|
|
|
new(
|
|
|
|
|
private static CriticalBranchEditorItem CreateBranchEditorItem(SharedParsing.ParsedCriticalBranch branch, int branchIndex)
|
|
|
|
|
{
|
|
|
|
|
var originKey = CreateBranchOriginKey(branchIndex, branch.BranchKind, branch.ConditionKey, branch.ConditionText);
|
|
|
|
|
|
|
|
|
|
return new(
|
|
|
|
|
branch.BranchKind,
|
|
|
|
|
branch.ConditionKey,
|
|
|
|
|
branch.ConditionText,
|
|
|
|
|
@@ -518,11 +513,15 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
branch.RawAffixText,
|
|
|
|
|
SerializeParsedEffects(branch.Effects),
|
|
|
|
|
branch.SortOrder,
|
|
|
|
|
originKey,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
branch.Effects
|
|
|
|
|
.Select(CreateEffectEditorItem)
|
|
|
|
|
.Select((effect, effectIndex) => CreateEffectEditorItem(effect, CreateBranchEffectOriginKey(originKey, effectIndex, effect.EffectCode)))
|
|
|
|
|
.ToList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CriticalEffectEditorItem CreateEffectEditorItem(CriticalEffect effect) =>
|
|
|
|
|
private static CriticalEffectEditorItem CreateEffectEditorItem(CriticalEffect effect, string originKey) =>
|
|
|
|
|
new(
|
|
|
|
|
effect.EffectCode,
|
|
|
|
|
effect.Target,
|
|
|
|
|
@@ -535,9 +534,11 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
effect.BodyPart,
|
|
|
|
|
effect.IsPermanent,
|
|
|
|
|
effect.SourceType,
|
|
|
|
|
effect.SourceText);
|
|
|
|
|
effect.SourceText,
|
|
|
|
|
originKey,
|
|
|
|
|
false);
|
|
|
|
|
|
|
|
|
|
private static CriticalEffectEditorItem CreateEffectEditorItem(SharedParsing.ParsedCriticalEffect effect) =>
|
|
|
|
|
private static CriticalEffectEditorItem CreateEffectEditorItem(SharedParsing.ParsedCriticalEffect effect, string originKey) =>
|
|
|
|
|
new(
|
|
|
|
|
effect.EffectCode,
|
|
|
|
|
effect.Target,
|
|
|
|
|
@@ -550,7 +551,192 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
|
|
|
|
effect.BodyPart,
|
|
|
|
|
effect.IsPermanent,
|
|
|
|
|
effect.SourceType,
|
|
|
|
|
effect.SourceText);
|
|
|
|
|
effect.SourceText,
|
|
|
|
|
originKey,
|
|
|
|
|
false);
|
|
|
|
|
|
|
|
|
|
private static CriticalCellUpdateRequest CreateCurrentEditorState(CriticalResult result)
|
|
|
|
|
{
|
|
|
|
|
if (CriticalCellEditorSnapshot.TryParse(result.ParsedJson, out var snapshot) && snapshot is not null)
|
|
|
|
|
{
|
|
|
|
|
return new CriticalCellUpdateRequest(
|
|
|
|
|
result.RawCellText,
|
|
|
|
|
result.DescriptionText,
|
|
|
|
|
result.RawAffixText,
|
|
|
|
|
result.ParseStatus,
|
|
|
|
|
result.ParsedJson,
|
|
|
|
|
snapshot.IsDescriptionOverridden,
|
|
|
|
|
snapshot.IsRawAffixTextOverridden,
|
|
|
|
|
snapshot.AreEffectsOverridden,
|
|
|
|
|
snapshot.AreBranchesOverridden,
|
|
|
|
|
snapshot.Effects.ToList(),
|
|
|
|
|
snapshot.Branches.ToList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var effects = result.Effects
|
|
|
|
|
.OrderBy(effect => effect.Id)
|
|
|
|
|
.Select((effect, effectIndex) => CreateEffectEditorItem(effect, CreateBaseEffectOriginKey(effectIndex, effect.EffectCode)))
|
|
|
|
|
.ToList();
|
|
|
|
|
var branches = result.Branches
|
|
|
|
|
.OrderBy(branch => branch.SortOrder)
|
|
|
|
|
.Select((branch, branchIndex) => CreateBranchEditorItem(branch, branchIndex))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
return new CriticalCellUpdateRequest(
|
|
|
|
|
result.RawCellText,
|
|
|
|
|
result.DescriptionText,
|
|
|
|
|
result.RawAffixText,
|
|
|
|
|
result.ParseStatus,
|
|
|
|
|
result.ParsedJson,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
effects,
|
|
|
|
|
branches);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CriticalCellUpdateRequest CreateGeneratedEditorState(SharedParsing.CriticalCellParseContent content)
|
|
|
|
|
{
|
|
|
|
|
var effects = content.Effects
|
|
|
|
|
.Select((effect, effectIndex) => CreateEffectEditorItem(effect, CreateBaseEffectOriginKey(effectIndex, effect.EffectCode)))
|
|
|
|
|
.ToList();
|
|
|
|
|
var branches = content.Branches
|
|
|
|
|
.OrderBy(branch => branch.SortOrder)
|
|
|
|
|
.Select((branch, branchIndex) => CreateBranchEditorItem(branch, branchIndex))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
return new CriticalCellUpdateRequest(
|
|
|
|
|
content.RawCellText,
|
|
|
|
|
content.DescriptionText,
|
|
|
|
|
content.RawAffixText,
|
|
|
|
|
ResolveParseStatus(content.Effects, content.Branches),
|
|
|
|
|
SerializeParsedEffects(content.Effects),
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
effects,
|
|
|
|
|
branches);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CriticalCellUpdateRequest MergeGeneratedState(
|
|
|
|
|
CriticalCellUpdateRequest currentState,
|
|
|
|
|
CriticalCellUpdateRequest generatedState) =>
|
|
|
|
|
new(
|
|
|
|
|
currentState.RawCellText,
|
|
|
|
|
currentState.IsDescriptionOverridden ? currentState.DescriptionText : generatedState.DescriptionText,
|
|
|
|
|
currentState.IsRawAffixTextOverridden ? currentState.RawAffixText : generatedState.RawAffixText,
|
|
|
|
|
generatedState.ParseStatus,
|
|
|
|
|
generatedState.ParsedJson,
|
|
|
|
|
currentState.IsDescriptionOverridden,
|
|
|
|
|
currentState.IsRawAffixTextOverridden,
|
|
|
|
|
currentState.AreEffectsOverridden,
|
|
|
|
|
currentState.AreBranchesOverridden,
|
|
|
|
|
currentState.AreEffectsOverridden
|
|
|
|
|
? currentState.Effects.ToList()
|
|
|
|
|
: MergeEffectItems(currentState.Effects, generatedState.Effects),
|
|
|
|
|
currentState.AreBranchesOverridden
|
|
|
|
|
? currentState.Branches
|
|
|
|
|
.OrderBy(branch => branch.SortOrder)
|
|
|
|
|
.ToList()
|
|
|
|
|
: MergeBranchItems(currentState.Branches, generatedState.Branches));
|
|
|
|
|
|
|
|
|
|
private static List<CriticalBranchEditorItem> MergeBranchItems(
|
|
|
|
|
IReadOnlyList<CriticalBranchEditorItem> currentBranches,
|
|
|
|
|
IReadOnlyList<CriticalBranchEditorItem> generatedBranches)
|
|
|
|
|
{
|
|
|
|
|
var currentByOrigin = currentBranches
|
|
|
|
|
.Where(branch => !string.IsNullOrWhiteSpace(branch.OriginKey))
|
|
|
|
|
.ToDictionary(branch => branch.OriginKey!, StringComparer.Ordinal);
|
|
|
|
|
var merged = new List<CriticalBranchEditorItem>(generatedBranches.Count);
|
|
|
|
|
var matchedOrigins = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
|
|
|
|
|
|
foreach (var generatedBranch in generatedBranches)
|
|
|
|
|
{
|
|
|
|
|
if (generatedBranch.OriginKey is not null &&
|
|
|
|
|
currentByOrigin.TryGetValue(generatedBranch.OriginKey, out var currentBranch))
|
|
|
|
|
{
|
|
|
|
|
matchedOrigins.Add(generatedBranch.OriginKey);
|
|
|
|
|
|
|
|
|
|
if (currentBranch.IsOverridden)
|
|
|
|
|
{
|
|
|
|
|
merged.Add(currentBranch);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
merged.Add(generatedBranch with
|
|
|
|
|
{
|
|
|
|
|
AreEffectsOverridden = currentBranch.AreEffectsOverridden,
|
|
|
|
|
Effects = currentBranch.AreEffectsOverridden
|
|
|
|
|
? currentBranch.Effects.ToList()
|
|
|
|
|
: MergeEffectItems(currentBranch.Effects, generatedBranch.Effects)
|
|
|
|
|
});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
merged.Add(generatedBranch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
merged.AddRange(currentBranches.Where(branch =>
|
|
|
|
|
branch.IsOverridden &&
|
|
|
|
|
branch.OriginKey is not null &&
|
|
|
|
|
!matchedOrigins.Contains(branch.OriginKey)));
|
|
|
|
|
|
|
|
|
|
return merged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<CriticalEffectEditorItem> MergeEffectItems(
|
|
|
|
|
IReadOnlyList<CriticalEffectEditorItem> currentEffects,
|
|
|
|
|
IReadOnlyList<CriticalEffectEditorItem> generatedEffects)
|
|
|
|
|
{
|
|
|
|
|
var currentByOrigin = currentEffects
|
|
|
|
|
.Where(effect => !string.IsNullOrWhiteSpace(effect.OriginKey))
|
|
|
|
|
.ToDictionary(effect => effect.OriginKey!, StringComparer.Ordinal);
|
|
|
|
|
var merged = new List<CriticalEffectEditorItem>(generatedEffects.Count);
|
|
|
|
|
var matchedOrigins = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
|
|
|
|
|
|
foreach (var generatedEffect in generatedEffects)
|
|
|
|
|
{
|
|
|
|
|
if (generatedEffect.OriginKey is not null &&
|
|
|
|
|
currentByOrigin.TryGetValue(generatedEffect.OriginKey, out var currentEffect))
|
|
|
|
|
{
|
|
|
|
|
matchedOrigins.Add(generatedEffect.OriginKey);
|
|
|
|
|
merged.Add(currentEffect.IsOverridden ? currentEffect : generatedEffect);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
merged.Add(generatedEffect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
merged.AddRange(currentEffects.Where(effect =>
|
|
|
|
|
effect.IsOverridden &&
|
|
|
|
|
effect.OriginKey is not null &&
|
|
|
|
|
!matchedOrigins.Contains(effect.OriginKey)));
|
|
|
|
|
|
|
|
|
|
return merged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string CreateBaseEffectOriginKey(int effectIndex, string effectCode) =>
|
|
|
|
|
$"base:{NormalizeOriginSegment(effectCode)}:{effectIndex + 1}";
|
|
|
|
|
|
|
|
|
|
private static string CreateBranchOriginKey(int branchIndex, string branchKind, string? conditionKey, string conditionText) =>
|
|
|
|
|
$"branch:{NormalizeOriginSegment(branchKind)}:{NormalizeOriginSegment(conditionKey ?? conditionText)}:{branchIndex + 1}";
|
|
|
|
|
|
|
|
|
|
private static string CreateBranchEffectOriginKey(string branchOriginKey, int effectIndex, string effectCode) =>
|
|
|
|
|
$"{branchOriginKey}:effect:{NormalizeOriginSegment(effectCode)}:{effectIndex + 1}";
|
|
|
|
|
|
|
|
|
|
private static string NormalizeOriginSegment(string value)
|
|
|
|
|
{
|
|
|
|
|
var normalized = new string(value
|
|
|
|
|
.Trim()
|
|
|
|
|
.ToLowerInvariant()
|
|
|
|
|
.Select(character => char.IsLetterOrDigit(character) ? character : '_')
|
|
|
|
|
.ToArray())
|
|
|
|
|
.Trim('_');
|
|
|
|
|
|
|
|
|
|
return string.IsNullOrWhiteSpace(normalized) ? "empty" : normalized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ReplaceBaseEffects(
|
|
|
|
|
RolemasterDbContext dbContext,
|
|
|
|
|
|