using cfg.DialogueCfg; using Cysharp.Threading.Tasks; using Framework; using Gameplay; using Gameplay.Guide; using Gameplay.Level; using PhxhSDK; using System; using System.Collections.Generic; using System.Linq; using Random = UnityEngine.Random; /// /// 台词管理器 /// public class DialogueManager : Singlenton, IInitable, IUpdatable { /// /// 台词播放记录数据 /// private class DialoguePlayData { /// /// 台词对应次数字典 /// public Dictionary DicCount; /// /// 上一次日期 /// public DateTime LastTime; public DialoguePlayData() { DicCount = new(); } public void SetNewTime(DateTime dateTime) { LastTime = dateTime; DicCount.Clear(); } } /// /// 实际语音id = 角色id * RATIO + 台词id /// 实际台词文本key = "Dialogue_{实际语音id}" /// private const int RATIO = 1000; /// /// 台词播放记录数据key /// private const string DIALOGUE_PLAY_DATA = "Dialogue_Play_Data"; /// /// 角色台词对应的权重字典,key:角色id,value: subKey:台词id,subValue:台词权重 /// private Dictionary> dicDialogueWeight; /// /// 台词数据字典字典,key:台词类型,value:台词配置数据列表 /// private Dictionary> dicDialogue; /// /// 台词播放记录数据 /// private DialoguePlayData dialoguePlayData; /// /// 记录战斗台词上一次播放时间 /// private Dictionary dicFightPreTime; #region 战斗台词 /// /// 当前播放的战斗台词 /// private AudioInst _curFightAudioCri; /// /// 当前战斗台词优先级 /// private int curFightDialoguePriority; /// /// 当前战斗语音类型 /// private E_DialogueType curFightDialogueType; /// /// 等待播放的获得增益buff语音,value:角色id /// private readonly Queue queueWaitGetBuffDialogue = new(); #endregion /// /// 是否初始化 /// private bool isInit; #region 临时数据 /// /// 符合条件的台词数据 /// private readonly List listDialogueDataTemp = new(); private readonly List listDialogueWeight = new(); #endregion /// /// 是否正在播放战斗语音 /// public bool IsPlayingFightDialogue { get { return null != _curFightAudioCri && _curFightAudioCri.IsPlaying; } } public void Init() { isInit = false; dicDialogueWeight = new(); dicFightPreTime = new(); dialoguePlayData = StorageMgr.Instance.GetStorage(DIALOGUE_PLAY_DATA); EventManager.Instance.Register(EventManager.EventName.AllConfigLoad, OnInit); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_SELF_RETREAT, (Action)OnFightRetreat); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_SELECT_UNIT, (Action)OnFightChoose); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_CREATED_ROLE, (Action)OnFightCreateRole); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_CLICK_MOVE, (Action)OnFightMove); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_CLICK_ATTACK, (Action)OnFightAttack); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_USE_SKILL, (Action)OnFightUseSkill); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_UP_VEHICLE, (Action)OnFightBoardVehicle); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_DOWN_VEHICLE, (Action)OnFightLeaveVehicle); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_GET_P_BUFF, (Action)OnFightGetBuff); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_KILL_ENEMY, (Action)OnFightKillEnemy); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_LET_ENEMY_RETREAT, (Action)OnFightRepelEnemy); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_HAS_SHEILD, (Action)OnFightArmourDown); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_NO_SHEILD, (Action)OnFightArmourBloodZero); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_DEAD, (Action)OnFightKnockedDown); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_ENTER_LOW_MORALE, (Action)OnFightStifleState); EventManager.Instance.Register(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_ENTER_RETREAT, (Action)OnFightRetreatState); } public void Update(float deltaTime) { if (null == LevelManager.Instance.CurrentLevel) return; if (!LevelManager.Instance.CurrentLevel.IsInBattle()) return; if (IsPlayingFightDialogue) return; if (E_DialogueType.FightUseSkill != curFightDialogueType) { if (queueWaitGetBuffDialogue.Count > 0) queueWaitGetBuffDialogue.Clear(); return; } while (queueWaitGetBuffDialogue.TryDequeue(out int roleId)) { PlayFightDiaogue(roleId, E_DialogueType.FightGetBuff); } } public void Release() { EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_ENTER_RETREAT, (Action)OnFightRetreatState); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_ENTER_LOW_MORALE, (Action)OnFightStifleState); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_DEAD, (Action)OnFightKnockedDown); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_NO_SHEILD, (Action)OnFightArmourBloodZero); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_BE_ATTACK_AND_HAS_SHEILD, (Action)OnFightArmourDown); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_LET_ENEMY_RETREAT, (Action)OnFightRepelEnemy); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_KILL_ENEMY, (Action)OnFightKillEnemy); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_GET_P_BUFF, (Action)OnFightGetBuff); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_DOWN_VEHICLE, (Action)OnFightLeaveVehicle); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_UP_VEHICLE, (Action)OnFightBoardVehicle); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_USE_SKILL, (Action)OnFightUseSkill); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_CLICK_ATTACK, (Action)OnFightAttack); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_CLICK_MOVE, (Action)OnFightMove); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_CREATED_ROLE, (Action)OnFightCreateRole); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_SELECT_UNIT, (Action)OnFightChoose); EventManager.Instance.Unregister(EventManager.EventName.INFIGHT_AUDIO_SELF_RETREAT, (Action)OnFightRetreat); EventManager.Instance.Unregister(EventManager.EventName.AllConfigLoad, OnInit); listDialogueDataTemp.Clear(); } /// /// 播放台词 /// /// /// /// /// public AudioInst PlayDiaogue(int roleId, E_DialogueType dialogueType, Action callback = null) { if (!isInit) { DebugUtil.LogError("台词数据没有初始化"); return null; } if (!dicDialogue.TryGetValue(dialogueType, out List listDialogueData)) { DebugUtil.LogWarning($"没有{dialogueType}类型的台词数据"); return null; } #region 每日重置次数,后续应该通过跨天事件触发 DateTime curTime = CommonUtils.GetCurTime(CommonUtils.E_TimeType.Server); if (curTime.Hour > TableManager.Instance.Tables.GlobalConfig.DailyChangeHour && // 超过5点 curTime.Day != dialoguePlayData.LastTime.Day) // 不是同一天 { dialoguePlayData.SetNewTime(curTime); } #endregion #region 获取需要播放的台词,根据优先级,台词类型,播放次数,条件 listDialogueDataTemp.Clear(); int minPriority = int.MaxValue; // 值越小优先级越高 foreach (DataDialogueCfg cfg in listDialogueData) { if (cfg.Priority > minPriority) // 优先级不足,排除 continue; if (cfg.DayPlayCount > 0 && // 0表示无限次 dialoguePlayData.DicCount.TryGetValue(cfg.Id, out int count) && count >= cfg.DayPlayCount) // 超过播放次数 continue; if (!IsCheckValid(roleId, cfg)) // 条件不满足,排除 continue; if (cfg.Priority < minPriority) { minPriority = cfg.Priority; listDialogueDataTemp.Clear(); } listDialogueDataTemp.Add(cfg); } #endregion if (listDialogueDataTemp.Count <= 0) { DebugUtil.LogWarning("没有满足条件要播放的台词"); return null; } #region 根据权重随机台词 DataDialogueCfg playCfg; listDialogueWeight.Clear(); if (dicDialogueWeight.TryGetValue(roleId, out Dictionary dicWeight)) { foreach (DataDialogueCfg cfg in listDialogueDataTemp) { if (dicWeight.TryGetValue(cfg.Id, out float weight)) listDialogueWeight.Add(weight); else listDialogueWeight.Add(1f); } playCfg = listDialogueDataTemp[RandomIndexByWeight()]; } else playCfg = listDialogueDataTemp[Random.Range(0, listDialogueDataTemp.Count)]; #endregion #region 设置其他的权重 if (dicWeight != null) { foreach (DataDialogueCfg cfg in listDialogueDataTemp) { if (dicWeight.TryGetValue(cfg.Id, out float weight) && weight <= 0f) dicWeight[cfg.Id] = 0.3f; // TODO 需要全局配置 else dicWeight[cfg.Id] = 1f; } dicWeight[playCfg.Id] = 0f; // 当前使用的权重置为0 } #endregion #region 记录播放次数 if (playCfg.DayPlayCount > 0) { dialoguePlayData.DicCount.TryGetValue(playCfg.Id, out int count); dialoguePlayData.DicCount[playCfg.Id] = count + 1; } #endregion int realId = roleId * RATIO + playCfg.Id; string audioKey = GetAudioKey(roleId, playCfg.Id); DebugUtil.Log($"角色id:{roleId}, 播放台词id:{playCfg.Id}, 优先级{playCfg.Priority},音频key:{audioKey}"); // 测试Log //int audioId = await AudioManager.Instance.PlayAudio(GetAudioKey(roleId, playCfg.Id)); int audioId = AudioManager.Instance.PlayAudio(GetAudioKey(roleId, playCfg.Id), AudioCriManager.EPlayType.Speak); callback?.Invoke(CommonUtils.GetLocalizeText(E_LocalizePrefix.Dialogue, realId)); return AudioManager.Instance.GetAudioByID(AudioCriManager.EPlayType.Speak, audioId); } /// /// 播放战斗台词 /// /// public void PlayFightDiaogue(int roleId, E_DialogueType dialogueType) { #region 初始化校验 if (!isInit) { DebugUtil.LogError("台词数据没有初始化"); return; } #endregion #region 战斗类型台词校验 if (!IsFightTriggerType(dialogueType)) { DebugUtil.LogError("不是战斗类型台词"); return; } #endregion #region 获取要播放的战斗台词 if (!dicDialogue.TryGetValue(dialogueType, out List listDialogueData)) { DebugUtil.LogWarning($"没有{dialogueType}类型的台词数据"); return; } #endregion #region 数据校验 if (listDialogueData.Count < 1) { DebugUtil.LogWarning($"没有{dialogueType}类型的台词数据"); return; } #endregion #region 根据权重随机台词 DataDialogueCfg playCfg; listDialogueWeight.Clear(); if (dicDialogueWeight.TryGetValue(roleId, out Dictionary dicWeight)) { foreach (DataDialogueCfg tempCfg in listDialogueData) { if (dicWeight.TryGetValue(tempCfg.Id, out float weight)) listDialogueWeight.Add(weight); else listDialogueWeight.Add(1f); } playCfg = listDialogueData[RandomIndexByWeight()]; } else playCfg = listDialogueData[Random.Range(0, listDialogueData.Count)]; #endregion #region 设置其他的权重 if (dicWeight != null) { foreach (DataDialogueCfg tempCfg in listDialogueData) { if (dicWeight.TryGetValue(tempCfg.Id, out float weight) && weight <= 0f) dicWeight[tempCfg.Id] = 0.3f; // TODO 需要全局配置 else dicWeight[tempCfg.Id] = 1f; } dicWeight[playCfg.Id] = 0f; // 当前使用的权重置为0 } #endregion #region 是否正在播放,正在播放比较优先级 if (_curFightAudioCri != null && _curFightAudioCri.IsPlaying) { if (curFightDialoguePriority <= playCfg.Priority) return; } #endregion #region 时间间隔校验 string audioKey = GetAudioKey(roleId, playCfg.Id); DateTime curTime = CommonUtils.GetCurTime(CommonUtils.E_TimeType.Server); if (dicFightPreTime.TryGetValue(audioKey, out DateTime preTime)) { TimeSpan timeSpan = curTime - preTime; if (timeSpan.TotalSeconds <= TableManager.Instance.Tables.GlobalConfig.BattleVoiceTriggerTime) return; } #endregion #region 随机概率 if (!CommonUtils.RandomBool(playCfg.TriggerProbability)) return; #endregion dicFightPreTime[audioKey] = curTime; _curFightAudioCri?.Stop(); DebugUtil.Log($"角色id:{roleId}, 播放台词id:{playCfg.Id}, 优先级{playCfg.Priority},实际音频key:{audioKey},概率:{playCfg.TriggerProbability * 100}%"); // 测试Log //int audioId = await AudioManager.Instance.PlayAudio(audioKey); int audioId = AudioManager.Instance.PlayAudio(audioKey, AudioCriManager.EPlayType.Speak); curFightDialoguePriority = playCfg.Priority; curFightDialogueType = dialogueType; _curFightAudioCri = AudioManager.Instance.GetAudioByID(AudioCriManager.EPlayType.Speak, audioId); } /// /// 获取音频key /// /// /// /// private string GetAudioKey(int roleId, int numId) { const string KEY = "Voice"; string res = $"{KEY}_{roleId}_{numId}"; return res; } /// /// 检查台词是否有效 /// /// private bool IsCheckValid(int roleId, DataDialogueCfg dataDialogueCfg) { foreach (E_DialogueType dialogueType in dataDialogueCfg.DialogueType) { if (!IsCheckValidByOne(roleId, dialogueType)) return false; } return true; } /// /// 检查个单个条件是否有效 /// /// /// /// private bool IsCheckValidByOne(int roleId, E_DialogueType dialogueType) { switch (dialogueType) { case E_DialogueType.Morning: // 上午 { DateTime dateTime = CommonUtils.GetCurTime(CommonUtils.E_TimeType.Server); return dateTime.Hour >= 6 && dateTime.Hour < 16; } case E_DialogueType.Afternoon: { DateTime dateTime = CommonUtils.GetCurTime(CommonUtils.E_TimeType.Server); return dateTime.Hour >= 16 && dateTime.Hour < 21; } case E_DialogueType.Evening: { DateTime dateTime = CommonUtils.GetCurTime(CommonUtils.E_TimeType.Server); return dateTime.Hour >= 21 && dateTime.Hour < 23; } case E_DialogueType.Midnight: { DateTime dateTime = CommonUtils.GetCurTime(CommonUtils.E_TimeType.Server); return dateTime.Hour < 6 || dateTime.Hour >= 23; } case E_DialogueType.PlayerBirthday: { DateTime kanbanbanBirth = new(2024, 4, 29); // TODO 测试玩家生日 return CommonUtils.IsBirth(kanbanbanBirth); } case E_DialogueType.KanbanbanBirthday: { CharacterDataInfo characterDataInfo = CharacterDataInfoManager.Instance.GetCharacterInfo((uint)roleId); if (null == characterDataInfo) { DebugUtil.LogError("未获取到角色"); return false; } DateTime dateTime = CommonUtils.GetTimeByTimestamp((uint)characterDataInfo.Cfg.BirthDay); return CommonUtils.IsBirth(dateTime); } case E_DialogueType.Fetters: // 好感度 { CharacterDataInfo characterDataInfo = CharacterDataInfoManager.Instance.GetCharacterInfo((uint)roleId); if (null == characterDataInfo) { DebugUtil.LogError("未获取到角色"); return false; } return characterDataInfo.IsMaxLikely(); // 是否满好感度 } case E_DialogueType.Awaken: { CharacterDataInfo characterDataInfo = CharacterDataInfoManager.Instance.GetCharacterInfo((uint)roleId); if (characterDataInfo == null) { DebugUtil.LogError("未获取到角色"); return false; } return characterDataInfo.IsMaxAwake(); // 是否满觉醒 } default: // 其余条件默认满足 return true; } } /// /// 根据权重随机一个索引 /// /// private int RandomIndexByWeight() { float totalWeight = listDialogueWeight.Sum(); float randomWeight = Random.Range(0, 1f) * totalWeight; totalWeight = 0f; for (int i = 0; i < listDialogueWeight.Count; ++i) { totalWeight += listDialogueWeight[i]; if (randomWeight <= totalWeight) return i; } return 0; } /// /// 是否触发类型 /// /// private bool IsTriggerType(E_DialogueType dialogueType) { return dialogueType switch { E_DialogueType.MainEnter or E_DialogueType.MainClick or E_DialogueType.Place or E_DialogueType.QuickClick or E_DialogueType.RaffleGet or E_DialogueType.Awakening or E_DialogueType.UpLevel or E_DialogueType.JoinTeam or E_DialogueType.LevelWin or E_DialogueType.LevelFailure => true, _ => IsFightTriggerType(dialogueType), }; } /// /// 是否战斗触发类型 /// /// /// private bool IsFightTriggerType(E_DialogueType dialogueType) { return dialogueType switch { E_DialogueType.FightEnterLevel or E_DialogueType.FightBegin or E_DialogueType.FightRetreat or E_DialogueType.FightChoose or E_DialogueType.FightMove or E_DialogueType.FightAttack or E_DialogueType.FightUseSkill or E_DialogueType.FightBoardVehicle or E_DialogueType.FightLeaveVehicle or E_DialogueType.FightGetBuff or E_DialogueType.FightKillEnemy or E_DialogueType.FightRepelEnemy or E_DialogueType.FightArmourDown or E_DialogueType.FightArmourBloodZero or E_DialogueType.FightKnockedDown or E_DialogueType.FightStifleState or E_DialogueType.FightRetreatState => true, _ => false }; } #region 回调 private void OnInit() { dicDialogue = new(); foreach (DataDialogueCfg cfg in TableManager.Instance.Tables.DialogueConfig.DataList) { foreach (E_DialogueType dialogueType in cfg.DialogueType) { if (!IsTriggerType(dialogueType)) continue; if (!dicDialogue.TryGetValue(dialogueType, out List listDialogueData)) { listDialogueData = new List(); dicDialogue[dialogueType] = listDialogueData; } listDialogueData.Add(cfg); } } isInit = true; } /// /// 角色被指挥官技能撤退 /// /// private void OnFightRetreat(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightRetreat); } /// /// 选中角色 /// /// private void OnFightChoose(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightChoose); } /// /// 上阵角色 /// /// private void OnFightCreateRole(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightEnterLevel); } /// /// 移动角色 /// /// private void OnFightMove(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightMove); } /// /// 角色攻击 /// /// private void OnFightAttack(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightAttack); } /// /// 角色使用技能 /// /// private void OnFightUseSkill(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightUseSkill); } /// /// 角色登乘载具 /// /// private void OnFightBoardVehicle(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightBoardVehicle); } /// /// 角色离开载具 /// /// private void OnFightLeaveVehicle(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightLeaveVehicle); } /// /// 角色获得增益 /// /// private void OnFightGetBuff(int roleId) { if (IsPlayingFightDialogue && E_DialogueType.FightUseSkill == curFightDialogueType) queueWaitGetBuffDialogue.Enqueue(roleId); else PlayFightDiaogue(roleId, E_DialogueType.FightGetBuff); } /// /// 角色击杀敌人 /// /// private void OnFightKillEnemy(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightKillEnemy); } /// /// 角色击溃敌人 /// /// private void OnFightRepelEnemy(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightRepelEnemy); } /// /// 角色被攻击,装甲值降低 /// /// private void OnFightArmourDown(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightArmourDown); } /// /// 角色被攻击,装甲值降低 /// /// private void OnFightArmourBloodZero(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightArmourBloodZero); } /// /// 角色被击倒 /// /// private void OnFightKnockedDown(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightKnockedDown); } /// /// 角色进入被压制状态 /// /// private void OnFightStifleState(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightStifleState); } /// /// 角色进入溃败撤退状态 /// /// private void OnFightRetreatState(int roleId) { PlayFightDiaogue(roleId, E_DialogueType.FightRetreatState); } #endregion }