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 curFightAudio;
///
/// 当前战斗台词优先级
///
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 != curFightAudio && curFightAudio.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_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_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));
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
DataDialogueCfg cfg = listDialogueData[0];
#region 是否正在播放,正在播放比较优先级
if (curFightAudio != null && curFightAudio.IsPlaying)
{
if (curFightDialoguePriority <= cfg.Priority)
return;
}
#endregion
#region 时间间隔校验
string audioKey = GetAudioKey(roleId, cfg.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(cfg.TriggerProbability))
return;
#endregion
dicFightPreTime[audioKey] = curTime;
curFightAudio?.Stop();
DebugUtil.Log($"角色id:{roleId}, 播放台词id:{cfg.Id}, 优先级{cfg.Priority},实际音频key:{audioKey},概率:{cfg.TriggerProbability * 100}%"); // 测试Log
//int audioId = await AudioManager.Instance.PlayAudio(audioKey);
int audioId = AudioManager.Instance.PlayAudio(audioKey);
curFightDialoguePriority = cfg.Priority;
curFightDialogueType = dialogueType;
curFightAudio = 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 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
}