Generalize Rolemaster standard dice parsing
This commit is contained in:
@@ -48,8 +48,6 @@ public sealed class SkillFormModel
|
||||
public int WildDice { get; set; }
|
||||
public bool AllowFumble { get; set; }
|
||||
public int? FumbleRange { get; set; }
|
||||
public string RolemasterRollType { get; set; } = HomeControls.RulesetFormHelpers.RolemasterRollTypes.Initiative;
|
||||
public int RolemasterModifier { get; set; }
|
||||
}
|
||||
|
||||
public sealed class SkillGroupFormModel
|
||||
@@ -60,8 +58,6 @@ public sealed class SkillGroupFormModel
|
||||
public int WildDice { get; set; }
|
||||
public bool AllowFumble { get; set; }
|
||||
public int? FumbleRange { get; set; }
|
||||
public string RolemasterRollType { get; set; } = HomeControls.RulesetFormHelpers.RolemasterRollTypes.Initiative;
|
||||
public int RolemasterModifier { get; set; }
|
||||
}
|
||||
|
||||
public enum HomeViewMode
|
||||
|
||||
@@ -173,16 +173,6 @@
|
||||
}
|
||||
else if (IsRolemasterRuleset)
|
||||
{
|
||||
<label for="skill-group-rolemaster-roll-type">Prototype roll type</label>
|
||||
<select id="skill-group-rolemaster-roll-type" value="@SkillGroupState.Model.RolemasterRollType" @onchange="OnSkillGroupRolemasterRollTypeChanged">
|
||||
<option value="@RulesetFormHelpers.RolemasterRollTypes.Initiative">Initiative</option>
|
||||
<option value="@RulesetFormHelpers.RolemasterRollTypes.Percentile">Percentile</option>
|
||||
<option value="@RulesetFormHelpers.RolemasterRollTypes.OpenEndedPercentile">Open-ended percentile</option>
|
||||
</select>
|
||||
|
||||
<label for="skill-group-rolemaster-modifier">Prototype modifier</label>
|
||||
<input id="skill-group-rolemaster-modifier" type="number" step="1" value="@SkillGroupState.Model.RolemasterModifier" @oninput="OnSkillGroupRolemasterModifierChanged"/>
|
||||
|
||||
@if (IsSkillGroupRolemasterOpenEnded)
|
||||
{
|
||||
<label for="skill-group-fumble-range">Prototype fumble range</label>
|
||||
@@ -224,8 +214,6 @@
|
||||
SkillGroupInputId="skill-create-group"
|
||||
WildDiceInputId="skill-create-wild-dice"
|
||||
AllowFumbleInputId="skill-create-allow-fumble"
|
||||
RolemasterRollTypeInputId="skill-create-rolemaster-roll-type"
|
||||
RolemasterModifierInputId="skill-create-rolemaster-modifier"
|
||||
FumbleRangeInputId="skill-create-fumble-range"
|
||||
InitialModel="CreateSkillInitialModel"
|
||||
FormVersion="CreateSkillFormVersion"
|
||||
@@ -246,8 +234,6 @@
|
||||
SkillGroupInputId="skill-edit-group"
|
||||
WildDiceInputId="skill-edit-wild-dice"
|
||||
AllowFumbleInputId="skill-edit-allow-fumble"
|
||||
RolemasterRollTypeInputId="skill-edit-rolemaster-roll-type"
|
||||
RolemasterModifierInputId="skill-edit-rolemaster-modifier"
|
||||
FumbleRangeInputId="skill-edit-fumble-range"
|
||||
InitialModel="EditSkillInitialModel"
|
||||
FormVersion="EditSkillFormVersion"
|
||||
|
||||
@@ -22,13 +22,11 @@ public partial class CharacterPanel
|
||||
SkillGroupId = selectedGroup?.Id.ToString() ?? string.Empty,
|
||||
WildDice = selectedGroup?.WildDice ?? (IsD6Ruleset ? 1 : 0),
|
||||
AllowFumble = selectedGroup?.AllowFumble ?? IsD6Ruleset,
|
||||
FumbleRange = selectedGroup?.FumbleRange,
|
||||
RolemasterRollType = RulesetFormHelpers.InferRolemasterRollType(selectedGroup?.DiceRollDefinition),
|
||||
RolemasterModifier = RulesetFormHelpers.InferRolemasterModifier(selectedGroup?.DiceRollDefinition)
|
||||
FumbleRange = selectedGroup?.FumbleRange
|
||||
};
|
||||
|
||||
if (IsRolemasterRuleset && string.IsNullOrWhiteSpace(CreateSkillInitialModel.DiceRollDefinition))
|
||||
CreateSkillInitialModel.DiceRollDefinition = RulesetFormHelpers.BuildRolemasterExpression(CreateSkillInitialModel.RolemasterRollType, CreateSkillInitialModel.RolemasterModifier);
|
||||
CreateSkillInitialModel.DiceRollDefinition = "d100";
|
||||
|
||||
CreateSkillFormVersion++;
|
||||
ShowCreateSkillModal = true;
|
||||
@@ -45,9 +43,7 @@ public partial class CharacterPanel
|
||||
SkillGroupId = skill.SkillGroupId?.ToString() ?? string.Empty,
|
||||
WildDice = skill.WildDice,
|
||||
AllowFumble = skill.AllowFumble,
|
||||
FumbleRange = skill.FumbleRange,
|
||||
RolemasterRollType = RulesetFormHelpers.InferRolemasterRollType(skill.DiceRollDefinition),
|
||||
RolemasterModifier = RulesetFormHelpers.InferRolemasterModifier(skill.DiceRollDefinition)
|
||||
FumbleRange = skill.FumbleRange
|
||||
};
|
||||
|
||||
EditSkillFormVersion++;
|
||||
@@ -113,10 +109,8 @@ public partial class CharacterPanel
|
||||
SkillGroupState.Model.WildDice = IsD6Ruleset ? 1 : 0;
|
||||
SkillGroupState.Model.AllowFumble = IsD6Ruleset;
|
||||
SkillGroupState.Model.FumbleRange = null;
|
||||
SkillGroupState.Model.RolemasterRollType = RulesetFormHelpers.RolemasterRollTypes.Initiative;
|
||||
SkillGroupState.Model.RolemasterModifier = 0;
|
||||
if (IsRolemasterRuleset)
|
||||
SynchronizeSkillGroupExpression();
|
||||
SkillGroupState.Model.DiceRollDefinition = "d100";
|
||||
SkillGroupState.ResetValidation();
|
||||
ShowCreateSkillGroupModal = true;
|
||||
}
|
||||
@@ -130,8 +124,6 @@ public partial class CharacterPanel
|
||||
SkillGroupState.Model.WildDice = skillGroup.WildDice;
|
||||
SkillGroupState.Model.AllowFumble = skillGroup.AllowFumble;
|
||||
SkillGroupState.Model.FumbleRange = skillGroup.FumbleRange;
|
||||
SkillGroupState.Model.RolemasterRollType = RulesetFormHelpers.InferRolemasterRollType(skillGroup.DiceRollDefinition);
|
||||
SkillGroupState.Model.RolemasterModifier = RulesetFormHelpers.InferRolemasterModifier(skillGroup.DiceRollDefinition);
|
||||
NormalizeSkillGroupFumbleRange();
|
||||
SkillGroupState.ResetValidation();
|
||||
ShowEditSkillGroupModal = true;
|
||||
@@ -325,36 +317,7 @@ public partial class CharacterPanel
|
||||
{
|
||||
SkillGroupState.Model.DiceRollDefinition = args.Value?.ToString() ?? string.Empty;
|
||||
if (IsRolemasterRuleset)
|
||||
{
|
||||
SkillGroupState.Model.RolemasterRollType = RulesetFormHelpers.InferRolemasterRollType(SkillGroupState.Model.DiceRollDefinition);
|
||||
SkillGroupState.Model.RolemasterModifier = RulesetFormHelpers.InferRolemasterModifier(SkillGroupState.Model.DiceRollDefinition);
|
||||
NormalizeSkillGroupFumbleRange();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSkillGroupRolemasterRollTypeChanged(ChangeEventArgs args)
|
||||
{
|
||||
SkillGroupState.Model.RolemasterRollType = args.Value?.ToString() ?? RulesetFormHelpers.RolemasterRollTypes.Initiative;
|
||||
NormalizeSkillGroupFumbleRange();
|
||||
SynchronizeSkillGroupExpression();
|
||||
}
|
||||
|
||||
private void OnSkillGroupRolemasterModifierChanged(ChangeEventArgs args)
|
||||
{
|
||||
var rawValue = args.Value?.ToString();
|
||||
if (!int.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modifier))
|
||||
modifier = 0;
|
||||
|
||||
SkillGroupState.Model.RolemasterModifier = modifier;
|
||||
SynchronizeSkillGroupExpression();
|
||||
}
|
||||
|
||||
private void SynchronizeSkillGroupExpression()
|
||||
{
|
||||
if (!IsRolemasterRuleset)
|
||||
return;
|
||||
|
||||
SkillGroupState.Model.DiceRollDefinition = RulesetFormHelpers.BuildRolemasterExpression(SkillGroupState.Model.RolemasterRollType, SkillGroupState.Model.RolemasterModifier);
|
||||
}
|
||||
|
||||
private void NormalizeSkillGroupFumbleRange()
|
||||
@@ -376,9 +339,9 @@ public partial class CharacterPanel
|
||||
|
||||
private bool IsD6Ruleset => RulesetFormHelpers.IsD6(SelectedCampaignRulesetId);
|
||||
private bool IsRolemasterRuleset => RulesetFormHelpers.IsRolemaster(SelectedCampaignRulesetId);
|
||||
private bool IsSkillGroupRolemasterOpenEnded => string.Equals(SkillGroupState.Model.RolemasterRollType, RulesetFormHelpers.RolemasterRollTypes.OpenEndedPercentile, StringComparison.OrdinalIgnoreCase);
|
||||
private bool IsSkillGroupRolemasterOpenEnded => RulesetFormHelpers.IsRolemasterOpenEndedExpression(SkillGroupState.Model.DiceRollDefinition);
|
||||
private string SkillGroupExpressionHelpText => IsRolemasterRuleset
|
||||
? $"{RulesetFormHelpers.RolemasterExampleText(SkillGroupState.Model.RolemasterRollType)}. Negative modifiers are allowed."
|
||||
? $"{RulesetFormHelpers.RolemasterExampleText()}. Negative modifiers are allowed."
|
||||
: "Enter the default expression for skills created in this group.";
|
||||
|
||||
private bool ShowCreateSkillModal { get; set; }
|
||||
|
||||
@@ -54,6 +54,9 @@ public partial class RollDiceStrip
|
||||
|
||||
switch (die.Kind)
|
||||
{
|
||||
case RollDieKinds.RolemasterStandard:
|
||||
classes.Add("rolemaster-standard");
|
||||
break;
|
||||
case RollDieKinds.RolemasterInitiative:
|
||||
classes.Add("rolemaster-initiative");
|
||||
break;
|
||||
@@ -97,6 +100,9 @@ public partial class RollDiceStrip
|
||||
|
||||
switch (die.Kind)
|
||||
{
|
||||
case RollDieKinds.RolemasterStandard:
|
||||
labels.Add("Rolemaster roll");
|
||||
break;
|
||||
case RollDieKinds.RolemasterInitiative:
|
||||
labels.Add("Rolemaster initiative");
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using RpgRoller.Domain;
|
||||
using RpgRoller.Services;
|
||||
|
||||
@@ -15,13 +14,6 @@ internal static class RulesetFormHelpers
|
||||
public const string Rolemaster = "rolemaster";
|
||||
}
|
||||
|
||||
internal static class RolemasterRollTypes
|
||||
{
|
||||
public const string Initiative = "initiative";
|
||||
public const string Percentile = "percentile";
|
||||
public const string OpenEndedPercentile = "open-ended-percentile";
|
||||
}
|
||||
|
||||
public static bool IsD6(string? rulesetId)
|
||||
{
|
||||
return string.Equals(rulesetId, RulesetIds.D6, StringComparison.OrdinalIgnoreCase);
|
||||
@@ -32,34 +24,12 @@ internal static class RulesetFormHelpers
|
||||
return string.Equals(rulesetId, RulesetIds.Rolemaster, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string InferRolemasterRollType(string? expression)
|
||||
public static bool IsRolemasterOpenEndedExpression(string? expression)
|
||||
{
|
||||
var parseResult = TryParseRolemasterExpression(expression);
|
||||
if (!parseResult.Succeeded || parseResult.Value is null)
|
||||
return RolemasterRollTypes.Initiative;
|
||||
|
||||
return parseResult.Value.Kind switch
|
||||
{
|
||||
DiceExpressionKind.RolemasterPercentile => RolemasterRollTypes.Percentile,
|
||||
DiceExpressionKind.RolemasterOpenEndedPercentile => RolemasterRollTypes.OpenEndedPercentile,
|
||||
_ => RolemasterRollTypes.Initiative
|
||||
};
|
||||
}
|
||||
|
||||
public static int InferRolemasterModifier(string? expression)
|
||||
{
|
||||
var parseResult = TryParseRolemasterExpression(expression);
|
||||
return parseResult.Succeeded && parseResult.Value is not null ? parseResult.Value.Modifier : 0;
|
||||
}
|
||||
|
||||
public static string BuildRolemasterExpression(string? rollType, int modifier)
|
||||
{
|
||||
return rollType switch
|
||||
{
|
||||
RolemasterRollTypes.Percentile => $"d100{FormatModifier(modifier)}",
|
||||
RolemasterRollTypes.OpenEndedPercentile => $"d100!{FormatModifier(modifier)}",
|
||||
_ => $"2d10{FormatModifier(modifier)}"
|
||||
};
|
||||
return parseResult.Succeeded &&
|
||||
parseResult.Value is not null &&
|
||||
parseResult.Value.Kind == DiceExpressionKind.RolemasterOpenEndedPercentile;
|
||||
}
|
||||
|
||||
public static string DescribeRolemasterExpression(string expression, int? fumbleRange)
|
||||
@@ -70,22 +40,16 @@ internal static class RulesetFormHelpers
|
||||
|
||||
return parseResult.Value.Kind switch
|
||||
{
|
||||
DiceExpressionKind.RolemasterPercentile => $"Percentile: {parseResult.Value.Canonical}",
|
||||
DiceExpressionKind.RolemasterOpenEndedPercentile => fumbleRange.HasValue
|
||||
? $"Open-ended percentile: {parseResult.Value.Canonical}, fumble <= {fumbleRange.Value}"
|
||||
: $"Open-ended percentile: {parseResult.Value.Canonical}",
|
||||
_ => $"Initiative: {parseResult.Value.Canonical}"
|
||||
_ => $"Rolemaster: {parseResult.Value.Canonical}"
|
||||
};
|
||||
}
|
||||
|
||||
public static string RolemasterExampleText(string? rollType)
|
||||
public static string RolemasterExampleText()
|
||||
{
|
||||
return rollType switch
|
||||
{
|
||||
RolemasterRollTypes.Percentile => "Example: d100+48",
|
||||
RolemasterRollTypes.OpenEndedPercentile => "Example: d100!+85",
|
||||
_ => "Example: 2d10+48"
|
||||
};
|
||||
return "Examples: d10, 15d10, d100-15, d100!+85";
|
||||
}
|
||||
|
||||
private static ServiceResult<DiceExpression> TryParseRolemasterExpression(string? expression)
|
||||
@@ -95,9 +59,4 @@ internal static class RulesetFormHelpers
|
||||
|
||||
return DiceRules.ParseExpression(RulesetKind.Rolemaster, expression);
|
||||
}
|
||||
|
||||
private static string FormatModifier(int modifier)
|
||||
{
|
||||
return modifier >= 0 ? $"+{modifier}" : modifier.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,16 +47,6 @@
|
||||
}
|
||||
else if (IsRolemasterRuleset)
|
||||
{
|
||||
<label for="@RolemasterRollTypeInputId">Roll type</label>
|
||||
<select id="@RolemasterRollTypeInputId" value="@FormState.Model.RolemasterRollType" @onchange="OnRolemasterRollTypeChanged">
|
||||
<option value="@RulesetFormHelpers.RolemasterRollTypes.Initiative">Initiative</option>
|
||||
<option value="@RulesetFormHelpers.RolemasterRollTypes.Percentile">Percentile</option>
|
||||
<option value="@RulesetFormHelpers.RolemasterRollTypes.OpenEndedPercentile">Open-ended percentile</option>
|
||||
</select>
|
||||
|
||||
<label for="@RolemasterModifierInputId">Modifier</label>
|
||||
<input id="@RolemasterModifierInputId" type="number" step="1" value="@FormState.Model.RolemasterModifier" @oninput="OnRolemasterModifierChanged"/>
|
||||
|
||||
@if (IsRolemasterOpenEndedSelected)
|
||||
{
|
||||
<label for="@FumbleRangeInputId">Fumble range</label>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using RpgRoller.Contracts;
|
||||
@@ -21,8 +20,6 @@ public partial class SkillFormModal
|
||||
FormState.Model.WildDice = InitialModel.WildDice;
|
||||
FormState.Model.AllowFumble = InitialModel.AllowFumble;
|
||||
FormState.Model.FumbleRange = InitialModel.FumbleRange;
|
||||
FormState.Model.RolemasterRollType = InitialModel.RolemasterRollType;
|
||||
FormState.Model.RolemasterModifier = InitialModel.RolemasterModifier;
|
||||
SynchronizeRulesetSpecificFields();
|
||||
FormState.ResetValidation();
|
||||
AppliedFormVersion = FormVersion;
|
||||
@@ -117,31 +114,14 @@ public partial class SkillFormModal
|
||||
{
|
||||
FormState.Model.DiceRollDefinition = args.Value?.ToString() ?? string.Empty;
|
||||
if (IsRolemasterRuleset)
|
||||
SynchronizeRolemasterInputsFromExpression();
|
||||
}
|
||||
|
||||
private void OnRolemasterRollTypeChanged(ChangeEventArgs args)
|
||||
{
|
||||
FormState.Model.RolemasterRollType = args.Value?.ToString() ?? RulesetFormHelpers.RolemasterRollTypes.Initiative;
|
||||
NormalizeRolemasterFumbleRange();
|
||||
SynchronizeRolemasterExpression();
|
||||
}
|
||||
|
||||
private void OnRolemasterModifierChanged(ChangeEventArgs args)
|
||||
{
|
||||
var rawValue = args.Value?.ToString();
|
||||
if (!int.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modifier))
|
||||
modifier = 0;
|
||||
|
||||
FormState.Model.RolemasterModifier = modifier;
|
||||
SynchronizeRolemasterExpression();
|
||||
NormalizeRolemasterFumbleRange();
|
||||
}
|
||||
|
||||
private bool IsD6Ruleset => RulesetFormHelpers.IsD6(RulesetId);
|
||||
private bool IsRolemasterRuleset => RulesetFormHelpers.IsRolemaster(RulesetId);
|
||||
private bool IsRolemasterOpenEndedSelected => string.Equals(FormState.Model.RolemasterRollType, RulesetFormHelpers.RolemasterRollTypes.OpenEndedPercentile, StringComparison.OrdinalIgnoreCase);
|
||||
private bool IsRolemasterOpenEndedSelected => RulesetFormHelpers.IsRolemasterOpenEndedExpression(FormState.Model.DiceRollDefinition);
|
||||
private string ExpressionHelpText => IsRolemasterRuleset
|
||||
? $"{RulesetFormHelpers.RolemasterExampleText(FormState.Model.RolemasterRollType)}. Negative modifiers are allowed."
|
||||
? $"{RulesetFormHelpers.RolemasterExampleText()}. Negative modifiers are allowed."
|
||||
: "Enter the dice expression used for this skill.";
|
||||
|
||||
private void SynchronizeRulesetSpecificFields()
|
||||
@@ -149,24 +129,9 @@ public partial class SkillFormModal
|
||||
if (!IsRolemasterRuleset)
|
||||
return;
|
||||
|
||||
SynchronizeRolemasterInputsFromExpression();
|
||||
NormalizeRolemasterFumbleRange();
|
||||
}
|
||||
|
||||
private void SynchronizeRolemasterInputsFromExpression()
|
||||
{
|
||||
FormState.Model.RolemasterRollType = RulesetFormHelpers.InferRolemasterRollType(FormState.Model.DiceRollDefinition);
|
||||
FormState.Model.RolemasterModifier = RulesetFormHelpers.InferRolemasterModifier(FormState.Model.DiceRollDefinition);
|
||||
}
|
||||
|
||||
private void SynchronizeRolemasterExpression()
|
||||
{
|
||||
if (!IsRolemasterRuleset)
|
||||
return;
|
||||
|
||||
FormState.Model.DiceRollDefinition = RulesetFormHelpers.BuildRolemasterExpression(FormState.Model.RolemasterRollType, FormState.Model.RolemasterModifier);
|
||||
}
|
||||
|
||||
private void NormalizeRolemasterFumbleRange()
|
||||
{
|
||||
if (!IsRolemasterRuleset)
|
||||
@@ -220,12 +185,6 @@ public partial class SkillFormModal
|
||||
[Parameter]
|
||||
public string AllowFumbleInputId { get; set; } = "skill-fumble";
|
||||
|
||||
[Parameter]
|
||||
public string RolemasterRollTypeInputId { get; set; } = "skill-rolemaster-roll-type";
|
||||
|
||||
[Parameter]
|
||||
public string RolemasterModifierInputId { get; set; } = "skill-rolemaster-modifier";
|
||||
|
||||
[Parameter]
|
||||
public string FumbleRangeInputId { get; set; } = "skill-fumble-range";
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public sealed record RollSkillRequest(string Visibility);
|
||||
|
||||
public static class RollDieKinds
|
||||
{
|
||||
public const string RolemasterStandard = "rolemaster-standard";
|
||||
public const string RolemasterInitiative = "rolemaster-initiative";
|
||||
public const string RolemasterPercentile = "rolemaster-percentile";
|
||||
public const string RolemasterOpenEndedInitial = "rolemaster-open-ended-initial";
|
||||
|
||||
@@ -93,8 +93,7 @@ public sealed class RollLogEntry
|
||||
public enum DiceExpressionKind
|
||||
{
|
||||
Standard,
|
||||
RolemasterInitiative,
|
||||
RolemasterPercentile,
|
||||
RolemasterStandard,
|
||||
RolemasterOpenEndedPercentile
|
||||
}
|
||||
|
||||
|
||||
@@ -78,32 +78,30 @@ public static partial class DiceRules
|
||||
|
||||
private static ServiceResult<DiceExpression> ParseRolemaster(string expression)
|
||||
{
|
||||
var initiativeMatch = RolemasterInitiativeRegex().Match(expression);
|
||||
if (initiativeMatch.Success)
|
||||
{
|
||||
var modifier = ParseModifier(initiativeMatch.Groups["modifier"].Value);
|
||||
var validation = ValidateDiceParts(2, 10, modifier, -MaxModifier, MaxModifier);
|
||||
if (!validation.Succeeded)
|
||||
return ServiceResult<DiceExpression>.Failure(validation.Error!.Code, validation.Error.Message);
|
||||
var match = RolemasterRegex().Match(expression);
|
||||
if (!match.Success)
|
||||
return ServiceResult<DiceExpression>.Failure("invalid_expression", "Expected Rolemaster format like d10+4, 15d10, d100-15, or d100!+85.");
|
||||
|
||||
return ServiceResult<DiceExpression>.Success(new(2, 10, modifier, $"2d10{FormatModifier(modifier)}", DiceExpressionKind.RolemasterInitiative));
|
||||
var countValue = match.Groups["count"].Value;
|
||||
var diceCount = string.IsNullOrEmpty(countValue) ? 1 : int.Parse(countValue);
|
||||
var sides = int.Parse(match.Groups["sides"].Value);
|
||||
var modifier = ParseModifier(match.Groups["modifier"].Value);
|
||||
var validation = ValidateDiceParts(diceCount, sides, modifier, -MaxModifier, MaxModifier);
|
||||
if (!validation.Succeeded)
|
||||
return ServiceResult<DiceExpression>.Failure(validation.Error!.Code, validation.Error.Message);
|
||||
|
||||
var isOpenEnded = match.Groups["openEnded"].Success;
|
||||
if (isOpenEnded && (diceCount != 1 || sides != 100))
|
||||
{
|
||||
return ServiceResult<DiceExpression>.Failure(
|
||||
"invalid_expression",
|
||||
"Open-ended Rolemaster rolls must use d100! with an implicit or explicit dice count of 1.");
|
||||
}
|
||||
|
||||
var percentileMatch = RolemasterPercentileRegex().Match(expression);
|
||||
if (percentileMatch.Success)
|
||||
{
|
||||
var modifier = ParseModifier(percentileMatch.Groups["modifier"].Value);
|
||||
var validation = ValidateDiceParts(1, 100, modifier, -MaxModifier, MaxModifier);
|
||||
if (!validation.Succeeded)
|
||||
return ServiceResult<DiceExpression>.Failure(validation.Error!.Code, validation.Error.Message);
|
||||
|
||||
var isOpenEnded = percentileMatch.Groups["openEnded"].Success;
|
||||
var canonical = isOpenEnded ? $"d100!{FormatModifier(modifier)}" : $"d100{FormatModifier(modifier)}";
|
||||
var kind = isOpenEnded ? DiceExpressionKind.RolemasterOpenEndedPercentile : DiceExpressionKind.RolemasterPercentile;
|
||||
return ServiceResult<DiceExpression>.Success(new(1, 100, modifier, canonical, kind));
|
||||
}
|
||||
|
||||
return ServiceResult<DiceExpression>.Failure("invalid_expression", "Expected Rolemaster format like 2d10+48, d100+4, or d100!+85.");
|
||||
var countPrefix = diceCount == 1 ? string.Empty : diceCount.ToString();
|
||||
var canonical = $"{countPrefix}d{sides}{(isOpenEnded ? "!" : string.Empty)}{FormatModifier(modifier)}";
|
||||
var kind = isOpenEnded ? DiceExpressionKind.RolemasterOpenEndedPercentile : DiceExpressionKind.RolemasterStandard;
|
||||
return ServiceResult<DiceExpression>.Success(new(diceCount, sides, modifier, canonical, kind));
|
||||
}
|
||||
|
||||
private static ServiceResult<bool> ValidateDiceParts(int diceCount, int sides, int modifier, int minModifier = 0, int maxModifier = MaxModifier)
|
||||
@@ -141,11 +139,8 @@ public static partial class DiceRules
|
||||
[GeneratedRegex("^(?<count>\\d+)d(?<sides>\\d+)(?:\\+(?<modifier>\\d+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex Dnd5eRegex();
|
||||
|
||||
[GeneratedRegex("^2d10(?<modifier>[+-]\\d+)?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex RolemasterInitiativeRegex();
|
||||
|
||||
[GeneratedRegex("^(?:1)?d100(?<openEnded>!)?(?<modifier>[+-]\\d+)?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex RolemasterPercentileRegex();
|
||||
[GeneratedRegex("^(?<count>\\d+)?d(?<sides>\\d+)(?<openEnded>!)?(?<modifier>[+-]\\d+)?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
|
||||
private static partial Regex RolemasterRegex();
|
||||
|
||||
private const int MaxDiceCount = 50;
|
||||
private const int MaxSides = 1000;
|
||||
@@ -155,6 +150,6 @@ public static partial class DiceRules
|
||||
[
|
||||
(RulesetKind.D6, "d6", "D6 System", "countD(+modifier), e.g. 5D+4"),
|
||||
(RulesetKind.Dnd5e, "dnd5e", "D&D 5e", "countdSides(+modifier), e.g. 2d12+2"),
|
||||
(RulesetKind.Rolemaster, "rolemaster", "Rolemaster", "2d10+48, d100+4, d100!+85")
|
||||
(RulesetKind.Rolemaster, "rolemaster", "Rolemaster", "countdSides(+/-modifier), e.g. d10, 15d10, d100-15, d100!+85")
|
||||
];
|
||||
}
|
||||
|
||||
@@ -949,8 +949,7 @@ public sealed class GameService : IGameService
|
||||
{
|
||||
return expression.Kind switch
|
||||
{
|
||||
DiceExpressionKind.RolemasterInitiative => ComputeRolemasterInitiativeRoll(expression),
|
||||
DiceExpressionKind.RolemasterPercentile => ComputeRolemasterPercentileRoll(expression),
|
||||
DiceExpressionKind.RolemasterStandard => ComputeRolemasterStandardRoll(expression),
|
||||
DiceExpressionKind.RolemasterOpenEndedPercentile => ComputeRolemasterOpenEndedRoll(expression, skill.FumbleRange.GetValueOrDefault()),
|
||||
_ => ComputeStandardRoll(expression)
|
||||
};
|
||||
@@ -975,7 +974,7 @@ public sealed class GameService : IGameService
|
||||
return (total, BuildBreakdown(diceValues, expression.Modifier, total), dice);
|
||||
}
|
||||
|
||||
private (int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice) ComputeRolemasterInitiativeRoll(DiceExpression expression)
|
||||
private (int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice) ComputeRolemasterStandardRoll(DiceExpression expression)
|
||||
{
|
||||
var diceValues = new int[expression.DiceCount];
|
||||
var dice = new RollDieResult[expression.DiceCount];
|
||||
@@ -984,25 +983,13 @@ public sealed class GameService : IGameService
|
||||
{
|
||||
var value = m_DiceRoller.Roll(expression.Sides);
|
||||
diceValues[i] = value;
|
||||
dice[i] = CreateRolemasterDie(value, i + 1, RollDieKinds.RolemasterInitiative, value);
|
||||
dice[i] = CreateRolemasterDie(value, i + 1, RollDieKinds.RolemasterStandard, value);
|
||||
total += value;
|
||||
}
|
||||
|
||||
return (total, BuildBreakdown(diceValues, expression.Modifier, total), dice);
|
||||
}
|
||||
|
||||
private (int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice) ComputeRolemasterPercentileRoll(DiceExpression expression)
|
||||
{
|
||||
var roll = m_DiceRoller.Roll(expression.Sides);
|
||||
var total = roll + expression.Modifier;
|
||||
var dice = new[]
|
||||
{
|
||||
CreateRolemasterDie(roll, 1, RollDieKinds.RolemasterPercentile, roll)
|
||||
};
|
||||
|
||||
return (total, BuildBreakdown([roll], expression.Modifier, total), dice);
|
||||
}
|
||||
|
||||
private (int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice) ComputeRolemasterOpenEndedRoll(DiceExpression expression, int fumbleRange)
|
||||
{
|
||||
var initialRoll = m_DiceRoller.Roll(expression.Sides);
|
||||
@@ -1427,6 +1414,15 @@ public sealed class GameService : IGameService
|
||||
return $"{openEndedInitial.Roll} | open-ended";
|
||||
}
|
||||
|
||||
if (dice.Any(die => string.Equals(die.Kind, RollDieKinds.RolemasterStandard, StringComparison.Ordinal)))
|
||||
{
|
||||
var preview = string.Join(" + ", dice.Take(3).Select(die => die.Roll.ToString()));
|
||||
if (dice.Count > 3)
|
||||
preview = $"{preview} + ...";
|
||||
|
||||
return $"{preview} | rolemaster";
|
||||
}
|
||||
|
||||
if (dice.Any(die => string.Equals(die.Kind, RollDieKinds.RolemasterInitiative, StringComparison.Ordinal)))
|
||||
return $"{string.Join(" + ", dice.Select(die => die.Roll.ToString()))} | initiative";
|
||||
|
||||
@@ -1438,7 +1434,8 @@ public sealed class GameService : IGameService
|
||||
|
||||
private static bool IsRolemasterDieKind(string? kind)
|
||||
{
|
||||
return kind is RollDieKinds.RolemasterInitiative or
|
||||
return kind is RollDieKinds.RolemasterStandard or
|
||||
RollDieKinds.RolemasterInitiative or
|
||||
RollDieKinds.RolemasterPercentile or
|
||||
RollDieKinds.RolemasterOpenEndedInitial or
|
||||
RollDieKinds.RolemasterOpenEndedHigh or
|
||||
|
||||
Reference in New Issue
Block a user