Files

204 lines
7.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AibisDream.Kit;
using Yarn.Markup;
using Yarn.Unity;
namespace AibisDream.Utility
{
public static class YarnUtil
{
private const string AutoNext = "auto_next";
private const string AutoNextParameterPrefix = AutoNext + ":";
public const string OptionPrompt = "option_prompt";
#region 处理行信息
/// <summary>
/// 当前行是否应该自动跳过
/// </summary>
/// <param name="line">当前行</param>
/// <returns>是否应该自动跳过</returns>
public static bool IsAutoSkipLine(this LocalizedLine line)
{
return line?.Metadata != null && line.Metadata.Any(IsAutoNextMetadata);
}
/// <summary>
/// 尝试读取 auto_next 标签中以秒为单位的停留时间。
/// </summary>
/// <remarks>
/// 无参数的 auto_next 返回 false;无效参数会输出警告并由调用方回退到默认延时。
/// 如果一行存在多个参数标签,则使用第一个。
/// </remarks>
public static bool TryGetAutoNextDelaySeconds(this LocalizedLine line, out float delaySeconds)
{
delaySeconds = default;
if (line?.Metadata == null)
{
return false;
}
var parameterizedTags = line.Metadata
.Where(metadata => metadata != null &&
metadata.StartsWith(AutoNextParameterPrefix, StringComparison.Ordinal))
.ToArray();
if (parameterizedTags.Length == 0)
{
return false;
}
if (parameterizedTags.Length > 1)
{
UnityEngine.Debug.LogWarning(
$"[YarnUtil] 行 {line.TextID} 存在多个 auto_next 参数标签,将使用第一个:{parameterizedTags[0]}");
}
var parameterizedTag = parameterizedTags[0];
var rawValue = parameterizedTag.Substring(AutoNextParameterPrefix.Length);
var isValid = float.TryParse(
rawValue,
NumberStyles.Float,
CultureInfo.InvariantCulture,
out delaySeconds)
&& !float.IsNaN(delaySeconds)
&& !float.IsInfinity(delaySeconds)
&& delaySeconds >= 0f
&& delaySeconds * 1000d <= int.MaxValue;
if (isValid)
{
return true;
}
UnityEngine.Debug.LogWarning(
$"[YarnUtil] 行 {line.TextID} 的 auto_next 参数无效:{parameterizedTag},将使用默认延时。");
delaySeconds = default;
return false;
}
private static bool IsAutoNextMetadata(string metadata)
{
return string.Equals(metadata, AutoNext, StringComparison.Ordinal)
|| (metadata != null && metadata.StartsWith(AutoNextParameterPrefix, StringComparison.Ordinal));
}
/// <summary>
/// 当前行是否应作为下一组梦境选项的提示语。
/// </summary>
public static bool IsOptionPromptLine(this LocalizedLine line)
{
return line.Metadata != null && line.Metadata.Contains(OptionPrompt);
}
/// <summary>
/// 从line中获取角色vo定义
/// </summary>
/// <param name="line">行数据</param>
/// <returns>结果</returns>
public static CharacterVo GetCharacterVo(this LocalizedLine line)
{
if (line.Text.TryGetAttributeWithName("character", out var characterAttribute))
{
// 直接写就使用配置选项
if (characterAttribute.Properties.TryGetValue("name", out var nameValue))
{
var charVo = GetCharacterVoByKey(nameValue.StringValue);
if (characterAttribute.Properties.Count > 1)
{
charVo = FullCharVoWithProps(charVo, characterAttribute.Properties);
}
return charVo;
}
// 找不到Name和Key
return new CharacterVo();
}
// 没有Character就直接返回空
return new CharacterVo();
}
private static CharacterVo GetCharacterVoByKey(string charKey)
{
if (ConfigUtil.Instance.TryGetCharacter(charKey, out var character))
{
return character.GetStructVo();
}
return new CharacterVo
{
key = charKey,
cn = charKey,
en = charKey,
ja = charKey,
ru = charKey,
es = charKey,
ptBr = charKey,
role = ActorRole.Aside
};
}
private static CharacterVo FullCharVoWithProps(CharacterVo vo, IReadOnlyDictionary<string, MarkupValue> props)
{
foreach (var pair in props)
{
switch (pair.Key)
{
case "cn":
vo.cn = pair.Value.StringValue;
break;
case "nameColor":
vo.nameColor = pair.Value.StringValue;
break;
case "headPicPath":
vo.headPicPath = pair.Value.StringValue;
break;
case "bubbleIdx":
vo.bubbleIdx = pair.Value.IntegerValue;
break;
case "voicePath":
vo.voicePath = pair.Value.StringValue;
break;
case "role":
vo.role = Enum.TryParse<ActorRole>(pair.Value.StringValue, out var type) ? type : ActorRole.Aside;
break;
case "dialogViewType":
vo.dialogViewType = Enum.TryParse<DialogViewType>(pair.Value.StringValue, out var viewType) ? viewType : DialogViewType.Default;
break;
}
}
return vo;
}
#endregion
#region 部分命令
[YarnFunction("set_option_visited")]
public static int SetOptionVisited(int visitedState, int optionIdx)
{
return visitedState | ( 1 << (optionIdx - 1));
}
[YarnFunction("check_visited")]
public static bool CheckVisitedOptionNum(int visitedState, int totalOptionNum)
{
return visitedState == (1 << totalOptionNum) - 1;
}
[YarnFunction("str_contains")]
public static bool StrContains(string str, string substring)
{
return str.Contains(substring);
}
#endregion
}
}