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 处理行信息 /// /// 当前行是否应该自动跳过 /// /// 当前行 /// 是否应该自动跳过 public static bool IsAutoSkipLine(this LocalizedLine line) { return line?.Metadata != null && line.Metadata.Any(IsAutoNextMetadata); } /// /// 尝试读取 auto_next 标签中以秒为单位的停留时间。 /// /// /// 无参数的 auto_next 返回 false;无效参数会输出警告并由调用方回退到默认延时。 /// 如果一行存在多个参数标签,则使用第一个。 /// 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)); } /// /// 当前行是否应作为下一组梦境选项的提示语。 /// public static bool IsOptionPromptLine(this LocalizedLine line) { return line.Metadata != null && line.Metadata.Contains(OptionPrompt); } /// /// 从line中获取角色vo定义 /// /// 行数据 /// 结果 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 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(pair.Value.StringValue, out var type) ? type : ActorRole.Aside; break; case "dialogViewType": vo.dialogViewType = Enum.TryParse(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 } }