199 lines
6.8 KiB
C#
199 lines
6.8 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,
|
|
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
|
|
}
|
|
}
|