From a93f9a274eb5b4784d27467f12f7671633fe5963 Mon Sep 17 00:00:00 2001 From: milimoe Date: Thu, 15 Jan 2026 01:27:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A=E9=92=A9?= =?UTF-8?q?=E5=AD=90=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9BBUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Entity/Character/Character.cs | 5 +- Entity/Skill/Effect.cs | 28 +++++++- Entity/Skill/Skill.cs | 25 ++++++- Interface/Base/IGamingQueue.cs | 5 ++ Model/DeathRelation.cs | 11 +++ Model/GamingQueue.cs | 118 ++++++++++++++++++++++----------- Model/MixGamingQueue.cs | 14 ++-- Model/TeamGamingQueue.cs | 9 ++- 8 files changed, 168 insertions(+), 47 deletions(-) create mode 100644 Model/DeathRelation.cs diff --git a/Entity/Character/Character.cs b/Entity/Character/Character.cs index f8800ee..71ea295 100644 --- a/Entity/Character/Character.cs +++ b/Entity/Character/Character.cs @@ -726,7 +726,7 @@ namespace Milimoe.FunGame.Core.Entity get { double value = SPD / GameplayEquilibriumConstant.SPDUpperLimit + ExActionCoefficient; - return Calculation.PercentageCheck(value); + return Math.Max(0, Math.Min(value, 0.9)); } } @@ -2210,7 +2210,8 @@ namespace Milimoe.FunGame.Core.Entity Skill newskill = skill.Copy(); newskill.Character = this; newskill.Level = skill.Level; - newskill.CurrentCD = 0; + newskill.CurrentCD = skill.CurrentCD; + skill.OnCharacterRespawn(newskill); Skills.Add(newskill); } foreach (Item item in items) diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs index e1142a8..4d00bd1 100644 --- a/Entity/Skill/Effect.cs +++ b/Entity/Skill/Effect.cs @@ -435,6 +435,20 @@ namespace Milimoe.FunGame.Core.Entity } + /// + /// 在技能释放前触发 + /// + /// + /// + /// + /// + /// + /// 返回 false 将角色从目标集合中移除 + public virtual bool BeforeSkillCasted(Character caster, Skill skill, List targets, List grids, Dictionary others) + { + return true; + } + /// /// 在时间流逝期间应用生命/魔法回复前修改 [ 允许取消回复 ] /// @@ -581,9 +595,10 @@ namespace Milimoe.FunGame.Core.Entity /// /// /// + /// /// /// 返回 false 表示不进行暴击检定 - public virtual bool BeforeCriticalCheck(Character actor, Character enemy, ref double throwingBonus) + public virtual bool BeforeCriticalCheck(Character actor, Character enemy, bool isNormalAttack, ref double throwingBonus) { return true; } @@ -880,6 +895,17 @@ namespace Milimoe.FunGame.Core.Entity return true; } + /// + /// 在角色开始行动时触发 + /// + /// + /// + /// + public virtual void OnCharacterActionStart(Character actor, DecisionPoints dp, CharacterActionType type) + { + + } + /// /// 在角色行动后触发 /// diff --git a/Entity/Skill/Skill.cs b/Entity/Skill/Skill.cs index b2b3757..ee9fa3f 100644 --- a/Entity/Skill/Skill.cs +++ b/Entity/Skill/Skill.cs @@ -505,7 +505,7 @@ namespace Milimoe.FunGame.Core.Entity if (IsNonDirectional || CanSelectTargetRange < 0 || GamingQueue?.Map is not GameMap map) { - return []; + return [.. selected]; } foreach (Character selectedCharacter in selected) @@ -652,6 +652,19 @@ namespace Milimoe.FunGame.Core.Entity public void OnSkillCasted(IGamingQueue queue, Character caster, List targets, List grids) { GamingQueue = queue; + Character[] characters = [caster, .. targets]; + foreach (Character target in characters) + { + Effect[] effects = [.. target.Effects.Where(e => e.IsInEffect)]; + foreach (Effect e in effects) + { + e.GamingQueue = GamingQueue; + if (!e.BeforeSkillCasted(caster, this, targets, grids, Values)) + { + targets.Remove(target); + } + } + } foreach (Effect e in Effects) { e.GamingQueue = GamingQueue; @@ -691,6 +704,16 @@ namespace Milimoe.FunGame.Core.Entity return []; } + /// + /// 在复活时,因为复活是重新构建角色,如果需要继承死亡角色的技能数据,可以重写此方法并设置相关属性 + /// + /// + /// + public virtual void OnCharacterRespawn(Skill newSkill) + { + + } + /// /// 返回技能的详细说明 /// diff --git a/Interface/Base/IGamingQueue.cs b/Interface/Base/IGamingQueue.cs index e7b2894..ed95675 100644 --- a/Interface/Base/IGamingQueue.cs +++ b/Interface/Base/IGamingQueue.cs @@ -55,6 +55,11 @@ namespace Milimoe.FunGame.Core.Interface.Base /// public Dictionary CharacterStatistics { get; } + /// + /// 助攻记录 + /// + public Dictionary AssistDetails { get; } + /// /// 角色的决策点 /// diff --git a/Model/DeathRelation.cs b/Model/DeathRelation.cs new file mode 100644 index 0000000..e75e5c3 --- /dev/null +++ b/Model/DeathRelation.cs @@ -0,0 +1,11 @@ +using Milimoe.FunGame.Core.Entity; + +namespace Milimoe.FunGame.Core.Model +{ + public class DeathRelation(Character death, Character? killer, params Character[] assists) + { + public Character Death { get; set; } = death; + public Character? Killer { get; set; } = killer; + public Character[] Assists { get; set; } = assists; + } +} diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs index d372122..8c3fe2c 100644 --- a/Model/GamingQueue.cs +++ b/Model/GamingQueue.cs @@ -67,6 +67,11 @@ namespace Milimoe.FunGame.Core.Model /// public Dictionary CharacterStatistics => _stats; + /// + /// 助攻记录 + /// + public Dictionary AssistDetails => _assistDetail; + /// /// 游戏运行的时间 /// @@ -163,6 +168,11 @@ namespace Milimoe.FunGame.Core.Model /// public Dictionary CharacterDecisionPoints => _decisionPoints; + /// + /// 游戏结束标识 + /// + public bool GameOver => _isGameEnd; + #endregion #region 保护变量 @@ -260,7 +270,7 @@ namespace Milimoe.FunGame.Core.Model /// /// 当前回合死亡角色和参与击杀的人 /// - protected readonly Dictionary _roundDeaths = []; + protected readonly List _roundDeaths = []; /// /// 回合奖励 @@ -910,7 +920,8 @@ namespace Milimoe.FunGame.Core.Model } // 减少复活倒计时 - foreach (Character character in _respawnCountdown.Keys) + Character[] willRespawns = [.. _respawnCountdown.Keys]; + foreach (Character character in willRespawns) { _respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce); if (_respawnCountdown[character] <= 0) @@ -919,6 +930,8 @@ namespace Milimoe.FunGame.Core.Model } } + ProcessCharacterDeath(); + WriteLine("\r\n"); return timeToReduce; @@ -1276,6 +1289,12 @@ namespace Milimoe.FunGame.Core.Model int costDP = dp.GetActionPointCost(type); + effects = [.. character.Effects.Where(e => e.IsInEffect)]; + foreach (Effect effect in effects) + { + effect.OnCharacterActionStart(character, dp, type); + } + if (type == CharacterActionType.Move) { if (_map != null) @@ -1800,7 +1819,7 @@ namespace Milimoe.FunGame.Core.Model } } - if (character.CharacterState != CharacterState.Casting) + if (character.CharacterState != CharacterState.Casting && dp.ActionsHardnessTime.Count > 0) { baseTime = dp.ActionsTaken > 1 ? (dp.ActionsHardnessTime.Max() + dp.ActionsTaken) : dp.ActionsHardnessTime.Max(); } @@ -1819,7 +1838,7 @@ namespace Milimoe.FunGame.Core.Model } // 统一在回合结束时处理角色的死亡 - ProcessCharacterDeath(character); + ProcessCharacterDeath(); // 移除回合奖励 RemoveRoundRewards(character, rewards); @@ -1899,29 +1918,20 @@ namespace Milimoe.FunGame.Core.Model /// /// 处理角色死亡 /// - /// - protected void ProcessCharacterDeath(Character character) + protected void ProcessCharacterDeath() { - foreach (Character death in _roundDeaths.Keys) + foreach (DeathRelation dr in _roundDeaths) { - Character[] assists = _roundDeaths[death]; + Character death = dr.Death; + Character? killer = dr.Killer; + Character[] assists = dr.Assists; - if (!OnCharacterDeathEvent(character, death, assists)) + if (!_isGameEnd) { - continue; + AfterDeathCalculation(death, killer, assists); } - - // 给所有角色的特效广播角色死亡结算 - List effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.IsInEffect))]; - foreach (Effect effect in effects) - { - effect.AfterDeathCalculation(death, character, _continuousKilling, _earnedMoney, assists); - } - // 将死者移出队列 - _queue.Remove(death); - - AfterDeathCalculation(death, character, assists); } + _roundDeaths.Clear(); } /// @@ -1967,16 +1977,23 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected virtual void AfterDeathCalculation(Character death, Character killer, Character[] assists) + protected virtual void AfterDeathCalculation(Character death, Character? killer, Character[] assists) { - if (!_queue.Any(c => c != killer && c.Master != killer && killer.Master != c)) + if (!_queue.Any(c => c != killer && c.Master != killer && killer?.Master != c)) { // 没有其他的角色了,游戏结束 - WriteLine("[ " + killer + " ] 是胜利者。"); - _queue.Remove(killer); - _eliminated.Add(killer); + if (killer != null) + { + WriteLine("[ " + killer + " ] 是胜利者。"); + _queue.Remove(killer); + _eliminated.Add(killer); + OnGameEndEvent(killer); + } + else + { + WriteLine("游戏结束。"); + } _isGameEnd = true; - OnGameEndEvent(killer); } } @@ -2389,7 +2406,6 @@ namespace Milimoe.FunGame.Core.Model if (enemy.HP <= 0 && !_eliminated.Contains(enemy) && !_respawnCountdown.ContainsKey(enemy)) { LastRound.HasKill = true; - _roundDeaths.Add(enemy, []); DeathCalculation(actor, enemy); } } @@ -2401,6 +2417,9 @@ namespace Milimoe.FunGame.Core.Model /// public void DeathCalculation(Character killer, Character death) { + DeathRelation dr = new(death, killer); + _roundDeaths.Add(dr); + if (killer == death) { if (!OnDeathCalculationEvent(killer, death)) @@ -2453,6 +2472,9 @@ namespace Milimoe.FunGame.Core.Model Character[] assists = [.. _assistDetail.Keys.Where(c => c != death && ((TotalTime - _assistDetail[c].GetLastTime(death) <= 30) || (TotalTime - _assistDetail[c].GetNotDamageAssistLastTime(killer) <= 20)))]; + // 获取队友列表 + Character[] teammates = [.. GetTeammates(killer)]; + // 获取贡献百分比 以伤害为主,非伤害助攻贡献不足 10% 的按 10% 计算 double minPercentage = 0.1; Dictionary assistPercentage = _assistDetail.Keys.Where(assists.Contains).ToDictionary(c => c, @@ -2509,7 +2531,10 @@ namespace Milimoe.FunGame.Core.Model cmoney += calDiff(death, assist); if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney; assist.User.Inventory.Credits += cmoney; - _stats[assist].Assists += 1; + if (teammates.Length == 0 || (teammates.Length > 0 && teammates.Contains(assist))) + { + _stats[assist].Assists += 1; + } if (!LastRound.Assists.Contains(assist)) LastRound.Assists.Add(assist); } else @@ -2544,7 +2569,7 @@ namespace Milimoe.FunGame.Core.Model } WriteLine(msg); } - _roundDeaths[death] = assists; + dr.Assists = assists; if (FirstKiller is null) { @@ -2586,6 +2611,17 @@ namespace Milimoe.FunGame.Core.Model } DealWithCharacterDied(killer, death); + + // 给所有角色的特效广播角色死亡结算 + List effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Union(killer.Effects).Distinct()]; + foreach (Effect effect in effects) + { + effect.AfterDeathCalculation(death, killer, _continuousKilling, _earnedMoney, assists); + } + // 将死者移出队列 + _queue.Remove(death); + + OnCharacterDeathEvent(death, killer, assists); } /// @@ -2630,6 +2666,12 @@ namespace Milimoe.FunGame.Core.Model /// public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false, bool triggerEffects = true) { + // 死人怎么能对自己治疗呢? + if (actor.HP <= 0) + { + return; + } + if (target.HP == target.MaxHP) { return; @@ -3287,7 +3329,7 @@ namespace Milimoe.FunGame.Core.Model effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) { - if (!effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus)) + if (!effect.BeforeCriticalCheck(actor, enemy, isNormalAttack, ref throwingBonus)) { checkCritical = false; } @@ -3414,7 +3456,7 @@ namespace Milimoe.FunGame.Core.Model effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()]; foreach (Effect effect in effects) { - if (!effect.BeforeCriticalCheck(actor, enemy, ref throwingBonus)) + if (!effect.BeforeCriticalCheck(actor, enemy, isNormalAttack, ref throwingBonus)) { checkCritical = false; } @@ -4189,7 +4231,7 @@ namespace Milimoe.FunGame.Core.Model { stats.TotalTrueDamage += damage; } - if (damageType == DamageType.Magical) + else if (damageType == DamageType.Magical) { stats.TotalMagicDamage += damage; } @@ -4205,7 +4247,7 @@ namespace Milimoe.FunGame.Core.Model { statsTaken.TotalTakenTrueDamage = Calculation.Round2Digits(statsTaken.TotalTakenTrueDamage + takenDamage); } - if (damageType == DamageType.Magical) + else if (damageType == DamageType.Magical) { statsTaken.TotalTakenMagicDamage = Calculation.Round2Digits(statsTaken.TotalTakenMagicDamage + takenDamage); } @@ -4513,7 +4555,7 @@ namespace Milimoe.FunGame.Core.Model return DeathCalculationByTeammateEvent?.Invoke(this, killer, death) ?? true; } - public delegate bool CharacterDeathEventHandler(GamingQueue queue, Character current, Character death, Character[] assists); + public delegate bool CharacterDeathEventHandler(GamingQueue queue, Character death, Character? killer, Character[] assists); /// /// 角色死亡事件,此事件位于 之后 /// @@ -4521,13 +4563,13 @@ namespace Milimoe.FunGame.Core.Model /// /// 角色死亡事件,此事件位于 之后 /// - /// /// + /// /// /// - protected bool OnCharacterDeathEvent(Character current, Character death, Character[] assists) + protected bool OnCharacterDeathEvent(Character death, Character? killer, Character[] assists) { - return CharacterDeathEvent?.Invoke(this, current, death, assists) ?? true; + return CharacterDeathEvent?.Invoke(this, death, killer, assists) ?? true; } public delegate void HealToTargetEventHandler(GamingQueue queue, Character actor, Character target, double heal, bool isRespawn); diff --git a/Model/MixGamingQueue.cs b/Model/MixGamingQueue.cs index 4e107bc..bc168b4 100644 --- a/Model/MixGamingQueue.cs +++ b/Model/MixGamingQueue.cs @@ -15,7 +15,7 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override void AfterDeathCalculation(Character death, Character killer, Character[] assists) + protected override void AfterDeathCalculation(Character death, Character? killer, Character[] assists) { if (MaxRespawnTimes != 0 && MaxScoreToWin > 0) { @@ -23,13 +23,13 @@ namespace Milimoe.FunGame.Core.Model .Select(kv => $"[ {kv.Key} ] {kv.Value.Kills} 分"))}\r\n剩余存活人数:{_queue.Count}"); } - if (!_queue.Any(c => c != killer && c.Master != killer && killer.Master != c)) + if (!_queue.Any(c => c != killer && c.Master != killer && killer?.Master != c)) { // 没有其他的角色了,游戏结束 EndGameInfo(killer); } - if (MaxScoreToWin > 0 && _stats[killer].Kills >= MaxScoreToWin) + if (MaxScoreToWin > 0 && killer != null && _stats[killer].Kills >= MaxScoreToWin) { EndGameInfo(killer); return; @@ -58,8 +58,14 @@ namespace Milimoe.FunGame.Core.Model /// /// 游戏结束信息 /// - public void EndGameInfo(Character winner) + public void EndGameInfo(Character? winner) { + winner ??= _queue.FirstOrDefault(); + if (winner is null) + { + WriteLine("游戏结束。"); + return; + } WriteLine("[ " + winner + " ] 是胜利者。"); foreach (Character character in _stats.OrderBy(kv => kv.Value.Kills) .ThenByDescending(kv => kv.Value.Deaths) diff --git a/Model/TeamGamingQueue.cs b/Model/TeamGamingQueue.cs index fbf34e0..e3adf9d 100644 --- a/Model/TeamGamingQueue.cs +++ b/Model/TeamGamingQueue.cs @@ -159,8 +159,15 @@ namespace Milimoe.FunGame.Core.Model /// /// /// - protected override void AfterDeathCalculation(Character death, Character killer, Character[] assists) + protected override void AfterDeathCalculation(Character death, Character? killer, Character[] assists) { + killer ??= _queue.FirstOrDefault(); + if (killer is null) + { + WriteLine("游戏结束。"); + return; + } + Team? killTeam = GetTeam(killer); Team? deathTeam = GetTeam(death);