feat(lucky-dice): 提交 demo 前核心流程
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
Confs
|
||||
Client/ServerData
|
||||
/assetbundles
|
||||
.DS_Store
|
||||
.vscode/
|
||||
/cfgPack/encryptcfg
|
||||
.gemini/
|
||||
GEMINI.md
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
QWEN.md
|
||||
Client/.claude/
|
||||
dev-ab-svr/debugconfig.json
|
||||
doc/
|
||||
|
||||
# Codex/AI local runtime and generated artifacts.
|
||||
# CasualFishingSkills is the shared source repo; this game repo only keeps local installs/outputs.
|
||||
/.agents/
|
||||
/.worktrees/
|
||||
/CodexAgent/
|
||||
/CodexOutput/
|
||||
/skills-lock.json
|
||||
|
||||
# Legacy local output roots kept ignored while workflows migrate to CodexOutput.
|
||||
/output/
|
||||
/outputs/
|
||||
/temp/
|
||||
/tmp/
|
||||
/FishingFrenzy/output/
|
||||
101
FishDice/.gitignore
vendored
Normal file
101
FishDice/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# This .gitignore file should be placed at the root of your Unity project directory
|
||||
#
|
||||
# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
|
||||
#
|
||||
/[Ll]ibrary/
|
||||
/[Tt]emp/
|
||||
/[Oo]bj/
|
||||
/[Bb]uild/
|
||||
/[Bb]uilds/
|
||||
/[Ll]ogs/
|
||||
/[Uu]ser[Ss]ettings/
|
||||
/[Bb]in/
|
||||
/SerializedBuildInfo/
|
||||
|
||||
# ignore all files and folders ends with "~"
|
||||
*~
|
||||
|
||||
# MemoryCaptures can get excessive in size.
|
||||
# They also could contain extremely sensitive data
|
||||
/[Mm]emoryCaptures/
|
||||
|
||||
# Asset meta data should only be ignored when the corresponding asset is also ignored
|
||||
!/[Aa]ssets/**/*.meta
|
||||
|
||||
# Uncomment this line if you wish to ignore the asset store tools plugin
|
||||
# /[Aa]ssets/AssetStoreTools*
|
||||
|
||||
# Autogenerated Jetbrains Rider plugin
|
||||
/[Aa]ssets/Plugins/Editor/JetBrains*
|
||||
|
||||
# Visual Studio cache directory
|
||||
.vs/
|
||||
|
||||
# Gradle cache directory
|
||||
.gradle/
|
||||
|
||||
# Autogenerated VS/MD/Consulo solution and project files
|
||||
ExportedObj/
|
||||
.consulo/
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.tmp
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.svd
|
||||
*.pdb
|
||||
*.mdb
|
||||
*.opendb
|
||||
*.VC.db
|
||||
.vsconfig
|
||||
.vshistory
|
||||
.vscode/
|
||||
|
||||
# Unity3D generated meta files
|
||||
*.pidb.meta
|
||||
*.pdb.meta
|
||||
*.mdb.meta
|
||||
|
||||
# Unity3D generated file on crash reports
|
||||
sysinfo.txt
|
||||
|
||||
# Builds
|
||||
*.apk
|
||||
*.unitypackage
|
||||
|
||||
# Crashlytics generated file
|
||||
crashlytics-build.properties
|
||||
|
||||
|
||||
# ignore Test Scripts & resources in test folder
|
||||
/[Aa]ssets/[Tt]emp[Tt]est/
|
||||
/[Aa]ssets/[Tt]emp[Tt]est
|
||||
/[Aa]ssets/[Tt]emp[Tt]est.meta
|
||||
|
||||
|
||||
# ignore build info from reporter
|
||||
/[Aa]ssets/StreamingAssets/build_info/
|
||||
/[Aa]ssets/StreamingAssets/build_info
|
||||
/[Aa]ssets/StreamingAssets/build_info.meta
|
||||
|
||||
## ignore generated playfab async scripts
|
||||
#/LocalPackages/asap.playfab.async/Runtime/Gen.meta
|
||||
#/LocalPackages/asap.playfab.async/Runtime/Gen/
|
||||
#/LocalPackages/asap.playfab.async/Runtime/Gen
|
||||
|
||||
# ignore core source code
|
||||
/LocalPackages/GameCore/
|
||||
/LocalPackages/asap.core/
|
||||
|
||||
# HybridCLR Temp project
|
||||
/HybridCLRData/StrippedAOTDllsTempProj
|
||||
|
||||
# ignore idea files
|
||||
.idea/
|
||||
|
||||
# ignore new slnx files
|
||||
*.slnx
|
||||
9
FishDice/Assets/FishDice.meta
Normal file
9
FishDice/Assets/FishDice.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f266ba0b7dd4949b3e14abe82b5fc32
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime.meta
Normal file
9
FishDice/Assets/FishDice/Runtime.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 678f1a9001a34e9eb1a79bcc58b8f347
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
14
FishDice/Assets/FishDice/Runtime/FishDice.Runtime.asmdef
Normal file
14
FishDice/Assets/FishDice/Runtime/FishDice.Runtime.asmdef
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "FishDice.Runtime",
|
||||
"rootNamespace": "FishDice",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea1dba60786d4669be927ea3ec48f6f3
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62d522d99c944966acf214919da3046a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Actions.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Actions.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64a35bdcdd7447d49db30885a065cc3d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class GrantRewardExecutor : IRollActionExecutor
|
||||
{
|
||||
public string ActionType => LuckyDiceKeys.GrantRewardAction;
|
||||
|
||||
public RollActionResult Execute(RollActionContext context, RollActionSpec action)
|
||||
{
|
||||
return RollActionResult.Succeeded(
|
||||
action.ActionType,
|
||||
new RollEffect[] { new RewardEffect() });
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AddMultiplierExecutor : IRollActionExecutor
|
||||
{
|
||||
public string ActionType => LuckyDiceKeys.AddMultiplierAction;
|
||||
|
||||
public RollActionResult Execute(RollActionContext context, RollActionSpec action)
|
||||
{
|
||||
action.Params.TryGetValue("reason", out var reason);
|
||||
return RollActionResult.Succeeded(
|
||||
action.ActionType,
|
||||
new RollEffect[] { new MultiplierEffect(reason) });
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TriggerLuckyDiceExecutor : IRollActionExecutor
|
||||
{
|
||||
public string ActionType => LuckyDiceKeys.TriggerLuckyDiceAction;
|
||||
|
||||
public RollActionResult Execute(RollActionContext context, RollActionSpec action)
|
||||
{
|
||||
return RollActionResult.Succeeded(
|
||||
action.ActionType,
|
||||
new RollEffect[] { new LuckyDiceRequestedEffect() });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2973688c08e47babf53588cd7d3e72c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public interface IRollActionExecutor
|
||||
{
|
||||
string ActionType { get; }
|
||||
|
||||
RollActionResult Execute(RollActionContext context, RollActionSpec action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a96e9c7c4cd442fa9e4f74d201c9cbdf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollActionDispatcher
|
||||
{
|
||||
private readonly Dictionary<string, IRollActionExecutor> _executors;
|
||||
|
||||
public RollActionDispatcher(IEnumerable<IRollActionExecutor> executors)
|
||||
{
|
||||
if (executors == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(executors));
|
||||
}
|
||||
|
||||
_executors = executors.ToDictionary(executor => executor.ActionType, executor => executor);
|
||||
}
|
||||
|
||||
public IReadOnlyList<RollActionResult> Execute(
|
||||
RollActionContext context,
|
||||
IReadOnlyList<RollActionSpec> actions)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (actions == null)
|
||||
{
|
||||
return Array.Empty<RollActionResult>();
|
||||
}
|
||||
|
||||
var results = new List<RollActionResult>(actions.Count);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var result = ExecuteSingle(context, action);
|
||||
results.Add(result);
|
||||
context.Trace.RecordActionResult(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private RollActionResult ExecuteSingle(RollActionContext context, RollActionSpec action)
|
||||
{
|
||||
if (!_executors.TryGetValue(action.ActionType, out var executor))
|
||||
{
|
||||
return RollActionResult.Failure(
|
||||
action.ActionType,
|
||||
"No executor registered for action type: " + action.ActionType);
|
||||
}
|
||||
|
||||
return executor.Execute(context, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 771e68c81ca14d2fb5998ac049f9ca5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public static class RollActionExecutorRegistry
|
||||
{
|
||||
public static IReadOnlyList<IRollActionExecutor> CreateDefaultExecutors()
|
||||
{
|
||||
return new IRollActionExecutor[]
|
||||
{
|
||||
new GrantRewardExecutor(),
|
||||
new AddMultiplierExecutor(),
|
||||
new TriggerLuckyDiceExecutor()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bc162f33bbf4660b1599dce107dc223
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Config.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Config.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1eeebb9f2ae47058ce0524e8539b8cc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class ComboRuleDefinition
|
||||
{
|
||||
public string RuleId;
|
||||
public int Priority;
|
||||
public string MatcherType;
|
||||
public string ComboKey;
|
||||
public string FaceKey;
|
||||
public int? RequiredCount;
|
||||
public int? MinCount;
|
||||
public int? MaxCount;
|
||||
public int? DiceCount;
|
||||
public int? OfAKindCount;
|
||||
public int? MinSum;
|
||||
public int? MaxSum;
|
||||
public bool UseOrderedKey;
|
||||
public bool StopAfterMatched = true;
|
||||
public RollActionDefinition[] Actions;
|
||||
}
|
||||
|
||||
public sealed class RollActionDefinition
|
||||
{
|
||||
public string ActionType;
|
||||
public ActionParamDefinition[] Params;
|
||||
}
|
||||
|
||||
public sealed class ActionParamDefinition
|
||||
{
|
||||
public string Key;
|
||||
public string Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1f14033988649cebb5429c594a53de9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public static class ComboRuleDefinitionLoader
|
||||
{
|
||||
public static IReadOnlyList<ComboRule> Load(IReadOnlyList<ComboRuleDefinition> definitions)
|
||||
{
|
||||
if (definitions == null)
|
||||
{
|
||||
return Array.Empty<ComboRule>();
|
||||
}
|
||||
|
||||
return definitions.Select(ToRuntimeRule).ToArray();
|
||||
}
|
||||
|
||||
private static ComboRule ToRuntimeRule(ComboRuleDefinition definition)
|
||||
{
|
||||
if (definition == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(definition));
|
||||
}
|
||||
|
||||
return new ComboRule(
|
||||
definition.RuleId,
|
||||
definition.Priority,
|
||||
new ComboMatcherSpec(
|
||||
definition.MatcherType,
|
||||
comboKey: definition.ComboKey,
|
||||
faceKey: definition.FaceKey,
|
||||
requiredCount: definition.RequiredCount,
|
||||
minCount: definition.MinCount,
|
||||
maxCount: definition.MaxCount,
|
||||
diceCount: definition.DiceCount,
|
||||
ofAKindCount: definition.OfAKindCount,
|
||||
minSum: definition.MinSum,
|
||||
maxSum: definition.MaxSum,
|
||||
useOrderedKey: definition.UseOrderedKey),
|
||||
ToRuntimeActions(definition.Actions),
|
||||
definition.StopAfterMatched);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<RollActionSpec> ToRuntimeActions(IReadOnlyList<RollActionDefinition> definitions)
|
||||
{
|
||||
if (definitions == null)
|
||||
{
|
||||
return Array.Empty<RollActionSpec>();
|
||||
}
|
||||
|
||||
return definitions.Select(ToRuntimeAction).ToArray();
|
||||
}
|
||||
|
||||
private static RollActionSpec ToRuntimeAction(RollActionDefinition definition)
|
||||
{
|
||||
if (definition == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(definition));
|
||||
}
|
||||
|
||||
var parameters = definition.Params == null
|
||||
? null
|
||||
: definition.Params.ToDictionary(param => param.Key, param => param.Value);
|
||||
|
||||
return new RollActionSpec(definition.ActionType, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8b93de4100240c792be3c214c06218c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class InvalidDiceSetException : Exception
|
||||
{
|
||||
public InvalidDiceSetException(string diceSetId)
|
||||
: base("Invalid dice set id: " + diceSetId)
|
||||
{
|
||||
DiceSetId = diceSetId;
|
||||
}
|
||||
|
||||
public string DiceSetId { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6f07727f4e84a88a31eb2d4caec3dc6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class InvalidLuckyDiceConfigException : Exception
|
||||
{
|
||||
public InvalidLuckyDiceConfigException(ConfigValidationResult validationResult)
|
||||
: base(BuildMessage(validationResult))
|
||||
{
|
||||
ValidationResult = validationResult;
|
||||
}
|
||||
|
||||
public ConfigValidationResult ValidationResult { get; }
|
||||
|
||||
private static string BuildMessage(ConfigValidationResult validationResult)
|
||||
{
|
||||
if (validationResult == null || validationResult.Errors.Count == 0)
|
||||
{
|
||||
return "Invalid Lucky Dice config.";
|
||||
}
|
||||
|
||||
return "Invalid Lucky Dice config: " + validationResult.Errors[0].Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56d5d29378624c30b6510846b4fd45e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceCandidateConfig
|
||||
{
|
||||
public LuckyDiceCandidateConfig(
|
||||
string resultKey,
|
||||
string targetKey,
|
||||
int? resultSlotCount,
|
||||
int weight,
|
||||
bool enabled,
|
||||
int minLevel,
|
||||
IReadOnlyList<string> sourceFilter,
|
||||
bool isDefault)
|
||||
{
|
||||
ResultKey = resultKey;
|
||||
TargetKey = targetKey;
|
||||
ResultSlotCount = resultSlotCount;
|
||||
Weight = weight;
|
||||
Enabled = enabled;
|
||||
MinLevel = minLevel;
|
||||
SourceFilter = sourceFilter == null
|
||||
? Array.Empty<string>()
|
||||
: new List<string>(sourceFilter).AsReadOnly();
|
||||
IsDefault = isDefault;
|
||||
}
|
||||
|
||||
public string ResultKey { get; }
|
||||
|
||||
public string TargetKey { get; }
|
||||
|
||||
public int? ResultSlotCount { get; }
|
||||
|
||||
public int Weight { get; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public int MinLevel { get; }
|
||||
|
||||
public IReadOnlyList<string> SourceFilter { get; }
|
||||
|
||||
public bool IsDefault { get; }
|
||||
}
|
||||
|
||||
public sealed class TargetModeConfig
|
||||
{
|
||||
public TargetModeConfig(string resultKey, string targetKey, string modeKey)
|
||||
{
|
||||
ResultKey = resultKey;
|
||||
TargetKey = targetKey;
|
||||
ModeKey = modeKey;
|
||||
}
|
||||
|
||||
public string ResultKey { get; }
|
||||
|
||||
public string TargetKey { get; }
|
||||
|
||||
public string ModeKey { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d22247243d584a8fa28b45e708517c1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceCandidateDefinition
|
||||
{
|
||||
public string ResultKey;
|
||||
public string TargetKey;
|
||||
public int? ResultSlotCount;
|
||||
public int Weight;
|
||||
public bool Enabled = true;
|
||||
public int MinLevel;
|
||||
public string[] SourceFilter;
|
||||
public bool IsDefault;
|
||||
}
|
||||
|
||||
public sealed class TargetModeDefinition
|
||||
{
|
||||
public string ResultKey;
|
||||
public string TargetKey;
|
||||
public string ModeKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98f58d378176481380ec6413753c5717
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceConfigValidationContext
|
||||
{
|
||||
public LuckyDiceConfigValidationContext(
|
||||
IReadOnlyList<IComboMatcher> matchers,
|
||||
IReadOnlyList<IRollActionExecutor> actionExecutors)
|
||||
{
|
||||
Matchers = matchers == null ? Array.Empty<IComboMatcher>() : matchers.ToArray();
|
||||
ActionExecutors = actionExecutors == null ? Array.Empty<IRollActionExecutor>() : actionExecutors.ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyList<IComboMatcher> Matchers { get; }
|
||||
|
||||
public IReadOnlyList<IRollActionExecutor> ActionExecutors { get; }
|
||||
|
||||
public static LuckyDiceConfigValidationContext CreateDefault()
|
||||
{
|
||||
return new LuckyDiceConfigValidationContext(
|
||||
ComboMatcherRegistry.CreateDefaultMatchers(),
|
||||
RollActionExecutorRegistry.CreateDefaultExecutors());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bd13dcfd4a54d9fa90b11b60df1a7e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public static class LuckyDiceConfigValidator
|
||||
{
|
||||
public static ConfigValidationResult Validate(
|
||||
IReadOnlyList<ComboRuleDefinition> rules,
|
||||
IReadOnlyList<LuckyDiceCandidateDefinition> candidates,
|
||||
IReadOnlyList<TargetModeDefinition> targetModes,
|
||||
IReadOnlyList<IComboMatcher> matchers,
|
||||
IReadOnlyList<IRollActionExecutor> actionExecutors)
|
||||
{
|
||||
var errors = new List<ConfigValidationError>();
|
||||
var matcherTypes = ValidateMatchers(errors, matchers);
|
||||
var actionTypes = ValidateActionExecutors(errors, actionExecutors);
|
||||
var ruleIds = new HashSet<string>();
|
||||
|
||||
foreach (var rule in rules ?? Array.Empty<ComboRuleDefinition>())
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullRule", "Rule cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(rule.RuleId))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyRuleId", "Rule id is required."));
|
||||
}
|
||||
else if (!ruleIds.Add(rule.RuleId))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DuplicateRuleId", "Duplicate rule id: " + rule.RuleId));
|
||||
}
|
||||
|
||||
if (!matcherTypes.Contains(rule.MatcherType))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("UnknownMatcher", "Unknown matcher: " + rule.MatcherType));
|
||||
}
|
||||
|
||||
foreach (var action in rule.Actions ?? Array.Empty<RollActionDefinition>())
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullAction", "Action cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!actionTypes.Contains(action.ActionType))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("UnknownAction", "Unknown action: " + action.ActionType));
|
||||
}
|
||||
|
||||
ValidateActionParams(errors, action.Params);
|
||||
}
|
||||
}
|
||||
|
||||
if ((candidates ?? Array.Empty<LuckyDiceCandidateDefinition>()).All(candidate => candidate == null || !candidate.IsDefault))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("MissingDefaultCandidate", "Lucky Dice needs one default candidate."));
|
||||
}
|
||||
|
||||
var targetModeKeys = new HashSet<string>();
|
||||
foreach (var targetMode in targetModes ?? Array.Empty<TargetModeDefinition>())
|
||||
{
|
||||
if (targetMode == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullTargetMode", "Target mode cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(targetMode.ResultKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyTargetModeResultKey", "Target mode result key is required."));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(targetMode.TargetKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyTargetModeTargetKey", "Target mode target key is required."));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(targetMode.ModeKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyTargetModeModeKey", "Target mode mode key is required."));
|
||||
}
|
||||
|
||||
var targetModeKey = targetMode.ResultKey + "|" + targetMode.TargetKey;
|
||||
if (!targetModeKeys.Add(targetModeKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DuplicateTargetMode", "Duplicate target mode: " + targetModeKey));
|
||||
}
|
||||
}
|
||||
|
||||
var candidateKeys = new HashSet<string>();
|
||||
foreach (var candidate in candidates ?? Array.Empty<LuckyDiceCandidateDefinition>())
|
||||
{
|
||||
if (candidate == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullCandidate", "Candidate cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(candidate.ResultKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyCandidateResultKey", "Candidate result key is required."));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(candidate.TargetKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyCandidateTargetKey", "Candidate target key is required."));
|
||||
}
|
||||
|
||||
var candidateKey = candidate.ResultKey + "|" + candidate.TargetKey;
|
||||
if (!candidateKeys.Add(candidateKey))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DuplicateCandidate", "Duplicate candidate: " + candidateKey));
|
||||
}
|
||||
|
||||
if (candidate.Weight < 0)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NegativeCandidateWeight", "Candidate weight cannot be negative: " + candidate.ResultKey));
|
||||
}
|
||||
|
||||
if (candidate.ResultSlotCount.HasValue && candidate.ResultSlotCount.Value <= 0)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("InvalidResultSlotCount", "Result slot count must be greater than zero: " + candidate.ResultKey));
|
||||
}
|
||||
|
||||
if (candidate.IsDefault && !candidate.Enabled)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DisabledDefaultCandidate", "Default candidate cannot be disabled: " + candidate.ResultKey));
|
||||
}
|
||||
|
||||
var hasMode = (targetModes ?? Array.Empty<TargetModeDefinition>())
|
||||
.Any(mode => mode != null && mode.ResultKey == candidate.ResultKey && mode.TargetKey == candidate.TargetKey);
|
||||
if (!hasMode)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("MissingTargetMode", "Missing target mode for result: " + candidate.ResultKey));
|
||||
}
|
||||
}
|
||||
|
||||
return new ConfigValidationResult(errors);
|
||||
}
|
||||
|
||||
private static HashSet<string> ValidateMatchers(
|
||||
List<ConfigValidationError> errors,
|
||||
IReadOnlyList<IComboMatcher> matchers)
|
||||
{
|
||||
var matcherTypes = new HashSet<string>();
|
||||
foreach (var matcher in matchers ?? Array.Empty<IComboMatcher>())
|
||||
{
|
||||
if (matcher == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullMatcher", "Matcher cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(matcher.MatcherType))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyMatcherType", "Matcher type is required."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!matcherTypes.Add(matcher.MatcherType))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DuplicateMatcherType", "Duplicate matcher type: " + matcher.MatcherType));
|
||||
}
|
||||
}
|
||||
|
||||
return matcherTypes;
|
||||
}
|
||||
|
||||
private static HashSet<string> ValidateActionExecutors(
|
||||
List<ConfigValidationError> errors,
|
||||
IReadOnlyList<IRollActionExecutor> actionExecutors)
|
||||
{
|
||||
var actionTypes = new HashSet<string>();
|
||||
foreach (var executor in actionExecutors ?? Array.Empty<IRollActionExecutor>())
|
||||
{
|
||||
if (executor == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullActionExecutor", "Action executor cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(executor.ActionType))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyActionExecutorType", "Action executor type is required."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!actionTypes.Add(executor.ActionType))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DuplicateActionExecutorType", "Duplicate action executor type: " + executor.ActionType));
|
||||
}
|
||||
}
|
||||
|
||||
return actionTypes;
|
||||
}
|
||||
|
||||
private static void ValidateActionParams(
|
||||
List<ConfigValidationError> errors,
|
||||
IReadOnlyList<ActionParamDefinition> actionParams)
|
||||
{
|
||||
var paramKeys = new HashSet<string>();
|
||||
foreach (var param in actionParams ?? Array.Empty<ActionParamDefinition>())
|
||||
{
|
||||
if (param == null)
|
||||
{
|
||||
errors.Add(new ConfigValidationError("NullActionParam", "Action param cannot be null."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(param.Key))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("EmptyActionParamKey", "Action param key is required."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!paramKeys.Add(param.Key))
|
||||
{
|
||||
errors.Add(new ConfigValidationError("DuplicateActionParamKey", "Duplicate action param key: " + param.Key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConfigValidationResult
|
||||
{
|
||||
public ConfigValidationResult(IReadOnlyList<ConfigValidationError> errors)
|
||||
{
|
||||
Errors = errors == null ? Array.Empty<ConfigValidationError>() : errors.ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyList<ConfigValidationError> Errors { get; }
|
||||
|
||||
public bool IsValid => Errors.Count == 0;
|
||||
}
|
||||
|
||||
public sealed class ConfigValidationError
|
||||
{
|
||||
public ConfigValidationError(string code, string message)
|
||||
{
|
||||
Code = code;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Code { get; }
|
||||
|
||||
public string Message { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31bec0fb5e6c45ddadf3a0039a287423
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,238 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceDefaultConfig
|
||||
{
|
||||
private readonly Dictionary<string, DiceSetConfig> _diceSets;
|
||||
|
||||
private LuckyDiceDefaultConfig(
|
||||
Dictionary<string, DiceSetConfig> diceSets,
|
||||
IReadOnlyList<ComboRule> comboRules,
|
||||
IReadOnlyList<LuckyDiceCandidateConfig> luckyDiceCandidates,
|
||||
IReadOnlyList<TargetModeConfig> targetModeDefinitions)
|
||||
{
|
||||
_diceSets = diceSets;
|
||||
ComboRules = comboRules;
|
||||
LuckyDiceCandidates = luckyDiceCandidates;
|
||||
TargetModeDefinitions = targetModeDefinitions;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ComboRule> ComboRules { get; }
|
||||
|
||||
public IReadOnlyList<LuckyDiceCandidateConfig> LuckyDiceCandidates { get; }
|
||||
|
||||
public IReadOnlyList<TargetModeConfig> TargetModeDefinitions { get; }
|
||||
|
||||
public DiceSetConfig GetDiceSet(string diceSetId)
|
||||
{
|
||||
DiceSetConfig diceSet;
|
||||
if (!_diceSets.TryGetValue(diceSetId, out diceSet))
|
||||
{
|
||||
throw new InvalidDiceSetException(diceSetId);
|
||||
}
|
||||
|
||||
return diceSet;
|
||||
}
|
||||
|
||||
public static LuckyDiceDefaultConfig Create()
|
||||
{
|
||||
return CreateValidated(LuckyDiceConfigValidationContext.CreateDefault());
|
||||
}
|
||||
|
||||
public static LuckyDiceDefaultConfig CreateValidated(LuckyDiceConfigValidationContext validationContext)
|
||||
{
|
||||
return CreateValidated(
|
||||
DefaultComboRuleDefinitions,
|
||||
DefaultLuckyDiceCandidateDefinitions,
|
||||
DefaultTargetModeDefinitions,
|
||||
validationContext);
|
||||
}
|
||||
|
||||
public static LuckyDiceDefaultConfig CreateValidated(
|
||||
IReadOnlyList<ComboRuleDefinition> ruleDefinitions,
|
||||
IReadOnlyList<LuckyDiceCandidateDefinition> candidateDefinitions,
|
||||
IReadOnlyList<TargetModeDefinition> targetModeDefinitions)
|
||||
{
|
||||
return CreateValidated(
|
||||
ruleDefinitions,
|
||||
candidateDefinitions,
|
||||
targetModeDefinitions,
|
||||
LuckyDiceConfigValidationContext.CreateDefault());
|
||||
}
|
||||
|
||||
public static LuckyDiceDefaultConfig CreateValidated(
|
||||
IReadOnlyList<ComboRuleDefinition> ruleDefinitions,
|
||||
IReadOnlyList<LuckyDiceCandidateDefinition> candidateDefinitions,
|
||||
IReadOnlyList<TargetModeDefinition> targetModeDefinitions,
|
||||
LuckyDiceConfigValidationContext validationContext)
|
||||
{
|
||||
validationContext = validationContext ?? LuckyDiceConfigValidationContext.CreateDefault();
|
||||
var validation = LuckyDiceConfigValidator.Validate(
|
||||
ruleDefinitions,
|
||||
candidateDefinitions,
|
||||
targetModeDefinitions,
|
||||
validationContext.Matchers,
|
||||
validationContext.ActionExecutors);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
throw new InvalidLuckyDiceConfigException(validation);
|
||||
}
|
||||
|
||||
var normalSet = new DiceSetConfig(
|
||||
LuckyDiceKeys.NormalDiceSetId,
|
||||
DiceSetType.Normal,
|
||||
diceCount: 2,
|
||||
faces: new[]
|
||||
{
|
||||
DiceFace.NormalNumber(2),
|
||||
DiceFace.NormalNumber(3),
|
||||
DiceFace.NormalNumber(4),
|
||||
DiceFace.NormalNumber(5),
|
||||
DiceFace.NormalNumber(6),
|
||||
DiceFace.NormalClover()
|
||||
},
|
||||
defaultResultSlotCount: null);
|
||||
|
||||
var luckySet = new DiceSetConfig(
|
||||
LuckyDiceKeys.LuckyDiceSetId,
|
||||
DiceSetType.Lucky,
|
||||
diceCount: 1,
|
||||
faces: new[]
|
||||
{
|
||||
DiceFace.LuckyResult(LuckyDiceKeys.RocketResultKey),
|
||||
DiceFace.LuckyResult(LuckyDiceKeys.ThiefResultKey)
|
||||
},
|
||||
defaultResultSlotCount: 3);
|
||||
|
||||
return new LuckyDiceDefaultConfig(
|
||||
new Dictionary<string, DiceSetConfig>
|
||||
{
|
||||
{ normalSet.DiceSetId, normalSet },
|
||||
{ luckySet.DiceSetId, luckySet }
|
||||
},
|
||||
ComboRuleDefinitionLoader.Load(ruleDefinitions),
|
||||
ToCandidateConfigs(candidateDefinitions),
|
||||
ToTargetModeConfigs(targetModeDefinitions));
|
||||
}
|
||||
|
||||
private static LuckyDiceCandidateConfig[] ToCandidateConfigs(IReadOnlyList<LuckyDiceCandidateDefinition> definitions)
|
||||
{
|
||||
var result = new LuckyDiceCandidateConfig[definitions.Count];
|
||||
for (var i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
var definition = definitions[i];
|
||||
result[i] = new LuckyDiceCandidateConfig(
|
||||
definition.ResultKey,
|
||||
definition.TargetKey,
|
||||
definition.ResultSlotCount,
|
||||
definition.Weight,
|
||||
definition.Enabled,
|
||||
definition.MinLevel,
|
||||
definition.SourceFilter,
|
||||
definition.IsDefault);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static TargetModeConfig[] ToTargetModeConfigs(IReadOnlyList<TargetModeDefinition> definitions)
|
||||
{
|
||||
var result = new TargetModeConfig[definitions.Count];
|
||||
for (var i = 0; i < definitions.Count; i++)
|
||||
{
|
||||
var definition = definitions[i];
|
||||
result[i] = new TargetModeConfig(definition.ResultKey, definition.TargetKey, definition.ModeKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static readonly ComboRuleDefinition[] DefaultComboRuleDefinitions =
|
||||
{
|
||||
new ComboRuleDefinition
|
||||
{
|
||||
RuleId = "normal_clover_clover_lucky_dice",
|
||||
Priority = 100,
|
||||
MatcherType = LuckyDiceKeys.ExactComboMatcher,
|
||||
ComboKey = "clover_clover",
|
||||
Actions = new[]
|
||||
{
|
||||
new RollActionDefinition { ActionType = LuckyDiceKeys.TriggerLuckyDiceAction }
|
||||
}
|
||||
},
|
||||
new ComboRuleDefinition
|
||||
{
|
||||
RuleId = "normal_number_clover_bonus",
|
||||
Priority = 50,
|
||||
MatcherType = LuckyDiceKeys.ContainsFaceMatcher,
|
||||
FaceKey = LuckyDiceKeys.CloverFaceKey,
|
||||
RequiredCount = 1,
|
||||
Actions = new[]
|
||||
{
|
||||
new RollActionDefinition { ActionType = LuckyDiceKeys.GrantRewardAction },
|
||||
new RollActionDefinition
|
||||
{
|
||||
ActionType = LuckyDiceKeys.AddMultiplierAction,
|
||||
Params = new[]
|
||||
{
|
||||
new ActionParamDefinition { Key = "reason", Value = "clover_bonus" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new ComboRuleDefinition
|
||||
{
|
||||
RuleId = "normal_all_numbers_reward",
|
||||
Priority = 10,
|
||||
MatcherType = LuckyDiceKeys.AllNumbersMatcher,
|
||||
Actions = new[]
|
||||
{
|
||||
new RollActionDefinition { ActionType = LuckyDiceKeys.GrantRewardAction }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly LuckyDiceCandidateDefinition[] DefaultLuckyDiceCandidateDefinitions =
|
||||
{
|
||||
new LuckyDiceCandidateDefinition
|
||||
{
|
||||
ResultKey = LuckyDiceKeys.RocketResultKey,
|
||||
TargetKey = "slap_down",
|
||||
ResultSlotCount = 3,
|
||||
Weight = 1,
|
||||
Enabled = true,
|
||||
MinLevel = 0,
|
||||
SourceFilter = new[] { LuckyDiceKeys.NormalRollTriggerSource },
|
||||
IsDefault = true
|
||||
},
|
||||
new LuckyDiceCandidateDefinition
|
||||
{
|
||||
ResultKey = LuckyDiceKeys.ThiefResultKey,
|
||||
TargetKey = "treasure_heist",
|
||||
ResultSlotCount = 3,
|
||||
Weight = 1,
|
||||
Enabled = true,
|
||||
MinLevel = 0,
|
||||
SourceFilter = new[] { LuckyDiceKeys.NormalRollTriggerSource },
|
||||
IsDefault = false
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly TargetModeDefinition[] DefaultTargetModeDefinitions =
|
||||
{
|
||||
new TargetModeDefinition
|
||||
{
|
||||
ResultKey = LuckyDiceKeys.RocketResultKey,
|
||||
TargetKey = "slap_down",
|
||||
ModeKey = "slap_down_normal"
|
||||
},
|
||||
new TargetModeDefinition
|
||||
{
|
||||
ResultKey = LuckyDiceKeys.ThiefResultKey,
|
||||
TargetKey = "treasure_heist",
|
||||
ModeKey = "treasure_heist_normal"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2872fbab847848f9bf968e717a4acd70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public static class LuckyDiceKeys
|
||||
{
|
||||
public const string NormalDiceSetId = "normal_main";
|
||||
public const string LuckyDiceSetId = "lucky_dice";
|
||||
|
||||
public const string CloverFaceKey = "clover";
|
||||
public const string RocketResultKey = "rocket";
|
||||
public const string ThiefResultKey = "thief";
|
||||
|
||||
public const string NormalRollTriggerSource = "normal_roll";
|
||||
|
||||
public const string ExactComboMatcher = "ExactCombo";
|
||||
public const string AllNumbersMatcher = "AllNumbers";
|
||||
public const string ContainsFaceMatcher = "ContainsFace";
|
||||
public const string FaceCountMatcher = "FaceCount";
|
||||
public const string FaceCountRangeMatcher = "FaceCountRange";
|
||||
public const string DiceCountMatcher = "DiceCount";
|
||||
public const string NumberPairMatcher = "NumberPair";
|
||||
public const string NumberOfAKindMatcher = "NumberOfAKind";
|
||||
public const string NumberSumRangeMatcher = "NumberSumRange";
|
||||
|
||||
public const string GrantRewardAction = "grant_reward";
|
||||
public const string AddMultiplierAction = "add_multiplier";
|
||||
public const string TriggerLuckyDiceAction = "trigger_lucky_dice";
|
||||
public const string EnterModeAction = "enter_mode";
|
||||
public const string ShowPopupAction = "show_popup";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a0439dddf734a14a1f49eeae48e349b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/CoreFlow.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/CoreFlow.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2867533dcede4ee9b8222a2b4d63deab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceRollFlowService
|
||||
{
|
||||
private readonly LuckyDiceDefaultConfig _config;
|
||||
private readonly DiceRollService _rollService;
|
||||
private readonly ComboRuleMatcher _ruleMatcher;
|
||||
private readonly RollActionDispatcher _actionDispatcher;
|
||||
private readonly LuckyDiceFlowService _luckyDiceFlowService;
|
||||
private readonly TargetModeResolver _targetModeResolver;
|
||||
|
||||
public LuckyDiceRollFlowService(
|
||||
LuckyDiceDefaultConfig config,
|
||||
DiceRollService rollService,
|
||||
ComboRuleMatcher ruleMatcher,
|
||||
RollActionDispatcher actionDispatcher,
|
||||
LuckyDiceFlowService luckyDiceFlowService,
|
||||
TargetModeResolver targetModeResolver)
|
||||
{
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_rollService = rollService ?? throw new ArgumentNullException(nameof(rollService));
|
||||
_ruleMatcher = ruleMatcher ?? throw new ArgumentNullException(nameof(ruleMatcher));
|
||||
_actionDispatcher = actionDispatcher ?? throw new ArgumentNullException(nameof(actionDispatcher));
|
||||
_luckyDiceFlowService = luckyDiceFlowService ?? throw new ArgumentNullException(nameof(luckyDiceFlowService));
|
||||
_targetModeResolver = targetModeResolver ?? throw new ArgumentNullException(nameof(targetModeResolver));
|
||||
}
|
||||
|
||||
public RollFlowResult Execute(RollFlowRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.RollSessionId))
|
||||
{
|
||||
throw new ArgumentException("Roll session id is required.", nameof(request));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.DiceSetId))
|
||||
{
|
||||
throw new ArgumentException("Dice set id is required.", nameof(request));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.TriggerSource))
|
||||
{
|
||||
throw new ArgumentException("Trigger source is required.", nameof(request));
|
||||
}
|
||||
|
||||
var trace = new RollTrace(request.RollSessionId);
|
||||
var rollResult = _rollService.Roll(
|
||||
_config.GetDiceSet(request.DiceSetId),
|
||||
request.RollSessionId,
|
||||
request.TriggerSource);
|
||||
var facts = ComboFactBuilder.Build(rollResult);
|
||||
trace.RecordComboFacts(facts);
|
||||
|
||||
var match = _ruleMatcher.Match(facts, _config.ComboRules);
|
||||
trace.RecordRuleMatch(match);
|
||||
|
||||
var actionContext = new RollActionContext(request.RollSessionId, facts.NormalizedKey, trace);
|
||||
var actionResults = _actionDispatcher.Execute(
|
||||
actionContext,
|
||||
match.Matches.SelectMany(rule => rule.Actions).ToArray());
|
||||
var effects = actionResults.SelectMany(result => result.Effects).ToList();
|
||||
|
||||
LuckyDiceSelectionResult luckyDiceResult = null;
|
||||
TargetModeEntry targetMode = null;
|
||||
if (effects.OfType<LuckyDiceRequestedEffect>().Any())
|
||||
{
|
||||
luckyDiceResult = _luckyDiceFlowService.Select(
|
||||
_config.LuckyDiceCandidates,
|
||||
request,
|
||||
_config.GetDiceSet(LuckyDiceKeys.LuckyDiceSetId));
|
||||
trace.RecordLuckyDiceSelection(luckyDiceResult);
|
||||
|
||||
var targetModeResult = _targetModeResolver.Resolve(
|
||||
luckyDiceResult.Selected,
|
||||
request,
|
||||
rollResult.Multiplier,
|
||||
_config.TargetModeDefinitions);
|
||||
|
||||
if (targetModeResult.Success)
|
||||
{
|
||||
targetMode = targetModeResult.Entry;
|
||||
trace.RecordTargetMode(targetMode);
|
||||
effects.Add(new EnterTargetModeEffect(targetMode.TargetKey, targetMode.ModeKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
trace.RecordTargetModeFailure(targetModeResult.FailureReason);
|
||||
effects.Add(new TargetModeFailedEffect(targetModeResult.FailureReason, targetModeResult.FallbackMode));
|
||||
}
|
||||
}
|
||||
|
||||
return new RollFlowResult(
|
||||
request.RollSessionId,
|
||||
rollResult,
|
||||
facts,
|
||||
match.Matches,
|
||||
actionResults,
|
||||
effects,
|
||||
luckyDiceResult,
|
||||
targetMode,
|
||||
trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1934c9ee9d2f40d897afde268a169778
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceRuntimeFactory
|
||||
{
|
||||
private readonly LuckyDiceConfigValidationContext _context;
|
||||
private readonly IRandomSource _random;
|
||||
|
||||
public LuckyDiceRuntimeFactory(
|
||||
LuckyDiceConfigValidationContext context,
|
||||
IRandomSource random)
|
||||
{
|
||||
_context = context ?? LuckyDiceConfigValidationContext.CreateDefault();
|
||||
_random = random ?? throw new ArgumentNullException(nameof(random));
|
||||
}
|
||||
|
||||
public static LuckyDiceRuntimeFactory CreateDefault(IRandomSource random)
|
||||
{
|
||||
return new LuckyDiceRuntimeFactory(LuckyDiceConfigValidationContext.CreateDefault(), random);
|
||||
}
|
||||
|
||||
public LuckyDiceRollFlowService CreateDefaultFlow()
|
||||
{
|
||||
return CreateFlow(LuckyDiceDefaultConfig.CreateValidated(_context));
|
||||
}
|
||||
|
||||
public LuckyDiceRollFlowService CreateFlow(
|
||||
IReadOnlyList<ComboRuleDefinition> ruleDefinitions,
|
||||
IReadOnlyList<LuckyDiceCandidateDefinition> candidateDefinitions,
|
||||
IReadOnlyList<TargetModeDefinition> targetModeDefinitions)
|
||||
{
|
||||
var config = LuckyDiceDefaultConfig.CreateValidated(
|
||||
ruleDefinitions,
|
||||
candidateDefinitions,
|
||||
targetModeDefinitions,
|
||||
_context);
|
||||
|
||||
return CreateFlow(config);
|
||||
}
|
||||
|
||||
public LuckyDiceRollFlowService CreateFlow(LuckyDiceDefaultConfig config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
return new LuckyDiceRollFlowService(
|
||||
config,
|
||||
new DiceRollService(_random),
|
||||
new ComboRuleMatcher(_context.Matchers),
|
||||
new RollActionDispatcher(_context.ActionExecutors),
|
||||
new LuckyDiceFlowService(_random),
|
||||
new TargetModeResolver());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19d0cc9a1e284be7a23c2ee1c6b7bd21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Data.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Data.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ff1f71d729f48438f1a81077076ca5a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class ComboFacts
|
||||
{
|
||||
public ComboFacts(
|
||||
string rollSessionId,
|
||||
DiceSetType diceSetType,
|
||||
int diceCount,
|
||||
IReadOnlyList<DiceFace> faces,
|
||||
IReadOnlyDictionary<string, int> faceCounts,
|
||||
int numberSum,
|
||||
IReadOnlyList<int> numberValues,
|
||||
int cloverCount,
|
||||
string normalizedKey,
|
||||
string orderedKey)
|
||||
{
|
||||
RollSessionId = rollSessionId;
|
||||
DiceSetType = diceSetType;
|
||||
DiceCount = diceCount;
|
||||
Faces = faces.ToArray();
|
||||
FaceCounts = faceCounts.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
NumberSum = numberSum;
|
||||
NumberValues = numberValues.ToArray();
|
||||
CloverCount = cloverCount;
|
||||
HasClover = cloverCount > 0;
|
||||
NormalizedKey = normalizedKey;
|
||||
OrderedKey = orderedKey;
|
||||
}
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
public DiceSetType DiceSetType { get; }
|
||||
|
||||
public int DiceCount { get; }
|
||||
|
||||
public IReadOnlyList<DiceFace> Faces { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, int> FaceCounts { get; }
|
||||
|
||||
public int NumberSum { get; }
|
||||
|
||||
public IReadOnlyList<int> NumberValues { get; }
|
||||
|
||||
public int CloverCount { get; }
|
||||
|
||||
public bool HasClover { get; }
|
||||
|
||||
public string NormalizedKey { get; }
|
||||
|
||||
public string OrderedKey { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74faceaa58a144008abe8f08f3b3db27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class ComboMatcherSpec
|
||||
{
|
||||
public ComboMatcherSpec(
|
||||
string matcherType,
|
||||
string comboKey = null,
|
||||
string faceKey = null,
|
||||
int? requiredCount = null,
|
||||
int? minCount = null,
|
||||
int? maxCount = null,
|
||||
int? diceCount = null,
|
||||
int? ofAKindCount = null,
|
||||
int? minSum = null,
|
||||
int? maxSum = null,
|
||||
bool useOrderedKey = false)
|
||||
{
|
||||
MatcherType = matcherType ?? throw new ArgumentNullException(nameof(matcherType));
|
||||
ComboKey = comboKey;
|
||||
FaceKey = faceKey;
|
||||
RequiredCount = requiredCount;
|
||||
MinCount = minCount;
|
||||
MaxCount = maxCount;
|
||||
DiceCount = diceCount;
|
||||
OfAKindCount = ofAKindCount;
|
||||
MinSum = minSum;
|
||||
MaxSum = maxSum;
|
||||
UseOrderedKey = useOrderedKey;
|
||||
}
|
||||
|
||||
public string MatcherType { get; }
|
||||
|
||||
public string ComboKey { get; }
|
||||
|
||||
public string FaceKey { get; }
|
||||
|
||||
public int? RequiredCount { get; }
|
||||
|
||||
public int? MinCount { get; }
|
||||
|
||||
public int? MaxCount { get; }
|
||||
|
||||
public int? DiceCount { get; }
|
||||
|
||||
public int? OfAKindCount { get; }
|
||||
|
||||
public int? MinSum { get; }
|
||||
|
||||
public int? MaxSum { get; }
|
||||
|
||||
public bool UseOrderedKey { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11a380073489461c997f9ce85c45d607
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
33
FishDice/Assets/FishDice/Runtime/LuckyDice/Data/ComboRule.cs
Normal file
33
FishDice/Assets/FishDice/Runtime/LuckyDice/Data/ComboRule.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class ComboRule
|
||||
{
|
||||
public ComboRule(
|
||||
string ruleId,
|
||||
int priority,
|
||||
ComboMatcherSpec matcher,
|
||||
IReadOnlyList<RollActionSpec> actions,
|
||||
bool stopAfterMatched)
|
||||
{
|
||||
RuleId = ruleId ?? throw new ArgumentNullException(nameof(ruleId));
|
||||
Priority = priority;
|
||||
Matcher = matcher ?? throw new ArgumentNullException(nameof(matcher));
|
||||
Actions = actions == null ? Array.Empty<RollActionSpec>() : actions.ToArray();
|
||||
StopAfterMatched = stopAfterMatched;
|
||||
}
|
||||
|
||||
public string RuleId { get; }
|
||||
|
||||
public int Priority { get; }
|
||||
|
||||
public ComboMatcherSpec Matcher { get; }
|
||||
|
||||
public IReadOnlyList<RollActionSpec> Actions { get; }
|
||||
|
||||
public bool StopAfterMatched { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a94f5fa117fb4bada6467d3d7d0c31d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class ComboRuleMatch
|
||||
{
|
||||
public ComboRuleMatch(ComboRule rule)
|
||||
{
|
||||
RuleId = rule.RuleId;
|
||||
Priority = rule.Priority;
|
||||
MatcherType = rule.Matcher.MatcherType;
|
||||
Actions = rule.Actions;
|
||||
}
|
||||
|
||||
public string RuleId { get; }
|
||||
|
||||
public int Priority { get; }
|
||||
|
||||
public string MatcherType { get; }
|
||||
|
||||
public IReadOnlyList<RollActionSpec> Actions { get; }
|
||||
}
|
||||
|
||||
public sealed class ComboRuleMatchResult
|
||||
{
|
||||
public ComboRuleMatchResult(IReadOnlyList<ComboRuleMatch> matches)
|
||||
{
|
||||
Matches = matches == null ? Array.Empty<ComboRuleMatch>() : matches.ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyList<ComboRuleMatch> Matches { get; }
|
||||
|
||||
public bool HasMatch => Matches.Count > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9df28fce97b4d4fa5cf29a5b6f38d23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
35
FishDice/Assets/FishDice/Runtime/LuckyDice/Data/DiceFace.cs
Normal file
35
FishDice/Assets/FishDice/Runtime/LuckyDice/Data/DiceFace.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public readonly struct DiceFace
|
||||
{
|
||||
public DiceFace(DiceSetType setType, string key, int? numberValue)
|
||||
{
|
||||
SetType = setType;
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
NumberValue = numberValue;
|
||||
}
|
||||
|
||||
public DiceSetType SetType { get; }
|
||||
|
||||
public string Key { get; }
|
||||
|
||||
public int? NumberValue { get; }
|
||||
|
||||
public static DiceFace NormalNumber(int value)
|
||||
{
|
||||
return new DiceFace(DiceSetType.Normal, value.ToString(), value);
|
||||
}
|
||||
|
||||
public static DiceFace NormalClover()
|
||||
{
|
||||
return new DiceFace(DiceSetType.Normal, LuckyDiceKeys.CloverFaceKey, null);
|
||||
}
|
||||
|
||||
public static DiceFace LuckyResult(string key)
|
||||
{
|
||||
return new DiceFace(DiceSetType.Lucky, key, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9175dd08e215408faa01a3ee1cc86a13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class DiceRollResult
|
||||
{
|
||||
private DiceRollResult(
|
||||
string rollSessionId,
|
||||
string diceSetId,
|
||||
DiceSetType setType,
|
||||
int diceCount,
|
||||
IReadOnlyList<DiceFace> faces,
|
||||
int multiplier,
|
||||
string triggerSource)
|
||||
{
|
||||
RollSessionId = rollSessionId;
|
||||
DiceSetId = diceSetId;
|
||||
SetType = setType;
|
||||
DiceCount = diceCount;
|
||||
Faces = faces.ToArray();
|
||||
Multiplier = multiplier;
|
||||
TriggerSource = triggerSource;
|
||||
}
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
public string DiceSetId { get; }
|
||||
|
||||
public DiceSetType SetType { get; }
|
||||
|
||||
public int DiceCount { get; }
|
||||
|
||||
public IReadOnlyList<DiceFace> Faces { get; }
|
||||
|
||||
public int Multiplier { get; }
|
||||
|
||||
public string TriggerSource { get; }
|
||||
|
||||
public static DiceRollResult Create(
|
||||
string rollSessionId,
|
||||
string diceSetId,
|
||||
DiceSetType setType,
|
||||
IReadOnlyList<DiceFace> faces,
|
||||
int multiplier,
|
||||
string triggerSource)
|
||||
{
|
||||
if (faces == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(faces));
|
||||
}
|
||||
|
||||
return new DiceRollResult(
|
||||
rollSessionId,
|
||||
diceSetId,
|
||||
setType,
|
||||
faces.Count,
|
||||
faces,
|
||||
multiplier,
|
||||
triggerSource);
|
||||
}
|
||||
|
||||
public static DiceRollResult FromConfig(
|
||||
string rollSessionId,
|
||||
DiceSetConfig config,
|
||||
IReadOnlyList<DiceFace> faces,
|
||||
int multiplier,
|
||||
string triggerSource)
|
||||
{
|
||||
return new DiceRollResult(
|
||||
rollSessionId,
|
||||
config.DiceSetId,
|
||||
config.SetType,
|
||||
config.DiceCount,
|
||||
faces,
|
||||
multiplier,
|
||||
triggerSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 839ecc30eed6498d93ce34d9ffe847eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class DiceSetConfig
|
||||
{
|
||||
public DiceSetConfig(
|
||||
string diceSetId,
|
||||
DiceSetType setType,
|
||||
int diceCount,
|
||||
IReadOnlyList<DiceFace> faces,
|
||||
int? defaultResultSlotCount)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(diceSetId))
|
||||
{
|
||||
throw new ArgumentException("Dice set id is required.", nameof(diceSetId));
|
||||
}
|
||||
|
||||
if (diceCount <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(diceCount), "Dice count must be greater than zero.");
|
||||
}
|
||||
|
||||
if (faces == null || faces.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one face is required.", nameof(faces));
|
||||
}
|
||||
|
||||
DiceSetId = diceSetId;
|
||||
SetType = setType;
|
||||
DiceCount = diceCount;
|
||||
Faces = faces.ToArray();
|
||||
DefaultResultSlotCount = defaultResultSlotCount;
|
||||
}
|
||||
|
||||
public string DiceSetId { get; }
|
||||
|
||||
public DiceSetType SetType { get; }
|
||||
|
||||
public int DiceCount { get; }
|
||||
|
||||
public IReadOnlyList<DiceFace> Faces { get; }
|
||||
|
||||
public int? DefaultResultSlotCount { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c890293bb854609a2b916d1d35e6063
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public enum DiceSetType
|
||||
{
|
||||
Normal,
|
||||
Lucky
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0147bf70503045fbb65baec35ba74936
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceSelectedResult
|
||||
{
|
||||
internal LuckyDiceSelectedResult(string resultKey, string targetKey, int slotCount)
|
||||
{
|
||||
ResultKey = resultKey;
|
||||
TargetKey = targetKey;
|
||||
SlotCount = slotCount;
|
||||
}
|
||||
|
||||
public string ResultKey { get; }
|
||||
|
||||
public string TargetKey { get; }
|
||||
|
||||
public int SlotCount { get; }
|
||||
}
|
||||
|
||||
public sealed class LuckyDiceSelectionResult
|
||||
{
|
||||
internal LuckyDiceSelectionResult(
|
||||
LuckyDiceCandidateConfig selectedCandidate,
|
||||
int initialCandidateCount,
|
||||
int filteredCandidateCount,
|
||||
int? randomValue,
|
||||
string fallbackType,
|
||||
int slotCount)
|
||||
{
|
||||
InitialCandidateCount = initialCandidateCount;
|
||||
FilteredCandidateCount = filteredCandidateCount;
|
||||
RandomValue = randomValue;
|
||||
FallbackType = fallbackType;
|
||||
SlotCount = selectedCandidate == null ? 0 : slotCount;
|
||||
Selected = selectedCandidate == null
|
||||
? null
|
||||
: new LuckyDiceSelectedResult(selectedCandidate.ResultKey, selectedCandidate.TargetKey, SlotCount);
|
||||
SlotResultKeys = selectedCandidate == null ? null : Enumerable.Repeat(selectedCandidate.ResultKey, SlotCount).ToArray();
|
||||
}
|
||||
|
||||
public LuckyDiceSelectedResult Selected { get; }
|
||||
|
||||
public int InitialCandidateCount { get; }
|
||||
|
||||
public int FilteredCandidateCount { get; }
|
||||
|
||||
public int? RandomValue { get; }
|
||||
|
||||
public string FallbackType { get; }
|
||||
|
||||
public int SlotCount { get; }
|
||||
|
||||
public string[] SlotResultKeys { get; }
|
||||
}
|
||||
|
||||
public sealed class TargetModeEntry
|
||||
{
|
||||
private TargetModeEntry(
|
||||
string targetKey,
|
||||
string modeKey,
|
||||
string resultKey,
|
||||
int multiplier,
|
||||
string triggerSource,
|
||||
string rollSessionId)
|
||||
{
|
||||
TargetKey = targetKey;
|
||||
ModeKey = modeKey;
|
||||
ResultKey = resultKey;
|
||||
Multiplier = multiplier;
|
||||
TriggerSource = triggerSource;
|
||||
RollSessionId = rollSessionId;
|
||||
}
|
||||
|
||||
public string TargetKey { get; }
|
||||
|
||||
public string ModeKey { get; }
|
||||
|
||||
public string ResultKey { get; }
|
||||
|
||||
public int Multiplier { get; }
|
||||
|
||||
public string TriggerSource { get; }
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
internal static TargetModeEntry From(TargetModeConfig definition, RollFlowRequest request, int multiplier)
|
||||
{
|
||||
return new TargetModeEntry(
|
||||
definition.TargetKey,
|
||||
definition.ModeKey,
|
||||
definition.ResultKey,
|
||||
multiplier,
|
||||
request.TriggerSource,
|
||||
request.RollSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TargetModeResolveResult
|
||||
{
|
||||
private TargetModeResolveResult(bool success, TargetModeEntry entry, string failureReason, string fallbackMode)
|
||||
{
|
||||
Success = success;
|
||||
Entry = entry;
|
||||
FailureReason = failureReason;
|
||||
FallbackMode = fallbackMode;
|
||||
}
|
||||
|
||||
public bool Success { get; }
|
||||
|
||||
public TargetModeEntry Entry { get; }
|
||||
|
||||
public string FailureReason { get; }
|
||||
|
||||
public string FallbackMode { get; }
|
||||
|
||||
public static TargetModeResolveResult Resolved(TargetModeEntry entry)
|
||||
{
|
||||
return new TargetModeResolveResult(true, entry, null, null);
|
||||
}
|
||||
|
||||
public static TargetModeResolveResult Failed(string failureReason, string fallbackMode = null)
|
||||
{
|
||||
return new TargetModeResolveResult(false, null, failureReason, fallbackMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e21edd33e2d469d9736fa7ce87409f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollActionContext
|
||||
{
|
||||
public RollActionContext(string rollSessionId, string comboNormalizedKey, RollTrace trace)
|
||||
{
|
||||
RollSessionId = rollSessionId;
|
||||
ComboNormalizedKey = comboNormalizedKey;
|
||||
Trace = trace ?? throw new ArgumentNullException(nameof(trace));
|
||||
}
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
public string ComboNormalizedKey { get; }
|
||||
|
||||
public RollTrace Trace { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b28add99b6540b19611b2e1ad429bae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollActionResult
|
||||
{
|
||||
private RollActionResult(
|
||||
string actionType,
|
||||
bool success,
|
||||
IReadOnlyList<RollEffect> effects,
|
||||
string outcome,
|
||||
string error)
|
||||
{
|
||||
ActionType = actionType;
|
||||
Success = success;
|
||||
Effects = effects == null ? Array.Empty<RollEffect>() : effects.ToArray();
|
||||
Outcome = outcome;
|
||||
Error = error;
|
||||
}
|
||||
|
||||
public string ActionType { get; }
|
||||
|
||||
public bool Success { get; }
|
||||
|
||||
public IReadOnlyList<RollEffect> Effects { get; }
|
||||
|
||||
public string Outcome { get; }
|
||||
|
||||
public string Error { get; }
|
||||
|
||||
public static RollActionResult Succeeded(string actionType, string outcome)
|
||||
{
|
||||
return new RollActionResult(actionType, true, Array.Empty<RollEffect>(), outcome, null);
|
||||
}
|
||||
|
||||
public static RollActionResult Succeeded(string actionType, IReadOnlyList<RollEffect> effects)
|
||||
{
|
||||
var outcome = effects == null || effects.Count == 0 ? null : effects[effects.Count - 1].EffectType;
|
||||
return new RollActionResult(actionType, true, effects, outcome, null);
|
||||
}
|
||||
|
||||
public static RollActionResult Failure(string actionType, string error)
|
||||
{
|
||||
return new RollActionResult(actionType, false, Array.Empty<RollEffect>(), null, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 218cb0b26b39475fb0b5eff1b7f939fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollActionSpec
|
||||
{
|
||||
public RollActionSpec(string actionType)
|
||||
: this(actionType, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RollActionSpec(string actionType, IReadOnlyDictionary<string, string> parameters)
|
||||
{
|
||||
ActionType = actionType ?? throw new ArgumentNullException(nameof(actionType));
|
||||
Params = parameters == null
|
||||
? new Dictionary<string, string>()
|
||||
: parameters.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
}
|
||||
|
||||
public string ActionType { get; }
|
||||
|
||||
public IReadOnlyDictionary<string, string> Params { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f8ccfed08184b1b9f553c455e747c10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public abstract class RollEffect
|
||||
{
|
||||
protected RollEffect(string effectType)
|
||||
{
|
||||
EffectType = effectType;
|
||||
}
|
||||
|
||||
public string EffectType { get; }
|
||||
}
|
||||
|
||||
public sealed class RewardEffect : RollEffect
|
||||
{
|
||||
public RewardEffect()
|
||||
: base("Reward")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MultiplierEffect : RollEffect
|
||||
{
|
||||
public MultiplierEffect(string reason)
|
||||
: base("Multiplier")
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
public string Reason { get; }
|
||||
}
|
||||
|
||||
public sealed class LuckyDiceRequestedEffect : RollEffect
|
||||
{
|
||||
public LuckyDiceRequestedEffect()
|
||||
: base("LuckyDiceRequested")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnterTargetModeEffect : RollEffect
|
||||
{
|
||||
public EnterTargetModeEffect(string targetKey, string modeKey)
|
||||
: base("EnterTargetMode")
|
||||
{
|
||||
TargetKey = targetKey;
|
||||
ModeKey = modeKey;
|
||||
}
|
||||
|
||||
public string TargetKey { get; }
|
||||
|
||||
public string ModeKey { get; }
|
||||
}
|
||||
|
||||
public sealed class TargetModeFailedEffect : RollEffect
|
||||
{
|
||||
public TargetModeFailedEffect(string failureReason, string fallbackMode)
|
||||
: base("TargetModeFailed")
|
||||
{
|
||||
FailureReason = failureReason;
|
||||
FallbackMode = fallbackMode;
|
||||
}
|
||||
|
||||
public string FailureReason { get; }
|
||||
|
||||
public string FallbackMode { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5524489d53e74a4c88a00bba1de976b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollFlowRequest
|
||||
{
|
||||
public RollFlowRequest(string rollSessionId)
|
||||
: this(
|
||||
rollSessionId,
|
||||
LuckyDiceKeys.NormalDiceSetId,
|
||||
LuckyDiceKeys.NormalRollTriggerSource,
|
||||
playerLevel: 0,
|
||||
isTutorial: false)
|
||||
{
|
||||
}
|
||||
|
||||
public RollFlowRequest(
|
||||
string rollSessionId,
|
||||
string diceSetId,
|
||||
string triggerSource,
|
||||
int playerLevel,
|
||||
bool isTutorial)
|
||||
{
|
||||
RollSessionId = rollSessionId;
|
||||
DiceSetId = diceSetId;
|
||||
TriggerSource = triggerSource;
|
||||
PlayerLevel = playerLevel;
|
||||
IsTutorial = isTutorial;
|
||||
}
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
public string DiceSetId { get; }
|
||||
|
||||
public string TriggerSource { get; }
|
||||
|
||||
public int PlayerLevel { get; }
|
||||
|
||||
public bool IsTutorial { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cb236f5ac214d77aae6c15c3c8429d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollFlowResult
|
||||
{
|
||||
public RollFlowResult(
|
||||
string rollSessionId,
|
||||
DiceRollResult rollResult,
|
||||
ComboFacts comboFacts,
|
||||
IReadOnlyList<ComboRuleMatch> matchedRules,
|
||||
IReadOnlyList<RollActionResult> actionResults,
|
||||
IReadOnlyList<RollEffect> effects,
|
||||
LuckyDiceSelectionResult luckyDiceResult,
|
||||
TargetModeEntry targetMode,
|
||||
RollTrace trace)
|
||||
{
|
||||
RollSessionId = rollSessionId;
|
||||
RollResult = rollResult;
|
||||
ComboFacts = comboFacts;
|
||||
MatchedRules = matchedRules == null ? Array.Empty<ComboRuleMatch>() : matchedRules.ToArray();
|
||||
ActionResults = actionResults == null ? Array.Empty<RollActionResult>() : actionResults.ToArray();
|
||||
Effects = effects == null ? Array.Empty<RollEffect>() : effects.ToArray();
|
||||
LuckyDiceResult = luckyDiceResult;
|
||||
TargetMode = targetMode;
|
||||
Trace = trace;
|
||||
}
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
public DiceRollResult RollResult { get; }
|
||||
|
||||
public ComboFacts ComboFacts { get; }
|
||||
|
||||
public IReadOnlyList<ComboRuleMatch> MatchedRules { get; }
|
||||
|
||||
public IReadOnlyList<RollActionResult> ActionResults { get; }
|
||||
|
||||
public IReadOnlyList<RollEffect> Effects { get; }
|
||||
|
||||
public LuckyDiceSelectionResult LuckyDiceResult { get; }
|
||||
|
||||
public TargetModeEntry TargetMode { get; }
|
||||
|
||||
public RollTrace Trace { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebb54a7e748c4b838f46a5d51e00e89f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92ce07ad741540bbbaee1c4c8b349ad7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class LuckyDiceFlowService
|
||||
{
|
||||
private readonly IRandomSource _random;
|
||||
|
||||
public LuckyDiceFlowService(IRandomSource random)
|
||||
{
|
||||
_random = random ?? throw new ArgumentNullException(nameof(random));
|
||||
}
|
||||
|
||||
public LuckyDiceSelectionResult Select(
|
||||
IReadOnlyList<LuckyDiceCandidateConfig> candidates,
|
||||
RollFlowRequest request,
|
||||
DiceSetConfig luckyDiceSetConfig)
|
||||
{
|
||||
var source = candidates ?? Array.Empty<LuckyDiceCandidateConfig>();
|
||||
var filtered = source
|
||||
.Where(candidate => candidate.Enabled)
|
||||
.Where(candidate => request.PlayerLevel >= candidate.MinLevel)
|
||||
.Where(candidate => candidate.SourceFilter == null ||
|
||||
candidate.SourceFilter.Count == 0 ||
|
||||
candidate.SourceFilter.Contains(request.TriggerSource))
|
||||
.ToArray();
|
||||
|
||||
var fallbackType = "None";
|
||||
int? randomValue = null;
|
||||
var selected = PickWeighted(filtered, out randomValue);
|
||||
|
||||
if (selected == null)
|
||||
{
|
||||
selected = filtered.FirstOrDefault(candidate => candidate.IsDefault);
|
||||
fallbackType = selected == null ? "NormalRewardFallback" : "DefaultResult";
|
||||
}
|
||||
|
||||
var slotCount = selected == null ? 0 : selected.ResultSlotCount ?? luckyDiceSetConfig.DefaultResultSlotCount ?? 3;
|
||||
|
||||
return new LuckyDiceSelectionResult(
|
||||
selected,
|
||||
source.Count,
|
||||
filtered.Length,
|
||||
randomValue,
|
||||
fallbackType,
|
||||
slotCount);
|
||||
}
|
||||
|
||||
private LuckyDiceCandidateConfig PickWeighted(IReadOnlyList<LuckyDiceCandidateConfig> candidates, out int? randomValue)
|
||||
{
|
||||
var weighted = candidates.Where(candidate => candidate.Weight > 0).ToArray();
|
||||
var totalWeight = weighted.Sum(candidate => candidate.Weight);
|
||||
if (totalWeight <= 0)
|
||||
{
|
||||
randomValue = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
var roll = _random.Next(0, totalWeight);
|
||||
randomValue = roll;
|
||||
var cursor = 0;
|
||||
foreach (var candidate in weighted)
|
||||
{
|
||||
cursor += candidate.Weight;
|
||||
if (roll < cursor)
|
||||
{
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return weighted[weighted.Length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6956f55ed064f2d872f52a9c9eba554
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Rules.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Rules.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac0d6f0f00124234809917e89e30eba3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class ComboRuleMatcher
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, IComboMatcher> _matchers;
|
||||
|
||||
public ComboRuleMatcher()
|
||||
: this(ComboMatcherRegistry.CreateDefaultMatchers())
|
||||
{
|
||||
}
|
||||
|
||||
public ComboRuleMatcher(IEnumerable<IComboMatcher> matchers)
|
||||
{
|
||||
if (matchers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(matchers));
|
||||
}
|
||||
|
||||
_matchers = matchers.ToDictionary(matcher => matcher.MatcherType, matcher => matcher);
|
||||
}
|
||||
|
||||
public ComboRuleMatchResult Match(ComboFacts facts, IReadOnlyList<ComboRule> rules)
|
||||
{
|
||||
if (facts == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(facts));
|
||||
}
|
||||
|
||||
if (rules == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rules));
|
||||
}
|
||||
|
||||
var matches = new List<ComboRuleMatch>();
|
||||
foreach (var rule in rules.OrderByDescending(rule => rule.Priority))
|
||||
{
|
||||
if (!MatchesRule(facts, rule.Matcher))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
matches.Add(new ComboRuleMatch(rule));
|
||||
if (rule.StopAfterMatched)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ComboRuleMatchResult(matches);
|
||||
}
|
||||
|
||||
private bool MatchesRule(ComboFacts facts, ComboMatcherSpec matcher)
|
||||
{
|
||||
if (!_matchers.TryGetValue(matcher.MatcherType, out var registeredMatcher))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return registeredMatcher.IsMatch(facts, matcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ffdaec353584cbf8006d846816af810
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f02fda27051d4ff8b44d459bd69c176b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public static class ComboMatcherRegistry
|
||||
{
|
||||
public static IReadOnlyList<IComboMatcher> CreateDefaultMatchers()
|
||||
{
|
||||
return new IComboMatcher[]
|
||||
{
|
||||
new ExactComboMatcher(),
|
||||
new AllNumbersMatcher(),
|
||||
new ContainsFaceMatcher(),
|
||||
new FaceCountMatcher(),
|
||||
new FaceCountRangeMatcher(),
|
||||
new DiceCountMatcher(),
|
||||
new NumberPairMatcher(),
|
||||
new NumberOfAKindMatcher(),
|
||||
new NumberSumRangeMatcher()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 024047cd3f684a5a813d1abe30fc350a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
internal sealed class ExactComboMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.ExactComboMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return string.Equals(
|
||||
spec.UseOrderedKey ? facts.OrderedKey : facts.NormalizedKey,
|
||||
spec.ComboKey,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AllNumbersMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.AllNumbersMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return facts.NumberValues.Count == facts.DiceCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ContainsFaceMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.ContainsFaceMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return ComboMatcherFacts.FaceCount(facts, spec.FaceKey) >= (spec.RequiredCount ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FaceCountMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.FaceCountMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return ComboMatcherFacts.FaceCount(facts, spec.FaceKey) == (spec.RequiredCount ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FaceCountRangeMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.FaceCountRangeMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return ComboMatcherFacts.IsInRange(
|
||||
ComboMatcherFacts.FaceCount(facts, spec.FaceKey),
|
||||
spec.MinCount,
|
||||
spec.MaxCount);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DiceCountMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.DiceCountMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return !spec.DiceCount.HasValue || facts.DiceCount == spec.DiceCount.Value;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NumberPairMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.NumberPairMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return facts.NumberValues.Count == 2 && facts.NumberValues[0] == facts.NumberValues[1];
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NumberOfAKindMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.NumberOfAKindMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return facts.NumberValues
|
||||
.GroupBy(value => value)
|
||||
.Any(group => group.Count() >= (spec.OfAKindCount ?? 2));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NumberSumRangeMatcher : IComboMatcher
|
||||
{
|
||||
public string MatcherType => LuckyDiceKeys.NumberSumRangeMatcher;
|
||||
|
||||
public bool IsMatch(ComboFacts facts, ComboMatcherSpec spec)
|
||||
{
|
||||
return ComboMatcherFacts.IsInRange(facts.NumberSum, spec.MinSum, spec.MaxSum);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ComboMatcherFacts
|
||||
{
|
||||
public static int FaceCount(ComboFacts facts, string faceKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(faceKey))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return facts.FaceCounts.TryGetValue(faceKey, out var count) ? count : 0;
|
||||
}
|
||||
|
||||
public static bool IsInRange(int value, int? min, int? max)
|
||||
{
|
||||
if (min.HasValue && value < min.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (max.HasValue && value > max.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 670344b8b7c74696baef9fe7a61ffc34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public interface IComboMatcher
|
||||
{
|
||||
string MatcherType { get; }
|
||||
|
||||
bool IsMatch(ComboFacts facts, ComboMatcherSpec spec);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5252627bba5a463bb7006295c672a164
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Services.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Services.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32a9a61586304026ad6aabe05b1316c2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public static class ComboFactBuilder
|
||||
{
|
||||
public static ComboFacts Build(DiceRollResult result)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (result.SetType != DiceSetType.Normal)
|
||||
{
|
||||
throw new InvalidOperationException("Only normal dice rolls can be converted to combo facts.");
|
||||
}
|
||||
|
||||
if (result.DiceCount != result.Faces.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Dice count does not match rolled face count.");
|
||||
}
|
||||
|
||||
if (result.Faces.Any(face => face.SetType != DiceSetType.Normal))
|
||||
{
|
||||
throw new InvalidOperationException("Combo facts cannot include lucky dice result faces.");
|
||||
}
|
||||
|
||||
var faceCounts = result.Faces
|
||||
.GroupBy(face => face.Key)
|
||||
.ToDictionary(group => group.Key, group => group.Count());
|
||||
var numberValues = result.Faces
|
||||
.Where(face => face.NumberValue.HasValue)
|
||||
.Select(face => face.NumberValue.Value)
|
||||
.ToArray();
|
||||
var cloverCount = faceCounts.TryGetValue(LuckyDiceKeys.CloverFaceKey, out var count) ? count : 0;
|
||||
var orderedKey = string.Join("_", result.Faces.Select(face => face.Key));
|
||||
var normalizedKey = string.Join("_", result.Faces.Select(face => face.Key).OrderBy(key => key, StringComparer.Ordinal));
|
||||
|
||||
return new ComboFacts(
|
||||
result.RollSessionId,
|
||||
result.SetType,
|
||||
result.DiceCount,
|
||||
result.Faces,
|
||||
faceCounts,
|
||||
numberValues.Sum(),
|
||||
numberValues,
|
||||
cloverCount,
|
||||
normalizedKey,
|
||||
orderedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb0a92fa21434dc6b216eaf039c8eb86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public interface IRandomSource
|
||||
{
|
||||
int Next(int minInclusive, int maxExclusive);
|
||||
}
|
||||
|
||||
public sealed class SystemRandomSource : IRandomSource
|
||||
{
|
||||
private readonly Random _random;
|
||||
|
||||
public SystemRandomSource()
|
||||
: this(new Random())
|
||||
{
|
||||
}
|
||||
|
||||
public SystemRandomSource(Random random)
|
||||
{
|
||||
_random = random ?? throw new ArgumentNullException(nameof(random));
|
||||
}
|
||||
|
||||
public int Next(int minInclusive, int maxExclusive)
|
||||
{
|
||||
return _random.Next(minInclusive, maxExclusive);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DiceRollService
|
||||
{
|
||||
private readonly IRandomSource _random;
|
||||
|
||||
public DiceRollService(IRandomSource random)
|
||||
{
|
||||
_random = random ?? throw new ArgumentNullException(nameof(random));
|
||||
}
|
||||
|
||||
public DiceRollResult Roll(DiceSetConfig config, string rollSessionId, string triggerSource)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
var rolledFaces = new List<DiceFace>(config.DiceCount);
|
||||
for (var i = 0; i < config.DiceCount; i++)
|
||||
{
|
||||
var index = _random.Next(0, config.Faces.Count);
|
||||
rolledFaces.Add(config.Faces[index]);
|
||||
}
|
||||
|
||||
return DiceRollResult.FromConfig(
|
||||
rollSessionId,
|
||||
config,
|
||||
rolledFaces,
|
||||
multiplier: 1,
|
||||
triggerSource: triggerSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a9d95f67cf84b62b960aae1b964e9db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48e76359cf424f43b578f3098179d64f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class TargetModeResolver
|
||||
{
|
||||
public TargetModeResolveResult Resolve(
|
||||
LuckyDiceSelectedResult selected,
|
||||
RollFlowRequest request,
|
||||
int multiplier,
|
||||
IReadOnlyList<TargetModeConfig> definitions)
|
||||
{
|
||||
if (selected == null)
|
||||
{
|
||||
return TargetModeResolveResult.Failed("MissingLuckyDiceCandidate");
|
||||
}
|
||||
|
||||
var definition = (definitions ?? Array.Empty<TargetModeConfig>())
|
||||
.FirstOrDefault(item =>
|
||||
item.ResultKey == selected.ResultKey &&
|
||||
item.TargetKey == selected.TargetKey);
|
||||
|
||||
if (definition == null)
|
||||
{
|
||||
return TargetModeResolveResult.Failed("MissingTargetMode");
|
||||
}
|
||||
|
||||
return TargetModeResolveResult.Resolved(TargetModeEntry.From(definition, request, multiplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8560c502d0b42cd856b23761cf7ea4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Trace.meta
Normal file
9
FishDice/Assets/FishDice/Runtime/LuckyDice/Trace.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d407eb4e046b45ff99f112e6646ab1d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
121
FishDice/Assets/FishDice/Runtime/LuckyDice/Trace/RollTrace.cs
Normal file
121
FishDice/Assets/FishDice/Runtime/LuckyDice/Trace/RollTrace.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishDice.LuckyDice
|
||||
{
|
||||
public sealed class RollTrace
|
||||
{
|
||||
private readonly List<string> _executedActions = new List<string>();
|
||||
private readonly List<ComboRuleMatch> _matchedRules = new List<ComboRuleMatch>();
|
||||
private readonly List<RollActionResult> _actionResults = new List<RollActionResult>();
|
||||
|
||||
public RollTrace(string rollSessionId)
|
||||
{
|
||||
RollSessionId = rollSessionId;
|
||||
}
|
||||
|
||||
public string RollSessionId { get; }
|
||||
|
||||
public IReadOnlyList<string> NormalFaces { get; private set; }
|
||||
|
||||
public int NormalDiceCount { get; private set; }
|
||||
|
||||
public string ComboNormalizedKey { get; private set; }
|
||||
|
||||
public string MatchedRuleId { get; private set; }
|
||||
|
||||
public int? MatchedRulePriority { get; private set; }
|
||||
|
||||
public IReadOnlyList<ComboRuleMatch> MatchedRules => _matchedRules;
|
||||
|
||||
public IReadOnlyList<string> ExecutedActions => _executedActions;
|
||||
|
||||
public IReadOnlyList<RollActionResult> ActionResults => _actionResults;
|
||||
|
||||
public int LuckyDiceInitialCandidateCount { get; private set; }
|
||||
|
||||
public int LuckyDiceFilteredCandidateCount { get; private set; }
|
||||
|
||||
public string LuckyDiceFallbackType { get; private set; }
|
||||
|
||||
public int? LuckyDiceRandomValue { get; private set; }
|
||||
|
||||
public string LuckyDiceSelectedResultKey { get; private set; }
|
||||
|
||||
public string LuckyDiceSelectedTargetKey { get; private set; }
|
||||
|
||||
public int LuckyDiceResultSlotCount { get; private set; }
|
||||
|
||||
public IReadOnlyList<string> LuckyDiceSlotResultKeys { get; private set; }
|
||||
|
||||
public string ResolvedTargetKey { get; private set; }
|
||||
|
||||
public string ResolvedModeKey { get; private set; }
|
||||
|
||||
public string TargetModeFailureReason { get; private set; }
|
||||
|
||||
public string FinalOutcome { get; private set; }
|
||||
|
||||
public void RecordComboFacts(ComboFacts facts)
|
||||
{
|
||||
NormalFaces = facts.Faces.Select(face => face.Key).ToArray();
|
||||
NormalDiceCount = facts.DiceCount;
|
||||
ComboNormalizedKey = facts.NormalizedKey;
|
||||
}
|
||||
|
||||
public void RecordRuleMatch(ComboRuleMatchResult matchResult)
|
||||
{
|
||||
_matchedRules.Clear();
|
||||
_matchedRules.AddRange(matchResult.Matches);
|
||||
|
||||
var first = matchResult.Matches.FirstOrDefault();
|
||||
if (first == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MatchedRuleId = first.RuleId;
|
||||
MatchedRulePriority = first.Priority;
|
||||
}
|
||||
|
||||
public void RecordActionResult(RollActionResult result)
|
||||
{
|
||||
_executedActions.Add(result.ActionType);
|
||||
_actionResults.Add(result);
|
||||
if (result.Success && !string.IsNullOrEmpty(result.Outcome))
|
||||
{
|
||||
FinalOutcome = result.Outcome;
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordLuckyDiceSelection(LuckyDiceSelectionResult result)
|
||||
{
|
||||
LuckyDiceInitialCandidateCount = result.InitialCandidateCount;
|
||||
LuckyDiceFilteredCandidateCount = result.FilteredCandidateCount;
|
||||
LuckyDiceFallbackType = result.FallbackType;
|
||||
LuckyDiceRandomValue = result.RandomValue;
|
||||
LuckyDiceSelectedResultKey = result.Selected == null ? null : result.Selected.ResultKey;
|
||||
LuckyDiceSelectedTargetKey = result.Selected == null ? null : result.Selected.TargetKey;
|
||||
LuckyDiceResultSlotCount = result.SlotCount;
|
||||
LuckyDiceSlotResultKeys = result.SlotResultKeys;
|
||||
}
|
||||
|
||||
public void RecordTargetMode(TargetModeEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ResolvedTargetKey = entry.TargetKey;
|
||||
ResolvedModeKey = entry.ModeKey;
|
||||
FinalOutcome = "TargetModeResolved";
|
||||
}
|
||||
|
||||
public void RecordTargetModeFailure(string failureReason)
|
||||
{
|
||||
TargetModeFailureReason = failureReason;
|
||||
FinalOutcome = "TargetModeFailed";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c57c4ac2f54a638326e104f5afac01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Tests.meta
Normal file
9
FishDice/Assets/FishDice/Tests.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 243feaaf2c05440ebb1fff35768a653c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
9
FishDice/Assets/FishDice/Tests/EditMode.meta
Normal file
9
FishDice/Assets/FishDice/Tests/EditMode.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 020efc0ae0324888bb93be8b772cd5fd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "FishDice.Tests.EditMode",
|
||||
"rootNamespace": "FishDice.Tests",
|
||||
"references": [
|
||||
"FishDice.Runtime",
|
||||
"FishDice.Demo"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false,
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user