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);
///