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