Implement phase 6 critical effect normalization
This commit is contained in:
@@ -49,6 +49,43 @@
|
||||
<div class="callout">
|
||||
<h4>Affix Text</h4>
|
||||
<p class="stacked-copy">@Result.AffixText</p>
|
||||
|
||||
@if (Result.Effects.Count > 0)
|
||||
{
|
||||
<div class="effect-stack">
|
||||
<h5>Parsed Affixes</h5>
|
||||
<ul class="effect-list">
|
||||
@foreach (var effect in Result.Effects)
|
||||
{
|
||||
<li class="effect-item">
|
||||
@if (!string.IsNullOrWhiteSpace(effect.SourceText))
|
||||
{
|
||||
<code class="effect-token">@effect.SourceText</code>
|
||||
}
|
||||
<span>@FormatEffect(effect)</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else if (Result.Effects.Count > 0)
|
||||
{
|
||||
<div class="callout">
|
||||
<h4>Parsed Affixes</h4>
|
||||
<ul class="effect-list">
|
||||
@foreach (var effect in Result.Effects)
|
||||
{
|
||||
<li class="effect-item">
|
||||
@if (!string.IsNullOrWhiteSpace(effect.SourceText))
|
||||
{
|
||||
<code class="effect-token">@effect.SourceText</code>
|
||||
}
|
||||
<span>@FormatEffect(effect)</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -71,6 +108,22 @@
|
||||
{
|
||||
<p class="stacked-copy branch-affix">@branch.AffixText</p>
|
||||
}
|
||||
|
||||
@if (branch.Effects.Count > 0)
|
||||
{
|
||||
<ul class="effect-list branch-effects">
|
||||
@foreach (var effect in branch.Effects)
|
||||
{
|
||||
<li class="effect-item">
|
||||
@if (!string.IsNullOrWhiteSpace(effect.SourceText))
|
||||
{
|
||||
<code class="effect-token">@effect.SourceText</code>
|
||||
}
|
||||
<span>@FormatEffect(effect)</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
@@ -116,4 +169,21 @@
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatEffect(CriticalEffectLookupResponse effect) =>
|
||||
effect.EffectCode switch
|
||||
{
|
||||
"direct_hits" when effect.ValueInteger is not null => $"{effect.ValueInteger} direct hits",
|
||||
"must_parry_rounds" when effect.DurationRounds is not null => $"Must parry for {FormatRounds(effect.DurationRounds.Value)}",
|
||||
"no_parry_rounds" when effect.DurationRounds is not null => $"No parry for {FormatRounds(effect.DurationRounds.Value)}",
|
||||
"stunned_rounds" when effect.DurationRounds is not null => $"Stunned for {FormatRounds(effect.DurationRounds.Value)}",
|
||||
"bleed_per_round" when effect.PerRound is not null => $"Bleeds {effect.PerRound} hits per round",
|
||||
"foe_penalty" when effect.Modifier is not null => $"Foe penalty {effect.Modifier:+#;-#;0}",
|
||||
"attacker_bonus_next_round" when effect.Modifier is not null => $"Attacker bonus next round {effect.Modifier:+#;-#;0}",
|
||||
"power_point_modifier" when !string.IsNullOrWhiteSpace(effect.ValueExpression) => $"Foe power-point modifier {effect.ValueExpression}",
|
||||
_ => effect.EffectCode.Replace('_', ' ')
|
||||
};
|
||||
|
||||
private static string FormatRounds(int value) =>
|
||||
value == 1 ? "1 round" : $"{value} rounds";
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public sealed class RolemasterDbContext(DbContextOptions<RolemasterDbContext> op
|
||||
public DbSet<CriticalRollBand> CriticalRollBands => Set<CriticalRollBand>();
|
||||
public DbSet<CriticalResult> CriticalResults => Set<CriticalResult>();
|
||||
public DbSet<CriticalBranch> CriticalBranches => Set<CriticalBranch>();
|
||||
public DbSet<CriticalEffect> CriticalEffects => Set<CriticalEffect>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -87,5 +88,17 @@ public sealed class RolemasterDbContext(DbContextOptions<RolemasterDbContext> op
|
||||
entity.Property(item => item.BranchKind).HasMaxLength(32);
|
||||
entity.Property(item => item.ConditionKey).HasMaxLength(128);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CriticalEffect>(entity =>
|
||||
{
|
||||
entity.HasIndex(item => item.EffectCode);
|
||||
entity.HasIndex(item => item.CriticalResultId);
|
||||
entity.HasIndex(item => item.CriticalBranchId);
|
||||
entity.Property(item => item.EffectCode).HasMaxLength(64);
|
||||
entity.Property(item => item.Target).HasMaxLength(32);
|
||||
entity.Property(item => item.ValueExpression).HasMaxLength(128);
|
||||
entity.Property(item => item.BodyPart).HasMaxLength(64);
|
||||
entity.Property(item => item.SourceType).HasMaxLength(32);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,5 +39,52 @@ public static class RolemasterDbSchemaUpgrader
|
||||
ON "CriticalBranches" ("CriticalResultId", "SortOrder");
|
||||
""",
|
||||
cancellationToken);
|
||||
|
||||
await dbContext.Database.ExecuteSqlRawAsync(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS "CriticalEffects" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CriticalEffects" PRIMARY KEY AUTOINCREMENT,
|
||||
"CriticalResultId" INTEGER NULL,
|
||||
"CriticalBranchId" INTEGER NULL,
|
||||
"EffectCode" TEXT NOT NULL,
|
||||
"Target" TEXT NULL,
|
||||
"ValueInteger" INTEGER NULL,
|
||||
"ValueDecimal" TEXT NULL,
|
||||
"ValueExpression" TEXT NULL,
|
||||
"DurationRounds" INTEGER NULL,
|
||||
"PerRound" INTEGER NULL,
|
||||
"Modifier" INTEGER NULL,
|
||||
"BodyPart" TEXT NULL,
|
||||
"IsPermanent" INTEGER NOT NULL,
|
||||
"SourceType" TEXT NOT NULL,
|
||||
"SourceText" TEXT NULL,
|
||||
CONSTRAINT "FK_CriticalEffects_CriticalResults_CriticalResultId"
|
||||
FOREIGN KEY ("CriticalResultId") REFERENCES "CriticalResults" ("Id") ON DELETE CASCADE,
|
||||
CONSTRAINT "FK_CriticalEffects_CriticalBranches_CriticalBranchId"
|
||||
FOREIGN KEY ("CriticalBranchId") REFERENCES "CriticalBranches" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
""",
|
||||
cancellationToken);
|
||||
|
||||
await dbContext.Database.ExecuteSqlRawAsync(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_CriticalEffects_EffectCode"
|
||||
ON "CriticalEffects" ("EffectCode");
|
||||
""",
|
||||
cancellationToken);
|
||||
|
||||
await dbContext.Database.ExecuteSqlRawAsync(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_CriticalEffects_CriticalResultId"
|
||||
ON "CriticalEffects" ("CriticalResultId");
|
||||
""",
|
||||
cancellationToken);
|
||||
|
||||
await dbContext.Database.ExecuteSqlRawAsync(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS "IX_CriticalEffects_CriticalBranchId"
|
||||
ON "CriticalEffects" ("CriticalBranchId");
|
||||
""",
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,5 @@ public sealed class CriticalBranch
|
||||
public string ParsedJson { get; set; } = "{}";
|
||||
public int SortOrder { get; set; }
|
||||
public CriticalResult CriticalResult { get; set; } = null!;
|
||||
public List<CriticalEffect> Effects { get; set; } = [];
|
||||
}
|
||||
|
||||
22
src/RolemasterDb.App/Domain/CriticalEffect.cs
Normal file
22
src/RolemasterDb.App/Domain/CriticalEffect.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace RolemasterDb.App.Domain;
|
||||
|
||||
public sealed class CriticalEffect
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int? CriticalResultId { get; set; }
|
||||
public int? CriticalBranchId { get; set; }
|
||||
public string EffectCode { get; set; } = string.Empty;
|
||||
public string? Target { get; set; }
|
||||
public int? ValueInteger { get; set; }
|
||||
public decimal? ValueDecimal { get; set; }
|
||||
public string? ValueExpression { get; set; }
|
||||
public int? DurationRounds { get; set; }
|
||||
public int? PerRound { get; set; }
|
||||
public int? Modifier { get; set; }
|
||||
public string? BodyPart { get; set; }
|
||||
public bool IsPermanent { get; set; }
|
||||
public string SourceType { get; set; } = "symbol";
|
||||
public string? SourceText { get; set; }
|
||||
public CriticalResult? CriticalResult { get; set; }
|
||||
public CriticalBranch? CriticalBranch { get; set; }
|
||||
}
|
||||
13
src/RolemasterDb.App/Domain/CriticalEffectCodes.cs
Normal file
13
src/RolemasterDb.App/Domain/CriticalEffectCodes.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace RolemasterDb.App.Domain;
|
||||
|
||||
public static class CriticalEffectCodes
|
||||
{
|
||||
public const string DirectHits = "direct_hits";
|
||||
public const string MustParryRounds = "must_parry_rounds";
|
||||
public const string NoParryRounds = "no_parry_rounds";
|
||||
public const string StunnedRounds = "stunned_rounds";
|
||||
public const string BleedPerRound = "bleed_per_round";
|
||||
public const string FoePenalty = "foe_penalty";
|
||||
public const string AttackerBonusNextRound = "attacker_bonus_next_round";
|
||||
public const string PowerPointModifier = "power_point_modifier";
|
||||
}
|
||||
@@ -17,4 +17,5 @@ public sealed class CriticalResult
|
||||
public CriticalColumn CriticalColumn { get; set; } = null!;
|
||||
public CriticalRollBand CriticalRollBand { get; set; } = null!;
|
||||
public List<CriticalBranch> Branches { get; set; } = [];
|
||||
public List<CriticalEffect> Effects { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace RolemasterDb.App.Features;
|
||||
|
||||
public sealed record CriticalEffectLookupResponse(
|
||||
string EffectCode,
|
||||
string? Target,
|
||||
int? ValueInteger,
|
||||
string? ValueExpression,
|
||||
int? DurationRounds,
|
||||
int? PerRound,
|
||||
int? Modifier,
|
||||
string? BodyPart,
|
||||
bool IsPermanent,
|
||||
string SourceType,
|
||||
string? SourceText);
|
||||
@@ -52,6 +52,7 @@ public sealed record CriticalBranchLookupResponse(
|
||||
string ConditionText,
|
||||
string Description,
|
||||
string? AffixText,
|
||||
IReadOnlyList<CriticalEffectLookupResponse> Effects,
|
||||
string RawText,
|
||||
int SortOrder);
|
||||
|
||||
@@ -73,6 +74,7 @@ public sealed record CriticalLookupResponse(
|
||||
string RawCellText,
|
||||
string Description,
|
||||
string? AffixText,
|
||||
IReadOnlyList<CriticalEffectLookupResponse> Effects,
|
||||
IReadOnlyList<CriticalBranchLookupResponse> Branches,
|
||||
string ParseStatus,
|
||||
string ParsedJson);
|
||||
|
||||
@@ -132,6 +132,21 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
item.RawCellText,
|
||||
item.DescriptionText,
|
||||
item.RawAffixText,
|
||||
item.Effects
|
||||
.OrderBy(effect => effect.Id)
|
||||
.Select(effect => new CriticalEffectLookupResponse(
|
||||
effect.EffectCode,
|
||||
effect.Target,
|
||||
effect.ValueInteger,
|
||||
effect.ValueExpression,
|
||||
effect.DurationRounds,
|
||||
effect.PerRound,
|
||||
effect.Modifier,
|
||||
effect.BodyPart,
|
||||
effect.IsPermanent,
|
||||
effect.SourceType,
|
||||
effect.SourceText))
|
||||
.ToList(),
|
||||
item.Branches
|
||||
.OrderBy(branch => branch.SortOrder)
|
||||
.Select(branch => new CriticalBranchLookupResponse(
|
||||
@@ -140,6 +155,21 @@ public sealed class LookupService(IDbContextFactory<RolemasterDbContext> dbConte
|
||||
branch.ConditionText,
|
||||
branch.DescriptionText,
|
||||
branch.RawAffixText,
|
||||
branch.Effects
|
||||
.OrderBy(effect => effect.Id)
|
||||
.Select(effect => new CriticalEffectLookupResponse(
|
||||
effect.EffectCode,
|
||||
effect.Target,
|
||||
effect.ValueInteger,
|
||||
effect.ValueExpression,
|
||||
effect.DurationRounds,
|
||||
effect.PerRound,
|
||||
effect.Modifier,
|
||||
effect.BodyPart,
|
||||
effect.IsPermanent,
|
||||
effect.SourceType,
|
||||
effect.SourceText))
|
||||
.ToList(),
|
||||
branch.RawText,
|
||||
branch.SortOrder))
|
||||
.ToList(),
|
||||
|
||||
@@ -255,6 +255,49 @@ textarea {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.effect-stack {
|
||||
margin-top: 0.85rem;
|
||||
}
|
||||
|
||||
.effect-stack h5 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #75562f;
|
||||
}
|
||||
|
||||
.effect-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.7rem 0 0;
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.effect-item {
|
||||
display: flex;
|
||||
gap: 0.65rem;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.55rem 0.7rem;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 252, 244, 0.72);
|
||||
border: 1px solid rgba(127, 96, 55, 0.12);
|
||||
}
|
||||
|
||||
.effect-token {
|
||||
padding: 0.12rem 0.38rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(238, 223, 193, 0.72);
|
||||
color: #5b4327;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.branch-effects {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #8d2b1e;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user