FunGame-Core/Model/ActionQueue.cs

2946 lines
121 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Model
{
/// <summary>
/// 行动顺序表
/// </summary>
public class ActionQueue : IGamingQueue
{
/// <summary>
/// 使用的游戏平衡常数
/// </summary>
public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant;
/// <summary>
/// 用于文本输出
/// </summary>
public Action<string> WriteLine { get; }
/// <summary>
/// 原始的角色字典
/// </summary>
public Dictionary<Guid, Character> Original => _original;
/// <summary>
/// 当前的行动顺序
/// </summary>
public List<Character> Queue => _queue;
/// <summary>
/// 硬直时间表
/// </summary>
public Dictionary<Character, double> HardnessTime => _hardnessTimes;
/// <summary>
/// 当前已死亡的角色顺序(第一个是最早死的)
/// </summary>
public List<Character> Eliminated => _eliminated;
/// <summary>
/// 当前团灭的团队顺序(第一个是最早死的)
/// </summary>
public List<Team> EliminatedTeams => _eliminatedTeams;
/// <summary>
/// 角色是否在 AI 控制下
/// </summary>
public HashSet<Character> CharactersInAI => _charactersInAI;
/// <summary>
/// 角色数据
/// </summary>
public Dictionary<Character, CharacterStatistics> CharacterStatistics => _stats;
/// <summary>
/// 团队及其成员
/// </summary>
public Dictionary<string, Team> Teams => _teams;
/// <summary>
/// 游戏运行的时间
/// </summary>
public double TotalTime { get; set; } = 0;
/// <summary>
/// 游戏运行的回合
/// 对于某角色而言,在其行动的回合叫 Turn而所有角色行动的回合都称之为 Round。
/// </summary>
public int TotalRound { get; set; } = 0;
/// <summary>
/// 第一滴血获得者
/// </summary>
public Character? FirstKiller { get; set; } = null;
/// <summary>
/// 最大复活次数
/// 0不复活 / -1无限复活
/// </summary>
public int MaxRespawnTimes { get; set; } = 0;
/// <summary>
/// 复活次数统计
/// </summary>
public Dictionary<Character, int> RespawnTimes => _respawnTimes;
/// <summary>
/// 复活倒计时
/// </summary>
public Dictionary<Character, double> RespawnCountdown => _respawnCountdown;
/// <summary>
/// 最大获胜积分 [ 适用于团队模式 ]
/// 设置一个大于0的数以启用
/// </summary>
public int MaxScoreToWin { get; set; } = 0;
/// <summary>
/// 上回合记录
/// </summary>
public RoundRecord LastRound { get; set; } = new(0);
/// <summary>
/// 所有回合的记录
/// </summary>
public List<RoundRecord> Rounds { get; } = [];
/// <summary>
/// 回合奖励
/// </summary>
public Dictionary<int, List<Skill>> RoundRewards => _roundRewards;
/// <summary>
/// 自定义数据
/// </summary>
public Dictionary<string, object> CustomData { get; } = [];
/// <summary>
/// 原始的角色字典
/// </summary>
protected readonly Dictionary<Guid, Character> _original = [];
/// <summary>
/// 当前的行动顺序
/// </summary>
protected readonly List<Character> _queue = [];
/// <summary>
/// 当前已死亡的角色顺序(第一个是最早死的)
/// </summary>
protected readonly List<Character> _eliminated = [];
/// <summary>
/// 当前团灭的团队顺序(第一个是最早死的)
/// </summary>
protected readonly List<Team> _eliminatedTeams = [];
/// <summary>
/// 角色是否在 AI 控制下
/// </summary>
protected readonly HashSet<Character> _charactersInAI = [];
/// <summary>
/// 硬直时间表
/// </summary>
protected readonly Dictionary<Character, double> _hardnessTimes = [];
/// <summary>
/// 角色正在吟唱的技能(通常是魔法)
/// </summary>
protected readonly Dictionary<Character, SkillTarget> _castingSkills = [];
/// <summary>
/// 角色预释放的爆发技
/// </summary>
protected readonly Dictionary<Character, Skill> _castingSuperSkills = [];
/// <summary>
/// 角色即将使用的物品
/// </summary>
protected readonly Dictionary<Character, Skill> _willUseItems = [];
/// <summary>
/// 角色目前赚取的金钱
/// </summary>
protected readonly Dictionary<Character, int> _earnedMoney = [];
/// <summary>
/// 角色最高连杀数
/// </summary>
protected readonly Dictionary<Character, int> _maxContinuousKilling = [];
/// <summary>
/// 角色目前的连杀数
/// </summary>
protected readonly Dictionary<Character, int> _continuousKilling = [];
/// <summary>
/// 角色被插队次数
/// </summary>
protected readonly Dictionary<Character, int> _cutCount = [];
/// <summary>
/// 助攻伤害
/// </summary>
protected readonly Dictionary<Character, AssistDetail> _assistDamage = [];
/// <summary>
/// 角色数据
/// </summary>
protected readonly Dictionary<Character, CharacterStatistics> _stats = [];
/// <summary>
/// 团队及其成员
/// </summary>
protected readonly Dictionary<string, Team> _teams = [];
/// <summary>
/// 复活次数统计
/// </summary>
protected readonly Dictionary<Character, int> _respawnTimes = [];
/// <summary>
/// 复活倒计时
/// </summary>
protected readonly Dictionary<Character, double> _respawnCountdown = [];
/// <summary>
/// 当前回合死亡角色
/// </summary>
protected readonly List<Character> _roundDeaths = [];
/// <summary>
/// 回合奖励
/// </summary>
protected readonly Dictionary<int, List<Skill>> _roundRewards = [];
/// <summary>
/// 回合奖励的特效工厂
/// </summary>
protected Func<long, Dictionary<string, object>> _factoryRoundRewardEffects = id => [];
/// <summary>
/// 是否是团队模式
/// </summary>
protected bool _isTeamMode = false;
/// <summary>
/// 游戏是否结束
/// </summary>
protected bool _isGameEnd = false;
/// <summary>
/// 新建一个行动顺序表
/// </summary>
/// <param name="isTeamMdoe">是否是团队模式</param>
/// <param name="writer">用于文本输出</param>
public ActionQueue(bool isTeamMdoe = false, Action<string>? writer = null)
{
_isTeamMode = isTeamMdoe;
if (writer != null)
{
WriteLine = writer;
}
WriteLine ??= new Action<string>(Console.WriteLine);
}
/// <summary>
/// 新建一个行动顺序表并初始化
/// </summary>
/// <param name="characters">参与本次游戏的角色列表</param>
/// <param name="isTeamMdoe">是否是团队模式</param>
/// <param name="writer">用于文本输出</param>
public ActionQueue(List<Character> characters, bool isTeamMdoe = false, Action<string>? writer = null)
{
_isTeamMode = isTeamMdoe;
if (writer != null)
{
WriteLine = writer;
}
WriteLine ??= new Action<string>(Console.WriteLine);
InitCharacterQueue(characters);
}
/// <summary>
/// 初始化行动顺序表
/// </summary>
/// <param name="characters"></param>
public void InitCharacterQueue(List<Character> characters)
{
// 保存原始的角色信息。用于复活时还原状态
foreach (Character character in characters)
{
Character original = character.Copy();
original.Guid = Guid.NewGuid();
character.Guid = original.Guid;
_original.Add(original.Guid, original);
}
// 初始排序:按速度排序
List<IGrouping<double, Character>> groupedBySpeed = [.. characters
.GroupBy(c => c.SPD)
.OrderByDescending(g => g.Key)];
Random random = new();
foreach (IGrouping<double, Character> group in groupedBySpeed)
{
if (group.Count() == 1)
{
// 如果只有一个角色,直接加入队列
Character character = group.First();
AddCharacter(character, Calculation.Round2Digits(_queue.Count * 0.1), false);
_assistDamage.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<Character> 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);
_assistDamage.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);
}
}
}
}
}
/// <summary>
/// 清空行动队列
/// </summary>
public void ClearQueue()
{
FirstKiller = null;
CustomData.Clear();
_original.Clear();
_queue.Clear();
_hardnessTimes.Clear();
_assistDamage.Clear();
_stats.Clear();
_cutCount.Clear();
_castingSkills.Clear();
_castingSuperSkills.Clear();
_willUseItems.Clear();
_maxContinuousKilling.Clear();
_continuousKilling.Clear();
_earnedMoney.Clear();
_eliminated.Clear();
_charactersInAI.Clear();
}
/// <summary>
/// 添加一个团队
/// </summary>
/// <param name="teamName"></param>
/// <param name="characters"></param>
public void AddTeam(string teamName, IEnumerable<Character> characters)
{
if (teamName != "" && characters.Any())
{
_teams.Add(teamName, new(teamName, characters));
}
}
/// <summary>
/// 获取角色的团队
/// </summary>
/// <param name="character"></param>
public Team? GetTeam(Character character)
{
foreach (Team team in _teams.Values)
{
if (team.IsOnThisTeam(character))
{
return team;
}
}
return null;
}
/// <summary>
/// 从已淘汰的团队中获取角色的团队
/// </summary>
/// <param name="character"></param>
public Team? GetTeamFromEliminated(Character character)
{
foreach (Team team in _eliminatedTeams)
{
if (team.IsOnThisTeam(character))
{
return team;
}
}
return null;
}
/// <summary>
/// 获取某角色的团队成员
/// </summary>
/// <param name="character"></param>
public List<Character> GetTeammates(Character character)
{
foreach (string team in _teams.Keys)
{
if (_teams[team].IsOnThisTeam(character))
{
return _teams[team].GetTeammates(character);
}
}
return [];
}
/// <summary>
/// 将角色加入行动顺序表
/// </summary>
/// <param name="character"></param>
/// <param name="hardnessTime"></param>
/// <param name="isCheckProtected"></param>
public void AddCharacter(Character character, double hardnessTime, bool isCheckProtected = true)
{
// 插队机制:按硬直时间排序
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;
}
}
}
}
/// <summary>
/// 从行动顺序表取出第一个角色
/// </summary>
/// <returns></returns>
public Character? NextCharacter()
{
if (_queue.Count == 0) return null;
// 硬直时间为0的角色将执行行动
Character? character = _queue.FirstOrDefault(c => _hardnessTimes[c] == 0);
if (character != null)
{
_queue.Remove(character);
_cutCount.Remove(character);
// 进入下一回合
TotalRound++;
LastRound = new(TotalRound);
Rounds.Add(LastRound);
return character;
}
return null;
}
/// <summary>
/// 显示当前所有角色的状态和硬直时间
/// </summary>
public void DisplayQueue()
{
WriteLine("==== 角色状态 ====");
foreach (Character c in _queue)
{
WriteLine(c.GetInBattleInfo(_hardnessTimes[c]));
}
}
/// <summary>
/// 回合开始前触发
/// </summary>
/// <returns></returns>
public virtual async Task<bool> BeforeTurnAsync(Character character)
{
return await Task.FromResult(true);
}
/// <summary>
/// 角色 <paramref name="character"/> 的回合进行中
/// </summary>
/// <param name="character"></param>
/// <returns>是否结束游戏</returns>
public async Task<bool> ProcessTurnAsync(Character character)
{
LastRound.Actor = character;
_roundDeaths.Clear();
if (!await BeforeTurnAsync(character))
{
return _isGameEnd;
}
// 获取回合奖励
List<Skill> rewards = GetRoundRewards(TotalRound, character);
// 基础硬直时间
double baseTime = 10;
bool isCheckProtected = true;
// 队友列表
List<Character> teammates = [.. GetTeammates(character).Where(_queue.Contains)];
// 敌人列表
List<Character> enemys = [.. _queue.Where(c => c != character && !c.IsUnselectable && !teammates.Contains(c))];
// 技能列表
List<Skill> 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<Item> 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<Effect> effects = [.. character.Effects.Where(e => e.Level > 0)];
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.Level > 0)];
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<Character, int> continuousKillingTemp = new(_continuousKilling);
Dictionary<Character, int> earnedMoneyTemp = new(_earnedMoney);
effects = [.. character.Effects.Where(e => e.Level > 0)];
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<Character> 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.HardnessTime;
effects = [.. character.Effects.Where(e => e.Level > 0)];
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<Character> 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)
{
LastRound.Targets = [.. targets];
decided = true;
character.CharacterState = CharacterState.Casting;
SkillTarget skillTarget = new(skill, targets);
await OnCharacterPreCastSkillAsync(character, skillTarget);
_castingSkills[character] = skillTarget;
baseTime = skill.CastTime;
skill.OnSkillCasting(this, character, targets);
}
}
else
{
// 只有魔法需要吟唱,战技和爆发技直接释放
if (CheckCanCast(character, skill, out double cost))
{
List<Character> 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)
{
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.HardnessTime;
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.Level > 0)];
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;
Skill skill = skillTarget.Skill;
List<Character> targets = [.. skillTarget.Targets.Where(c => !c.IsUnselectable)];
// 判断是否能够释放技能
if (targets.Count > 0 && CheckCanCast(character, skill, out double cost))
{
decided = true;
LastRound.Targets = [.. targets];
LastRound.Skill = skill;
_castingSkills.Remove(character);
skill.BeforeSkillCasted();
character.MP -= cost;
baseTime = skill.HardnessTime;
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.Level > 0)];
foreach (Effect effect in effects)
{
effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
}
}
else
{
// 原吟唱的技能丢失(被打断或者被取消),允许角色再次决策
character.CharacterState = CharacterState.Actionable;
}
}
else if (type == CharacterActionType.CastSuperSkill)
{
decided = true;
// 结束预释放爆发技的状态
character.CharacterState = CharacterState.Actionable;
Skill skill = _castingSuperSkills[character];
LastRound.Skill = skill;
_castingSuperSkills.Remove(character);
// 判断是否能够释放技能
if (CheckCanCast(character, skill, out double cost))
{
// 预释放的爆发技不可取消
List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates);
LastRound.Targets = [.. targets];
skill.BeforeSkillCasted();
character.EP -= cost;
baseTime = skill.HardnessTime;
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.Level > 0)];
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.HardnessTime;
effects = [.. character.Effects.Where(e => e.Level > 0)];
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 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 = Math.Max(0, Calculation.Round2Digits(baseTime * (1 - character.ActionCoefficient)));
WriteLine($"[ {character} ] 回合结束,获得硬直时间:{newHardnessTime} {GameplayEquilibriumConstant.InGameTime}");
}
else
{
newHardnessTime = Math.Max(0, Calculation.Round2Digits(baseTime * (1 - character.AccelerationCoefficient)));
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.Level > 0)];
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;
}
/// <summary>
/// 回合结束后触发
/// </summary>
/// <returns></returns>
public virtual async Task AfterTurnAsync(Character character)
{
await Task.CompletedTask;
}
/// <summary>
/// 时间进行流逝,减少硬直时间,减少技能冷却时间,角色也会因此回复状态
/// </summary>
/// <returns>流逝的时间</returns>
public async Task<double> 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 _respawnCountdown.Keys)
{
_respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce);
if (_respawnCountdown[character] <= 0)
{
await SetCharacterRespawn(character);
}
}
foreach (Character character in _queue)
{
// 减少所有角色的硬直时间
_hardnessTimes[character] = Calculation.Round2Digits(_hardnessTimes[character] - timeToReduce);
// 统计
_stats[character].LiveRound += 1;
_stats[character].LiveTime = Calculation.Round2Digits(_stats[character].LiveTime + timeToReduce);
_stats[character].DamagePerRound = Calculation.Round2Digits(_stats[character].TotalDamage / _stats[character].LiveRound);
_stats[character].DamagePerTurn = Calculation.Round2Digits(_stats[character].TotalDamage / _stats[character].ActionTurn);
_stats[character].DamagePerSecond = Calculation.Round2Digits(_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<Effect> effects = [.. character.Effects.Where(e => e.Level > 0)];
foreach (Effect effect in effects)
{
if (effect.Level == 0)
{
character.Effects.Remove(effect);
continue;
}
effect.OnTimeElapsed(character, timeToReduce);
// 自身被动不会考虑
if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive)
{
continue;
}
if (effect.Durative)
{
effect.RemainDuration -= timeToReduce;
if (effect.RemainDuration <= 0)
{
effect.RemainDuration = 0;
character.Effects.Remove(effect);
effect.OnEffectLost(character);
}
}
}
}
WriteLine("\r\n");
return timeToReduce;
}
/// <summary>
/// 对敌人造成伤害
/// </summary>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="magicType"></param>
/// <param name="damageResult"></param>
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;
}
bool isEvaded = damageResult == DamageResult.Evaded;
Dictionary<Effect, double> totalDamageBonus = [];
List<Effect> effects = [.. actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0)];
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 (!isEvaded)
{
if (damage < 0) damage = 0;
if (isMagicDamage)
{
string dmgType = CharacterSet.GetMagicDamageName(magicType);
WriteLine("[ " + enemy + $" ] 受到了 {damage:0.##} 点{dmgType}");
}
else WriteLine("[ " + enemy + $" ] 受到了 {damage:0.##} 点物理伤害!");
enemy.HP -= damage;
// 统计伤害
CalculateCharacterDamageStatistics(actor, enemy, damage, isMagicDamage);
// 计算助攻
_assistDamage[actor][enemy] += damage;
// 造成伤害和受伤都可以获得能量
double ep = GetEP(damage, GameplayEquilibriumConstant.DamageGetEPFactor, GameplayEquilibriumConstant.DamageGetEPMax);
effects = [.. actor.Effects];
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.Level > 0)];
foreach (Effect effect in effects)
{
effect.AlterEPAfterGetDamage(enemy, ref ep);
}
enemy.EP += ep;
}
await OnDamageToEnemyAsync(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult);
effects = [.. actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0)];
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);
}
}
/// <summary>
/// 治疗一个目标
/// </summary>
/// <param name="actor"></param>
/// <param name="target"></param>
/// <param name="heal"></param>
/// <param name="canRespawn"></param>
public async Task HealToTargetAsync(Character actor, Character target, double heal, bool canRespawn = false)
{
if (target.HP == target.MaxHP)
{
return;
}
bool isDead = target.HP <= 0;
if (heal < 0) heal = 0;
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.##} 点生命值!");
}
await OnHealToTargetAsync(actor, target, heal, isRespawn);
}
/// <summary>
/// 获取EP
/// </summary>
/// <param name="a">参数1</param>
/// <param name="b">参数2</param>
/// <param name="max">最大获取量</param>
public static double GetEP(double a, double b, double max)
{
return Math.Min((a + Random.Shared.Next(30)) * b, max);
}
/// <summary>
/// 计算物理伤害
/// </summary>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="isNormalAttack"></param>
/// <param name="expectedDamage"></param>
/// <param name="finalDamage"></param>
/// <returns></returns>
public DamageResult CalculatePhysicalDamage(Character actor, Character enemy, bool isNormalAttack, double expectedDamage, out double finalDamage)
{
List<Character> characters = [actor, enemy];
bool isMagic = false;
MagicType magicType = MagicType.None;
List<Effect> effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))];
foreach (Effect effect in effects)
{
effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType);
}
if (isMagic)
{
return CalculateMagicalDamage(actor, enemy, isNormalAttack, magicType, expectedDamage, out finalDamage);
}
Dictionary<Effect, double> totalDamageBonus = [];
effects = [.. actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0)];
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.Level > 0))];
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.Level > 0))];
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.Level > 0))];
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.Level > 0))];
foreach (Effect effect in effects)
{
effect.OnCriticalDamageTriggered(actor, enemy, dice);
}
return DamageResult.Critical;
}
}
// 是否有效伤害
return DamageResult.Normal;
}
/// <summary>
/// 计算魔法伤害
/// </summary>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="isNormalAttack"></param>
/// <param name="magicType"></param>
/// <param name="expectedDamage"></param>
/// <param name="finalDamage"></param>
/// <returns></returns>
public DamageResult CalculateMagicalDamage(Character actor, Character enemy, bool isNormalAttack, MagicType magicType, double expectedDamage, out double finalDamage)
{
List<Character> characters = [actor, enemy];
bool isMagic = true;
List<Effect> effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))];
foreach (Effect effect in effects)
{
effect.AlterDamageTypeBeforeCalculation(actor, enemy, ref isNormalAttack, ref isMagic, ref magicType);
}
if (!isMagic)
{
return CalculatePhysicalDamage(actor, enemy, isNormalAttack, expectedDamage, out finalDamage);
}
Dictionary<Effect, double> totalDamageBonus = [];
effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))];
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.Level > 0))];
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.Level > 0))];
foreach (Effect effect in effects)
{
if (effect.OnEvadedTriggered(actor, enemy, dice))
{
isAlterEvaded = true;
}
}
if (!isAlterEvaded)
{
WriteLine("此魔法攻击被完美闪避了!");
return DamageResult.Evaded;
}
}
}
}
double MDF = magicType switch
{
MagicType.Starmark => enemy.MDF.Starmark,
MagicType.PurityNatural => enemy.MDF.PurityNatural,
MagicType.PurityContemporary => enemy.MDF.PurityContemporary,
MagicType.Bright => enemy.MDF.Bright,
MagicType.Shadow => enemy.MDF.Shadow,
MagicType.Element => enemy.MDF.Element,
MagicType.Fleabane => enemy.MDF.Fleabane,
MagicType.Particle => enemy.MDF.Particle,
_ => enemy.MDF.None
};
// 魔法穿透后的魔法抗性
MDF = (1 - actor.MagicalPenetration) * MDF;
// 最终的魔法伤害
finalDamage = expectedDamage * (1 - MDF);
// 暴击判定
effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.Level > 0))];
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.Level > 0))];
foreach (Effect effect in effects)
{
effect.OnCriticalDamageTriggered(actor, enemy, dice);
}
return DamageResult.Critical;
}
}
// 是否有效伤害
return DamageResult.Normal;
}
/// <summary>
/// 处理角色死亡
/// </summary>
/// <param name="character"></param>
public async Task ProcessCharacterDeathAsync(Character character)
{
foreach (Character death in _roundDeaths)
{
if(!await OnCharacterDeathAsync(character, death))
{
continue;
}
// 给所有角色的特效广播角色死亡结算
List<Effect> effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.Level > 0))];
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<Character> 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<Character> 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<Team> 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);
}
}
}
}
/// <summary>
/// 死亡结算
/// </summary>
/// <param name="killer"></param>
/// <param name="death"></param>
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);
Character[] assists = [.. _assistDamage.Keys.Where(c => c != death && _assistDamage[c].GetPercentage(death) > 0.10)];
double totalDamagePercentage = _assistDamage.Keys.Where(assists.Contains).Select(c => _assistDamage[c].GetPercentage(death)).Sum();
int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425); // 防止刷伤害设置金钱上限
// 按伤害比分配金钱 只有造成10%伤害以上才能参与
foreach (Character assist in assists)
{
int cmoney = Convert.ToInt32(_assistDamage[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;
WriteLine($"[ {killer} ] 拿下了第一滴血!额外奖励 200 {GameplayEquilibriumConstant.InGameCurrency}");
}
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<AssistDetail> ads = [.. _assistDamage.Values.Where(ad => ad.Character != death)];
foreach (AssistDetail ad in ads)
{
ad[death] = 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(90, death.Level * 0.15 + times * 2.77 + coefficient * Random.Shared.Next(1, 3)));
_respawnCountdown.TryAdd(death, respawnTime);
LastRound.RespawnCountdowns.TryAdd(death, respawnTime);
WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} {GameplayEquilibriumConstant.InGameTime}");
}
// 移除死者的施法
_castingSkills.Remove(death);
_castingSuperSkills.Remove(death);
// 因丢失目标而中断施法
List<Character> 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)
{
_hardnessTimes[caster] = 3;
}
}
}
}
/// <summary>
/// 游戏结束信息
/// </summary>
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;
}
/// <summary>
/// 游戏结束信息 [ 团队版 ]
/// </summary>
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;
}
/// <summary>
/// 检查是否可以释放技能
/// </summary>
/// <param name="caster"></param>
/// <param name="skill"></param>
/// <param name="cost"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 检查是否可以释放技能(物品版)
/// </summary>
/// <param name="caster"></param>
/// <param name="item"></param>
/// <param name="costMP"></param>
/// <param name="costEP"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 是否在回合外释放爆发技插队(仅自动化,手动设置请调用:<see cref="SetCharacterPreCastSuperSkill"/>
/// </summary>
/// <returns></returns>
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<Skill> 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);
}
}
}
}
/// <summary>
/// 打断施法
/// </summary>
/// <param name="caster"></param>
/// <param name="interrupter"></param>
public async Task InterruptCastingAsync(Character caster, Character interrupter)
{
Skill? skill = null;
if (_castingSkills.TryGetValue(caster, out SkillTarget target))
{
skill = target.Skill;
_castingSkills.Remove(caster);
}
else if (_castingSuperSkills.TryGetValue(caster, out skill))
{
_castingSuperSkills.Remove(caster);
}
if (skill != null)
{
WriteLine($"[ {caster} ] 的{(skill.IsSuperSkill ? "" : "")}被 [ {interrupter} ] 打断了!!");
List<Effect> effects = [.. caster.Effects.Union(interrupter.Effects).Where(e => e.Level > 0)];
foreach (Effect effect in effects)
{
effect.OnSkillCastInterrupted(caster, skill, interrupter);
}
await OnInterruptCastingAsync(caster, skill, interrupter);
}
}
/// <summary>
/// 打断施法 [ 用于使敌人目标丢失 ]
/// </summary>
/// <param name="interrupter"></param>
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<Effect> effects = [.. caster.Effects.Union(interrupter.Effects).Where(e => e.Level > 0)];
foreach (Effect effect in effects)
{
effect.OnSkillCastInterrupted(caster, skill, interrupter);
}
await OnInterruptCastingAsync(caster, skill, interrupter);
}
}
}
/// <summary>
/// 通过概率计算角色要干嘛
/// </summary>
/// <param name="pUseItem"></param>
/// <param name="pCastSkill"></param>
/// <param name="pNormalAttack"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 计算角色的数据
/// </summary>
public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, bool isMagic)
{
if (_stats.TryGetValue(character, out CharacterStatistics? stats) && stats != null)
{
if (isMagic)
{
stats.TotalMagicDamage = Calculation.Round2Digits(stats.TotalMagicDamage + damage);
}
else
{
stats.TotalPhysicalDamage = Calculation.Round2Digits(stats.TotalPhysicalDamage + damage);
}
stats.TotalDamage = Calculation.Round2Digits(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;
}
}
/// <summary>
/// 装备物品
/// </summary>
/// <param name="character"></param>
/// <param name="item"></param>
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)} 栏位)" : ""));
}
}
/// <summary>
/// 装备物品到指定栏位,并返回被替换的装备(如果有的话)
/// </summary>
/// <param name="character"></param>
/// <param name="type"></param>
/// <param name="item"></param>
/// <param name="previous"></param>
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)} 栏位)");
}
}
/// <summary>
/// 取消装备,并返回被替换的装备(如果有的话)
/// </summary>
/// <param name="character"></param>
/// <param name="type"></param>
/// <returns></returns>
public Item? UnEquip(Character character, EquipSlotType type)
{
Item? item = character.UnEquip(type);
if (item != null)
{
WriteLine($"[ {character} ] 取消装备了 [ {item.Name} ]。({ItemSet.GetEquipSlotTypeName(type)} 栏位)");
}
return item;
}
/// <summary>
/// 使用物品实际逻辑
/// </summary>
/// <param name="item"></param>
/// <param name="character"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
public async Task<bool> UseItemAsync(Item item, Character character, List<Character> enemys, List<Character> teammates)
{
if (CheckCanCast(character, item, out double costMP, out double costEP))
{
Skill? skill = item.Skills.Active;
if (skill != null)
{
List<Character> 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)
{
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;
}
/// <summary>
/// 设置角色复活
/// </summary>
/// <param name="character"></param>
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;
}
}
/// <summary>
/// 设置角色将预释放爆发技
/// </summary>
/// <param name="character"></param>
/// <param name="skill"></param>
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);
AddCharacter(character, 0, false);
await OnQueueUpdatedAsync(_queue, character, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。");
WriteLine("[ " + character + " ] 预释放了爆发技!!");
foreach (Character c in _hardnessTimes.Keys)
{
if (_hardnessTimes[c] != 0)
{
_hardnessTimes[c] = Calculation.Round2Digits(_hardnessTimes[c] + 0.01);
}
}
skill.OnSkillCasting(this, character, []);
}
}
/// <summary>
/// 设置角色为 AI 控制
/// </summary>
/// <param name="cancel"></param>
/// <param name="characters"></param>
public void SetCharactersToAIControl(bool cancel = false, params IEnumerable<Character> characters)
{
foreach (Character character in characters)
{
if (cancel)
{
_charactersInAI.Remove(character);
}
else
{
_charactersInAI.Add(character);
}
}
}
/// <summary>
/// 检查角色是否在 AI 控制状态
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
public bool IsCharacterInAIControlling(Character character)
{
return _charactersInAI.Contains(character);
}
/// <summary>
/// 初始化回合奖励
/// </summary>
/// <param name="maxRound">最大回合数</param>
/// <param name="maxRewardsInRound">每个奖励回合生成多少技能</param>
/// <param name="effects">key: 特效的数字标识符value: 是否是主动技能的特效</param>
/// <param name="factoryEffects">通过数字标识符来获取构造特效的参数</param>
/// <returns></returns>
public virtual void InitRoundRewards(int maxRound, int maxRewardsInRound, Dictionary<long, bool> effects, Func<long, Dictionary<string, object>>? factoryEffects = null)
{
_roundRewards.Clear();
int currentRound = 1;
long[] effectIDs = [.. effects.Keys];
while (currentRound <= maxRound)
{
currentRound += Random.Shared.Next(1, 9);
if (currentRound <= maxRound)
{
List<Skill> skills = [];
if (maxRewardsInRound <= 0) maxRewardsInRound = 1;
do
{
long effectID = effectIDs[Random.Shared.Next(effects.Count)];
Dictionary<string, object> args = [];
if (effects[effectID])
{
args.Add("active", true);
args.Add("self", true);
args.Add("enemy", false);
}
Skill skill = Factory.OpenFactory.GetInstance<Skill>(effectID, "", args);
Dictionary<string, object> effectArgs = factoryEffects != null ? factoryEffects(effectID) : [];
args.Clear();
args.Add("skill", skill);
args.Add("values", effectArgs);
Effect effect = Factory.OpenFactory.GetInstance<Effect>(effectID, "", args);
skill.Effects.Add(effect);
skill.Name = $"[R] {effect.Name}";
skills.Add(skill);
}
while (skills.Count < maxRewardsInRound);
_roundRewards[currentRound] = skills;
}
}
}
/// <summary>
/// 获取回合奖励
/// </summary>
/// <param name="round"></param>
/// <param name="character"></param>
/// <returns></returns>
public virtual List<Skill> GetRoundRewards(int round, Character character)
{
if (_roundRewards.TryGetValue(round, out List<Skill>? value) && value is List<Skill> 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 [];
}
/// <summary>
/// 移除回合奖励
/// </summary>
/// <param name="round"></param>
/// <param name="character"></param>
/// <param name="skills"></param>
public virtual void RemoveRoundRewards(int round, Character character, List<Skill> skills)
{
foreach (Skill skill in skills)
{
foreach (Effect e in skill.Effects)
{
e.OnEffectLost(character);
character.Effects.Remove(e);
}
character.Skills.Remove(skill);
skill.Character = null;
}
}
/// <summary>
/// 选取技能目标
/// </summary>
/// <param name="caster"></param>
/// <param name="skill"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
public virtual async Task<List<Character>> SelectTargetsAsync(Character caster, Skill skill, List<Character> enemys, List<Character> teammates)
{
List<Effect> effects = [.. caster.Effects.Where(e => e.Level > 0)];
foreach (Effect effect in effects)
{
effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates);
}
List<Character> targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates);
if (targets.Count == 0 && _charactersInAI.Contains(caster))
{
targets = skill.SelectTargets(caster, enemys, teammates);
}
return targets;
}
/// <summary>
/// 选取普通攻击目标
/// </summary>
/// <param name="character"></param>
/// <param name="attack"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
public virtual async Task<List<Character>> SelectTargetsAsync(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates)
{
List<Effect> effects = [.. character.Effects.Where(e => e.Level > 0)];
foreach (Effect effect in effects)
{
effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates);
}
List<Character> targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates);
return targets;
}
#region
public delegate Task<bool> TurnStartEventHandler(ActionQueue queue, Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items);
/// <summary>
/// 回合开始事件
/// </summary>
public event TurnStartEventHandler? TurnStart;
/// <summary>
/// 回合开始事件
/// </summary>
/// <param name="character"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="skills"></param>
/// <param name="items"></param>
/// <returns></returns>
protected async Task<bool> OnTurnStartAsync(Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items)
{
return await (TurnStart?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(true));
}
public delegate Task TurnEndEventHandler(ActionQueue queue, Character character);
/// <summary>
/// 回合结束事件
/// </summary>
public event TurnEndEventHandler? TurnEnd;
/// <summary>
/// 回合结束事件
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
protected async Task OnTurnEndAsync(Character character)
{
await (TurnEnd?.Invoke(this, character) ?? Task.CompletedTask);
}
public delegate Task<CharacterActionType> DecideActionEventHandler(ActionQueue queue, Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items);
/// <summary>
/// 决定角色的行动事件
/// </summary>
public event DecideActionEventHandler? DecideAction;
/// <summary>
/// 决定角色的行动事件
/// </summary>
/// <param name="character"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="skills"></param>
/// <param name="items"></param>
/// <returns></returns>
protected async Task<CharacterActionType> OnDecideActionAsync(Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items)
{
return await (DecideAction?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(CharacterActionType.None));
}
public delegate Task<Skill?> SelectSkillEventHandler(ActionQueue queue, Character character, List<Skill> skills);
/// <summary>
/// 角色需要选择一个技能
/// </summary>
public event SelectSkillEventHandler? SelectSkill;
/// <summary>
/// 角色需要选择一个技能
/// </summary>
/// <param name="character"></param>
/// <param name="skills"></param>
/// <returns></returns>
protected async Task<Skill?> OnSelectSkillAsync(Character character, List<Skill> skills)
{
return await (SelectSkill?.Invoke(this, character, skills) ?? Task.FromResult<Skill?>(null));
}
public delegate Task<Item?> SelectItemEventHandler(ActionQueue queue, Character character, List<Item> items);
/// <summary>
/// 角色需要选择一个物品
/// </summary>
public event SelectItemEventHandler? SelectItem;
/// <summary>
/// 角色需要选择一个物品
/// </summary>
/// <param name="character"></param>
/// <param name="items"></param>
/// <returns></returns>
protected async Task<Item?> OnSelectItemAsync(Character character, List<Item> items)
{
return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult<Item?>(null));
}
public delegate Task<List<Character>> SelectSkillTargetsEventHandler(ActionQueue queue, Character caster, Skill skill, List<Character> enemys, List<Character> teammates);
/// <summary>
/// 选取技能目标事件
/// </summary>
public event SelectSkillTargetsEventHandler? SelectSkillTargets;
/// <summary>
/// 选取技能目标事件
/// </summary>
/// <param name="caster"></param>
/// <param name="skill"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
protected async Task<List<Character>> OnSelectSkillTargetsAsync(Character caster, Skill skill, List<Character> enemys, List<Character> teammates)
{
return await (SelectSkillTargets?.Invoke(this, caster, skill, enemys, teammates) ?? Task.FromResult(new List<Character>()));
}
public delegate Task<List<Character>> SelectNormalAttackTargetsEventHandler(ActionQueue queue, Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates);
/// <summary>
/// 选取普通攻击目标事件
/// </summary>
public event SelectNormalAttackTargetsEventHandler? SelectNormalAttackTargets;
/// <summary>
/// 选取普通攻击目标事件
/// </summary>
/// <param name="character"></param>
/// <param name="attack"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
protected async Task<List<Character>> OnSelectNormalAttackTargetsAsync(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates)
{
return await (SelectNormalAttackTargets?.Invoke(this, character, attack, enemys, teammates) ?? Task.FromResult(new List<Character>()));
}
public delegate Task InterruptCastingEventHandler(ActionQueue queue, Character cast, Skill? skill, Character interrupter);
/// <summary>
/// 打断施法事件
/// </summary>
public event InterruptCastingEventHandler? InterruptCasting;
/// <summary>
/// 打断施法事件
/// </summary>
/// <param name="cast"></param>
/// <param name="skill"></param>
/// <param name="interrupter"></param>
/// <returns></returns>
protected async Task OnInterruptCastingAsync(Character cast, Skill skill, Character interrupter)
{
await (InterruptCasting?.Invoke(this, cast, skill, interrupter) ?? Task.CompletedTask);
}
public delegate Task<bool> DeathCalculationEventHandler(ActionQueue queue, Character killer, Character death);
/// <summary>
/// 死亡结算事件
/// </summary>
public event DeathCalculationEventHandler? DeathCalculation;
/// <summary>
/// 死亡结算事件
/// </summary>
/// <param name="killer"></param>
/// <param name="death"></param>
/// <returns></returns>
protected async Task<bool> OnDeathCalculationAsync(Character killer, Character death)
{
return await (DeathCalculation?.Invoke(this, killer, death) ?? Task.FromResult(true));
}
public delegate Task<bool> CharacterDeathEventHandler(ActionQueue queue, Character current, Character death);
/// <summary>
/// 角色死亡事件,此事件位于 <see cref="DeathCalculation"/> 之后
/// </summary>
public event CharacterDeathEventHandler? CharacterDeath;
/// <summary>
/// 角色死亡事件,此事件位于 <see cref="DeathCalculation"/> 之后
/// </summary>
/// <param name="current"></param>
/// <param name="death"></param>
/// <returns></returns>
protected async Task<bool> 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);
/// <summary>
/// 治疗事件
/// </summary>
public event HealToTargetEventHandler? HealToTarget;
/// <summary>
/// 治疗事件
/// </summary>
/// <param name="actor"></param>
/// <param name="target"></param>
/// <param name="heal"></param>
/// <param name="isRespawn"></param>
/// <returns></returns>
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);
/// <summary>
/// 造成伤害事件
/// </summary>
public event DamageToEnemyEventHandler? DamageToEnemy;
/// <summary>
/// 造成伤害事件
/// </summary>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="magicType"></param>
/// <param name="damageResult"></param>
/// <returns></returns>
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<Character> targets);
/// <summary>
/// 角色普通攻击事件
/// </summary>
public event CharacterNormalAttackEventHandler? CharacterNormalAttack;
/// <summary>
/// 角色普通攻击事件
/// </summary>
/// <param name="actor"></param>
/// <param name="targets"></param>
/// <returns></returns>
protected async Task OnCharacterNormalAttackAsync(Character actor, List<Character> targets)
{
await (CharacterNormalAttack?.Invoke(this, actor, targets) ?? Task.CompletedTask);
}
public delegate Task CharacterPreCastSkillEventHandler(ActionQueue queue, Character actor, SkillTarget skillTarget);
/// <summary>
/// 角色吟唱技能事件(包括直接释放战技)
/// </summary>
public event CharacterPreCastSkillEventHandler? CharacterPreCastSkill;
/// <summary>
/// 角色吟唱技能事件(包括直接释放战技)
/// </summary>
/// <param name="actor"></param>
/// <param name="skillTarget"></param>
/// <returns></returns>
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);
/// <summary>
/// 角色释放技能事件
/// </summary>
public event CharacterCastSkillEventHandler? CharacterCastSkill;
/// <summary>
/// 角色释放技能事件
/// </summary>
/// <param name="actor"></param>
/// <param name="skillTarget"></param>
/// <param name="cost"></param>
/// <returns></returns>
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<Character> targets);
/// <summary>
/// 角色使用物品事件
/// </summary>
public event CharacterUseItemEventHandler? CharacterUseItem;
/// <summary>
/// 角色使用物品事件
/// </summary>
/// <param name="actor"></param>
/// <param name="item"></param>
/// <param name="targets"></param>
/// <returns></returns>
protected async Task OnCharacterUseItemAsync(Character actor, Item item, List<Character> 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);
/// <summary>
/// 角色释放物品的技能事件
/// </summary>
public event CharacterCastItemSkillEventHandler? CharacterCastItemSkill;
/// <summary>
/// 角色释放物品的技能事件
/// </summary>
/// <param name="actor"></param>
/// <param name="item"></param>
/// <param name="skillTarget"></param>
/// <param name="costMP"></param>
/// <param name="costEP"></param>
/// <returns></returns>
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 CharacterDoNothingEventHandler(ActionQueue queue, Character actor);
/// <summary>
/// 角色主动结束回合事件(区别于放弃行动,这个是主动的)
/// </summary>
public event CharacterDoNothingEventHandler? CharacterDoNothing;
/// <summary>
/// 角色主动结束回合事件(区别于放弃行动,这个是主动的)
/// </summary>
/// <param name="actor"></param>
/// <returns></returns>
protected async Task OnCharacterDoNothingAsync(Character actor)
{
await (CharacterDoNothing?.Invoke(this, actor) ?? Task.CompletedTask);
}
public delegate Task CharacterGiveUpEventHandler(ActionQueue queue, Character actor);
/// <summary>
/// 角色放弃行动事件
/// </summary>
public event CharacterGiveUpEventHandler? CharacterGiveUp;
/// <summary>
/// 角色放弃行动事件
/// </summary>
/// <param name="actor"></param>
/// <returns></returns>
protected async Task OnCharacterGiveUpAsync(Character actor)
{
await (CharacterGiveUp?.Invoke(this, actor) ?? Task.CompletedTask);
}
public delegate Task<bool> GameEndEventHandler(ActionQueue queue, Character winner);
/// <summary>
/// 游戏结束事件
/// </summary>
public event GameEndEventHandler? GameEnd;
/// <summary>
/// 游戏结束事件
/// </summary>
/// <param name="winner"></param>
/// <returns></returns>
protected async Task<bool> OnGameEndAsync(Character winner)
{
return await (GameEnd?.Invoke(this, winner) ?? Task.FromResult(true));
}
public delegate Task<bool> GameEndTeamEventHandler(ActionQueue queue, Team winner);
/// <summary>
/// 游戏结束事件(团队版)
/// </summary>
public event GameEndTeamEventHandler? GameEndTeam;
/// <summary>
/// 游戏结束事件(团队版)
/// </summary>
/// <param name="winner"></param>
/// <returns></returns>
protected async Task<bool> OnGameEndTeamAsync(Team winner)
{
return await (GameEndTeam?.Invoke(this, winner) ?? Task.FromResult(true));
}
public delegate Task QueueUpdatedEventHandler(ActionQueue queue, List<Character> characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg);
/// <summary>
/// 行动顺序表更新事件
/// </summary>
public event QueueUpdatedEventHandler? QueueUpdated;
/// <summary>
/// 行动顺序表更新事件
/// </summary>
/// <param name="characters"></param>
/// <param name="character"></param>
/// <param name="hardnessTime"></param>
/// <param name="reason"></param>
/// <param name="msg"></param>
/// <returns></returns>
protected async Task OnQueueUpdatedAsync(List<Character> characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg = "")
{
await (QueueUpdated?.Invoke(this, characters, character, hardnessTime, reason, msg) ?? Task.CompletedTask);
}
#endregion
}
}