diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs index c0fd3c0..b2df164 100644 --- a/Entity/Skill/Effect.cs +++ b/Entity/Skill/Effect.cs @@ -258,6 +258,38 @@ namespace Milimoe.FunGame.Core.Entity { return 0; } + + /// + /// 在应用真实伤害前修改伤害 [ 允许取消伤害 ] + /// + /// + /// + /// + /// + /// + /// 返回 true 取消伤害 + public virtual bool BeforeApplyTrueDamage(Character character, Character enemy, double damage, bool isNormalAttack, DamageResult damageResult) + { + return false; + } + + /// + /// 伤害应用时触发 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public virtual void OnApplyDamage(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult, string shieldMessage, ref string originalMessage) + { + + } /// /// 在完成普通攻击动作之后修改硬直时间 @@ -989,6 +1021,17 @@ namespace Milimoe.FunGame.Core.Entity GamingQueue?.ChangeCharacterHardnessTime(character, addValue, isPercentage, isCheckProtected); } + /// + /// 设置角色为 AI 控制 [ 系统控制 ] + /// + /// + /// + /// + public void SetCharactersToAIControl(bool cancel = false, params IEnumerable characters) + { + GamingQueue?.SetCharactersToAIControl(true, cancel, characters); + } + /// /// 检查角色是否在 AI 控制状态 /// diff --git a/Interface/Base/IGamingQueue.cs b/Interface/Base/IGamingQueue.cs index bdc97af..925296f 100644 --- a/Interface/Base/IGamingQueue.cs +++ b/Interface/Base/IGamingQueue.cs @@ -184,6 +184,12 @@ namespace Milimoe.FunGame.Core.Interface.Base /// public Dictionary GetIsTeammateDictionary(Character character, IEnumerable targets); + /// + /// 设置角色为 AI 控制 + /// + /// + public void SetCharactersToAIControl(bool bySystem = true, bool cancel = false, params IEnumerable characters); + /// /// 检查角色是否在 AI 控制状态 /// diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs index a3fc9ef..90015b0 100644 --- a/Model/GamingQueue.cs +++ b/Model/GamingQueue.cs @@ -52,7 +52,7 @@ namespace Milimoe.FunGame.Core.Model /// /// 角色是否在 AI 控制下 /// - public HashSet CharactersInAI => _charactersInAI; + public List CharactersInAI => [.. _charactersInAIBySystem.Union(_charactersInAIByUser).Distinct()]; /// /// 角色数据 @@ -142,9 +142,14 @@ namespace Milimoe.FunGame.Core.Model protected readonly List _eliminated = []; /// - /// 角色是否在 AI 控制下 + /// 角色是否在 AI 控制下 [ 系统控制 ] /// - protected readonly HashSet _charactersInAI = []; + protected readonly HashSet _charactersInAIBySystem = []; + + /// + /// 角色是否在 AI 控制下 [ 玩家手动设置 ] + /// + protected readonly HashSet _charactersInAIByUser = []; /// /// 硬直时间表 @@ -492,7 +497,8 @@ namespace Milimoe.FunGame.Core.Model _continuousKilling.Clear(); _earnedMoney.Clear(); _eliminated.Clear(); - _charactersInAI.Clear(); + _charactersInAIBySystem.Clear(); + _charactersInAIByUser.Clear(); } #endregion @@ -775,17 +781,26 @@ namespace Milimoe.FunGame.Core.Model // 最大取消次数 int cancelTimes = 3; + // 行动开始前,可以修改可选取的角色列表 + 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); + } + // 作出了什么行动 CharacterActionType type = CharacterActionType.None; // 循环条件: // AI 控制下:未决策、取消次数大于0 // 手动控制下:未决策 - bool isAI = _charactersInAI.Contains(character); + bool isAI = CharactersInAI.Contains(character); while (!decided && (!isAI || cancelTimes > 0)) { type = CharacterActionType.None; - + // 是否能使用物品和释放技能 bool canUseItem = items.Count > 0; bool canCastSkill = skills.Count > 0; @@ -921,14 +936,6 @@ namespace Milimoe.FunGame.Core.Model } } - 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 || @@ -972,7 +979,7 @@ namespace Milimoe.FunGame.Core.Model { // 预使用技能,即开始吟唱逻辑 Skill? skill = await OnSelectSkillAsync(character, skills); - if (skill is null && _charactersInAI.Contains(character) && skills.Count > 0) + if (skill is null && CharactersInAI.Contains(character) && skills.Count > 0) { skill = skills[Random.Shared.Next(skills.Count)]; } @@ -1154,7 +1161,7 @@ namespace Milimoe.FunGame.Core.Model { // 使用物品逻辑 Item? item = await OnSelectItemAsync(character, items); - if (item is null && _charactersInAI.Contains(character) && items.Count > 0) + if (item is null && CharactersInAI.Contains(character) && items.Count > 0) { // AI 控制下随机选取一个物品 item = items[Random.Shared.Next(items.Count)]; @@ -1356,6 +1363,18 @@ namespace Milimoe.FunGame.Core.Model } } + /// + /// 获取复活时间 + /// + /// + /// + /// + protected virtual double GetRespawnTime(Character character, int times) + { + _continuousKilling.TryGetValue(character, out int coefficient); + return Calculation.Round2Digits(Math.Min(30, character.Level * 0.15 + times * 0.87 + coefficient)); + } + /// /// 回合开始前触发 /// @@ -1423,6 +1442,17 @@ namespace Milimoe.FunGame.Core.Model } damage += totalDamageBonus.Sum(kv => kv.Value); } + else + { + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + if (effect.BeforeApplyTrueDamage(actor, enemy, damage, isNormalAttack, damageResult)) + { + damageResult = DamageResult.Evaded; + } + } + } double actualDamage = damage; // 闪避了就没伤害了 @@ -1651,7 +1681,13 @@ namespace Milimoe.FunGame.Core.Model } enemy.HP -= actualDamage; - WriteLine($"[ {enemy} ] 受到了 {actualDamage:0.##} 点{damageTypeString}!{shieldMsg}"); + string strDamageMessage = $"[ {enemy} ] 受到了 {actualDamage:0.##} 点{damageTypeString}!{shieldMsg}"; + effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; + foreach (Effect effect in effects) + { + effect.OnApplyDamage(enemy, actor, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult, shieldMsg, ref strDamageMessage); + } + WriteLine(strDamageMessage); // 生命偷取,攻击者为全额 double steal = damage * actor.Lifesteal; @@ -1713,89 +1749,6 @@ namespace Milimoe.FunGame.Core.Model } } - /// - /// 治疗一个目标 - /// - /// - /// - /// - /// - 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; - } - - double realHeal = heal; - if (target.HP > 0 || (isDead && canRespawn)) - { - // 用于数据统计,不能是全额,溢出的部分需要扣除 - if (target.HP + heal > target.MaxHP) - { - realHeal = target.MaxHP - target.HP; - } - 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.##} 点生命值!!"); - } - double hp = target.HP; - double mp = target.MP; - await SetCharacterRespawn(target); - target.HP = hp; - target.MP = mp; - } - else - { - WriteLine($"[ {target} ] 回复了 {heal:0.##} 点生命值!"); - } - - // 添加助攻 - SetNotDamageAssistTime(actor, target); - - // 统计数据 - if (_stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null) - { - stats.TotalHeal += realHeal; - } - - await OnHealToTargetAsync(actor, target, heal, isRespawn); - } - /// /// 死亡结算 /// @@ -1803,11 +1756,25 @@ namespace Milimoe.FunGame.Core.Model /// public async Task DeathCalculationAsync(Character killer, Character death) { + if (IsTeammate(killer, death)) + { + await DeathCalculationByTeammateAsync(killer, death); + return; + } + if (!await OnDeathCalculationAsync(killer, death)) { return; } + if (killer == death) + { + _stats[death].Deaths += 1; + WriteLine($"[ {death} ] 自杀了!"); + await DealWithCharacterDied(killer, death); + return; + } + if (!_continuousKilling.TryAdd(killer, 1)) _continuousKilling[killer] += 1; if (!_maxContinuousKilling.TryAdd(killer, 1) && _continuousKilling[killer] > _maxContinuousKilling[killer]) { @@ -1955,6 +1922,125 @@ namespace Milimoe.FunGame.Core.Model WriteLine(actorContinuousKilling); } + await DealWithCharacterDied(killer, death); + } + + /// + /// 死亡结算,击杀队友的情况 + /// + /// + /// + /// + public async Task DeathCalculationByTeammateAsync(Character killer, Character death) + { + if (!await OnDeathCalculationByTeammateAsync(killer, death)) + { + return; + } + + _stats[death].Deaths += 1; + string msg = $"[ {killer} ] 反补了 [ {death} ]!"; + LastRound.DeathContinuousKilling.Add(msg); + WriteLine(msg); + + await DealWithCharacterDied(killer, death); + } + + /// + /// 治疗一个目标 + /// + /// + /// + /// + /// + 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; + } + + double realHeal = heal; + if (target.HP > 0 || (isDead && canRespawn)) + { + // 用于数据统计,不能是全额,溢出的部分需要扣除 + if (target.HP + heal > target.MaxHP) + { + realHeal = target.MaxHP - target.HP; + } + 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.##} 点生命值!!"); + } + double hp = target.HP; + double mp = target.MP; + await SetCharacterRespawn(target); + target.HP = hp; + target.MP = mp; + } + else + { + WriteLine($"[ {target} ] 回复了 {heal:0.##} 点生命值!"); + } + + // 添加助攻 + SetNotDamageAssistTime(actor, target); + + // 统计数据 + if (_stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null) + { + stats.TotalHeal += realHeal; + } + + await OnHealToTargetAsync(actor, target, heal, isRespawn); + } + + #endregion + + #region 回合内-辅助方法 + + /// + /// 需要处理复活和解除施法等 + /// + /// + /// + /// + public async Task DealWithCharacterDied(Character killer, Character death) + { await OnDeathCalculation(death, killer); death.EP = 0; @@ -1979,7 +2065,7 @@ namespace Milimoe.FunGame.Core.Model else { // 进入复活倒计时 - double respawnTime = Calculation.Round2Digits(Math.Min(30, death.Level * 0.15 + times * 0.87 + coefficient)); + double respawnTime = GetRespawnTime(death, times); _respawnCountdown.TryAdd(death, respawnTime); LastRound.RespawnCountdowns.TryAdd(death, respawnTime); WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} {GameplayEquilibriumConstant.InGameTime}!"); @@ -2010,10 +2096,6 @@ namespace Milimoe.FunGame.Core.Model } } - #endregion - - #region 回合内-辅助方法 - /// /// 使用物品实际逻辑 /// @@ -2145,7 +2227,7 @@ namespace Milimoe.FunGame.Core.Model effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates); } List targets = await OnSelectSkillTargetsAsync(caster, skill, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(caster)) + if (targets.Count == 0 && CharactersInAI.Contains(caster)) { targets = skill.SelectTargets(caster, enemys, teammates); } @@ -2168,7 +2250,7 @@ namespace Milimoe.FunGame.Core.Model effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates); } List targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates); - if (targets.Count == 0 && _charactersInAI.Contains(character)) + if (targets.Count == 0 && CharactersInAI.Contains(character)) { targets = character.NormalAttack.GetSelectableTargets(character, enemys, teammates); if (targets.Count > 0) @@ -2642,7 +2724,7 @@ namespace Milimoe.FunGame.Core.Model protected async Task WillPreCastSuperSkill() { // 选取所有 AI 控制角色 - foreach (Character other in _queue.Where(c => c.CharacterState == CharacterState.Actionable && _charactersInAI.Contains(c)).ToList()) + foreach (Character other in _queue.Where(c => c.CharacterState == CharacterState.Actionable && CharactersInAI.Contains(c)).ToList()) { // 有 65% 欲望插队 if (Random.Shared.NextDouble() < 0.65) @@ -2810,23 +2892,48 @@ namespace Milimoe.FunGame.Core.Model /// /// 设置角色为 AI 控制 /// + /// /// /// - public void SetCharactersToAIControl(bool cancel = false, params IEnumerable characters) + public void SetCharactersToAIControl(bool bySystem = true, bool cancel = false, params IEnumerable characters) { foreach (Character character in characters) { if (cancel) { - _charactersInAI.Remove(character); + if (bySystem) + { + _charactersInAIBySystem.Remove(character); + } + else + { + _charactersInAIByUser.Remove(character); + } } else { - _charactersInAI.Add(character); + if (bySystem) + { + _charactersInAIBySystem.Add(character); + } + else + { + _charactersInAIByUser.Add(character); + } } } } - + + /// + /// 设置角色为 AI 控制 [ 玩家手动设置 ] + /// + /// + /// + public void SetCharactersToAIControl(bool cancel = false, params IEnumerable characters) + { + SetCharactersToAIControl(false, cancel, characters); + } + /// /// 检查角色是否在 AI 控制状态 /// @@ -2834,7 +2941,27 @@ namespace Milimoe.FunGame.Core.Model /// public bool IsCharacterInAIControlling(Character character) { - return _charactersInAI.Contains(character); + return CharactersInAI.Contains(character); + } + + /// + /// 检查角色是否在 AI 控制状态 [ 系统控制 ] + /// + /// + /// + public bool IsCharacterInAIControllingBySystem(Character character) + { + return _charactersInAIBySystem.Contains(character); + } + + /// + /// 检查角色是否在 AI 控制状态 [ 玩家手动设置 ] + /// + /// + /// + public bool IsCharacterInAIControllingByUser(Character character) + { + return _charactersInAIByUser.Contains(character); } #endregion @@ -3142,6 +3269,22 @@ namespace Milimoe.FunGame.Core.Model { return await (DeathCalculation?.Invoke(this, killer, death) ?? Task.FromResult(true)); } + + public delegate Task DeathCalculationByTeammateEventHandler(GamingQueue queue, Character killer, Character death); + /// + /// 死亡结算(击杀队友)事件 + /// + public event DeathCalculationEventHandler? DeathCalculationByTeammate; + /// + /// 死亡结算(击杀队友)事件 + /// + /// + /// + /// + protected async Task OnDeathCalculationByTeammateAsync(Character killer, Character death) + { + return await (DeathCalculationByTeammate?.Invoke(this, killer, death) ?? Task.FromResult(true)); + } public delegate Task CharacterDeathEventHandler(GamingQueue queue, Character current, Character death); ///