From 290b9fe26b3f2a3e35fdcb76c9e954ab89069c3e Mon Sep 17 00:00:00 2001 From: milimoe <110188673+milimoe@users.noreply.github.com> Date: Sat, 3 May 2025 00:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E8=B1=A1=20ActionQueue=EF=BC=8C?= =?UTF-8?q?=E5=88=86=E7=A6=BB=E6=A8=A1=E5=BC=8F=20(#134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 抽象 ActionQueue,方便继承扩展;RoundRecord 添加了记录发动技能的字典;特效添加 IsSubsidiary 属性 * 修改包名 * modify --------- Co-authored-by: yeziuku --- Entity/Skill/Effect.cs | 7 +- Entity/System/RoundRecord.cs | 12 +- FunGame.Core.csproj | 2 +- Model/ActionQueue.cs | 3265 +--------------------------------- Model/GamingQueue.cs | 3049 +++++++++++++++++++++++++++++++ Model/MixGamingQueue.cs | 120 ++ Model/TeamGamingQueue.cs | 312 ++++ README.md | 4 +- 8 files changed, 3512 insertions(+), 3259 deletions(-) create mode 100644 Model/GamingQueue.cs create mode 100644 Model/MixGamingQueue.cs create mode 100644 Model/TeamGamingQueue.cs diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs index d5116ad..90a72aa 100644 --- a/Entity/Skill/Effect.cs +++ b/Entity/Skill/Effect.cs @@ -55,10 +55,15 @@ namespace Milimoe.FunGame.Core.Entity /// public virtual bool DurativeWithoutDuration { get; set; } = false; + /// + /// 是否是某个特效的附属 + /// + public virtual bool IsSubsidiary { get; set; } = false; + /// /// 是否显示在状态栏 /// - public bool ShowInStatusBar => Skill.Item is null || (Durative && Duration > 0) || DurationTurn > 0 || DurativeWithoutDuration; + public bool ShowInStatusBar => Skill.Item is null || (Durative && Duration > 0) || DurationTurn > 0 || DurativeWithoutDuration || IsSubsidiary; /// /// 特效是否生效 diff --git a/Entity/System/RoundRecord.cs b/Entity/System/RoundRecord.cs index 0bf9707..daf0ee0 100644 --- a/Entity/System/RoundRecord.cs +++ b/Entity/System/RoundRecord.cs @@ -19,7 +19,8 @@ namespace Milimoe.FunGame.Core.Entity public Dictionary IsEvaded { get; set; } = []; public Dictionary IsImmune { get; set; } = []; public Dictionary Heals { get; set; } = []; - public Dictionary> Effects { get; set; } = []; + public Dictionary Effects { get; set; } = []; + public Dictionary> ApplyEffects { get; set; } = []; public List ActorContinuousKilling { get; set; } = []; public List DeathContinuousKilling { get; set; } = []; public double CastTime { get; set; } = 0; @@ -27,6 +28,7 @@ namespace Milimoe.FunGame.Core.Entity public Dictionary RespawnCountdowns { get; set; } = []; public List Respawns { get; set; } = []; public List RoundRewards { get; set; } = []; + public List OtherMessages { get; set; } = []; public override string ToString() { @@ -37,6 +39,12 @@ namespace Milimoe.FunGame.Core.Entity { builder.AppendLine($"[ {Actor} ] 回合奖励 -> {string.Join(" / ", RoundRewards.Select(s => s.Description)).Trim()}"); } + + if (Effects.Count > 0) + { + builder.AppendLine($"[ {Actor} ] 发动了技能:{string.Join(",", Effects.Where(kv => kv.Key == Actor).Select(e => e.Value.Name))}"); + } + if (ActionType == CharacterActionType.NormalAttack || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill) { if (ActionType == CharacterActionType.NormalAttack) @@ -110,7 +118,7 @@ namespace Milimoe.FunGame.Core.Entity { hasHeal = $"治疗:{heals:0.##}"; } - if (Effects.TryGetValue(target, out List? effectTypes) && effectTypes != null) + if (ApplyEffects.TryGetValue(target, out List? effectTypes) && effectTypes != null) { hasEffect = $"施加:{string.Join(" + ", effectTypes.Select(SkillSet.GetEffectTypeName))}"; } diff --git a/FunGame.Core.csproj b/FunGame.Core.csproj index 0e05213..3d619d4 100644 --- a/FunGame.Core.csproj +++ b/FunGame.Core.csproj @@ -19,7 +19,7 @@ True Project Redbud and Contributors - ProjectRedbud.$(AssemblyName) + $(AssemblyName) FunGame.Core: A C#.NET library for turn-based games. game;turn-based;server;framework;dotnet;csharp;gamedev diff --git a/Model/ActionQueue.cs b/Model/ActionQueue.cs index 7d92269..c2c6361 100644 --- a/Model/ActionQueue.cs +++ b/Model/ActionQueue.cs @@ -1,3271 +1,30 @@ -using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; -using Milimoe.FunGame.Core.Interface.Base; -using Milimoe.FunGame.Core.Interface.Entity; using Milimoe.FunGame.Core.Library.Constant; namespace Milimoe.FunGame.Core.Model { /// - /// 行动顺序表 + /// 行动顺序表管理器,对 的封装 /// - public class ActionQueue : IGamingQueue + public class ActionQueue : GamingQueue { - /// - /// 使用的游戏平衡常数 - /// - public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant; - - /// - /// 用于文本输出 - /// - public Action WriteLine { get; } - - /// - /// 原始的角色字典 - /// - public Dictionary Original => _original; - - /// - /// 当前的行动顺序 - /// - public List Queue => _queue; - - /// - /// 硬直时间表 - /// - public Dictionary HardnessTime => _hardnessTimes; - - /// - /// 当前已死亡的角色顺序(第一个是最早死的) - /// - public List Eliminated => _eliminated; - - /// - /// 当前团灭的团队顺序(第一个是最早死的) - /// - public List EliminatedTeams => _eliminatedTeams; - - /// - /// 角色是否在 AI 控制下 - /// - public HashSet CharactersInAI => _charactersInAI; - - /// - /// 角色数据 - /// - public Dictionary CharacterStatistics => _stats; - - /// - /// 团队及其成员 - /// - public Dictionary Teams => _teams; - - /// - /// 游戏运行的时间 - /// - public double TotalTime { get; set; } = 0; - - /// - /// 游戏运行的回合 - /// 对于某角色而言,在其行动的回合叫 Turn,而所有角色行动的回合,都称之为 Round。 - /// - public int TotalRound { get; set; } = 0; - - /// - /// 第一滴血获得者 - /// - public Character? FirstKiller { get; set; } = null; - - /// - /// 最大复活次数 - /// 0:不复活 / -1:无限复活 - /// - public int MaxRespawnTimes { get; set; } = 0; - - /// - /// 复活次数统计 - /// - public Dictionary RespawnTimes => _respawnTimes; - - /// - /// 复活倒计时 - /// - public Dictionary RespawnCountdown => _respawnCountdown; - - /// - /// 最大获胜积分 [ 适用于团队模式 ] - /// 设置一个大于0的数以启用 - /// - public int MaxScoreToWin { get; set; } = 0; - - /// - /// 上回合记录 - /// - public RoundRecord LastRound { get; set; } = new(0); - - /// - /// 所有回合的记录 - /// - public List Rounds { get; } = []; - - /// - /// 回合奖励 - /// - public Dictionary> RoundRewards => _roundRewards; - - /// - /// 自定义数据 - /// - public Dictionary CustomData { get; } = []; - - /// - /// 原始的角色字典 - /// - protected readonly Dictionary _original = []; - - /// - /// 当前的行动顺序 - /// - protected readonly List _queue = []; - - /// - /// 当前已死亡的角色顺序(第一个是最早死的) - /// - protected readonly List _eliminated = []; - - /// - /// 当前团灭的团队顺序(第一个是最早死的) - /// - protected readonly List _eliminatedTeams = []; - - /// - /// 角色是否在 AI 控制下 - /// - protected readonly HashSet _charactersInAI = []; - - /// - /// 硬直时间表 - /// - protected readonly Dictionary _hardnessTimes = []; - - /// - /// 角色正在吟唱的技能(通常是魔法) - /// - protected readonly Dictionary _castingSkills = []; - - /// - /// 角色预释放的爆发技 - /// - protected readonly Dictionary _castingSuperSkills = []; - - /// - /// 角色即将使用的物品 - /// - protected readonly Dictionary _willUseItems = []; - - /// - /// 角色目前赚取的金钱 - /// - protected readonly Dictionary _earnedMoney = []; - - /// - /// 角色最高连杀数 - /// - protected readonly Dictionary _maxContinuousKilling = []; - - /// - /// 角色目前的连杀数 - /// - protected readonly Dictionary _continuousKilling = []; - - /// - /// 角色被插队次数 - /// - protected readonly Dictionary _cutCount = []; - - /// - /// 助攻伤害 - /// - protected readonly Dictionary _assistDetail = []; - - /// - /// 角色数据 - /// - protected readonly Dictionary _stats = []; - - /// - /// 团队及其成员 - /// - protected readonly Dictionary _teams = []; - - /// - /// 复活次数统计 - /// - protected readonly Dictionary _respawnTimes = []; - - /// - /// 复活倒计时 - /// - protected readonly Dictionary _respawnCountdown = []; - - /// - /// 当前回合死亡角色 - /// - protected readonly List _roundDeaths = []; - - /// - /// 回合奖励 - /// - protected readonly Dictionary> _roundRewards = []; - - /// - /// 回合奖励的特效工厂 - /// - protected Func> _factoryRoundRewardEffects = id => []; - - /// - /// 是否是团队模式 - /// - protected bool _isTeamMode = false; - - /// - /// 游戏是否结束 - /// - protected bool _isGameEnd = false; - - /// - /// 新建一个行动顺序表 - /// - /// 是否是团队模式 - /// 用于文本输出 - public ActionQueue(bool isTeamMdoe = false, Action? writer = null) - { - _isTeamMode = isTeamMdoe; - if (writer != null) - { - WriteLine = writer; - } - WriteLine ??= new Action(Console.WriteLine); - } - - /// - /// 新建一个行动顺序表并初始化 - /// - /// 参与本次游戏的角色列表 - /// 是否是团队模式 - /// 用于文本输出 - public ActionQueue(List characters, bool isTeamMdoe = false, Action? writer = null) - { - _isTeamMode = isTeamMdoe; - if (writer != null) - { - WriteLine = writer; - } - WriteLine ??= new Action(Console.WriteLine); - InitCharacterQueue(characters); - } - - /// - /// 初始化行动顺序表 - /// - /// - public void InitCharacterQueue(List characters) - { - // 保存原始的角色信息。用于复活时还原状态 - foreach (Character character in characters) - { - Character original = character.Copy(); - original.Guid = Guid.NewGuid(); - character.Guid = original.Guid; - _original.Add(original.Guid, original); - } - - // 初始排序:按速度排序 - List> groupedBySpeed = [.. characters - .GroupBy(c => c.SPD) - .OrderByDescending(g => g.Key)]; - - Random random = new(); - - foreach (IGrouping group in groupedBySpeed) - { - if (group.Count() == 1) - { - // 如果只有一个角色,直接加入队列 - Character character = group.First(); - AddCharacter(character, Calculation.Round2Digits(_queue.Count * 0.1), false); - _assistDetail.Add(character, new AssistDetail(character, characters.Where(c => c != character))); - _stats.Add(character, new()); - // 初始化技能 - foreach (Skill skill in character.Skills) - { - skill.OnSkillGained(this); - } - } - else - { - // 如果有多个角色,进行先行决定 - List sortedList = [.. group]; - - while (sortedList.Count > 0) - { - Character? selectedCharacter = null; - bool decided = false; - if (sortedList.Count == 1) - { - selectedCharacter = sortedList[0]; - decided = true; - } - - while (!decided) - { - // 每个角色进行两次随机数抽取 - var randomNumbers = sortedList.Select(c => new - { - Character = c, - FirstRoll = random.Next(1, 21), - SecondRoll = random.Next(1, 21) - }).ToList(); - - randomNumbers.ForEach(a => WriteLine(a.Character.Name + ": " + a.FirstRoll + " / " + a.SecondRoll)); - - // 找到两次都大于其他角色的角色 - int maxFirstRoll = randomNumbers.Max(r => r.FirstRoll); - int maxSecondRoll = randomNumbers.Max(r => r.SecondRoll); - - var candidates = randomNumbers - .Where(r => r.FirstRoll == maxFirstRoll && r.SecondRoll == maxSecondRoll) - .ToList(); - - if (candidates.Count == 1) - { - selectedCharacter = candidates.First().Character; - decided = true; - } - } - - // 将决定好的角色加入顺序表 - if (selectedCharacter != null) - { - AddCharacter(selectedCharacter, Calculation.Round2Digits(_queue.Count * 0.1), false); - _assistDetail.Add(selectedCharacter, new AssistDetail(selectedCharacter, characters.Where(c => c != selectedCharacter))); - _stats.Add(selectedCharacter, new()); - // 初始化技能 - foreach (Skill skill in selectedCharacter.Skills) - { - skill.OnSkillGained(this); - } - WriteLine("decided: " + selectedCharacter.Name + "\r\n"); - sortedList.Remove(selectedCharacter); - } - } - } - } - } - - /// - /// 清空行动队列 - /// - public void ClearQueue() - { - FirstKiller = null; - CustomData.Clear(); - _original.Clear(); - _queue.Clear(); - _hardnessTimes.Clear(); - _assistDetail.Clear(); - _stats.Clear(); - _cutCount.Clear(); - _castingSkills.Clear(); - _castingSuperSkills.Clear(); - _willUseItems.Clear(); - _maxContinuousKilling.Clear(); - _continuousKilling.Clear(); - _earnedMoney.Clear(); - _eliminated.Clear(); - _charactersInAI.Clear(); - } - - /// - /// 添加一个团队 - /// - /// - /// - public void AddTeam(string teamName, IEnumerable characters) - { - if (teamName != "" && characters.Any()) - { - _teams.Add(teamName, new(teamName, characters)); - } - } - - /// - /// 获取角色的团队 - /// - /// - public Team? GetTeam(Character character) - { - foreach (Team team in _teams.Values) - { - if (team.IsOnThisTeam(character)) - { - return team; - } - } - return null; - } - - /// - /// 从已淘汰的团队中获取角色的团队 - /// - /// - public Team? GetTeamFromEliminated(Character character) - { - foreach (Team team in _eliminatedTeams) - { - if (team.IsOnThisTeam(character)) - { - return team; - } - } - return null; - } - - /// - /// 获取某角色的团队成员 - /// - /// - public List GetTeammates(Character character) - { - foreach (string team in _teams.Keys) - { - if (_teams[team].IsOnThisTeam(character)) - { - return _teams[team].GetTeammates(character); - } - } - return []; - } - - /// - /// 将角色加入行动顺序表 - /// - /// - /// - /// - public void AddCharacter(Character character, double hardnessTime, bool isCheckProtected = true) - { - // 确保角色不在队列中 - _queue.RemoveAll(c => c == character); - - // 插队机制:按硬直时间排序 - int insertIndex = _queue.FindIndex(c => _hardnessTimes[c] > hardnessTime); - - if (isCheckProtected) - { - // 查找保护条件 被插队超过此次数便能获得插队补偿 即行动保护 - int countProtected = Math.Max(5, _queue.Count); - - // 查找队列中是否有满足插队补偿条件的角色(最后一个) - var list = _queue - .Select((c, index) => new { Character = c, Index = index }) - .Where(x => _cutCount.ContainsKey(x.Character) && _cutCount[x.Character] >= countProtected); - - // 如果没有找到满足条件的角色,返回 -1 - int protectIndex = list.Select(x => x.Index).LastOrDefault(-1); - - if (protectIndex != -1) - { - // 获取最后一个符合条件的角色 - Character lastProtectedCharacter = list.Last().Character; - double lastProtectedHardnessTime = _hardnessTimes[lastProtectedCharacter]; - - // 查找与最后一个受保护角色相同硬直时间的其他角色 - var sameHardnessList = _queue - .Select((c, index) => new { Character = c, Index = index }) - .Where(x => _hardnessTimes[x.Character] == lastProtectedHardnessTime && x.Index > protectIndex); - - // 如果找到了相同硬直时间的角色,更新 protectIndex 为它们中最后一个的索引 - if (sameHardnessList.Any()) - { - protectIndex = sameHardnessList.Select(x => x.Index).Last(); - } - - // 判断是否需要插入到受保护角色的后面 - if (insertIndex != -1 && insertIndex <= protectIndex) - { - // 如果按硬直时间插入的位置在受保护角色之前或相同,则插入到受保护角色的后面一位 - insertIndex = protectIndex + 1; - hardnessTime = lastProtectedHardnessTime; - - // 列出受保护角色的名单 - WriteLine($"由于 [ {string.Join(" ],[ ", list.Select(x => x.Character))} ] 受到行动保护,因此角色 [ {character} ] 将插入至顺序表第 {insertIndex + 1} 位。"); - } - } - } - - // 如果插入索引无效(为-1 或 大于等于队列长度),则添加到队列尾部 - if (insertIndex == -1 || insertIndex >= _queue.Count) - { - _queue.Add(character); - } - else - { - _queue.Insert(insertIndex, character); - } - _hardnessTimes[character] = hardnessTime; - - // 为所有被插队的角色增加 _cutCount - if (isCheckProtected && insertIndex != -1 && insertIndex < _queue.Count) - { - for (int i = insertIndex + 1; i < _queue.Count; i++) - { - Character queuedCharacter = _queue[i]; - if (!_cutCount.TryAdd(queuedCharacter, 1)) - { - _cutCount[queuedCharacter] += 1; - } - } - } - } - - /// - /// 从行动顺序表取出第一个角色 - /// - /// - public async Task NextCharacterAsync() - { - if (_queue.Count == 0) return null; - - // 硬直时间为 0 的角色或预释放爆发技的角色先行动,取第一个 - Character? character = _queue.FirstOrDefault(c => c.CharacterState == CharacterState.PreCastSuperSkill); - if (character is null) - { - Character temp = _queue[0]; - if (_hardnessTimes[temp] == 0) - { - character = temp; - } - } - else - { - _hardnessTimes[character] = 0; - } - - if (character != null) - { - _queue.Remove(character); - _cutCount.Remove(character); - - // 进入下一回合 - TotalRound++; - LastRound = new(TotalRound); - Rounds.Add(LastRound); - - return character; - } - else - { - await TimeLapse(); - return await NextCharacterAsync(); - } - } - - /// - /// 显示当前所有角色的状态和硬直时间 - /// - public void DisplayQueue() - { - WriteLine("==== 角色状态 ===="); - foreach (Character c in _queue) - { - WriteLine(c.GetInBattleInfo(_hardnessTimes[c])); - } - } - - /// - /// 回合开始前触发 - /// - /// - public virtual async Task BeforeTurnAsync(Character character) - { - return await Task.FromResult(true); - } - - /// - /// 角色 的回合进行中 - /// - /// - /// 是否结束游戏 - public async Task ProcessTurnAsync(Character character) - { - LastRound.Actor = character; - _roundDeaths.Clear(); - - if (!await BeforeTurnAsync(character)) - { - return _isGameEnd; - } - - // 获取回合奖励 - List rewards = GetRoundRewards(TotalRound, character); - - // 基础硬直时间 - double baseTime = 10; - bool isCheckProtected = true; - - // 队友列表 - List teammates = [.. GetTeammates(character).Where(_queue.Contains)]; - - // 敌人列表 - List enemys = [.. _queue.Where(c => c != character && !c.IsUnselectable && !teammates.Contains(c))]; - - // 技能列表 - List skills = [.. character.Skills.Where(s => s.Level > 0 && s.SkillType != SkillType.Passive && s.Enable && !s.IsInEffect && s.CurrentCD == 0 && - ((s.SkillType == SkillType.SuperSkill || s.SkillType == SkillType.Skill) && s.RealEPCost <= character.EP || s.SkillType == SkillType.Magic && s.RealMPCost <= character.MP))]; - - // 物品列表 - List items = [.. character.Items.Where(i => i.IsActive && i.Skills.Active != null && i.Enable && i.IsInGameItem && - i.Skills.Active.SkillType == SkillType.Item && i.Skills.Active.Enable && !i.Skills.Active.IsInEffect && i.Skills.Active.CurrentCD == 0 && i.Skills.Active.RealMPCost <= character.MP && i.Skills.Active.RealEPCost <= character.EP)]; - - // 回合开始事件,允许事件返回 false 接管回合操作 - // 如果事件全程接管回合操作,需要注意触发特效 - if (!await OnTurnStartAsync(character, enemys, teammates, skills, items)) - { - return _isGameEnd; - } - - foreach (Skill skillTurnStart in skills) - { - skillTurnStart.OnTurnStart(character, enemys, teammates, skills, items); - } - - List effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.OnTurnStart(character, enemys, teammates, skills, items); - } - - // 此变量用于在取消选择时,能够重新行动 - bool decided = false; - // 最大取消次数 - int cancelTimes = 3; - - // 作出了什么行动 - CharacterActionType type = CharacterActionType.None; - - // 循环条件: - // AI 控制下:未决策、取消次数大于0 - // 手动控制下:未决策 - bool isAI = _charactersInAI.Contains(character); - while (!decided && (!isAI || cancelTimes > 0)) - { - type = CharacterActionType.None; - - // 是否能使用物品和释放技能 - bool canUseItem = items.Count > 0; - bool canCastSkill = skills.Count > 0; - - // 使用物品和释放技能、使用普通攻击的概率 - double pUseItem = 0.33; - double pCastSkill = 0.33; - double pNormalAttack = 0.34; - - cancelTimes--; - // 不允许在吟唱和预释放状态下,修改角色的行动 - if (character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill) - { - CharacterActionType actionTypeTemp = CharacterActionType.None; - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - actionTypeTemp = effect.AlterActionTypeBeforeAction(character, character.CharacterState, ref canUseItem, ref canCastSkill, ref pUseItem, ref pCastSkill, ref pNormalAttack); - } - if (actionTypeTemp != CharacterActionType.None && actionTypeTemp != CharacterActionType.CastSkill && actionTypeTemp != CharacterActionType.CastSuperSkill) - { - type = actionTypeTemp; - } - } - - if (type == CharacterActionType.None) - { - if (character.CharacterState != CharacterState.NotActionable && character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill) - { - // 根据角色状态,设置一些参数 - if (character.CharacterState == CharacterState.Actionable) - { - // 可以任意行动 - if (canUseItem && canCastSkill) - { - // 不做任何处理 - } - else if (canUseItem && !canCastSkill) - { - pCastSkill = 0; - } - else if (!canUseItem && canCastSkill) - { - pUseItem = 0; - } - else - { - pUseItem = 0; - pCastSkill = 0; - } - } - else if (character.CharacterState == CharacterState.ActionRestricted) - { - // 行动受限,只能使用特殊物品 - if (canUseItem) - { - pCastSkill = 0; - pNormalAttack = 0; - } - else - { - pUseItem = 0; - pCastSkill = 0; - pNormalAttack = 0; - } - } - else if (character.CharacterState == CharacterState.BattleRestricted) - { - // 战斗不能,只能使用物品 - enemys.Clear(); - teammates.Clear(); - skills.Clear(); - if (canUseItem) - { - pCastSkill = 0; - pNormalAttack = 0; - } - else - { - pUseItem = 0; - pCastSkill = 0; - pNormalAttack = 0; - } - } - else if (character.CharacterState == CharacterState.SkillRestricted) - { - // 技能受限,无法使用技能,可以普通攻击,可以使用物品 - skills.Clear(); - if (canUseItem) - { - pCastSkill = 0; - } - else - { - pUseItem = 0; - pCastSkill = 0; - } - } - else if (character.CharacterState == CharacterState.AttackRestricted) - { - // 攻击受限,无法普通攻击,可以使用技能,可以使用物品 - pNormalAttack = 0; - if (!canUseItem) - { - pUseItem = 0; - } - } - - // 模组可以通过此事件来决定角色的行动 - type = await OnDecideActionAsync(character, enemys, teammates, skills, items); - // 若事件未完成决策,则将通过概率对角色进行自动化决策 - if (type == CharacterActionType.None) - { - type = GetActionType(pUseItem, pCastSkill, pNormalAttack); - } - - _stats[character].ActionTurn += 1; - } - else if (character.CharacterState == CharacterState.Casting) - { - // 如果角色上一次吟唱了魔法,这次的行动则是结算这个魔法 - type = CharacterActionType.CastSkill; - } - else if (character.CharacterState == CharacterState.PreCastSuperSkill) - { - // 角色使用回合外爆发技插队 - type = CharacterActionType.CastSuperSkill; - } - else - { - // 完全行动不能 - type = CharacterActionType.None; - } - } - - Dictionary continuousKillingTemp = new(_continuousKilling); - Dictionary earnedMoneyTemp = new(_earnedMoney); - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterSelectListBeforeAction(character, enemys, teammates, skills, continuousKillingTemp, earnedMoneyTemp); - } - - if (type == CharacterActionType.NormalAttack) - { - if (character.CharacterState == CharacterState.NotActionable || - character.CharacterState == CharacterState.ActionRestricted || - character.CharacterState == CharacterState.BattleRestricted || - character.CharacterState == CharacterState.AttackRestricted) - { - WriteLine($"角色 [ {character} ] 状态为:{CharacterSet.GetCharacterState(character.CharacterState)},无法使用普通攻击!"); - } - else - { - // 使用普通攻击逻辑 - List targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(character)) - { - // 如果没有选取目标,且角色在 AI 控制下,则随机选取目标 - if (enemys.Count > character.NormalAttack.CanSelectTargetCount) - targets = [.. enemys.OrderBy(o => Random.Shared.Next(enemys.Count)).Take(character.NormalAttack.CanSelectTargetCount)]; - else - targets = [.. enemys]; - } - if (targets.Count > 0) - { - LastRound.Targets = [.. targets]; - decided = true; - - await OnCharacterNormalAttackAsync(character, targets); - - character.NormalAttack.Attack(this, character, targets); - baseTime = character.NormalAttack.RealHardnessTime; - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterNormalAttack(character, ref baseTime, ref isCheckProtected); - } - } - } - } - else if (type == CharacterActionType.PreCastSkill) - { - // 预使用技能,即开始吟唱逻辑 - Skill? skill = await OnSelectSkillAsync(character, skills); - if (skill is null && _charactersInAI.Contains(character) && skills.Count > 0) - { - skill = skills[Random.Shared.Next(skills.Count)]; - } - if (skill != null) - { - // 吟唱前需要先选取目标 - if (skill.SkillType == SkillType.Magic) - { - List targets = await SelectTargetsAsync(character, skill, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0) - { - // 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标 - targets = [enemys[Random.Shared.Next(enemys.Count)]]; - } - if (targets.Count > 0) - { - // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); - - if (targets.Count > 0) - { - LastRound.Targets = [.. targets]; - decided = true; - - character.CharacterState = CharacterState.Casting; - SkillTarget skillTarget = new(skill, targets); - await OnCharacterPreCastSkillAsync(character, skillTarget); - - _castingSkills[character] = skillTarget; - baseTime = skill.RealCastTime; - skill.OnSkillCasting(this, character, targets); - } - } - } - else - { - // 只有魔法需要吟唱,战技和爆发技直接释放 - if (CheckCanCast(character, skill, out double cost)) - { - List targets = await SelectTargetsAsync(character, skill, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0) - { - // 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标 - targets = [enemys[Random.Shared.Next(enemys.Count)]]; - } - if (targets.Count > 0) - { - // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); - - if (targets.Count > 0) - { - LastRound.Targets = [.. targets]; - decided = true; - - SkillTarget skillTarget = new(skill, targets); - await OnCharacterPreCastSkillAsync(character, skillTarget); - - skill.OnSkillCasting(this, character, targets); - skill.BeforeSkillCasted(); - - character.EP -= cost; - baseTime = skill.RealHardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillCost = $"{-cost:0.##} EP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - - await OnCharacterCastSkillAsync(character, skillTarget, cost); - - skill.OnSkillCasted(this, character, targets); - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); - } - } - } - } - } - LastRound.Skill = skill; - } - } - else if (type == CharacterActionType.CastSkill) - { - if (_castingSkills.TryGetValue(character, out SkillTarget skillTarget)) - { - // 使用技能逻辑,结束吟唱状态 - character.CharacterState = CharacterState.Actionable; - character.UpdateCharacterState(); - Skill skill = skillTarget.Skill; - List targets = [.. skillTarget.Targets.Where(c => !c.IsUnselectable)]; - - // 判断是否能够释放技能 - if (targets.Count > 0 && CheckCanCast(character, skill, out double cost)) - { - // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); - - if (targets.Count > 0) - { - decided = true; - LastRound.Targets = [.. targets]; - LastRound.Skill = skill; - _castingSkills.Remove(character); - - skill.BeforeSkillCasted(); - - character.MP -= cost; - baseTime = skill.RealHardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillCost = $"{-cost:0.##} MP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - - await OnCharacterCastSkillAsync(character, skillTarget, cost); - - skill.OnSkillCasted(this, character, targets); - } - } - else - { - WriteLine($"[ {character} ] 放弃释放技能!"); - // 放弃释放技能会获得3的硬直时间 - baseTime = 3; - } - - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); - } - } - else - { - // 原吟唱的技能丢失(被打断或者被取消),允许角色再次决策 - character.CharacterState = CharacterState.Actionable; - character.UpdateCharacterState(); - } - } - else if (type == CharacterActionType.CastSuperSkill) - { - decided = true; - // 结束预释放爆发技的状态 - character.CharacterState = CharacterState.Actionable; - character.UpdateCharacterState(); - Skill skill = _castingSuperSkills[character]; - LastRound.Skill = skill; - _castingSuperSkills.Remove(character); - - // 判断是否能够释放技能 - if (CheckCanCast(character, skill, out double cost)) - { - // 预释放的爆发技不可取消 - List targets = await SelectTargetsAsync(character, skill, enemys, teammates); - // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill); - LastRound.Targets = [.. targets]; - - skill.BeforeSkillCasted(); - - character.EP -= cost; - baseTime = skill.RealHardnessTime; - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - LastRound.SkillCost = $"{-cost:0.##} EP"; - WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量值,释放了爆发技 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); - - SkillTarget skillTarget = new(skill, targets); - await OnCharacterCastSkillAsync(character, skillTarget, cost); - - skill.OnSkillCasted(this, character, targets); - } - else - { - WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!"); - // 放弃释放技能会获得3的硬直时间 - baseTime = 3; - } - - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); - } - } - else if (type == CharacterActionType.UseItem) - { - // 使用物品逻辑 - Item? item = await OnSelectItemAsync(character, items); - if (item is null && _charactersInAI.Contains(character) && items.Count > 0) - { - // AI 控制下随机选取一个物品 - item = items[Random.Shared.Next(items.Count)]; - } - if (item != null && item.Skills.Active != null) - { - Skill skill = item.Skills.Active; - if (await UseItemAsync(item, character, enemys, teammates)) - { - decided = true; - LastRound.Item = item; - baseTime = skill.RealHardnessTime; - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); - } - } - } - } - else if (type == CharacterActionType.EndTurn) - { - decided = true; - WriteLine($"[ {character} ] 结束了回合!"); - await OnCharacterDoNothingAsync(character); - } - else - { - decided = true; - WriteLine($"[ {character} ] 完全行动不能!"); - } - } - - if (type == CharacterActionType.None) - { - WriteLine($"[ {character} ] 放弃了行动!"); - await OnCharacterGiveUpAsync(character); - } - - LastRound.ActionType = type; - - // 如果目标都是队友,会考虑非伤害型助攻 - Team? team = GetTeam(character); - if (team != null) - { - SetNotDamageAssistTime(character, LastRound.Targets.Where(team.IsOnThisTeam)); - } - - // 统一在回合结束时处理角色的死亡 - await ProcessCharacterDeathAsync(character); - - // 移除回合奖励 - RemoveRoundRewards(TotalRound, character, rewards); - - if (_isGameEnd) - { - // 回合结束事件 - await OnTurnEndAsync(character); - - await AfterTurnAsync(character); - - return _isGameEnd; - } - - // 减少硬直时间 - double newHardnessTime = baseTime; - if (character.CharacterState != CharacterState.Casting) - { - newHardnessTime = Calculation.Round2Digits(baseTime); - WriteLine($"[ {character} ] 回合结束,获得硬直时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}"); - } - else - { - newHardnessTime = Calculation.Round2Digits(baseTime); - WriteLine($"[ {character} ] 进行吟唱,持续时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}"); - LastRound.CastTime = newHardnessTime; - } - AddCharacter(character, newHardnessTime, isCheckProtected); - await OnQueueUpdatedAsync(_queue, character, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。"); - LastRound.HardnessTime = newHardnessTime; - - effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.OnTurnEnd(character); - - // 自身被动不会考虑 - if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive) - { - continue; - } - - // 在回合结束时移除技能持续回合,而不是等时间流逝 - if (!effect.Durative && effect.DurationTurn > 0) - { - // 按回合移除特效 - effect.RemainDurationTurn--; - if (effect.RemainDurationTurn <= 0) - { - effect.RemainDurationTurn = 0; - character.Effects.Remove(effect); - effect.OnEffectLost(character); - } - } - } - - // 有人想要插队吗? - await WillPreCastSuperSkill(); - - // 回合结束事件 - await OnTurnEndAsync(character); - - await AfterTurnAsync(character); - - WriteLine(""); - return _isGameEnd; - } - - /// - /// 回合结束后触发 - /// - /// - public virtual async Task AfterTurnAsync(Character character) - { - await Task.CompletedTask; - } - - /// - /// 时间进行流逝,减少硬直时间,减少技能冷却时间,角色也会因此回复状态 - /// - /// 流逝的时间 - public async Task TimeLapse() - { - if (_queue.Count == 0) return 0; - - // 获取第一个角色的硬直时间 - double timeToReduce = _hardnessTimes[_queue[0]]; - // 如果复活时间更快,应该先流逝复活时间 - if (_respawnCountdown.Count != 0) - { - double timeToRespawn = _respawnCountdown.Values.Min(); - if (timeToRespawn < timeToReduce) - { - timeToReduce = Calculation.Round2Digits(timeToRespawn); - } - } - - TotalTime = Calculation.Round2Digits(TotalTime + timeToReduce); - WriteLine("时间流逝:" + timeToReduce); - - foreach (Character character in _queue) - { - // 减少所有角色的硬直时间 - _hardnessTimes[character] = Calculation.Round2Digits(_hardnessTimes[character] - timeToReduce); - - // 统计 - _stats[character].LiveRound += 1; - _stats[character].LiveTime += timeToReduce; - _stats[character].DamagePerRound = _stats[character].TotalDamage / _stats[character].LiveRound; - _stats[character].DamagePerTurn = _stats[character].TotalDamage / _stats[character].ActionTurn; - _stats[character].DamagePerSecond = _stats[character].TotalDamage / _stats[character].LiveTime; - - // 回血回蓝 - double recoveryHP = character.HR * timeToReduce; - double recoveryMP = character.MR * timeToReduce; - double needHP = character.MaxHP - character.HP; - double needMP = character.MaxMP - character.MP; - double reallyReHP = needHP >= recoveryHP ? recoveryHP : needHP; - double reallyReMP = needMP >= recoveryMP ? recoveryMP : needMP; - if (reallyReHP > 0 && reallyReMP > 0) - { - character.HP += reallyReHP; - character.MP += reallyReMP; - WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); - } - else - { - if (reallyReHP > 0) - { - character.HP += reallyReHP; - WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 当前能量:{character.EP:0.##}"); - } - if (reallyReMP > 0) - { - character.MP += reallyReMP; - WriteLine($"角色 {character.Name} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); - } - } - - // 减少所有技能的冷却时间 - foreach (Skill skill in character.Skills) - { - skill.CurrentCD -= timeToReduce; - if (skill.CurrentCD <= 0) - { - skill.CurrentCD = 0; - skill.Enable = true; - } - } - - // 移除到时间的特效 - List effects = [.. character.Effects]; - foreach (Effect effect in effects) - { - if (effect.IsBeingTemporaryDispelled) - { - effect.IsBeingTemporaryDispelled = false; - effect.OnEffectGained(character); - } - - if (effect.Level == 0) - { - character.Effects.Remove(effect); - continue; - } - - if (!effect.Durative) - { - // 防止特效在时间流逝后,持续时间已结束还能继续生效的情况 - effect.OnTimeElapsed(character, timeToReduce); - } - - // 自身被动不会考虑 - if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive) - { - continue; - } - - // 统计控制时长 - if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable) - { - _stats[effect.Source].ControlTime += timeToReduce; - SetNotDamageAssistTime(effect.Source, character); - } - - if (effect.Durative) - { - effect.RemainDuration -= timeToReduce; - if (effect.RemainDuration <= 0) - { - effect.RemainDuration = 0; - character.Effects.Remove(effect); - effect.OnEffectLost(character); - } - else - { - effect.OnTimeElapsed(character, timeToReduce); - } - } - } - } - - // 减少复活倒计时 - foreach (Character character in _respawnCountdown.Keys) - { - _respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce); - if (_respawnCountdown[character] <= 0) - { - await SetCharacterRespawn(character); - } - } - - WriteLine("\r\n"); - - return timeToReduce; - } - - /// - /// 对敌人造成伤害 - /// - /// - /// - /// - /// - /// - /// - /// - public async Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage = false, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal) - { - // 如果敌人在结算伤害之前就已经死亡,将不会继续下去 - if (enemy.HP <= 0) - { - return; - } - - if (!LastRound.IsCritical.TryAdd(enemy, damageResult == DamageResult.Critical) && damageResult == DamageResult.Critical) - { - LastRound.IsCritical[enemy] = true; - } - - List characters = [actor, enemy]; - bool isEvaded = damageResult == DamageResult.Evaded; - Dictionary totalDamageBonus = []; - List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - double damageBonus = effect.AlterActualDamageAfterCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult, ref isEvaded, totalDamageBonus); - totalDamageBonus[effect] = damageBonus; - if (isEvaded) - { - damageResult = DamageResult.Evaded; - } - } - damage += totalDamageBonus.Sum(kv => kv.Value); - - // 闪避了就没伤害了 - if (damageResult != DamageResult.Evaded) - { - // 计算伤害免疫 - bool ignore = false; - // 技能免疫无法免疫普通攻击,但是魔法免疫和物理免疫可以 - bool isImmune = (isNormalAttack && (enemy.ImmuneType == ImmuneType.All || enemy.ImmuneType == ImmuneType.Physical || enemy.ImmuneType == ImmuneType.Magical)) || - (!isNormalAttack && (enemy.ImmuneType == ImmuneType.All || enemy.ImmuneType == ImmuneType.Physical || enemy.ImmuneType == ImmuneType.Magical || enemy.ImmuneType == ImmuneType.Skilled)); - if (isImmune) - { - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - if (isNormalAttack) - { - if (actor.NormalAttack.IgnoreImmune == ImmuneType.All || - (!isMagicDamage && actor.NormalAttack.IgnoreImmune == ImmuneType.Physical) || - (isMagicDamage && actor.NormalAttack.IgnoreImmune == ImmuneType.Magical) || - !effect.OnDamageImmuneCheck(actor, enemy, isNormalAttack, isMagicDamage, magicType, damage)) - { - ignore = true; - } - } - else - { - if (!effect.OnDamageImmuneCheck(actor, enemy, isNormalAttack, isMagicDamage, magicType, damage)) - { - ignore = true; - } - } - } - } - - if (ignore) - { - // 无视免疫 - isImmune = false; - } - - if (isImmune) - { - // 免疫 - LastRound.IsImmune[enemy] = true; - WriteLine($"[ {enemy} ] 免疫了此伤害!"); - } - else - { - if (damage < 0) damage = 0; - - // 检查护盾 - double shield = enemy.Shield[isMagicDamage, magicType]; - string shieldMsg = ""; - if (shield > 0) - { - bool change = false; - - // 看特效有没有特殊护盾逻辑 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - if (!effect.BeforeShieldCalculation(actor, enemy, isMagicDamage, magicType, damage, shield, ref shieldMsg)) - { - change = true; - } - } - - if (!change) - { - double remain = shield - damage; - if (remain < 0) - { - remain = Math.Abs(remain); - enemy.Shield[isMagicDamage, magicType] = 0; - - change = false; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - if (!effect.OnShieldBroken(actor, enemy, isMagicDamage, magicType, damage, shield, remain)) - { - change = true; - } - } - - if (!change) - { - enemy.HP -= remain; - shieldMsg = $"(护盾抵消了 {shield:0.##} 点并破碎,角色承受了 {remain:0.##} 点)"; - } - else - { - shieldMsg = $"(护盾抵消了 {shield:0.##} 点并破碎,角色没有承受伤害)"; - } - } - else - { - enemy.Shield[isMagicDamage, magicType] = remain; - shieldMsg = $"(护盾抵消了 {damage:0.##} 点,剩余可用 {remain:0.##} 点)"; - } - } - else if (shieldMsg.Trim() == "") - { - shieldMsg = $"(护盾已使其无效化)"; - } - } - else enemy.HP -= damage; - - if (isMagicDamage) - { - string dmgType = CharacterSet.GetMagicDamageName(magicType); - WriteLine($"[ {enemy} ] 受到了 {damage:0.##} 点{dmgType}!{shieldMsg}"); - } - else WriteLine($"[ {enemy} ] 受到了 {damage:0.##} 点物理伤害!{shieldMsg}"); - - // 生命偷取 - double steal = damage * actor.Lifesteal; - await HealToTargetAsync(actor, actor, steal, false); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - effect.AfterLifesteal(actor, enemy, damage, steal); - } - - // 造成伤害和受伤都可以获得能量 - double ep = GetEP(damage, GameplayEquilibriumConstant.DamageGetEPFactor, GameplayEquilibriumConstant.DamageGetEPMax); - effects = [.. actor.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterEPAfterDamage(actor, ref ep); - } - actor.EP += ep; - ep = GetEP(damage, GameplayEquilibriumConstant.TakenDamageGetEPFactor, GameplayEquilibriumConstant.TakenDamageGetEPMax); - effects = [.. enemy.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterEPAfterGetDamage(enemy, ref ep); - } - enemy.EP += ep; - - // 统计伤害 - CalculateCharacterDamageStatistics(actor, enemy, damage, isMagicDamage); - - // 计算助攻 - _assistDetail[actor][enemy, TotalTime] += damage; - } - } - else - { - LastRound.IsEvaded[enemy] = true; - } - - await OnDamageToEnemyAsync(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); - - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - effect.AfterDamageCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); - } - - if (enemy.HP <= 0 && !_eliminated.Contains(enemy) && !_respawnCountdown.ContainsKey(enemy)) - { - LastRound.HasKill = true; - _roundDeaths.Add(enemy); - await DeathCalculationAsync(actor, enemy); - } - } - - /// - /// 治疗一个目标 - /// - /// - /// - /// - /// - public async Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false) - { - if (target.HP == target.MaxHP) - { - return; - } - - bool isDead = target.HP <= 0; - - Dictionary totalHealBonus = []; - List effects = [.. actor.Effects.Union(target.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - bool changeCanRespawn = false; - double healBonus = effect.AlterHealValueBeforeHealToTarget(actor, target, heal, ref changeCanRespawn, totalHealBonus); - if (changeCanRespawn && !canRespawn) - { - canRespawn = true; - } - } - heal += totalHealBonus.Sum(kv => kv.Value); - - if (heal <= 0) - { - return; - } - - if (target.HP > 0 || (isDead && canRespawn)) - { - target.HP += heal; - if (!LastRound.Heals.TryAdd(target, heal)) - { - LastRound.Heals[target] += heal; - } - } - - bool isRespawn = isDead && canRespawn; - if (isRespawn) - { - if (target != actor) - { - WriteLine($"[ {target} ] 被 [ {actor} ] 复苏了,并回复了 {heal:0.##} 点生命值!!"); - } - else - { - WriteLine($"[ {target} ] 复苏了,并回复了 {heal:0.##} 点生命值!!"); - } - await SetCharacterRespawn(target); - } - else - { - WriteLine($"[ {target} ] 回复了 {heal:0.##} 点生命值!"); - } - - // 添加助攻 - SetNotDamageAssistTime(actor, target); - - // 统计数据 - if (_stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null) - { - stats.TotalHeal += heal; - } - - await OnHealToTargetAsync(actor, target, heal, isRespawn); - } - - /// - /// 获取EP - /// - /// 参数1 - /// 参数2 - /// 最大获取量 - public static double GetEP(double a, double b, double max) - { - return Math.Min((a + Random.Shared.Next(30)) * b, max); - } - - /// - /// 计算物理伤害 - /// - /// - /// - /// - /// - /// - /// - /// - public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount) - { - List characters = [actor, enemy]; - bool isMagic = false; - MagicType magicType = MagicType.None; - List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - if (changeCount < 3) - { - foreach (Effect effect in effects) - { - effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); - } - if (isMagic) - { - changeCount++; - return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage, ref changeCount); - } - } - - Dictionary totalDamageBonus = []; - effects = [.. actor.Effects.Union(enemy.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, false, MagicType.None, totalDamageBonus); - totalDamageBonus[effect] = damageBonus; - } - expectedDamage += totalDamageBonus.Sum(kv => kv.Value); - - double dice = Random.Shared.NextDouble(); - double throwingBonus = 0; - bool checkEvade = true; - bool checkCritical = true; - if (isNormalAttack) - { - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - checkEvade = effect.BeforeEvadeCheck(actor, enemy, ref throwingBonus); - } - - if (checkEvade) - { - // 闪避检定 - if (dice < (enemy.EvadeRate + throwingBonus)) - { - finalDamage = 0; - bool isAlterEvaded = false; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - if (effect.OnEvadedTriggered(actor, enemy, dice)) - { - isAlterEvaded = true; - } - } - if (!isAlterEvaded) - { - WriteLine("此物理攻击被完美闪避了!"); - return DamageResult.Evaded; - } - } - } - } - - // 物理穿透后的护甲 - double penetratedDEF = (1 - actor.PhysicalPenetration) * enemy.DEF; - - // 物理伤害减免 - double physicalDamageReduction = penetratedDEF / (penetratedDEF + GameplayEquilibriumConstant.DEFReductionFactor); - - // 最终的物理伤害 - finalDamage = expectedDamage * (1 - Calculation.PercentageCheck(physicalDamageReduction + enemy.ExPDR)); - - // 暴击检定 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - checkCritical = effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus); - } - - if (checkCritical) - { - dice = Random.Shared.NextDouble(); - if (dice < (actor.CritRate + throwingBonus)) - { - finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 - WriteLine("暴击生效!!"); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - effect.OnCriticalDamageTriggered(actor, enemy, dice); - } - return DamageResult.Critical; - } - } - - // 是否有效伤害 - return DamageResult.Normal; - } - - /// - /// 计算魔法伤害 - /// - /// - /// - /// - /// - /// - /// - /// - /// - public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount) - { - List characters = [actor, enemy]; - bool isMagic = true; - List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - if (changeCount < 3) - { - foreach (Effect effect in effects) - { - effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); - } - if (!isMagic) - { - changeCount++; - return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage, ref changeCount); - } - } - - Dictionary totalDamageBonus = []; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, true, magicType, totalDamageBonus); - totalDamageBonus[effect] = damageBonus; - } - expectedDamage += totalDamageBonus.Sum(kv => kv.Value); - - double dice = Random.Shared.NextDouble(); - double throwingBonus = 0; - bool checkEvade = true; - bool checkCritical = true; - if (isNormalAttack) - { - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - checkEvade = effect.BeforeEvadeCheck(actor, enemy, ref throwingBonus); - } - - if (checkEvade) - { - // 闪避检定 - if (dice < (enemy.EvadeRate + throwingBonus)) - { - finalDamage = 0; - bool isAlterEvaded = false; - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - if (effect.OnEvadedTriggered(actor, enemy, dice)) - { - isAlterEvaded = true; - } - } - if (!isAlterEvaded) - { - WriteLine("此魔法攻击被完美闪避了!"); - return DamageResult.Evaded; - } - } - } - } - - double MDF = enemy.MDF[magicType]; - - // 魔法穿透后的魔法抗性 - MDF = (1 - actor.MagicalPenetration) * MDF; - - // 最终的魔法伤害 - finalDamage = expectedDamage * (1 - MDF); - - // 暴击检定 - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - checkCritical = effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus); - } - - if (checkCritical) - { - dice = Random.Shared.NextDouble(); - if (dice < (actor.CritRate + throwingBonus)) - { - finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 - WriteLine("暴击生效!!"); - effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - effect.OnCriticalDamageTriggered(actor, enemy, dice); - } - return DamageResult.Critical; - } - } - - // 是否有效伤害 - return DamageResult.Normal; - } - - /// - /// 处理角色死亡 - /// - /// - public async Task ProcessCharacterDeathAsync(Character character) - { - foreach (Character death in _roundDeaths) - { - if (!await OnCharacterDeathAsync(character, death)) - { - continue; - } - - // 给所有角色的特效广播角色死亡结算 - List effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.IsInEffect))]; - foreach (Effect effect in effects) - { - effect.AfterDeathCalculation(death, character, _continuousKilling, _earnedMoney); - } - // 将死者移出队列 - _queue.Remove(death); - if (_isTeamMode) - { - Team? killTeam = GetTeam(character); - Team? deathTeam = GetTeam(death); - - if (MaxRespawnTimes != 0) - { - string[] teamActive = [.. Teams.OrderByDescending(kv => kv.Value.Score).Select(kv => - { - int activeCount = kv.Value.GetActiveCharacters(this).Count; - if (kv.Value == killTeam) - { - activeCount += 1; - } - return kv.Key + ":" + kv.Value.Score + "(剩余存活人数:" + activeCount + ")"; - })]; - WriteLine($"\r\n=== 当前死亡竞赛比分 ===\r\n{string.Join("\r\n", teamActive)}"); - } - - if (deathTeam != null) - { - List remain = deathTeam.GetActiveCharacters(this); - int remainCount = remain.Count; - if (remainCount == 0) - { - // 团灭了 - _eliminatedTeams.Add(deathTeam); - _teams.Remove(deathTeam.Name); - } - else if (MaxRespawnTimes == 0) - { - WriteLine($"[ {deathTeam} ] 剩余成员:[ {string.Join(" ] / [ ", remain)} ]({remainCount} 人)"); - } - } - - if (killTeam != null) - { - List actives = killTeam.GetActiveCharacters(this); - actives.Add(character); - int remainCount = actives.Count; - if (remainCount > 0 && MaxRespawnTimes == 0) - { - WriteLine($"[ {killTeam} ] 剩余成员:[ {string.Join(" ] / [ ", actives)} ]({remainCount} 人)"); - } - if (!_teams.Keys.Where(str => str != killTeam.Name).Any()) - { - // 没有其他的团队了,游戏结束 - await EndGameInfo(killTeam); - return; - } - if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin) - { - List combinedTeams = [.. _eliminatedTeams, .. _teams.Values]; - combinedTeams.Remove(killTeam); - _eliminatedTeams.Clear(); - _eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score)); - await EndGameInfo(killTeam); - return; - } - } - } - else - { - if (!_queue.Where(c => c != character).Any()) - { - // 没有其他的角色了,游戏结束 - await EndGameInfo(character); - } - } - } - } - - /// - /// 死亡结算 - /// - /// - /// - public async Task DeathCalculationAsync(Character killer, Character death) - { - if (!await OnDeathCalculationAsync(killer, death)) - { - return; - } - - if (!_continuousKilling.TryAdd(killer, 1)) _continuousKilling[killer] += 1; - if (!_maxContinuousKilling.TryAdd(killer, 1) && _continuousKilling[killer] > _maxContinuousKilling[killer]) - { - _maxContinuousKilling[killer] = _continuousKilling[killer]; - } - _stats[killer].Kills += 1; - _stats[death].Deaths += 1; - int money = Random.Shared.Next(250, 350); - - // 按伤害比分配金钱 只有造成 10% 伤害以上并且是在 30 秒内造成的伤害才能参与 - // 现在 20 秒内的非伤害类型辅助也能参与助攻了 - Character[] assists = [.. _assistDetail.Keys.Where(c => c != death && _assistDetail[c].GetPercentage(death) > 0.10 && - (_assistDetail[c].GetLastTime(death) - TotalTime <= 30 || _assistDetail[c].GetNotDamageAssistLastTime(killer) - TotalTime <= 20))]; - double totalDamagePercentage = _assistDetail.Keys.Where(assists.Contains).Select(c => _assistDetail[c].GetPercentage(death)).Sum(); - int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425); // 防止刷伤害设置金钱上限 - - // 分配金钱和累计助攻 - foreach (Character assist in assists) - { - int cmoney = Convert.ToInt32(_assistDetail[assist].GetPercentage(death) / totalDamagePercentage * totalMoney); - if (assist != killer) - { - if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney; - _stats[assist].Assists += 1; - } - else - { - money = cmoney; - } - } - - // 终结击杀的奖励仍然是全额的 - if (_continuousKilling.TryGetValue(death, out int coefficient) && coefficient > 1) - { - money += (coefficient + 1) * Random.Shared.Next(50, 100); - string termination = CharacterSet.GetContinuousKilling(coefficient); - string msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " 的" + termination : "")},获得 {money} {GameplayEquilibriumConstant.InGameCurrency}!"; - LastRound.DeathContinuousKilling.Add(msg); - if (assists.Length > 1) - { - msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]"; - } - WriteLine(msg); - } - else - { - string msg = $"[ {killer} ] 杀死了 [ {death} ],获得 {money} {GameplayEquilibriumConstant.InGameCurrency}!"; - LastRound.DeathContinuousKilling.Add(msg); - if (assists.Length > 1) - { - msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]"; - } - WriteLine(msg); - } - - if (FirstKiller is null) - { - FirstKiller = killer; - _stats[killer].FirstKills += 1; - _stats[death].FirstDeaths += 1; - money += 200; - string firstKill = $"[ {killer} ] 拿下了第一滴血!额外奖励 200 {GameplayEquilibriumConstant.InGameCurrency}!!"; - WriteLine(firstKill); - LastRound.ActorContinuousKilling.Add(firstKill); - } - - int kills = _continuousKilling[killer]; - string continuousKilling = CharacterSet.GetContinuousKilling(kills); - string actorContinuousKilling = ""; - if (kills == 2 || kills == 3) - { - actorContinuousKilling = "[ " + killer + " ] 完成了一次" + continuousKilling + "!"; - } - else if (kills == 4) - { - actorContinuousKilling = "[ " + killer + " ] 正在" + continuousKilling + "!"; - } - else if (kills > 4 && kills < 10) - { - actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "!"; - } - else if (kills >= 10) - { - actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "!拜托谁去杀了他吧!!!"; - } - if (actorContinuousKilling != "") - { - LastRound.ActorContinuousKilling.Add(actorContinuousKilling); - WriteLine(actorContinuousKilling); - } - - if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money; - - if (_isTeamMode) - { - Team? team = GetTeam(killer); - if (team != null) - { - team.Score++; - } - } - - death.EP = 0; - - // 清除对死者的助攻数据 - List ads = [.. _assistDetail.Values.Where(ad => ad.Character != death)]; - foreach (AssistDetail ad in ads) - { - ad[death, 0] = 0; - } - - _continuousKilling.Remove(death); - if (MaxRespawnTimes == 0) - { - _eliminated.Add(death); - } - else if (_respawnTimes.TryGetValue(death, out int times) && MaxRespawnTimes != -1 && times > MaxRespawnTimes) - { - WriteLine($"[ {death} ] 已达到复活次数上限,将不能再复活!!"); - _eliminated.Add(death); - } - else - { - // 进入复活倒计时 - double respawnTime = Calculation.Round2Digits(Math.Min(30, death.Level * 0.15 + times * 0.87 + coefficient)); - _respawnCountdown.TryAdd(death, respawnTime); - LastRound.RespawnCountdowns.TryAdd(death, respawnTime); - WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} {GameplayEquilibriumConstant.InGameTime}!"); - } - - // 移除死者的施法 - _castingSkills.Remove(death); - _castingSuperSkills.Remove(death); - - // 因丢失目标而中断施法 - List castingSkills = [.. _castingSkills.Keys]; - foreach (Character caster in castingSkills) - { - SkillTarget st = _castingSkills[caster]; - if (st.Targets.Remove(death) && st.Targets.Count == 0) - { - _castingSkills.Remove(caster); - if (caster.CharacterState == CharacterState.Casting) - { - caster.CharacterState = CharacterState.Actionable; - } - WriteLine($"[ {caster} ] 终止了 [ {st.Skill.Name} ] 的施法" + (_hardnessTimes[caster] > 3 ? $",并获得了 3 {GameplayEquilibriumConstant.InGameTime}的硬直时间的补偿。" : "。")); - if (_hardnessTimes[caster] > 3) - { - AddCharacter(caster, 3, false); - } - } - } - } - - /// - /// 游戏结束信息 - /// - public async Task EndGameInfo(Character winner) - { - WriteLine("[ " + winner + " ] 是胜利者。"); - _queue.Remove(winner); - _eliminated.Add(winner); - - if (!await OnGameEndAsync(winner)) - { - return; - } - - int top = 1; - WriteLine(""); - WriteLine("=== 排名 ==="); - for (int i = _eliminated.Count - 1; i >= 0; i--) - { - Character ec = _eliminated[i]; - CharacterStatistics statistics = CharacterStatistics[ec]; - string topCharacter = ec.ToString() + - (statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") + - (_maxContinuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") + - (_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {GameplayEquilibriumConstant.InGameCurrency} ]" : ""); - if (top == 1) - { - WriteLine("冠军:" + topCharacter); - _stats[ec].Wins += 1; - _stats[ec].Top3s += 1; - } - else if (top == 2) - { - WriteLine("亚军:" + topCharacter); - _stats[ec].Loses += 1; - _stats[ec].Top3s += 1; - } - else if (top == 3) - { - WriteLine("季军:" + topCharacter); - _stats[ec].Loses += 1; - _stats[ec].Top3s += 1; - } - else - { - WriteLine($"第 {top} 名:" + topCharacter); - _stats[ec].Loses += 1; - } - _stats[ec].Plays += 1; - _stats[ec].TotalEarnedMoney += earned; - _stats[ec].LastRank = top; - top++; - } - WriteLine(""); - _isGameEnd = true; - } - - /// - /// 游戏结束信息 [ 团队版 ] - /// - public async Task EndGameInfo(Team winner) - { - winner.IsWinner = true; - WriteLine("[ " + winner + " ] 是胜利者。"); - - if (!await OnGameEndTeamAsync(winner)) - { - return; - } - - int top = 1; - WriteLine(""); - WriteLine("=== 排名 ==="); - WriteLine(""); - - _eliminatedTeams.Add(winner); - _teams.Remove(winner.Name); - - for (int i = _eliminatedTeams.Count - 1; i >= 0; i--) - { - Team team = _eliminatedTeams[i]; - string topTeam = ""; - if (top == 1) - { - topTeam = "冠军"; - } - if (top == 2) - { - topTeam = "亚军"; - } - if (top == 3) - { - topTeam = "季军"; - } - if (top > 3) - { - topTeam = $"第 {top} 名"; - } - topTeam = $"☆--- {topTeam}团队:" + team.Name + " ---☆" + $"(得分:{team.Score})\r\n"; - foreach (Character ec in team.Members) - { - CharacterStatistics statistics = CharacterStatistics[ec]; - - string respawning = ""; - if (ec.HP <= 0) - { - respawning = "[ " + (_respawnCountdown.TryGetValue(ec, out double time) && time > 0 ? $"{time:0.##} {GameplayEquilibriumConstant.InGameTime}后复活" : "阵亡") + " ] "; - } - - string topCharacter = respawning + ec.ToString() + - (statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") + - (_maxContinuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") + - (_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {GameplayEquilibriumConstant.InGameCurrency} ]" : "") + - $"({statistics.Kills} / {statistics.Assists}{(MaxRespawnTimes != 0 ? " / " + statistics.Deaths : "")})"; - topTeam += topCharacter + "\r\n"; - if (top == 1) - { - _stats[ec].Wins += 1; - _stats[ec].Top3s += 1; - } - else if (top == 2) - { - _stats[ec].Loses += 1; - _stats[ec].Top3s += 1; - } - else if (top == 3) - { - _stats[ec].Loses += 1; - _stats[ec].Top3s += 1; - } - else - { - _stats[ec].Loses += 1; - } - _stats[ec].Plays += 1; - _stats[ec].TotalEarnedMoney += earned; - _stats[ec].LastRank = top; - } - WriteLine(topTeam); - top++; - } - WriteLine(""); - _isGameEnd = true; - } - - /// - /// 检查是否可以释放技能 - /// - /// - /// - /// - /// - public bool CheckCanCast(Character caster, Skill skill, out double cost) - { - if (skill.SkillType == SkillType.Magic) - { - cost = skill.RealMPCost; - if (cost > 0 && cost <= caster.MP) - { - return true; - } - else - { - WriteLine("[ " + caster + $" ] 魔法不足!"); - } - } - else - { - cost = skill.RealEPCost; - if (cost > 0 && cost <= caster.EP) - { - return true; - } - else - { - WriteLine("[ " + caster + $" ] 能量不足!"); - } - } - return false; - } - - /// - /// 检查是否可以释放技能(物品版) - /// - /// - /// - /// - /// - /// - public bool CheckCanCast(Character caster, Item item, out double costMP, out double costEP) - { - Skill? skill = item.Skills.Active; - if (skill is null) - { - costMP = 0; - costEP = 0; - return false; - } - costMP = skill.RealMPCost; - costEP = skill.RealEPCost; - bool isMPOk = false; - bool isEPOk = false; - if (costMP > 0 && costMP <= caster.MP) - { - isMPOk = true; - } - else - { - WriteLine("[ " + caster + $" ] 魔法不足!"); - } - costEP = skill.RealEPCost; - if (costEP > 0 && costEP <= caster.EP) - { - isEPOk = true; - } - else - { - WriteLine("[ " + caster + $" ] 能量不足!"); - } - return isMPOk && isEPOk; - } - - /// - /// 是否在回合外释放爆发技插队(仅自动化,手动设置请调用:) - /// - /// - public async Task WillPreCastSuperSkill() - { - // 选取所有 AI 控制角色 - foreach (Character other in _queue.Where(c => c.CharacterState == CharacterState.Actionable && _charactersInAI.Contains(c)).ToList()) - { - // 有 65% 欲望插队 - if (Random.Shared.NextDouble() < 0.65) - { - List skills = [.. other.Skills.Where(s => s.Level > 0 && s.SkillType == SkillType.SuperSkill && s.Enable && !s.IsInEffect && s.CurrentCD == 0 && other.EP >= s.RealEPCost)]; - if (skills.Count > 0) - { - Skill skill = skills[Random.Shared.Next(skills.Count)]; - await SetCharacterPreCastSuperSkill(other, skill); - } - } - } - } - - /// - /// 打断施法 - /// - /// - /// - public async Task InterruptCastingAsync(Character caster, Character interrupter) - { - Skill? skill = null; - if (_castingSkills.TryGetValue(caster, out SkillTarget target)) - { - skill = target.Skill; - _castingSkills.Remove(caster); - } - if (skill is null && caster.CharacterState == CharacterState.PreCastSuperSkill) - { - WriteLine($"因 [ {caster} ] 的预释放爆发技状态不可驱散,[ {interrupter} ] 打断失败!!"); - } - if (skill != null) - { - WriteLine($"[ {caster} ] 的施法被 [ {interrupter} ] 打断了!!"); - List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.OnSkillCastInterrupted(caster, skill, interrupter); - } - await OnInterruptCastingAsync(caster, skill, interrupter); - } - } - - /// - /// 打断施法 [ 用于使敌人目标丢失 ] - /// - /// - public async Task InterruptCastingAsync(Character interrupter) - { - foreach (Character caster in _castingSkills.Keys) - { - SkillTarget skillTarget = _castingSkills[caster]; - if (skillTarget.Targets.Contains(interrupter)) - { - Skill skill = skillTarget.Skill; - WriteLine($"[ {interrupter} ] 打断了 [ {caster} ] 的施法!!"); - List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.OnSkillCastInterrupted(caster, skill, interrupter); - } - await OnInterruptCastingAsync(caster, skill, interrupter); - } - } - } - - /// - /// 通过概率计算角色要干嘛 - /// - /// - /// - /// - /// - public static CharacterActionType GetActionType(double pUseItem, double pCastSkill, double pNormalAttack) - { - if (pUseItem == 0 && pCastSkill == 0 && pNormalAttack == 0) - { - return CharacterActionType.None; - } - - double total = pUseItem + pCastSkill + pNormalAttack; - - // 浮点数比较时处理误差 - if (Math.Abs(total - 1) > 1e-6) - { - pUseItem /= total; - pCastSkill /= total; - pNormalAttack /= total; - } - - double rand = Random.Shared.NextDouble(); - - // 按概率进行检查 - if (rand < pUseItem) - { - return CharacterActionType.UseItem; - } - - if (rand < pUseItem + pCastSkill) - { - return CharacterActionType.PreCastSkill; - } - - if (rand < pUseItem + pCastSkill + pNormalAttack) - { - return CharacterActionType.NormalAttack; - } - - return CharacterActionType.None; - } - - /// - /// 计算角色的数据 - /// - public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, bool isMagic) - { - if (_stats.TryGetValue(character, out CharacterStatistics? stats) && stats != null) - { - if (isMagic) - { - stats.TotalMagicDamage += damage; - } - else - { - stats.TotalPhysicalDamage += damage; - } - stats.TotalDamage += damage; - } - if (_stats.TryGetValue(characterTaken, out CharacterStatistics? statsTaken) && statsTaken != null) - { - if (isMagic) - { - statsTaken.TotalTakenMagicDamage = Calculation.Round2Digits(statsTaken.TotalTakenMagicDamage + damage); - } - else - { - statsTaken.TotalTakenPhysicalDamage = Calculation.Round2Digits(statsTaken.TotalTakenPhysicalDamage + damage); - } - statsTaken.TotalTakenDamage = Calculation.Round2Digits(statsTaken.TotalTakenDamage + damage); - } - if (LastRound.Damages.TryGetValue(characterTaken, out double damageTotal)) - { - LastRound.Damages[characterTaken] = damageTotal + damage; - } - else - { - LastRound.Damages[characterTaken] = damage; - } - } - - /// - /// 装备物品 - /// - /// - /// - public void Equip(Character character, Item item) - { - if (character.Equip(item)) - { - EquipSlotType type = item.EquipSlotType; - WriteLine($"[ {character} ] 装备了 [ {item.Name} ]。" + (type != EquipSlotType.None ? $"({ItemSet.GetEquipSlotTypeName(type)} 栏位)" : "")); - } - } + private ActionQueue() { } /// - /// 装备物品到指定栏位,并返回被替换的装备(如果有的话) + /// 按房间类型创建行动顺序表 /// - /// /// - /// - /// - public void Equip(Character character, EquipSlotType type, Item item, out Item? previous) - { - if (character.Equip(item, type, out previous)) - { - WriteLine($"[ {character} ] 装备了 [ {item.Name} ]。({ItemSet.GetEquipSlotTypeName(type)} 栏位)"); - } - } - - /// - /// 取消装备,并返回被替换的装备(如果有的话) - /// - /// - /// - /// - public Item? UnEquip(Character character, EquipSlotType type) - { - Item? item = character.UnEquip(type); - if (item != null) - { - WriteLine($"[ {character} ] 取消装备了 [ {item.Name} ]。({ItemSet.GetEquipSlotTypeName(type)} 栏位)"); - } - return item; - } - - /// - /// 使用物品实际逻辑 - /// - /// - /// - /// - /// - /// - public async Task UseItemAsync(Item item, Character character, List enemys, List teammates) - { - if (CheckCanCast(character, item, out double costMP, out double costEP)) - { - Skill? skill = item.Skills.Active; - if (skill != null) - { - List targets = await SelectTargetsAsync(character, skill, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0) - { - // 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标 - targets = [enemys[Random.Shared.Next(enemys.Count)]]; - } - if (targets.Count > 0) - { - // 免疫检定 - await CheckSkilledImmuneAsync(character, targets, skill, item); - - if (targets.Count > 0) - { - LastRound.Targets = [.. targets]; - - await OnCharacterUseItemAsync(character, item, targets); - - string line = $"[ {character} ] 使用了物品 [ {item.Name} ]!\r\n[ {character} ] "; - - skill.OnSkillCasting(this, character, targets); - skill.BeforeSkillCasted(); - - skill.CurrentCD = skill.RealCD; - skill.Enable = false; - - if (costMP > 0) - { - character.MP -= costMP; - LastRound.SkillCost = $"{-costMP:0.##} MP"; - line += $"消耗了 {costMP:0.##} 点魔法值,"; - } - - if (costEP > 0) - { - character.EP -= costEP; - if (LastRound.SkillCost != "") LastRound.SkillCost += " / "; - LastRound.SkillCost += $"{-costEP:0.##} EP"; - line += $"消耗了 {costEP:0.##} 点能量,"; - } - - line += $"释放了物品技能 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"; - WriteLine(line); - - SkillTarget skillTarget = new(skill, targets); - await OnCharacterCastItemSkillAsync(character, item, skillTarget, costMP, costEP); - - skill.OnSkillCasted(this, character, targets); - return true; - } - } - } - } - return false; - } - - /// - /// 设置角色复活 - /// - /// - public async Task SetCharacterRespawn(Character character) - { - double hardnessTime = 5; - character.Respawn(_original[character.Guid]); - WriteLine($"[ {character} ] 已复活!获得 {hardnessTime} {GameplayEquilibriumConstant.InGameTime}的硬直时间。"); - AddCharacter(character, hardnessTime, false); - await OnQueueUpdatedAsync(_queue, character, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。"); - LastRound.Respawns.Add(character); - _respawnCountdown.Remove(character); - if (!_respawnTimes.TryAdd(character, 1)) - { - _respawnTimes[character] += 1; - } - } - - /// - /// 设置角色将预释放爆发技 - /// - /// - /// - public async Task SetCharacterPreCastSuperSkill(Character character, Skill skill) - { - if (character.CharacterState == CharacterState.Actionable) - { - _castingSuperSkills[character] = skill; - character.CharacterState = CharacterState.PreCastSuperSkill; - _queue.Remove(character); - _cutCount.Remove(character); - WriteLine("[ " + character + " ] 预释放了爆发技!!"); - int preCastSSCount = 0; - double baseHardnessTime = 0; - foreach (Character c in _hardnessTimes.Keys) - { - if (c.CharacterState != CharacterState.PreCastSuperSkill) - { - _hardnessTimes[c] = Calculation.Round2Digits(_hardnessTimes[c] + 0.01); - } - else if (c != character) - { - if (preCastSSCount == 0) - { - baseHardnessTime = _hardnessTimes[c]; - } - preCastSSCount++; - } - } - AddCharacter(character, Calculation.Round2Digits(baseHardnessTime + preCastSSCount * 0.01), false); - await OnQueueUpdatedAsync(_queue, character, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。"); - skill.OnSkillCasting(this, character, []); - } - } - - /// - /// 设置角色为 AI 控制 - /// - /// /// - public void SetCharactersToAIControl(bool cancel = false, params IEnumerable characters) + /// + /// + public static GamingQueue NewGame(RoomType type, List? characters = null, Action? writer = null) { - foreach (Character character in characters) + characters ??= []; + return type switch { - if (cancel) - { - _charactersInAI.Remove(character); - } - else - { - _charactersInAI.Add(character); - } - } + RoomType.Team => new TeamGamingQueue(writer), + _ => new MixGamingQueue(writer) + }; } - - /// - /// 检查角色是否在 AI 控制状态 - /// - /// - /// - public bool IsCharacterInAIControlling(Character character) - { - return _charactersInAI.Contains(character); - } - - /// - /// 初始化回合奖励 - /// - /// 最大回合数 - /// 每个奖励回合生成多少技能 - /// key: 特效的数字标识符;value: 是否是主动技能的特效 - /// 通过数字标识符来获取构造特效的参数 - /// - public virtual void InitRoundRewards(int maxRound, int maxRewardsInRound, Dictionary effects, Func>? factoryEffects = null) - { - _roundRewards.Clear(); - int currentRound = 1; - long[] effectIDs = [.. effects.Keys]; - while (currentRound <= maxRound) - { - currentRound += Random.Shared.Next(1, 9); - - if (currentRound <= maxRound) - { - List skills = []; - if (maxRewardsInRound <= 0) maxRewardsInRound = 1; - - do - { - long effectID = effectIDs[Random.Shared.Next(effects.Count)]; - Dictionary args = []; - if (effects[effectID]) - { - args.Add("active", true); - args.Add("self", true); - args.Add("enemy", false); - } - Skill skill = Factory.OpenFactory.GetInstance(effectID, "", args); - Dictionary effectArgs = factoryEffects != null ? factoryEffects(effectID) : []; - args.Clear(); - args.Add("skill", skill); - args.Add("values", effectArgs); - Effect effect = Factory.OpenFactory.GetInstance(effectID, "", args); - skill.Effects.Add(effect); - skill.Name = $"[R] {effect.Name}"; - skills.Add(skill); - } - while (skills.Count < maxRewardsInRound); - - _roundRewards[currentRound] = skills; - } - } - } - - /// - /// 获取回合奖励 - /// - /// - /// - /// - public virtual List GetRoundRewards(int round, Character character) - { - if (_roundRewards.TryGetValue(round, out List? value) && value is List list && list.Count > 0) - { - foreach (Skill skill in list) - { - skill.GamingQueue = this; - skill.Character = character; - skill.Level = 1; - LastRound.RoundRewards.Add(skill); - WriteLine($"[ {character} ] 获得了回合奖励!{skill.Description}".Trim()); - if (skill.IsActive) - { - LastRound.Targets.Add(character); - skill.OnSkillCasted(this, character, [character]); - } - else - { - character.Skills.Add(skill); - } - } - return list; - } - return []; - } - - /// - /// 移除回合奖励 - /// - /// - /// - /// - public virtual void RemoveRoundRewards(int round, Character character, List skills) - { - foreach (Skill skill in skills) - { - foreach (Effect e in skill.Effects) - { - e.OnEffectLost(character); - character.Effects.Remove(e); - } - character.Skills.Remove(skill); - } - } - - /// - /// 修改角色的硬直时间 - /// - /// 角色 - /// 加值 - /// 是否是百分比 - /// 是否使用插队保护机制 - public void ChangeCharacterHardnessTime(Character character, double addValue, bool isPercentage, bool isCheckProtected) - { - double hardnessTime = _hardnessTimes[character]; - if (isPercentage) - { - addValue = hardnessTime * addValue; - } - hardnessTime += addValue; - if (hardnessTime <= 0) hardnessTime = 0; - AddCharacter(character, hardnessTime, isCheckProtected); - } - - /// - /// 选取技能目标 - /// - /// - /// - /// - /// - /// - public virtual async Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates) - { - List effects = [.. caster.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates); - } - List targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(caster)) - { - targets = skill.SelectTargets(caster, enemys, teammates); - } - return targets; - } - - /// - /// 选取普通攻击目标 - /// - /// - /// - /// - /// - /// - public virtual async Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates) - { - List effects = [.. character.Effects.Where(e => e.IsInEffect)]; - foreach (Effect effect in effects) - { - effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates); - } - List targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates); - return targets; - } - - /// - /// 设置角色对目标们的非伤害辅助时间 - /// - /// - /// - public void SetNotDamageAssistTime(Character character, params IEnumerable targets) - { - foreach (Character target in targets) - { - _assistDetail[character].NotDamageAssistLastTime[target] = TotalTime; - } - } - - /// - /// 免疫检定 - /// - /// - /// - /// - /// - /// - public virtual async Task CheckSkilledImmuneAsync(Character character, List targets, Skill skill, Item? item = null) - { - Character[] loop = [.. targets]; - foreach (Character target in loop) - { - bool ignore = false; - bool isImmune = target.ImmuneType == ImmuneType.Magical || target.ImmuneType == ImmuneType.Skilled || target.ImmuneType == ImmuneType.All; - if (isImmune) - { - Character[] characters = [character, target]; - Effect[] effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; - foreach (Effect effect in effects) - { - // 自带无视免疫或者特效免疫检定不通过可无视免疫 - if (effect.IgnoreImmune == ImmuneType.All || effect.IgnoreImmune == ImmuneType.Skilled || (skill.IsMagic && effect.IgnoreImmune == ImmuneType.Magical) || !effect.OnImmuneCheck(character, target, skill, item)) - { - ignore = true; - } - } - } - if (ignore) - { - isImmune = false; - targets.Remove(target); - } - if (isImmune) - { - WriteLine($"[ {target} ] 免疫了此技能!"); - await OnCharacterImmunedAsync(character, target, skill, item); - } - } - await Task.CompletedTask; - return; - } - - #region 事件 - - public delegate Task TurnStartEventHandler(ActionQueue queue, Character character, List enemys, List teammates, List skills, List items); - /// - /// 回合开始事件 - /// - public event TurnStartEventHandler? TurnStart; - /// - /// 回合开始事件 - /// - /// - /// - /// - /// - /// - /// - protected async Task OnTurnStartAsync(Character character, List enemys, List teammates, List skills, List items) - { - return await (TurnStart?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(true)); - } - - public delegate Task TurnEndEventHandler(ActionQueue queue, Character character); - /// - /// 回合结束事件 - /// - public event TurnEndEventHandler? TurnEnd; - /// - /// 回合结束事件 - /// - /// - /// - protected async Task OnTurnEndAsync(Character character) - { - await (TurnEnd?.Invoke(this, character) ?? Task.CompletedTask); - } - - public delegate Task DecideActionEventHandler(ActionQueue queue, Character character, List enemys, List teammates, List skills, List items); - /// - /// 决定角色的行动事件 - /// - public event DecideActionEventHandler? DecideAction; - /// - /// 决定角色的行动事件 - /// - /// - /// - /// - /// - /// - /// - protected async Task OnDecideActionAsync(Character character, List enemys, List teammates, List skills, List items) - { - return await (DecideAction?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(CharacterActionType.None)); - } - - public delegate Task SelectSkillEventHandler(ActionQueue queue, Character character, List skills); - /// - /// 角色需要选择一个技能 - /// - public event SelectSkillEventHandler? SelectSkill; - /// - /// 角色需要选择一个技能 - /// - /// - /// - /// - protected async Task OnSelectSkillAsync(Character character, List skills) - { - return await (SelectSkill?.Invoke(this, character, skills) ?? Task.FromResult(null)); - } - - public delegate Task SelectItemEventHandler(ActionQueue queue, Character character, List items); - /// - /// 角色需要选择一个物品 - /// - public event SelectItemEventHandler? SelectItem; - /// - /// 角色需要选择一个物品 - /// - /// - /// - /// - protected async Task OnSelectItemAsync(Character character, List items) - { - return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult(null)); - } - - public delegate Task> SelectSkillTargetsEventHandler(ActionQueue queue, Character caster, Skill skill, List enemys, List teammates); - /// - /// 选取技能目标事件 - /// - public event SelectSkillTargetsEventHandler? SelectSkillTargets; - /// - /// 选取技能目标事件 - /// - /// - /// - /// - /// - /// - protected async Task> OnSelectSkillTargetsAsync(Character caster, Skill skill, List enemys, List teammates) - { - return await (SelectSkillTargets?.Invoke(this, caster, skill, enemys, teammates) ?? Task.FromResult(new List())); - } - - public delegate Task> SelectNormalAttackTargetsEventHandler(ActionQueue queue, Character character, NormalAttack attack, List enemys, List teammates); - /// - /// 选取普通攻击目标事件 - /// - public event SelectNormalAttackTargetsEventHandler? SelectNormalAttackTargets; - /// - /// 选取普通攻击目标事件 - /// - /// - /// - /// - /// - /// - protected async Task> OnSelectNormalAttackTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates) - { - return await (SelectNormalAttackTargets?.Invoke(this, character, attack, enemys, teammates) ?? Task.FromResult(new List())); - } - - public delegate Task InterruptCastingEventHandler(ActionQueue queue, Character cast, Skill? skill, Character interrupter); - /// - /// 打断施法事件 - /// - public event InterruptCastingEventHandler? InterruptCasting; - /// - /// 打断施法事件 - /// - /// - /// - /// - /// - protected async Task OnInterruptCastingAsync(Character cast, Skill skill, Character interrupter) - { - await (InterruptCasting?.Invoke(this, cast, skill, interrupter) ?? Task.CompletedTask); - } - - public delegate Task DeathCalculationEventHandler(ActionQueue queue, Character killer, Character death); - /// - /// 死亡结算事件 - /// - public event DeathCalculationEventHandler? DeathCalculation; - /// - /// 死亡结算事件 - /// - /// - /// - /// - protected async Task OnDeathCalculationAsync(Character killer, Character death) - { - return await (DeathCalculation?.Invoke(this, killer, death) ?? Task.FromResult(true)); - } - - public delegate Task CharacterDeathEventHandler(ActionQueue queue, Character current, Character death); - /// - /// 角色死亡事件,此事件位于 之后 - /// - public event CharacterDeathEventHandler? CharacterDeath; - /// - /// 角色死亡事件,此事件位于 之后 - /// - /// - /// - /// - protected async Task OnCharacterDeathAsync(Character current, Character death) - { - return await (CharacterDeath?.Invoke(this, current, death) ?? Task.FromResult(true)); - } - - public delegate Task HealToTargetEventHandler(ActionQueue queue, Character actor, Character target, double heal, bool isRespawn); - /// - /// 治疗事件 - /// - public event HealToTargetEventHandler? HealToTarget; - /// - /// 治疗事件 - /// - /// - /// - /// - /// - /// - protected async Task OnHealToTargetAsync(Character actor, Character target, double heal, bool isRespawn) - { - await (HealToTarget?.Invoke(this, actor, target, heal, isRespawn) ?? Task.CompletedTask); - } - - public delegate Task DamageToEnemyEventHandler(ActionQueue queue, Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, DamageResult damageResult); - /// - /// 造成伤害事件 - /// - public event DamageToEnemyEventHandler? DamageToEnemy; - /// - /// 造成伤害事件 - /// - /// - /// - /// - /// - /// - /// - /// - /// - protected async Task OnDamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, DamageResult damageResult) - { - await (DamageToEnemy?.Invoke(this, actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult) ?? Task.CompletedTask); - } - - public delegate Task CharacterNormalAttackEventHandler(ActionQueue queue, Character actor, List targets); - /// - /// 角色普通攻击事件 - /// - public event CharacterNormalAttackEventHandler? CharacterNormalAttack; - /// - /// 角色普通攻击事件 - /// - /// - /// - /// - protected async Task OnCharacterNormalAttackAsync(Character actor, List targets) - { - await (CharacterNormalAttack?.Invoke(this, actor, targets) ?? Task.CompletedTask); - } - - public delegate Task CharacterPreCastSkillEventHandler(ActionQueue queue, Character actor, SkillTarget skillTarget); - /// - /// 角色吟唱技能事件(包括直接释放战技) - /// - public event CharacterPreCastSkillEventHandler? CharacterPreCastSkill; - /// - /// 角色吟唱技能事件(包括直接释放战技) - /// - /// - /// - /// - protected async Task OnCharacterPreCastSkillAsync(Character actor, SkillTarget skillTarget) - { - await (CharacterPreCastSkill?.Invoke(this, actor, skillTarget) ?? Task.CompletedTask); - } - - public delegate Task CharacterCastSkillEventHandler(ActionQueue queue, Character actor, SkillTarget skillTarget, double cost); - /// - /// 角色释放技能事件 - /// - public event CharacterCastSkillEventHandler? CharacterCastSkill; - /// - /// 角色释放技能事件 - /// - /// - /// - /// - /// - protected async Task OnCharacterCastSkillAsync(Character actor, SkillTarget skillTarget, double cost) - { - await (CharacterCastSkill?.Invoke(this, actor, skillTarget, cost) ?? Task.CompletedTask); - } - - public delegate Task CharacterUseItemEventHandler(ActionQueue queue, Character actor, Item item, List targets); - /// - /// 角色使用物品事件 - /// - public event CharacterUseItemEventHandler? CharacterUseItem; - /// - /// 角色使用物品事件 - /// - /// - /// - /// - /// - protected async Task OnCharacterUseItemAsync(Character actor, Item item, List targets) - { - await (CharacterUseItem?.Invoke(this, actor, item, targets) ?? Task.CompletedTask); - } - - public delegate Task CharacterCastItemSkillEventHandler(ActionQueue queue, Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP); - /// - /// 角色释放物品的技能事件 - /// - public event CharacterCastItemSkillEventHandler? CharacterCastItemSkill; - /// - /// 角色释放物品的技能事件 - /// - /// - /// - /// - /// - /// - /// - protected async Task OnCharacterCastItemSkillAsync(Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP) - { - await (CharacterCastItemSkill?.Invoke(this, actor, item, skillTarget, costMP, costEP) ?? Task.CompletedTask); - } - - public delegate Task CharacterImmunedEventHandler(ActionQueue queue, Character character, Character immune, ISkill skill, Item? item = null); - /// - /// 角色免疫事件 - /// - public event CharacterImmunedEventHandler? CharacterImmuned; - /// - /// 角色免疫事件 - /// - /// - /// - /// - /// - /// - protected async Task OnCharacterImmunedAsync(Character character, Character immune, ISkill skill, Item? item = null) - { - await (CharacterImmuned?.Invoke(this, character, immune, skill, item) ?? Task.CompletedTask); - } - - public delegate Task CharacterDoNothingEventHandler(ActionQueue queue, Character actor); - /// - /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) - /// - public event CharacterDoNothingEventHandler? CharacterDoNothing; - /// - /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) - /// - /// - /// - protected async Task OnCharacterDoNothingAsync(Character actor) - { - await (CharacterDoNothing?.Invoke(this, actor) ?? Task.CompletedTask); - } - - public delegate Task CharacterGiveUpEventHandler(ActionQueue queue, Character actor); - /// - /// 角色放弃行动事件 - /// - public event CharacterGiveUpEventHandler? CharacterGiveUp; - /// - /// 角色放弃行动事件 - /// - /// - /// - protected async Task OnCharacterGiveUpAsync(Character actor) - { - await (CharacterGiveUp?.Invoke(this, actor) ?? Task.CompletedTask); - } - - public delegate Task GameEndEventHandler(ActionQueue queue, Character winner); - /// - /// 游戏结束事件 - /// - public event GameEndEventHandler? GameEnd; - /// - /// 游戏结束事件 - /// - /// - /// - protected async Task OnGameEndAsync(Character winner) - { - return await (GameEnd?.Invoke(this, winner) ?? Task.FromResult(true)); - } - - public delegate Task GameEndTeamEventHandler(ActionQueue queue, Team winner); - /// - /// 游戏结束事件(团队版) - /// - public event GameEndTeamEventHandler? GameEndTeam; - /// - /// 游戏结束事件(团队版) - /// - /// - /// - protected async Task OnGameEndTeamAsync(Team winner) - { - return await (GameEndTeam?.Invoke(this, winner) ?? Task.FromResult(true)); - } - - public delegate Task QueueUpdatedEventHandler(ActionQueue queue, List characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg); - /// - /// 行动顺序表更新事件 - /// - public event QueueUpdatedEventHandler? QueueUpdated; - /// - /// 行动顺序表更新事件 - /// - /// - /// - /// - /// - /// - /// - protected async Task OnQueueUpdatedAsync(List characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg = "") - { - await (QueueUpdated?.Invoke(this, characters, character, hardnessTime, reason, msg) ?? Task.CompletedTask); - } - - #endregion } } diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs new file mode 100644 index 0000000..0a370cf --- /dev/null +++ b/Model/GamingQueue.cs @@ -0,0 +1,3049 @@ +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Interface.Base; +using Milimoe.FunGame.Core.Interface.Entity; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Model +{ + /// + /// 提供一个基础的回合制游戏队列基类实现,可继承扩展 + /// + public class GamingQueue : IGamingQueue + { + #region 公开属性 + + /// + /// 使用的游戏平衡常数 + /// + public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant; + + /// + /// 用于文本输出 + /// + public Action WriteLine { get; } + + /// + /// 原始的角色字典 + /// + public Dictionary Original => _original; + + /// + /// 当前的行动顺序 + /// + public List Queue => _queue; + + /// + /// 硬直时间表 + /// + public Dictionary HardnessTime => _hardnessTimes; + + /// + /// 当前已死亡的角色顺序(第一个是最早死的) + /// + public List Eliminated => _eliminated; + + /// + /// 角色是否在 AI 控制下 + /// + public HashSet CharactersInAI => _charactersInAI; + + /// + /// 角色数据 + /// + public Dictionary CharacterStatistics => _stats; + + /// + /// 游戏运行的时间 + /// + public double TotalTime { get; set; } = 0; + + /// + /// 游戏运行的回合 + /// 对于某角色而言,在其行动的回合叫 Turn,而所有角色行动的回合,都称之为 Round。 + /// + public int TotalRound { get; set; } = 0; + + /// + /// 第一滴血获得者 + /// + public Character? FirstKiller { get; set; } = null; + + /// + /// 最大复活次数 [ 不为 0 时开启死斗模式 ] + /// 0:不复活 / -1:无限复活 + /// + public int MaxRespawnTimes { get; set; } = 0; + + /// + /// 复活次数统计 + /// + public Dictionary RespawnTimes => _respawnTimes; + + /// + /// 复活倒计时 + /// + public Dictionary RespawnCountdown => _respawnCountdown; + + /// + /// 最大获胜积分 + /// 设置一个大于0的数以启用 + /// + public int MaxScoreToWin { get; set; } = 0; + + /// + /// 上回合记录 + /// + public RoundRecord LastRound { get; set; } = new(0); + + /// + /// 所有回合的记录 + /// + public List Rounds { get; } = []; + + /// + /// 回合奖励 + /// + public Dictionary> RoundRewards => _roundRewards; + + /// + /// 自定义数据 + /// + public Dictionary CustomData { get; } = []; + + #endregion + + #region 保护变量 + + /// + /// 原始的角色字典 + /// + protected readonly Dictionary _original = []; + + /// + /// 当前的行动顺序 + /// + protected readonly List _queue = []; + + /// + /// 当前已死亡的角色顺序(第一个是最早死的) + /// + protected readonly List _eliminated = []; + + /// + /// 角色是否在 AI 控制下 + /// + protected readonly HashSet _charactersInAI = []; + + /// + /// 硬直时间表 + /// + protected readonly Dictionary _hardnessTimes = []; + + /// + /// 角色正在吟唱的技能(通常是魔法) + /// + protected readonly Dictionary _castingSkills = []; + + /// + /// 角色预释放的爆发技 + /// + protected readonly Dictionary _castingSuperSkills = []; + + /// + /// 角色即将使用的物品 + /// + protected readonly Dictionary _willUseItems = []; + + /// + /// 角色目前赚取的金钱 + /// + protected readonly Dictionary _earnedMoney = []; + + /// + /// 角色最高连杀数 + /// + protected readonly Dictionary _maxContinuousKilling = []; + + /// + /// 角色目前的连杀数 + /// + protected readonly Dictionary _continuousKilling = []; + + /// + /// 角色被插队次数 + /// + protected readonly Dictionary _cutCount = []; + + /// + /// 助攻伤害 + /// + protected readonly Dictionary _assistDetail = []; + + /// + /// 角色数据 + /// + protected readonly Dictionary _stats = []; + + /// + /// 复活次数统计 + /// + protected readonly Dictionary _respawnTimes = []; + + /// + /// 复活倒计时 + /// + protected readonly Dictionary _respawnCountdown = []; + + /// + /// 当前回合死亡角色 + /// + protected readonly List _roundDeaths = []; + + /// + /// 回合奖励 + /// + protected readonly Dictionary> _roundRewards = []; + + /// + /// 回合奖励的特效工厂 + /// + protected Func> _factoryRoundRewardEffects = id => []; + + /// + /// 游戏是否结束 + /// + protected bool _isGameEnd = false; + + #endregion + + #region 构造函数 + + /// + /// 新建一个基础回合制游戏队列 + /// + /// 用于文本输出 + public GamingQueue(Action? writer = null) + { + if (writer != null) + { + WriteLine = writer; + } + WriteLine ??= new Action(Console.WriteLine); + } + + /// + /// 新建一个基础回合制游戏队列并初始化 + /// + /// 参与本次游戏的角色列表 + /// 用于文本输出 + public GamingQueue(List characters, Action? writer = null) + { + if (writer != null) + { + WriteLine = writer; + } + WriteLine ??= new Action(Console.WriteLine); + InitCharacterQueue(characters); + } + + #endregion + + #region 队列框架 + + /// + /// 初始化基础回合制游戏队列 + /// + /// + public void InitCharacterQueue(List characters) + { + // 保存原始的角色信息。用于复活时还原状态 + foreach (Character character in characters) + { + Character original = character.Copy(); + original.Guid = Guid.NewGuid(); + character.Guid = original.Guid; + _original.Add(original.Guid, original); + } + + // 初始排序:按速度排序 + List> groupedBySpeed = [.. characters + .GroupBy(c => c.SPD) + .OrderByDescending(g => g.Key)]; + + Random random = new(); + + foreach (IGrouping group in groupedBySpeed) + { + if (group.Count() == 1) + { + // 如果只有一个角色,直接加入队列 + Character character = group.First(); + AddCharacter(character, Calculation.Round2Digits(_queue.Count * 0.1), false); + _assistDetail.Add(character, new AssistDetail(character, characters.Where(c => c != character))); + _stats.Add(character, new()); + // 初始化技能 + foreach (Skill skill in character.Skills) + { + skill.OnSkillGained(this); + } + } + else + { + // 如果有多个角色,进行先行决定 + List sortedList = [.. group]; + + while (sortedList.Count > 0) + { + Character? selectedCharacter = null; + bool decided = false; + if (sortedList.Count == 1) + { + selectedCharacter = sortedList[0]; + decided = true; + } + + while (!decided) + { + // 每个角色进行两次随机数抽取 + var randomNumbers = sortedList.Select(c => new + { + Character = c, + FirstRoll = random.Next(1, 21), + SecondRoll = random.Next(1, 21) + }).ToList(); + + randomNumbers.ForEach(a => WriteLine(a.Character.Name + ": " + a.FirstRoll + " / " + a.SecondRoll)); + + // 找到两次都大于其他角色的角色 + int maxFirstRoll = randomNumbers.Max(r => r.FirstRoll); + int maxSecondRoll = randomNumbers.Max(r => r.SecondRoll); + + var candidates = randomNumbers + .Where(r => r.FirstRoll == maxFirstRoll && r.SecondRoll == maxSecondRoll) + .ToList(); + + if (candidates.Count == 1) + { + selectedCharacter = candidates.First().Character; + decided = true; + } + } + + // 将决定好的角色加入队列 + if (selectedCharacter != null) + { + AddCharacter(selectedCharacter, Calculation.Round2Digits(_queue.Count * 0.1), false); + _assistDetail.Add(selectedCharacter, new AssistDetail(selectedCharacter, characters.Where(c => c != selectedCharacter))); + _stats.Add(selectedCharacter, new()); + // 初始化技能 + foreach (Skill skill in selectedCharacter.Skills) + { + skill.OnSkillGained(this); + } + WriteLine("decided: " + selectedCharacter.Name + "\r\n"); + sortedList.Remove(selectedCharacter); + } + } + } + } + } + + /// + /// 将角色加入行动顺序表 + /// + /// + /// + /// + public void AddCharacter(Character character, double hardnessTime, bool isCheckProtected = true) + { + // 确保角色不在队列中 + _queue.RemoveAll(c => c == character); + + // 插队机制:按硬直时间排序 + int insertIndex = _queue.FindIndex(c => _hardnessTimes[c] > hardnessTime); + + if (isCheckProtected) + { + // 查找保护条件 被插队超过此次数便能获得插队补偿 即行动保护 + int countProtected = Math.Max(5, _queue.Count); + + // 查找队列中是否有满足插队补偿条件的角色(最后一个) + var list = _queue + .Select((c, index) => new { Character = c, Index = index }) + .Where(x => _cutCount.ContainsKey(x.Character) && _cutCount[x.Character] >= countProtected); + + // 如果没有找到满足条件的角色,返回 -1 + int protectIndex = list.Select(x => x.Index).LastOrDefault(-1); + + if (protectIndex != -1) + { + // 获取最后一个符合条件的角色 + Character lastProtectedCharacter = list.Last().Character; + double lastProtectedHardnessTime = _hardnessTimes[lastProtectedCharacter]; + + // 查找与最后一个受保护角色相同硬直时间的其他角色 + var sameHardnessList = _queue + .Select((c, index) => new { Character = c, Index = index }) + .Where(x => _hardnessTimes[x.Character] == lastProtectedHardnessTime && x.Index > protectIndex); + + // 如果找到了相同硬直时间的角色,更新 protectIndex 为它们中最后一个的索引 + if (sameHardnessList.Any()) + { + protectIndex = sameHardnessList.Select(x => x.Index).Last(); + } + + // 判断是否需要插入到受保护角色的后面 + if (insertIndex != -1 && insertIndex <= protectIndex) + { + // 如果按硬直时间插入的位置在受保护角色之前或相同,则插入到受保护角色的后面一位 + insertIndex = protectIndex + 1; + hardnessTime = lastProtectedHardnessTime; + + // 列出受保护角色的名单 + WriteLine($"由于 [ {string.Join(" ],[ ", list.Select(x => x.Character))} ] 受到行动保护,因此角色 [ {character} ] 将插入至顺序表第 {insertIndex + 1} 位。"); + } + } + } + + // 如果插入索引无效(为-1 或 大于等于队列长度),则添加到队列尾部 + if (insertIndex == -1 || insertIndex >= _queue.Count) + { + _queue.Add(character); + } + else + { + _queue.Insert(insertIndex, character); + } + _hardnessTimes[character] = hardnessTime; + + // 为所有被插队的角色增加 _cutCount + if (isCheckProtected && insertIndex != -1 && insertIndex < _queue.Count) + { + for (int i = insertIndex + 1; i < _queue.Count; i++) + { + Character queuedCharacter = _queue[i]; + if (!_cutCount.TryAdd(queuedCharacter, 1)) + { + _cutCount[queuedCharacter] += 1; + } + } + } + } + + /// + /// 清空行动队列 + /// + public void ClearQueue() + { + FirstKiller = null; + CustomData.Clear(); + _original.Clear(); + _queue.Clear(); + _hardnessTimes.Clear(); + _assistDetail.Clear(); + _stats.Clear(); + _cutCount.Clear(); + _castingSkills.Clear(); + _castingSuperSkills.Clear(); + _willUseItems.Clear(); + _maxContinuousKilling.Clear(); + _continuousKilling.Clear(); + _earnedMoney.Clear(); + _eliminated.Clear(); + _charactersInAI.Clear(); + } + + #endregion + + #region 时间流逝 + + /// + /// 从行动顺序表取出第一个角色 + /// + /// + public async Task NextCharacterAsync() + { + if (_queue.Count == 0) return null; + + // 硬直时间为 0 的角色或预释放爆发技的角色先行动,取第一个 + Character? character = _queue.FirstOrDefault(c => c.CharacterState == CharacterState.PreCastSuperSkill); + if (character is null) + { + Character temp = _queue[0]; + if (_hardnessTimes[temp] == 0) + { + character = temp; + } + } + else + { + _hardnessTimes[character] = 0; + } + + if (character != null) + { + _queue.Remove(character); + _cutCount.Remove(character); + + // 进入下一回合 + TotalRound++; + LastRound = new(TotalRound); + Rounds.Add(LastRound); + + return character; + } + else + { + await TimeLapse(); + return await NextCharacterAsync(); + } + } + + /// + /// 时间进行流逝,减少硬直时间,减少技能冷却时间,角色也会因此回复状态 + /// + /// 流逝的时间 + public async Task TimeLapse() + { + if (_queue.Count == 0) return 0; + + // 获取第一个角色的硬直时间 + double timeToReduce = _hardnessTimes[_queue[0]]; + // 如果复活时间更快,应该先流逝复活时间 + if (_respawnCountdown.Count != 0) + { + double timeToRespawn = _respawnCountdown.Values.Min(); + if (timeToRespawn < timeToReduce) + { + timeToReduce = Calculation.Round2Digits(timeToRespawn); + } + } + + TotalTime = Calculation.Round2Digits(TotalTime + timeToReduce); + WriteLine("时间流逝:" + timeToReduce); + + foreach (Character character in _queue) + { + // 减少所有角色的硬直时间 + _hardnessTimes[character] = Calculation.Round2Digits(_hardnessTimes[character] - timeToReduce); + + // 统计 + _stats[character].LiveRound += 1; + _stats[character].LiveTime += timeToReduce; + _stats[character].DamagePerRound = _stats[character].TotalDamage / _stats[character].LiveRound; + _stats[character].DamagePerTurn = _stats[character].TotalDamage / _stats[character].ActionTurn; + _stats[character].DamagePerSecond = _stats[character].TotalDamage / _stats[character].LiveTime; + + // 回血回蓝 + double recoveryHP = character.HR * timeToReduce; + double recoveryMP = character.MR * timeToReduce; + double needHP = character.MaxHP - character.HP; + double needMP = character.MaxMP - character.MP; + double reallyReHP = needHP >= recoveryHP ? recoveryHP : needHP; + double reallyReMP = needMP >= recoveryMP ? recoveryMP : needMP; + if (reallyReHP > 0 && reallyReMP > 0) + { + character.HP += reallyReHP; + character.MP += reallyReMP; + WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); + } + else + { + if (reallyReHP > 0) + { + character.HP += reallyReHP; + WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 当前能量:{character.EP:0.##}"); + } + if (reallyReMP > 0) + { + character.MP += reallyReMP; + WriteLine($"角色 {character.Name} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}"); + } + } + + // 减少所有技能的冷却时间 + foreach (Skill skill in character.Skills) + { + skill.CurrentCD -= timeToReduce; + if (skill.CurrentCD <= 0) + { + skill.CurrentCD = 0; + skill.Enable = true; + } + } + + // 移除到时间的特效 + List effects = [.. character.Effects]; + foreach (Effect effect in effects) + { + if (effect.IsBeingTemporaryDispelled) + { + effect.IsBeingTemporaryDispelled = false; + effect.OnEffectGained(character); + } + + if (effect.Level == 0) + { + character.Effects.Remove(effect); + continue; + } + + if (!effect.Durative) + { + // 防止特效在时间流逝后,持续时间已结束还能继续生效的情况 + effect.OnTimeElapsed(character, timeToReduce); + } + + // 自身被动不会考虑 + if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive) + { + continue; + } + + // 统计控制时长 + if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable) + { + _stats[effect.Source].ControlTime += timeToReduce; + SetNotDamageAssistTime(effect.Source, character); + } + + if (effect.Durative) + { + effect.RemainDuration -= timeToReduce; + if (effect.RemainDuration <= 0) + { + effect.RemainDuration = 0; + character.Effects.Remove(effect); + effect.OnEffectLost(character); + } + else + { + effect.OnTimeElapsed(character, timeToReduce); + } + } + } + } + + // 减少复活倒计时 + foreach (Character character in _respawnCountdown.Keys) + { + _respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce); + if (_respawnCountdown[character] <= 0) + { + await SetCharacterRespawn(character); + } + } + + WriteLine("\r\n"); + + return timeToReduce; + } + + /// + /// 显示当前所有角色的状态和硬直时间 + /// + public void DisplayQueue() + { + WriteLine("==== 角色状态 ===="); + foreach (Character c in _queue) + { + WriteLine(c.GetInBattleInfo(_hardnessTimes[c])); + } + } + + #endregion + + #region 回合内 + + /// + /// 角色 的回合进行中 + /// + /// + /// 是否结束游戏 + public async Task ProcessTurnAsync(Character character) + { + LastRound.Actor = character; + _roundDeaths.Clear(); + + if (!await BeforeTurnAsync(character)) + { + return _isGameEnd; + } + + // 获取回合奖励 + List rewards = GetRoundRewards(TotalRound, character); + + // 基础硬直时间 + double baseTime = 10; + bool isCheckProtected = true; + + // 队友列表 + List teammates = [.. GetTeammates(character).Where(_queue.Contains)]; + + // 敌人列表 + List enemys = [.. _queue.Where(c => c != character && !c.IsUnselectable && !teammates.Contains(c))]; + + // 技能列表 + List skills = [.. character.Skills.Where(s => s.Level > 0 && s.SkillType != SkillType.Passive && s.Enable && !s.IsInEffect && s.CurrentCD == 0 && + ((s.SkillType == SkillType.SuperSkill || s.SkillType == SkillType.Skill) && s.RealEPCost <= character.EP || s.SkillType == SkillType.Magic && s.RealMPCost <= character.MP))]; + + // 物品列表 + List items = [.. character.Items.Where(i => i.IsActive && i.Skills.Active != null && i.Enable && i.IsInGameItem && + i.Skills.Active.SkillType == SkillType.Item && i.Skills.Active.Enable && !i.Skills.Active.IsInEffect && i.Skills.Active.CurrentCD == 0 && i.Skills.Active.RealMPCost <= character.MP && i.Skills.Active.RealEPCost <= character.EP)]; + + // 回合开始事件,允许事件返回 false 接管回合操作 + // 如果事件全程接管回合操作,需要注意触发特效 + if (!await OnTurnStartAsync(character, enemys, teammates, skills, items)) + { + return _isGameEnd; + } + + foreach (Skill skillTurnStart in skills) + { + skillTurnStart.OnTurnStart(character, enemys, teammates, skills, items); + } + + List effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.OnTurnStart(character, enemys, teammates, skills, items); + } + + // 此变量用于在取消选择时,能够重新行动 + bool decided = false; + // 最大取消次数 + int cancelTimes = 3; + + // 作出了什么行动 + CharacterActionType type = CharacterActionType.None; + + // 循环条件: + // AI 控制下:未决策、取消次数大于0 + // 手动控制下:未决策 + bool isAI = _charactersInAI.Contains(character); + while (!decided && (!isAI || cancelTimes > 0)) + { + type = CharacterActionType.None; + + // 是否能使用物品和释放技能 + bool canUseItem = items.Count > 0; + bool canCastSkill = skills.Count > 0; + + // 使用物品和释放技能、使用普通攻击的概率 + double pUseItem = 0.33; + double pCastSkill = 0.33; + double pNormalAttack = 0.34; + + cancelTimes--; + // 不允许在吟唱和预释放状态下,修改角色的行动 + if (character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill) + { + CharacterActionType actionTypeTemp = CharacterActionType.None; + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + actionTypeTemp = effect.AlterActionTypeBeforeAction(character, character.CharacterState, ref canUseItem, ref canCastSkill, ref pUseItem, ref pCastSkill, ref pNormalAttack); + } + if (actionTypeTemp != CharacterActionType.None && actionTypeTemp != CharacterActionType.CastSkill && actionTypeTemp != CharacterActionType.CastSuperSkill) + { + type = actionTypeTemp; + } + } + + if (type == CharacterActionType.None) + { + if (character.CharacterState != CharacterState.NotActionable && character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill) + { + // 根据角色状态,设置一些参数 + if (character.CharacterState == CharacterState.Actionable) + { + // 可以任意行动 + if (canUseItem && canCastSkill) + { + // 不做任何处理 + } + else if (canUseItem && !canCastSkill) + { + pCastSkill = 0; + } + else if (!canUseItem && canCastSkill) + { + pUseItem = 0; + } + else + { + pUseItem = 0; + pCastSkill = 0; + } + } + else if (character.CharacterState == CharacterState.ActionRestricted) + { + // 行动受限,只能使用特殊物品 + if (canUseItem) + { + pCastSkill = 0; + pNormalAttack = 0; + } + else + { + pUseItem = 0; + pCastSkill = 0; + pNormalAttack = 0; + } + } + else if (character.CharacterState == CharacterState.BattleRestricted) + { + // 战斗不能,只能使用物品 + enemys.Clear(); + teammates.Clear(); + skills.Clear(); + if (canUseItem) + { + pCastSkill = 0; + pNormalAttack = 0; + } + else + { + pUseItem = 0; + pCastSkill = 0; + pNormalAttack = 0; + } + } + else if (character.CharacterState == CharacterState.SkillRestricted) + { + // 技能受限,无法使用技能,可以普通攻击,可以使用物品 + skills.Clear(); + if (canUseItem) + { + pCastSkill = 0; + } + else + { + pUseItem = 0; + pCastSkill = 0; + } + } + else if (character.CharacterState == CharacterState.AttackRestricted) + { + // 攻击受限,无法普通攻击,可以使用技能,可以使用物品 + pNormalAttack = 0; + if (!canUseItem) + { + pUseItem = 0; + } + } + + // 模组可以通过此事件来决定角色的行动 + type = await OnDecideActionAsync(character, enemys, teammates, skills, items); + // 若事件未完成决策,则将通过概率对角色进行自动化决策 + if (type == CharacterActionType.None) + { + type = GetActionType(pUseItem, pCastSkill, pNormalAttack); + } + + _stats[character].ActionTurn += 1; + } + else if (character.CharacterState == CharacterState.Casting) + { + // 如果角色上一次吟唱了魔法,这次的行动则是结算这个魔法 + type = CharacterActionType.CastSkill; + } + else if (character.CharacterState == CharacterState.PreCastSuperSkill) + { + // 角色使用回合外爆发技插队 + type = CharacterActionType.CastSuperSkill; + } + else + { + // 完全行动不能 + type = CharacterActionType.None; + } + } + + Dictionary continuousKillingTemp = new(_continuousKilling); + Dictionary earnedMoneyTemp = new(_earnedMoney); + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterSelectListBeforeAction(character, enemys, teammates, skills, continuousKillingTemp, earnedMoneyTemp); + } + + if (type == CharacterActionType.NormalAttack) + { + if (character.CharacterState == CharacterState.NotActionable || + character.CharacterState == CharacterState.ActionRestricted || + character.CharacterState == CharacterState.BattleRestricted || + character.CharacterState == CharacterState.AttackRestricted) + { + WriteLine($"角色 [ {character} ] 状态为:{CharacterSet.GetCharacterState(character.CharacterState)},无法使用普通攻击!"); + } + else + { + // 使用普通攻击逻辑 + List targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates); + if (targets.Count == 0 && _charactersInAI.Contains(character)) + { + // 如果没有选取目标,且角色在 AI 控制下,则随机选取目标 + if (enemys.Count > character.NormalAttack.CanSelectTargetCount) + targets = [.. enemys.OrderBy(o => Random.Shared.Next(enemys.Count)).Take(character.NormalAttack.CanSelectTargetCount)]; + else + targets = [.. enemys]; + } + if (targets.Count > 0) + { + LastRound.Targets = [.. targets]; + decided = true; + + await OnCharacterNormalAttackAsync(character, targets); + + character.NormalAttack.Attack(this, character, targets); + baseTime = character.NormalAttack.RealHardnessTime; + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterNormalAttack(character, ref baseTime, ref isCheckProtected); + } + } + } + } + else if (type == CharacterActionType.PreCastSkill) + { + // 预使用技能,即开始吟唱逻辑 + Skill? skill = await OnSelectSkillAsync(character, skills); + if (skill is null && _charactersInAI.Contains(character) && skills.Count > 0) + { + skill = skills[Random.Shared.Next(skills.Count)]; + } + if (skill != null) + { + // 吟唱前需要先选取目标 + if (skill.SkillType == SkillType.Magic) + { + List targets = await SelectTargetsAsync(character, skill, enemys, teammates); + if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0) + { + // 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标 + targets = [enemys[Random.Shared.Next(enemys.Count)]]; + } + if (targets.Count > 0) + { + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); + + if (targets.Count > 0) + { + LastRound.Targets = [.. targets]; + decided = true; + + character.CharacterState = CharacterState.Casting; + SkillTarget skillTarget = new(skill, targets); + await OnCharacterPreCastSkillAsync(character, skillTarget); + + _castingSkills[character] = skillTarget; + baseTime = skill.RealCastTime; + skill.OnSkillCasting(this, character, targets); + } + } + } + else + { + // 只有魔法需要吟唱,战技和爆发技直接释放 + if (CheckCanCast(character, skill, out double cost)) + { + List targets = await SelectTargetsAsync(character, skill, enemys, teammates); + if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0) + { + // 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标 + targets = [enemys[Random.Shared.Next(enemys.Count)]]; + } + if (targets.Count > 0) + { + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); + + if (targets.Count > 0) + { + LastRound.Targets = [.. targets]; + decided = true; + + SkillTarget skillTarget = new(skill, targets); + await OnCharacterPreCastSkillAsync(character, skillTarget); + + skill.OnSkillCasting(this, character, targets); + skill.BeforeSkillCasted(); + + character.EP -= cost; + baseTime = skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillCost = $"{-cost:0.##} EP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + + await OnCharacterCastSkillAsync(character, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets); + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } + } + } + } + } + LastRound.Skill = skill; + } + } + else if (type == CharacterActionType.CastSkill) + { + if (_castingSkills.TryGetValue(character, out SkillTarget skillTarget)) + { + // 使用技能逻辑,结束吟唱状态 + character.CharacterState = CharacterState.Actionable; + character.UpdateCharacterState(); + Skill skill = skillTarget.Skill; + List targets = [.. skillTarget.Targets.Where(c => !c.IsUnselectable)]; + + // 判断是否能够释放技能 + if (targets.Count > 0 && CheckCanCast(character, skill, out double cost)) + { + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); + + if (targets.Count > 0) + { + decided = true; + LastRound.Targets = [.. targets]; + LastRound.Skill = skill; + _castingSkills.Remove(character); + + skill.BeforeSkillCasted(); + + character.MP -= cost; + baseTime = skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillCost = $"{-cost:0.##} MP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + + await OnCharacterCastSkillAsync(character, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets); + } + } + else + { + WriteLine($"[ {character} ] 放弃释放技能!"); + // 放弃释放技能会获得3的硬直时间 + baseTime = 3; + } + + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } + } + else + { + // 原吟唱的技能丢失(被打断或者被取消),允许角色再次决策 + character.CharacterState = CharacterState.Actionable; + character.UpdateCharacterState(); + } + } + else if (type == CharacterActionType.CastSuperSkill) + { + decided = true; + // 结束预释放爆发技的状态 + character.CharacterState = CharacterState.Actionable; + character.UpdateCharacterState(); + Skill skill = _castingSuperSkills[character]; + LastRound.Skill = skill; + _castingSuperSkills.Remove(character); + + // 判断是否能够释放技能 + if (CheckCanCast(character, skill, out double cost)) + { + // 预释放的爆发技不可取消 + List targets = await SelectTargetsAsync(character, skill, enemys, teammates); + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill); + LastRound.Targets = [.. targets]; + + skill.BeforeSkillCasted(); + + character.EP -= cost; + baseTime = skill.RealHardnessTime; + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + LastRound.SkillCost = $"{-cost:0.##} EP"; + WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量值,释放了爆发技 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"); + + SkillTarget skillTarget = new(skill, targets); + await OnCharacterCastSkillAsync(character, skillTarget, cost); + + skill.OnSkillCasted(this, character, targets); + } + else + { + WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!"); + // 放弃释放技能会获得3的硬直时间 + baseTime = 3; + } + + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } + } + else if (type == CharacterActionType.UseItem) + { + // 使用物品逻辑 + Item? item = await OnSelectItemAsync(character, items); + if (item is null && _charactersInAI.Contains(character) && items.Count > 0) + { + // AI 控制下随机选取一个物品 + item = items[Random.Shared.Next(items.Count)]; + } + if (item != null && item.Skills.Active != null) + { + Skill skill = item.Skills.Active; + if (await UseItemAsync(item, character, enemys, teammates)) + { + decided = true; + LastRound.Item = item; + baseTime = skill.RealHardnessTime; + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected); + } + } + } + } + else if (type == CharacterActionType.EndTurn) + { + decided = true; + WriteLine($"[ {character} ] 结束了回合!"); + await OnCharacterDoNothingAsync(character); + } + else + { + decided = true; + WriteLine($"[ {character} ] 完全行动不能!"); + } + } + + if (type == CharacterActionType.None) + { + WriteLine($"[ {character} ] 放弃了行动!"); + await OnCharacterGiveUpAsync(character); + } + + LastRound.ActionType = type; + + await AfterCharacterAction(character, type); + + // 统一在回合结束时处理角色的死亡 + await ProcessCharacterDeathAsync(character); + + // 移除回合奖励 + RemoveRoundRewards(character, rewards); + + if (_isGameEnd) + { + // 回合结束事件 + await OnTurnEndAsync(character); + + await AfterTurnAsync(character); + + return _isGameEnd; + } + + // 减少硬直时间 + double newHardnessTime = baseTime; + if (character.CharacterState != CharacterState.Casting) + { + newHardnessTime = Calculation.Round2Digits(baseTime); + WriteLine($"[ {character} ] 回合结束,获得硬直时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}"); + } + else + { + newHardnessTime = Calculation.Round2Digits(baseTime); + WriteLine($"[ {character} ] 进行吟唱,持续时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}"); + LastRound.CastTime = newHardnessTime; + } + AddCharacter(character, newHardnessTime, isCheckProtected); + await OnQueueUpdatedAsync(_queue, character, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。"); + LastRound.HardnessTime = newHardnessTime; + + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.OnTurnEnd(character); + + // 自身被动不会考虑 + if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive) + { + continue; + } + + // 在回合结束时移除技能持续回合,而不是等时间流逝 + if (!effect.Durative && effect.DurationTurn > 0) + { + // 按回合移除特效 + effect.RemainDurationTurn--; + if (effect.RemainDurationTurn <= 0) + { + effect.RemainDurationTurn = 0; + character.Effects.Remove(effect); + effect.OnEffectLost(character); + } + } + } + + // 有人想要插队吗? + await WillPreCastSuperSkill(); + + // 回合结束事件 + await OnTurnEndAsync(character); + + await AfterTurnAsync(character); + + WriteLine(""); + return _isGameEnd; + } + + /// + /// 处理角色死亡 + /// + /// + protected async Task ProcessCharacterDeathAsync(Character character) + { + foreach (Character death in _roundDeaths) + { + if (!await OnCharacterDeathAsync(character, death)) + { + continue; + } + + // 给所有角色的特效广播角色死亡结算 + List effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.IsInEffect))]; + foreach (Effect effect in effects) + { + effect.AfterDeathCalculation(death, character, _continuousKilling, _earnedMoney); + } + // 将死者移出队列 + _queue.Remove(death); + + await AfterDeathCalculation(death, character); + } + } + + /// + /// 获取某角色的团队成员 + /// + /// + /// + protected virtual List GetTeammates(Character character) + { + return []; + } + + /// + /// 角色行动后触发 + /// + /// + /// + protected virtual async Task AfterCharacterAction(Character character, CharacterActionType type) + { + await Task.CompletedTask; + } + + /// + /// 死亡结算时触发 + /// + /// + /// + protected virtual async Task OnDeathCalculation(Character death, Character killer) + { + await Task.CompletedTask; + } + + /// + /// 死亡结算完成后触发 + /// + /// + /// + protected virtual async Task AfterDeathCalculation(Character death, Character killer) + { + if (!_queue.Where(c => c != killer).Any()) + { + // 没有其他的角色了,游戏结束 + WriteLine("[ " + killer + " ] 是胜利者。"); + _queue.Remove(killer); + _eliminated.Add(killer); + _isGameEnd = true; + await OnGameEndAsync(killer); + } + } + + /// + /// 回合开始前触发 + /// + /// + protected virtual async Task BeforeTurnAsync(Character character) + { + return await Task.FromResult(true); + } + + /// + /// 回合结束后触发 + /// + /// + protected virtual async Task AfterTurnAsync(Character character) + { + await Task.CompletedTask; + } + + #endregion + + #region 回合内-结算 + + /// + /// 对敌人造成伤害 + /// + /// + /// + /// + /// + /// + /// + /// + public async Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage = false, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal) + { + // 如果敌人在结算伤害之前就已经死亡,将不会继续下去 + if (enemy.HP <= 0) + { + return; + } + + if (!LastRound.IsCritical.TryAdd(enemy, damageResult == DamageResult.Critical) && damageResult == DamageResult.Critical) + { + LastRound.IsCritical[enemy] = true; + } + + List characters = [actor, enemy]; + bool isEvaded = damageResult == DamageResult.Evaded; + Dictionary totalDamageBonus = []; + List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + double damageBonus = effect.AlterActualDamageAfterCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult, ref isEvaded, totalDamageBonus); + totalDamageBonus[effect] = damageBonus; + if (isEvaded) + { + damageResult = DamageResult.Evaded; + } + } + damage += totalDamageBonus.Sum(kv => kv.Value); + + // 闪避了就没伤害了 + if (damageResult != DamageResult.Evaded) + { + // 计算伤害免疫 + bool ignore = false; + // 技能免疫无法免疫普通攻击,但是魔法免疫和物理免疫可以 + bool isImmune = (isNormalAttack && (enemy.ImmuneType == ImmuneType.All || enemy.ImmuneType == ImmuneType.Physical || enemy.ImmuneType == ImmuneType.Magical)) || + (!isNormalAttack && (enemy.ImmuneType == ImmuneType.All || enemy.ImmuneType == ImmuneType.Physical || enemy.ImmuneType == ImmuneType.Magical || enemy.ImmuneType == ImmuneType.Skilled)); + if (isImmune) + { + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + if (isNormalAttack) + { + if (actor.NormalAttack.IgnoreImmune == ImmuneType.All || + (!isMagicDamage && actor.NormalAttack.IgnoreImmune == ImmuneType.Physical) || + (isMagicDamage && actor.NormalAttack.IgnoreImmune == ImmuneType.Magical) || + !effect.OnDamageImmuneCheck(actor, enemy, isNormalAttack, isMagicDamage, magicType, damage)) + { + ignore = true; + } + } + else + { + if (!effect.OnDamageImmuneCheck(actor, enemy, isNormalAttack, isMagicDamage, magicType, damage)) + { + ignore = true; + } + } + } + } + + if (ignore) + { + // 无视免疫 + isImmune = false; + } + + if (isImmune) + { + // 免疫 + LastRound.IsImmune[enemy] = true; + WriteLine($"[ {enemy} ] 免疫了此伤害!"); + } + else + { + if (damage < 0) damage = 0; + + // 检查护盾 + double shield = enemy.Shield[isMagicDamage, magicType]; + string shieldMsg = ""; + if (shield > 0) + { + bool change = false; + + // 看特效有没有特殊护盾逻辑 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + if (!effect.BeforeShieldCalculation(actor, enemy, isMagicDamage, magicType, damage, shield, ref shieldMsg)) + { + change = true; + } + } + + if (!change) + { + double remain = shield - damage; + if (remain < 0) + { + remain = Math.Abs(remain); + enemy.Shield[isMagicDamage, magicType] = 0; + + change = false; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + if (!effect.OnShieldBroken(actor, enemy, isMagicDamage, magicType, damage, shield, remain)) + { + change = true; + } + } + + if (!change) + { + enemy.HP -= remain; + shieldMsg = $"(护盾抵消了 {shield:0.##} 点并破碎,角色承受了 {remain:0.##} 点)"; + } + else + { + shieldMsg = $"(护盾抵消了 {shield:0.##} 点并破碎,角色没有承受伤害)"; + } + } + else + { + enemy.Shield[isMagicDamage, magicType] = remain; + shieldMsg = $"(护盾抵消了 {damage:0.##} 点,剩余可用 {remain:0.##} 点)"; + } + } + else if (shieldMsg.Trim() == "") + { + shieldMsg = $"(护盾已使其无效化)"; + } + } + else enemy.HP -= damage; + + if (isMagicDamage) + { + string dmgType = CharacterSet.GetMagicDamageName(magicType); + WriteLine($"[ {enemy} ] 受到了 {damage:0.##} 点{dmgType}!{shieldMsg}"); + } + else WriteLine($"[ {enemy} ] 受到了 {damage:0.##} 点物理伤害!{shieldMsg}"); + + // 生命偷取 + double steal = damage * actor.Lifesteal; + await HealToTargetAsync(actor, actor, steal, false); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.AfterLifesteal(actor, enemy, damage, steal); + } + + // 造成伤害和受伤都可以获得能量 + double ep = GetEP(damage, GameplayEquilibriumConstant.DamageGetEPFactor, GameplayEquilibriumConstant.DamageGetEPMax); + effects = [.. actor.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterEPAfterDamage(actor, ref ep); + } + actor.EP += ep; + ep = GetEP(damage, GameplayEquilibriumConstant.TakenDamageGetEPFactor, GameplayEquilibriumConstant.TakenDamageGetEPMax); + effects = [.. enemy.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterEPAfterGetDamage(enemy, ref ep); + } + enemy.EP += ep; + + // 统计伤害 + CalculateCharacterDamageStatistics(actor, enemy, damage, isMagicDamage); + + // 计算助攻 + _assistDetail[actor][enemy, TotalTime] += damage; + } + } + else + { + LastRound.IsEvaded[enemy] = true; + } + + await OnDamageToEnemyAsync(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); + + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.AfterDamageCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); + } + + if (enemy.HP <= 0 && !_eliminated.Contains(enemy) && !_respawnCountdown.ContainsKey(enemy)) + { + LastRound.HasKill = true; + _roundDeaths.Add(enemy); + await DeathCalculationAsync(actor, enemy); + } + } + + /// + /// 治疗一个目标 + /// + /// + /// + /// + /// + public async Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false) + { + if (target.HP == target.MaxHP) + { + return; + } + + bool isDead = target.HP <= 0; + + Dictionary totalHealBonus = []; + List effects = [.. actor.Effects.Union(target.Effects).Distinct().Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + bool changeCanRespawn = false; + double healBonus = effect.AlterHealValueBeforeHealToTarget(actor, target, heal, ref changeCanRespawn, totalHealBonus); + if (changeCanRespawn && !canRespawn) + { + canRespawn = true; + } + } + heal += totalHealBonus.Sum(kv => kv.Value); + + if (heal <= 0) + { + return; + } + + if (target.HP > 0 || (isDead && canRespawn)) + { + target.HP += heal; + if (!LastRound.Heals.TryAdd(target, heal)) + { + LastRound.Heals[target] += heal; + } + } + + bool isRespawn = isDead && canRespawn; + if (isRespawn) + { + if (target != actor) + { + WriteLine($"[ {target} ] 被 [ {actor} ] 复苏了,并回复了 {heal:0.##} 点生命值!!"); + } + else + { + WriteLine($"[ {target} ] 复苏了,并回复了 {heal:0.##} 点生命值!!"); + } + await SetCharacterRespawn(target); + } + else + { + WriteLine($"[ {target} ] 回复了 {heal:0.##} 点生命值!"); + } + + // 添加助攻 + SetNotDamageAssistTime(actor, target); + + // 统计数据 + if (_stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null) + { + stats.TotalHeal += heal; + } + + await OnHealToTargetAsync(actor, target, heal, isRespawn); + } + + /// + /// 死亡结算 + /// + /// + /// + public async Task DeathCalculationAsync(Character killer, Character death) + { + if (!await OnDeathCalculationAsync(killer, death)) + { + return; + } + + if (!_continuousKilling.TryAdd(killer, 1)) _continuousKilling[killer] += 1; + if (!_maxContinuousKilling.TryAdd(killer, 1) && _continuousKilling[killer] > _maxContinuousKilling[killer]) + { + _maxContinuousKilling[killer] = _continuousKilling[killer]; + } + _stats[killer].Kills += 1; + _stats[death].Deaths += 1; + int money = Random.Shared.Next(250, 350); + + // 按伤害比分配金钱 只有造成 10% 伤害以上并且是在 30 秒内造成的伤害才能参与 + // 现在 20 秒内的非伤害类型辅助也能参与助攻了 + Character[] assists = [.. _assistDetail.Keys.Where(c => c != death && _assistDetail[c].GetPercentage(death) > 0.10 && + (_assistDetail[c].GetLastTime(death) - TotalTime <= 30 || _assistDetail[c].GetNotDamageAssistLastTime(killer) - TotalTime <= 20))]; + double totalDamagePercentage = _assistDetail.Keys.Where(assists.Contains).Select(c => _assistDetail[c].GetPercentage(death)).Sum(); + int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425); // 防止刷伤害设置金钱上限 + + // 分配金钱和累计助攻 + foreach (Character assist in assists) + { + int cmoney = Convert.ToInt32(_assistDetail[assist].GetPercentage(death) / totalDamagePercentage * totalMoney); + if (assist != killer) + { + if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney; + _stats[assist].Assists += 1; + } + else + { + money = cmoney; + } + } + + // 终结击杀的奖励仍然是全额的 + if (_continuousKilling.TryGetValue(death, out int coefficient) && coefficient > 1) + { + money += (coefficient + 1) * Random.Shared.Next(50, 100); + string termination = CharacterSet.GetContinuousKilling(coefficient); + string msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " 的" + termination : "")},获得 {money} {GameplayEquilibriumConstant.InGameCurrency}!"; + LastRound.DeathContinuousKilling.Add(msg); + if (assists.Length > 1) + { + msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]"; + } + WriteLine(msg); + } + else + { + string msg = $"[ {killer} ] 杀死了 [ {death} ],获得 {money} {GameplayEquilibriumConstant.InGameCurrency}!"; + LastRound.DeathContinuousKilling.Add(msg); + if (assists.Length > 1) + { + msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]"; + } + WriteLine(msg); + } + + if (FirstKiller is null) + { + FirstKiller = killer; + _stats[killer].FirstKills += 1; + _stats[death].FirstDeaths += 1; + money += 200; + string firstKill = $"[ {killer} ] 拿下了第一滴血!额外奖励 200 {GameplayEquilibriumConstant.InGameCurrency}!!"; + WriteLine(firstKill); + LastRound.ActorContinuousKilling.Add(firstKill); + } + + int kills = _continuousKilling[killer]; + string continuousKilling = CharacterSet.GetContinuousKilling(kills); + string actorContinuousKilling = ""; + if (kills == 2 || kills == 3) + { + actorContinuousKilling = "[ " + killer + " ] 完成了一次" + continuousKilling + "!"; + } + else if (kills == 4) + { + actorContinuousKilling = "[ " + killer + " ] 正在" + continuousKilling + "!"; + } + else if (kills > 4 && kills < 10) + { + actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "!"; + } + else if (kills >= 10) + { + actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "!拜托谁去杀了他吧!!!"; + } + if (actorContinuousKilling != "") + { + LastRound.ActorContinuousKilling.Add(actorContinuousKilling); + WriteLine(actorContinuousKilling); + } + + if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money; + + await OnDeathCalculation(death, killer); + + death.EP = 0; + + // 清除对死者的助攻数据 + List ads = [.. _assistDetail.Values.Where(ad => ad.Character != death)]; + foreach (AssistDetail ad in ads) + { + ad[death, 0] = 0; + } + + _continuousKilling.Remove(death); + if (MaxRespawnTimes == 0) + { + _eliminated.Add(death); + } + else if (_respawnTimes.TryGetValue(death, out int times) && MaxRespawnTimes != -1 && times == MaxRespawnTimes) + { + WriteLine($"[ {death} ] 已达到复活次数上限,将不能再复活!!"); + _eliminated.Add(death); + } + else + { + // 进入复活倒计时 + double respawnTime = Calculation.Round2Digits(Math.Min(30, death.Level * 0.15 + times * 0.87 + coefficient)); + _respawnCountdown.TryAdd(death, respawnTime); + LastRound.RespawnCountdowns.TryAdd(death, respawnTime); + WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} {GameplayEquilibriumConstant.InGameTime}!"); + } + + // 移除死者的施法 + _castingSkills.Remove(death); + _castingSuperSkills.Remove(death); + + // 因丢失目标而中断施法 + List castingSkills = [.. _castingSkills.Keys]; + foreach (Character caster in castingSkills) + { + SkillTarget st = _castingSkills[caster]; + if (st.Targets.Remove(death) && st.Targets.Count == 0) + { + _castingSkills.Remove(caster); + if (caster.CharacterState == CharacterState.Casting) + { + caster.CharacterState = CharacterState.Actionable; + } + WriteLine($"[ {caster} ] 终止了 [ {st.Skill.Name} ] 的施法" + (_hardnessTimes[caster] > 3 ? $",并获得了 3 {GameplayEquilibriumConstant.InGameTime}的硬直时间的补偿。" : "。")); + if (_hardnessTimes[caster] > 3) + { + AddCharacter(caster, 3, false); + } + } + } + } + + #endregion + + #region 回合内-辅助方法 + + /// + /// 使用物品实际逻辑 + /// + /// + /// + /// + /// + /// + public async Task UseItemAsync(Item item, Character character, List enemys, List teammates) + { + if (CheckCanCast(character, item, out double costMP, out double costEP)) + { + Skill? skill = item.Skills.Active; + if (skill != null) + { + List targets = await SelectTargetsAsync(character, skill, enemys, teammates); + if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0) + { + // 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标 + targets = [enemys[Random.Shared.Next(enemys.Count)]]; + } + if (targets.Count > 0) + { + // 免疫检定 + await CheckSkilledImmuneAsync(character, targets, skill, item); + + if (targets.Count > 0) + { + LastRound.Targets = [.. targets]; + + await OnCharacterUseItemAsync(character, item, targets); + + string line = $"[ {character} ] 使用了物品 [ {item.Name} ]!\r\n[ {character} ] "; + + skill.OnSkillCasting(this, character, targets); + skill.BeforeSkillCasted(); + + skill.CurrentCD = skill.RealCD; + skill.Enable = false; + + if (costMP > 0) + { + character.MP -= costMP; + LastRound.SkillCost = $"{-costMP:0.##} MP"; + line += $"消耗了 {costMP:0.##} 点魔法值,"; + } + + if (costEP > 0) + { + character.EP -= costEP; + if (LastRound.SkillCost != "") LastRound.SkillCost += " / "; + LastRound.SkillCost += $"{-costEP:0.##} EP"; + line += $"消耗了 {costEP:0.##} 点能量,"; + } + + line += $"释放了物品技能 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}"; + WriteLine(line); + + SkillTarget skillTarget = new(skill, targets); + await OnCharacterCastItemSkillAsync(character, item, skillTarget, costMP, costEP); + + skill.OnSkillCasted(this, character, targets); + return true; + } + } + } + } + return false; + } + + /// + /// 通过概率计算角色要干嘛 + /// + /// + /// + /// + /// + public static CharacterActionType GetActionType(double pUseItem, double pCastSkill, double pNormalAttack) + { + if (pUseItem == 0 && pCastSkill == 0 && pNormalAttack == 0) + { + return CharacterActionType.None; + } + + double total = pUseItem + pCastSkill + pNormalAttack; + + // 浮点数比较时处理误差 + if (Math.Abs(total - 1) > 1e-6) + { + pUseItem /= total; + pCastSkill /= total; + pNormalAttack /= total; + } + + double rand = Random.Shared.NextDouble(); + + // 按概率进行检查 + if (rand < pUseItem) + { + return CharacterActionType.UseItem; + } + + if (rand < pUseItem + pCastSkill) + { + return CharacterActionType.PreCastSkill; + } + + if (rand < pUseItem + pCastSkill + pNormalAttack) + { + return CharacterActionType.NormalAttack; + } + + return CharacterActionType.None; + } + + /// + /// 选取技能目标 + /// + /// + /// + /// + /// + /// + public async Task> SelectTargetsAsync(Character caster, Skill skill, List enemys, List teammates) + { + List effects = [.. caster.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates); + } + List targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates); + if (targets.Count == 0 && _charactersInAI.Contains(caster)) + { + targets = skill.SelectTargets(caster, enemys, teammates); + } + return targets; + } + + /// + /// 选取普通攻击目标 + /// + /// + /// + /// + /// + /// + public async Task> SelectTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates) + { + List effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates); + } + List targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates); + return targets; + } + + /// + /// 检查是否可以释放技能 + /// + /// + /// + /// + /// + public bool CheckCanCast(Character caster, Skill skill, out double cost) + { + if (skill.SkillType == SkillType.Magic) + { + cost = skill.RealMPCost; + if (cost > 0 && cost <= caster.MP) + { + return true; + } + else + { + WriteLine("[ " + caster + $" ] 魔法不足!"); + } + } + else + { + cost = skill.RealEPCost; + if (cost > 0 && cost <= caster.EP) + { + return true; + } + else + { + WriteLine("[ " + caster + $" ] 能量不足!"); + } + } + return false; + } + + /// + /// 检查是否可以释放技能(物品版) + /// + /// + /// + /// + /// + /// + public bool CheckCanCast(Character caster, Item item, out double costMP, out double costEP) + { + Skill? skill = item.Skills.Active; + if (skill is null) + { + costMP = 0; + costEP = 0; + return false; + } + costMP = skill.RealMPCost; + costEP = skill.RealEPCost; + bool isMPOk = false; + bool isEPOk = false; + if (costMP > 0 && costMP <= caster.MP) + { + isMPOk = true; + } + else + { + WriteLine("[ " + caster + $" ] 魔法不足!"); + } + costEP = skill.RealEPCost; + if (costEP > 0 && costEP <= caster.EP) + { + isEPOk = true; + } + else + { + WriteLine("[ " + caster + $" ] 能量不足!"); + } + return isMPOk && isEPOk; + } + + /// + /// 计算物理伤害 + /// + /// + /// + /// + /// + /// + /// + /// + public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage, ref int changeCount) + { + List characters = [actor, enemy]; + bool isMagic = false; + MagicType magicType = MagicType.None; + List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + if (changeCount < 3) + { + foreach (Effect effect in effects) + { + effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); + } + if (isMagic) + { + changeCount++; + return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage, ref changeCount); + } + } + + Dictionary totalDamageBonus = []; + effects = [.. actor.Effects.Union(enemy.Effects).Distinct().Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, false, MagicType.None, totalDamageBonus); + totalDamageBonus[effect] = damageBonus; + } + expectedDamage += totalDamageBonus.Sum(kv => kv.Value); + + double dice = Random.Shared.NextDouble(); + double throwingBonus = 0; + bool checkEvade = true; + bool checkCritical = true; + if (isNormalAttack) + { + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + checkEvade = effect.BeforeEvadeCheck(actor, enemy, ref throwingBonus); + } + + if (checkEvade) + { + // 闪避检定 + if (dice < (enemy.EvadeRate + throwingBonus)) + { + finalDamage = 0; + bool isAlterEvaded = false; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + if (effect.OnEvadedTriggered(actor, enemy, dice)) + { + isAlterEvaded = true; + } + } + if (!isAlterEvaded) + { + WriteLine("此物理攻击被完美闪避了!"); + return DamageResult.Evaded; + } + } + } + } + + // 物理穿透后的护甲 + double penetratedDEF = (1 - actor.PhysicalPenetration) * enemy.DEF; + + // 物理伤害减免 + double physicalDamageReduction = penetratedDEF / (penetratedDEF + GameplayEquilibriumConstant.DEFReductionFactor); + + // 最终的物理伤害 + finalDamage = expectedDamage * (1 - Calculation.PercentageCheck(physicalDamageReduction + enemy.ExPDR)); + + // 暴击检定 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + checkCritical = effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus); + } + + if (checkCritical) + { + dice = Random.Shared.NextDouble(); + if (dice < (actor.CritRate + throwingBonus)) + { + finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 + WriteLine("暴击生效!!"); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.OnCriticalDamageTriggered(actor, enemy, dice); + } + return DamageResult.Critical; + } + } + + // 是否有效伤害 + return DamageResult.Normal; + } + + /// + /// 计算魔法伤害 + /// + /// + /// + /// + /// + /// + /// + /// + /// + public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage, ref int changeCount) + { + List characters = [actor, enemy]; + bool isMagic = true; + List effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + if (changeCount < 3) + { + foreach (Effect effect in effects) + { + effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType); + } + if (!isMagic) + { + changeCount++; + return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage, ref changeCount); + } + } + + Dictionary totalDamageBonus = []; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + double damageBonus = effect.AlterExpectedDamageBeforeCalculation(actor, enemy, expectedDamage, isNormalAttack, true, magicType, totalDamageBonus); + totalDamageBonus[effect] = damageBonus; + } + expectedDamage += totalDamageBonus.Sum(kv => kv.Value); + + double dice = Random.Shared.NextDouble(); + double throwingBonus = 0; + bool checkEvade = true; + bool checkCritical = true; + if (isNormalAttack) + { + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + checkEvade = effect.BeforeEvadeCheck(actor, enemy, ref throwingBonus); + } + + if (checkEvade) + { + // 闪避检定 + if (dice < (enemy.EvadeRate + throwingBonus)) + { + finalDamage = 0; + bool isAlterEvaded = false; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + if (effect.OnEvadedTriggered(actor, enemy, dice)) + { + isAlterEvaded = true; + } + } + if (!isAlterEvaded) + { + WriteLine("此魔法攻击被完美闪避了!"); + return DamageResult.Evaded; + } + } + } + } + + double MDF = enemy.MDF[magicType]; + + // 魔法穿透后的魔法抗性 + MDF = (1 - actor.MagicalPenetration) * MDF; + + // 最终的魔法伤害 + finalDamage = expectedDamage * (1 - MDF); + + // 暴击检定 + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + checkCritical = effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus); + } + + if (checkCritical) + { + dice = Random.Shared.NextDouble(); + if (dice < (actor.CritRate + throwingBonus)) + { + finalDamage *= actor.CritDMG; // 暴击伤害倍率加成 + WriteLine("暴击生效!!"); + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.OnCriticalDamageTriggered(actor, enemy, dice); + } + return DamageResult.Critical; + } + } + + // 是否有效伤害 + return DamageResult.Normal; + } + + /// + /// 获取EP + /// + /// 参数1 + /// 参数2 + /// 最大获取量 + public static double GetEP(double a, double b, double max) + { + return Math.Min((a + Random.Shared.Next(30)) * b, max); + } + + #endregion + + #region 回合奖励 + + /// + /// 初始化回合奖励 + /// + /// 最大回合数 + /// 每个奖励回合生成多少技能 + /// key: 特效的数字标识符;value: 是否是主动技能的特效 + /// 通过数字标识符来获取构造特效的参数 + /// + public void InitRoundRewards(int maxRound, int maxRewardsInRound, Dictionary effects, Func>? factoryEffects = null) + { + _roundRewards.Clear(); + int currentRound = 1; + long[] effectIDs = [.. effects.Keys]; + while (currentRound <= maxRound) + { + currentRound += Random.Shared.Next(1, 9); + + if (currentRound <= maxRound) + { + List skills = []; + if (maxRewardsInRound <= 0) maxRewardsInRound = 1; + + do + { + long effectID = effectIDs[Random.Shared.Next(effects.Count)]; + Dictionary args = []; + if (effects[effectID]) + { + args.Add("active", true); + args.Add("self", true); + args.Add("enemy", false); + } + Skill skill = Factory.OpenFactory.GetInstance(effectID, "", args); + Dictionary effectArgs = factoryEffects != null ? factoryEffects(effectID) : []; + args.Clear(); + args.Add("skill", skill); + args.Add("values", effectArgs); + Effect effect = Factory.OpenFactory.GetInstance(effectID, "", args); + skill.Effects.Add(effect); + skill.Name = $"[R] {effect.Name}"; + skills.Add(skill); + } + while (skills.Count < maxRewardsInRound); + + _roundRewards[currentRound] = skills; + } + } + } + + /// + /// 获取回合奖励 + /// + /// + /// + /// + protected List GetRoundRewards(int round, Character character) + { + if (_roundRewards.TryGetValue(round, out List? value) && value is List list && list.Count > 0) + { + foreach (Skill skill in list) + { + skill.GamingQueue = this; + skill.Character = character; + skill.Level = 1; + LastRound.RoundRewards.Add(skill); + WriteLine($"[ {character} ] 获得了回合奖励!{skill.Description}".Trim()); + if (skill.IsActive) + { + LastRound.Targets.Add(character); + skill.OnSkillCasted(this, character, [character]); + } + else + { + character.Skills.Add(skill); + } + } + return list; + } + return []; + } + + /// + /// 移除回合奖励 + /// + /// + /// + protected static void RemoveRoundRewards(Character character, List skills) + { + foreach (Skill skill in skills) + { + foreach (Effect e in skill.Effects) + { + e.OnEffectLost(character); + character.Effects.Remove(e); + } + character.Skills.Remove(skill); + } + } + + #endregion + + #region 回合外 + + /// + /// 是否在回合外释放爆发技插队(仅自动化,手动设置请调用:) + /// + /// + protected async Task WillPreCastSuperSkill() + { + // 选取所有 AI 控制角色 + foreach (Character other in _queue.Where(c => c.CharacterState == CharacterState.Actionable && _charactersInAI.Contains(c)).ToList()) + { + // 有 65% 欲望插队 + if (Random.Shared.NextDouble() < 0.65) + { + List skills = [.. other.Skills.Where(s => s.Level > 0 && s.SkillType == SkillType.SuperSkill && s.Enable && !s.IsInEffect && s.CurrentCD == 0 && other.EP >= s.RealEPCost)]; + if (skills.Count > 0) + { + Skill skill = skills[Random.Shared.Next(skills.Count)]; + await SetCharacterPreCastSuperSkill(other, skill); + } + } + } + } + + #endregion + + #region 回合外-辅助方法 + + /// + /// 打断施法 + /// + /// + /// + public async Task InterruptCastingAsync(Character caster, Character interrupter) + { + Skill? skill = null; + if (_castingSkills.TryGetValue(caster, out SkillTarget target)) + { + skill = target.Skill; + _castingSkills.Remove(caster); + } + if (skill is null && caster.CharacterState == CharacterState.PreCastSuperSkill) + { + WriteLine($"因 [ {caster} ] 的预释放爆发技状态不可驱散,[ {interrupter} ] 打断失败!!"); + } + if (skill != null) + { + WriteLine($"[ {caster} ] 的施法被 [ {interrupter} ] 打断了!!"); + List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.OnSkillCastInterrupted(caster, skill, interrupter); + } + await OnInterruptCastingAsync(caster, skill, interrupter); + } + } + + /// + /// 打断施法 [ 用于使敌人目标丢失 ] + /// + /// + public async Task InterruptCastingAsync(Character interrupter) + { + foreach (Character caster in _castingSkills.Keys) + { + SkillTarget skillTarget = _castingSkills[caster]; + if (skillTarget.Targets.Contains(interrupter)) + { + Skill skill = skillTarget.Skill; + WriteLine($"[ {interrupter} ] 打断了 [ {caster} ] 的施法!!"); + List effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.OnSkillCastInterrupted(caster, skill, interrupter); + } + await OnInterruptCastingAsync(caster, skill, interrupter); + } + } + } + + /// + /// 设置角色复活 + /// + /// + public async Task SetCharacterRespawn(Character character) + { + double hardnessTime = 5; + character.Respawn(_original[character.Guid]); + WriteLine($"[ {character} ] 已复活!获得 {hardnessTime} {GameplayEquilibriumConstant.InGameTime}的硬直时间。"); + AddCharacter(character, hardnessTime, false); + await OnQueueUpdatedAsync(_queue, character, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。"); + LastRound.Respawns.Add(character); + _respawnCountdown.Remove(character); + if (!_respawnTimes.TryAdd(character, 1)) + { + _respawnTimes[character] += 1; + } + } + + /// + /// 设置角色将预释放爆发技 + /// + /// + /// + public async Task SetCharacterPreCastSuperSkill(Character character, Skill skill) + { + if (character.CharacterState == CharacterState.Actionable) + { + _castingSuperSkills[character] = skill; + character.CharacterState = CharacterState.PreCastSuperSkill; + _queue.Remove(character); + _cutCount.Remove(character); + WriteLine("[ " + character + " ] 预释放了爆发技!!"); + int preCastSSCount = 0; + double baseHardnessTime = 0; + foreach (Character c in _hardnessTimes.Keys) + { + if (c.CharacterState != CharacterState.PreCastSuperSkill) + { + _hardnessTimes[c] = Calculation.Round2Digits(_hardnessTimes[c] + 0.01); + } + else if (c != character) + { + if (preCastSSCount == 0) + { + baseHardnessTime = _hardnessTimes[c]; + } + preCastSSCount++; + } + } + AddCharacter(character, Calculation.Round2Digits(baseHardnessTime + preCastSSCount * 0.01), false); + await OnQueueUpdatedAsync(_queue, character, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。"); + skill.OnSkillCasting(this, character, []); + } + } + + /// + /// 设置角色对目标们的非伤害辅助时间 + /// + /// + /// + public void SetNotDamageAssistTime(Character character, params IEnumerable targets) + { + foreach (Character target in targets) + { + _assistDetail[character].NotDamageAssistLastTime[target] = TotalTime; + } + } + + /// + /// 修改角色的硬直时间 + /// + /// 角色 + /// 加值 + /// 是否是百分比 + /// 是否使用插队保护机制 + public void ChangeCharacterHardnessTime(Character character, double addValue, bool isPercentage, bool isCheckProtected) + { + double hardnessTime = _hardnessTimes[character]; + if (isPercentage) + { + addValue = hardnessTime * addValue; + } + hardnessTime += addValue; + if (hardnessTime <= 0) hardnessTime = 0; + AddCharacter(character, hardnessTime, isCheckProtected); + } + + #endregion + + #region 自动化 + + /// + /// 设置角色为 AI 控制 + /// + /// + /// + public void SetCharactersToAIControl(bool cancel = false, params IEnumerable characters) + { + foreach (Character character in characters) + { + if (cancel) + { + _charactersInAI.Remove(character); + } + else + { + _charactersInAI.Add(character); + } + } + } + + /// + /// 检查角色是否在 AI 控制状态 + /// + /// + /// + public bool IsCharacterInAIControlling(Character character) + { + return _charactersInAI.Contains(character); + } + + #endregion + + #region 检定 + + /// + /// 免疫检定 + /// + /// + /// + /// + /// + /// + protected async Task CheckSkilledImmuneAsync(Character character, List targets, Skill skill, Item? item = null) + { + Character[] loop = [.. targets]; + foreach (Character target in loop) + { + bool ignore = false; + bool isImmune = target.ImmuneType == ImmuneType.Magical || target.ImmuneType == ImmuneType.Skilled || target.ImmuneType == ImmuneType.All; + if (isImmune) + { + Character[] characters = [character, target]; + Effect[] effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + // 自带无视免疫或者特效免疫检定不通过可无视免疫 + if (effect.IgnoreImmune == ImmuneType.All || effect.IgnoreImmune == ImmuneType.Skilled || (skill.IsMagic && effect.IgnoreImmune == ImmuneType.Magical) || !effect.OnImmuneCheck(character, target, skill, item)) + { + ignore = true; + } + } + } + if (ignore) + { + isImmune = false; + targets.Remove(target); + } + if (isImmune) + { + WriteLine($"[ {target} ] 免疫了此技能!"); + await OnCharacterImmunedAsync(character, target, skill, item); + } + } + await Task.CompletedTask; + return; + } + + #endregion + + #region 数据统计 + + /// + /// 计算角色的数据 + /// + public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, bool isMagic) + { + if (_stats.TryGetValue(character, out CharacterStatistics? stats) && stats != null) + { + if (isMagic) + { + stats.TotalMagicDamage += damage; + } + else + { + stats.TotalPhysicalDamage += damage; + } + stats.TotalDamage += damage; + } + if (_stats.TryGetValue(characterTaken, out CharacterStatistics? statsTaken) && statsTaken != null) + { + if (isMagic) + { + statsTaken.TotalTakenMagicDamage = Calculation.Round2Digits(statsTaken.TotalTakenMagicDamage + damage); + } + else + { + statsTaken.TotalTakenPhysicalDamage = Calculation.Round2Digits(statsTaken.TotalTakenPhysicalDamage + damage); + } + statsTaken.TotalTakenDamage = Calculation.Round2Digits(statsTaken.TotalTakenDamage + damage); + } + if (LastRound.Damages.TryGetValue(characterTaken, out double damageTotal)) + { + LastRound.Damages[characterTaken] = damageTotal + damage; + } + else + { + LastRound.Damages[characterTaken] = damage; + } + } + + #endregion + + #region 其他辅助方法 + + /// + /// 装备物品 + /// + /// + /// + public void Equip(Character character, Item item) + { + if (character.Equip(item)) + { + EquipSlotType type = item.EquipSlotType; + WriteLine($"[ {character} ] 装备了 [ {item.Name} ]。" + (type != EquipSlotType.None ? $"({ItemSet.GetEquipSlotTypeName(type)} 栏位)" : "")); + } + } + + /// + /// 装备物品到指定栏位,并返回被替换的装备(如果有的话) + /// + /// + /// + /// + /// + public void Equip(Character character, EquipSlotType type, Item item, out Item? previous) + { + if (character.Equip(item, type, out previous)) + { + WriteLine($"[ {character} ] 装备了 [ {item.Name} ]。({ItemSet.GetEquipSlotTypeName(type)} 栏位)"); + } + } + + /// + /// 取消装备,并返回被替换的装备(如果有的话) + /// + /// + /// + /// + public Item? UnEquip(Character character, EquipSlotType type) + { + Item? item = character.UnEquip(type); + if (item != null) + { + WriteLine($"[ {character} ] 取消装备了 [ {item.Name} ]。({ItemSet.GetEquipSlotTypeName(type)} 栏位)"); + } + return item; + } + + #endregion + + #region 事件 + + public delegate Task TurnStartEventHandler(GamingQueue queue, Character character, List enemys, List teammates, List skills, List items); + /// + /// 回合开始事件 + /// + public event TurnStartEventHandler? TurnStart; + /// + /// 回合开始事件 + /// + /// + /// + /// + /// + /// + /// + protected async Task OnTurnStartAsync(Character character, List enemys, List teammates, List skills, List items) + { + return await (TurnStart?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(true)); + } + + public delegate Task TurnEndEventHandler(GamingQueue queue, Character character); + /// + /// 回合结束事件 + /// + public event TurnEndEventHandler? TurnEnd; + /// + /// 回合结束事件 + /// + /// + /// + protected async Task OnTurnEndAsync(Character character) + { + await (TurnEnd?.Invoke(this, character) ?? Task.CompletedTask); + } + + public delegate Task DecideActionEventHandler(GamingQueue queue, Character character, List enemys, List teammates, List skills, List items); + /// + /// 决定角色的行动事件 + /// + public event DecideActionEventHandler? DecideAction; + /// + /// 决定角色的行动事件 + /// + /// + /// + /// + /// + /// + /// + protected async Task OnDecideActionAsync(Character character, List enemys, List teammates, List skills, List items) + { + return await (DecideAction?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(CharacterActionType.None)); + } + + public delegate Task SelectSkillEventHandler(GamingQueue queue, Character character, List skills); + /// + /// 角色需要选择一个技能 + /// + public event SelectSkillEventHandler? SelectSkill; + /// + /// 角色需要选择一个技能 + /// + /// + /// + /// + protected async Task OnSelectSkillAsync(Character character, List skills) + { + return await (SelectSkill?.Invoke(this, character, skills) ?? Task.FromResult(null)); + } + + public delegate Task SelectItemEventHandler(GamingQueue queue, Character character, List items); + /// + /// 角色需要选择一个物品 + /// + public event SelectItemEventHandler? SelectItem; + /// + /// 角色需要选择一个物品 + /// + /// + /// + /// + protected async Task OnSelectItemAsync(Character character, List items) + { + return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult(null)); + } + + public delegate Task> SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List enemys, List teammates); + /// + /// 选取技能目标事件 + /// + public event SelectSkillTargetsEventHandler? SelectSkillTargets; + /// + /// 选取技能目标事件 + /// + /// + /// + /// + /// + /// + protected async Task> OnSelectSkillTargetsAsync(Character caster, Skill skill, List enemys, List teammates) + { + return await (SelectSkillTargets?.Invoke(this, caster, skill, enemys, teammates) ?? Task.FromResult(new List())); + } + + public delegate Task> SelectNormalAttackTargetsEventHandler(GamingQueue queue, Character character, NormalAttack attack, List enemys, List teammates); + /// + /// 选取普通攻击目标事件 + /// + public event SelectNormalAttackTargetsEventHandler? SelectNormalAttackTargets; + /// + /// 选取普通攻击目标事件 + /// + /// + /// + /// + /// + /// + protected async Task> OnSelectNormalAttackTargetsAsync(Character character, NormalAttack attack, List enemys, List teammates) + { + return await (SelectNormalAttackTargets?.Invoke(this, character, attack, enemys, teammates) ?? Task.FromResult(new List())); + } + + public delegate Task InterruptCastingEventHandler(GamingQueue queue, Character cast, Skill? skill, Character interrupter); + /// + /// 打断施法事件 + /// + public event InterruptCastingEventHandler? InterruptCasting; + /// + /// 打断施法事件 + /// + /// + /// + /// + /// + protected async Task OnInterruptCastingAsync(Character cast, Skill skill, Character interrupter) + { + await (InterruptCasting?.Invoke(this, cast, skill, interrupter) ?? Task.CompletedTask); + } + + public delegate Task DeathCalculationEventHandler(GamingQueue queue, Character killer, Character death); + /// + /// 死亡结算事件 + /// + public event DeathCalculationEventHandler? DeathCalculation; + /// + /// 死亡结算事件 + /// + /// + /// + /// + protected async Task OnDeathCalculationAsync(Character killer, Character death) + { + return await (DeathCalculation?.Invoke(this, killer, death) ?? Task.FromResult(true)); + } + + public delegate Task CharacterDeathEventHandler(GamingQueue queue, Character current, Character death); + /// + /// 角色死亡事件,此事件位于 之后 + /// + public event CharacterDeathEventHandler? CharacterDeath; + /// + /// 角色死亡事件,此事件位于 之后 + /// + /// + /// + /// + protected async Task OnCharacterDeathAsync(Character current, Character death) + { + return await (CharacterDeath?.Invoke(this, current, death) ?? Task.FromResult(true)); + } + + public delegate Task HealToTargetEventHandler(GamingQueue queue, Character actor, Character target, double heal, bool isRespawn); + /// + /// 治疗事件 + /// + public event HealToTargetEventHandler? HealToTarget; + /// + /// 治疗事件 + /// + /// + /// + /// + /// + /// + protected async Task OnHealToTargetAsync(Character actor, Character target, double heal, bool isRespawn) + { + await (HealToTarget?.Invoke(this, actor, target, heal, isRespawn) ?? Task.CompletedTask); + } + + public delegate Task DamageToEnemyEventHandler(GamingQueue queue, Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, DamageResult damageResult); + /// + /// 造成伤害事件 + /// + public event DamageToEnemyEventHandler? DamageToEnemy; + /// + /// 造成伤害事件 + /// + /// + /// + /// + /// + /// + /// + /// + /// + protected async Task OnDamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, DamageResult damageResult) + { + await (DamageToEnemy?.Invoke(this, actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult) ?? Task.CompletedTask); + } + + public delegate Task CharacterNormalAttackEventHandler(GamingQueue queue, Character actor, List targets); + /// + /// 角色普通攻击事件 + /// + public event CharacterNormalAttackEventHandler? CharacterNormalAttack; + /// + /// 角色普通攻击事件 + /// + /// + /// + /// + protected async Task OnCharacterNormalAttackAsync(Character actor, List targets) + { + await (CharacterNormalAttack?.Invoke(this, actor, targets) ?? Task.CompletedTask); + } + + public delegate Task CharacterPreCastSkillEventHandler(GamingQueue queue, Character actor, SkillTarget skillTarget); + /// + /// 角色吟唱技能事件(包括直接释放战技) + /// + public event CharacterPreCastSkillEventHandler? CharacterPreCastSkill; + /// + /// 角色吟唱技能事件(包括直接释放战技) + /// + /// + /// + /// + protected async Task OnCharacterPreCastSkillAsync(Character actor, SkillTarget skillTarget) + { + await (CharacterPreCastSkill?.Invoke(this, actor, skillTarget) ?? Task.CompletedTask); + } + + public delegate Task CharacterCastSkillEventHandler(GamingQueue queue, Character actor, SkillTarget skillTarget, double cost); + /// + /// 角色释放技能事件 + /// + public event CharacterCastSkillEventHandler? CharacterCastSkill; + /// + /// 角色释放技能事件 + /// + /// + /// + /// + /// + protected async Task OnCharacterCastSkillAsync(Character actor, SkillTarget skillTarget, double cost) + { + await (CharacterCastSkill?.Invoke(this, actor, skillTarget, cost) ?? Task.CompletedTask); + } + + public delegate Task CharacterUseItemEventHandler(GamingQueue queue, Character actor, Item item, List targets); + /// + /// 角色使用物品事件 + /// + public event CharacterUseItemEventHandler? CharacterUseItem; + /// + /// 角色使用物品事件 + /// + /// + /// + /// + /// + protected async Task OnCharacterUseItemAsync(Character actor, Item item, List targets) + { + await (CharacterUseItem?.Invoke(this, actor, item, targets) ?? Task.CompletedTask); + } + + public delegate Task CharacterCastItemSkillEventHandler(GamingQueue queue, Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP); + /// + /// 角色释放物品的技能事件 + /// + public event CharacterCastItemSkillEventHandler? CharacterCastItemSkill; + /// + /// 角色释放物品的技能事件 + /// + /// + /// + /// + /// + /// + /// + protected async Task OnCharacterCastItemSkillAsync(Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP) + { + await (CharacterCastItemSkill?.Invoke(this, actor, item, skillTarget, costMP, costEP) ?? Task.CompletedTask); + } + + public delegate Task CharacterImmunedEventHandler(GamingQueue queue, Character character, Character immune, ISkill skill, Item? item = null); + /// + /// 角色免疫事件 + /// + public event CharacterImmunedEventHandler? CharacterImmuned; + /// + /// 角色免疫事件 + /// + /// + /// + /// + /// + /// + protected async Task OnCharacterImmunedAsync(Character character, Character immune, ISkill skill, Item? item = null) + { + await (CharacterImmuned?.Invoke(this, character, immune, skill, item) ?? Task.CompletedTask); + } + + public delegate Task CharacterDoNothingEventHandler(GamingQueue queue, Character actor); + /// + /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) + /// + public event CharacterDoNothingEventHandler? CharacterDoNothing; + /// + /// 角色主动结束回合事件(区别于放弃行动,这个是主动的) + /// + /// + /// + protected async Task OnCharacterDoNothingAsync(Character actor) + { + await (CharacterDoNothing?.Invoke(this, actor) ?? Task.CompletedTask); + } + + public delegate Task CharacterGiveUpEventHandler(GamingQueue queue, Character actor); + /// + /// 角色放弃行动事件 + /// + public event CharacterGiveUpEventHandler? CharacterGiveUp; + /// + /// 角色放弃行动事件 + /// + /// + /// + protected async Task OnCharacterGiveUpAsync(Character actor) + { + await (CharacterGiveUp?.Invoke(this, actor) ?? Task.CompletedTask); + } + + public delegate Task GameEndEventHandler(GamingQueue queue, Character winner); + /// + /// 游戏结束事件 + /// + public event GameEndEventHandler? GameEnd; + /// + /// 游戏结束事件 + /// + /// + /// + protected async Task OnGameEndAsync(Character winner) + { + return await (GameEnd?.Invoke(this, winner) ?? Task.FromResult(true)); + } + + public delegate Task QueueUpdatedEventHandler(GamingQueue queue, List characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg); + /// + /// 行动顺序表更新事件 + /// + public event QueueUpdatedEventHandler? QueueUpdated; + /// + /// 行动顺序表更新事件 + /// + /// + /// + /// + /// + /// + /// + protected async Task OnQueueUpdatedAsync(List characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg = "") + { + await (QueueUpdated?.Invoke(this, characters, character, hardnessTime, reason, msg) ?? Task.CompletedTask); + } + + #endregion + } +} diff --git a/Model/MixGamingQueue.cs b/Model/MixGamingQueue.cs new file mode 100644 index 0000000..e9627c0 --- /dev/null +++ b/Model/MixGamingQueue.cs @@ -0,0 +1,120 @@ +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Model +{ + public class MixGamingQueue : GamingQueue + { + /// + /// 死亡结算后 + /// + /// + /// + /// + protected override async Task AfterDeathCalculation(Character death, Character killer) + { + if (MaxRespawnTimes != 0 && MaxScoreToWin > 0) + { + WriteLine($"\r\n=== 当前死亡竞赛比分 ===\r\n{string.Join("\r\n", _stats.OrderByDescending(kv => kv.Value.Kills) + .Select(kv => $"[ {kv.Key} ] {kv.Value.Kills} 分"))}\r\n剩余存活人数:{_queue.Count}"); + } + + if (!_queue.Where(c => c != killer).Any()) + { + // 没有其他的角色了,游戏结束 + await EndGameInfo(killer); + } + + if (MaxScoreToWin > 0 && _stats[killer].Kills >= MaxScoreToWin) + { + await EndGameInfo(killer); + return; + } + } + + /// + /// 游戏结束信息 + /// + public async Task EndGameInfo(Character winner) + { + WriteLine("[ " + winner + " ] 是胜利者。"); + foreach (Character character in _stats.OrderBy(kv => kv.Value.Kills) + .ThenByDescending(kv => kv.Value.Deaths) + .ThenBy(kv => kv.Value.Assists).Select(kv => kv.Key)) + { + if (character != winner && !_eliminated.Contains(character)) + { + _eliminated.Add(character); + } + } + _eliminated.Add(winner); + _queue.Clear(); + _isGameEnd = true; + + if (!await OnGameEndAsync(winner)) + { + return; + } + + int top = 1; + WriteLine(""); + WriteLine("=== 排名 ==="); + for (int i = _eliminated.Count - 1; i >= 0; i--) + { + Character ec = _eliminated[i]; + CharacterStatistics statistics = CharacterStatistics[ec]; + string topCharacter = ec.ToString() + + (statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") + + (_maxContinuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") + + (_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {GameplayEquilibriumConstant.InGameCurrency} ]" : ""); + if (top == 1) + { + WriteLine("冠军:" + topCharacter); + _stats[ec].Wins += 1; + _stats[ec].Top3s += 1; + } + else if (top == 2) + { + WriteLine("亚军:" + topCharacter); + _stats[ec].Loses += 1; + _stats[ec].Top3s += 1; + } + else if (top == 3) + { + WriteLine("季军:" + topCharacter); + _stats[ec].Loses += 1; + _stats[ec].Top3s += 1; + } + else + { + WriteLine($"第 {top} 名:" + topCharacter); + _stats[ec].Loses += 1; + } + _stats[ec].Plays += 1; + _stats[ec].TotalEarnedMoney += earned; + _stats[ec].LastRank = top; + top++; + } + WriteLine(""); + } + + /// + /// 创建一个混战游戏队列 + /// + /// + public MixGamingQueue(Action? writer = null) : base(writer) + { + + } + + /// + /// 创建一个混战游戏队列并初始化行动顺序表 + /// + /// + /// + public MixGamingQueue(List characters, Action? writer = null) : base(characters, writer) + { + + } + } +} diff --git a/Model/TeamGamingQueue.cs b/Model/TeamGamingQueue.cs new file mode 100644 index 0000000..2c9efa2 --- /dev/null +++ b/Model/TeamGamingQueue.cs @@ -0,0 +1,312 @@ +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Model +{ + public class TeamGamingQueue : GamingQueue + { + /// + /// 当前团灭的团队顺序(第一个是最早死的) + /// + public List EliminatedTeams => _eliminatedTeams; + + /// + /// 团队及其成员 + /// + public Dictionary Teams => _teams; + + /// + /// 当前团灭的团队顺序(第一个是最早死的) + /// + protected readonly List _eliminatedTeams = []; + + /// + /// 团队及其成员 + /// + protected readonly Dictionary _teams = []; + + /// + /// 添加一个团队 + /// + /// + /// + public void AddTeam(string teamName, IEnumerable characters) + { + if (teamName != "" && characters.Any()) + { + _teams.Add(teamName, new(teamName, characters)); + } + } + + /// + /// 获取角色的团队 + /// + /// + public Team? GetTeam(Character character) + { + foreach (Team team in _teams.Values) + { + if (team.IsOnThisTeam(character)) + { + return team; + } + } + return null; + } + + /// + /// 从已淘汰的团队中获取角色的团队 + /// + /// + public Team? GetTeamFromEliminated(Character character) + { + foreach (Team team in _eliminatedTeams) + { + if (team.IsOnThisTeam(character)) + { + return team; + } + } + return null; + } + + /// + /// 获取某角色的团队成员 + /// + /// + protected override List GetTeammates(Character character) + { + foreach (string team in _teams.Keys) + { + if (_teams[team].IsOnThisTeam(character)) + { + return _teams[team].GetTeammates(character); + } + } + return []; + } + + /// + /// 角色行动后 + /// + /// + /// + /// + protected override async Task AfterCharacterAction(Character character, CharacterActionType type) + { + // 如果目标都是队友,会考虑非伤害型助攻 + Team? team = GetTeam(character); + if (team != null) + { + SetNotDamageAssistTime(character, LastRound.Targets.Where(team.IsOnThisTeam)); + } + else await Task.CompletedTask; + } + + /// + /// 死亡结算时 + /// + /// + /// + /// + protected override async Task OnDeathCalculation(Character death, Character killer) + { + Team? team = GetTeam(killer); + if (team != null) + { + team.Score++; + } + else await Task.CompletedTask; + } + + /// + /// 死亡结算后 + /// + /// + /// + /// + protected override async Task AfterDeathCalculation(Character death, Character killer) + { + Team? killTeam = GetTeam(killer); + Team? deathTeam = GetTeam(death); + + if (MaxRespawnTimes != 0) + { + string[] teamActive = [.. Teams.OrderByDescending(kv => kv.Value.Score).Select(kv => + { + int activeCount = kv.Value.GetActiveCharacters(this).Count; + if (kv.Value == killTeam) + { + activeCount += 1; + } + return kv.Key + ":" + kv.Value.Score + "(剩余存活人数:" + activeCount + ")"; + })]; + WriteLine($"\r\n=== 当前死亡竞赛比分 ===\r\n{string.Join("\r\n", teamActive)}"); + } + + if (deathTeam != null) + { + List remain = deathTeam.GetActiveCharacters(this); + int remainCount = remain.Count; + if (remainCount == 0) + { + // 团灭了 + _eliminatedTeams.Add(deathTeam); + _teams.Remove(deathTeam.Name); + } + else if (MaxRespawnTimes == 0) + { + WriteLine($"[ {deathTeam} ] 剩余成员:[ {string.Join(" ] / [ ", remain)} ]({remainCount} 人)"); + } + } + + if (killTeam != null) + { + List actives = killTeam.GetActiveCharacters(this); + actives.Add(killer); + int remainCount = actives.Count; + if (remainCount > 0 && MaxRespawnTimes == 0) + { + WriteLine($"[ {killTeam} ] 剩余成员:[ {string.Join(" ] / [ ", actives)} ]({remainCount} 人)"); + } + if (!_teams.Keys.Where(str => str != killTeam.Name).Any()) + { + // 没有其他的团队了,游戏结束 + await EndGameInfo(killTeam); + return; + } + if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin) + { + List combinedTeams = [.. _eliminatedTeams, .. _teams.Values]; + combinedTeams.Remove(killTeam); + _eliminatedTeams.Clear(); + _eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score)); + await EndGameInfo(killTeam); + return; + } + } + } + + /// + /// 游戏结束信息 [ 团队版 ] + /// + public async Task EndGameInfo(Team winner) + { + winner.IsWinner = true; + WriteLine("[ " + winner + " ] 是胜利者。"); + + if (!await OnGameEndTeamAsync(winner)) + { + return; + } + + int top = 1; + WriteLine(""); + WriteLine("=== 排名 ==="); + WriteLine(""); + + _eliminatedTeams.Add(winner); + _teams.Remove(winner.Name); + + for (int i = _eliminatedTeams.Count - 1; i >= 0; i--) + { + Team team = _eliminatedTeams[i]; + string topTeam = ""; + if (top == 1) + { + topTeam = "冠军"; + } + if (top == 2) + { + topTeam = "亚军"; + } + if (top == 3) + { + topTeam = "季军"; + } + if (top > 3) + { + topTeam = $"第 {top} 名"; + } + topTeam = $"☆--- {topTeam}团队:" + team.Name + " ---☆" + $"(得分:{team.Score})\r\n"; + foreach (Character ec in team.Members) + { + CharacterStatistics statistics = CharacterStatistics[ec]; + + string respawning = ""; + if (ec.HP <= 0) + { + respawning = "[ " + (_respawnCountdown.TryGetValue(ec, out double time) && time > 0 ? $"{time:0.##} {GameplayEquilibriumConstant.InGameTime}后复活" : "阵亡") + " ] "; + } + + string topCharacter = respawning + ec.ToString() + + (statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") + + (_maxContinuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") + + (_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {GameplayEquilibriumConstant.InGameCurrency} ]" : "") + + $"({statistics.Kills} / {statistics.Assists}{(MaxRespawnTimes != 0 ? " / " + statistics.Deaths : "")})"; + topTeam += topCharacter + "\r\n"; + if (top == 1) + { + _stats[ec].Wins += 1; + _stats[ec].Top3s += 1; + } + else if (top == 2) + { + _stats[ec].Loses += 1; + _stats[ec].Top3s += 1; + } + else if (top == 3) + { + _stats[ec].Loses += 1; + _stats[ec].Top3s += 1; + } + else + { + _stats[ec].Loses += 1; + } + _stats[ec].Plays += 1; + _stats[ec].TotalEarnedMoney += earned; + _stats[ec].LastRank = top; + } + WriteLine(topTeam); + top++; + } + WriteLine(""); + _isGameEnd = true; + } + + /// + /// 创建一个团队游戏队列 + /// + /// + public TeamGamingQueue(Action? writer = null) : base(writer) + { + + } + + /// + /// 创建一个团队游戏队列并初始化行动顺序表 + /// + /// + /// + public TeamGamingQueue(List characters, Action? writer = null) : base(characters, writer) + { + + } + + public delegate Task GameEndTeamEventHandler(TeamGamingQueue queue, Team winner); + /// + /// 游戏结束事件(团队版) + /// + public event GameEndTeamEventHandler? GameEndTeam; + /// + /// 游戏结束事件(团队版) + /// + /// + /// + protected async Task OnGameEndTeamAsync(Team winner) + { + return await (GameEndTeam?.Invoke(this, winner) ?? Task.FromResult(true)); + } + } +} diff --git a/README.md b/README.md index f88469e..b2d48c8 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ ## 安装 -- [NuGet](https://www.nuget.org/packages/ProjectRedbud.FunGame.Core/) +- [NuGet](https://www.nuget.org/packages/FunGame.Core/) ``` -dotnet add package ProjectRedbud.FunGame.Core --version 1.0.0-rc.1-0428 +dotnet add package FunGame.Core ``` - 在 [Release](https://github.com/project-redbud/FunGame-Core/releases) 页面中下载最新发布版本。