行动顺序表兼容单位设计 (#147)

* 初步添加单位判断

* 完成单位的特殊处理;新增技能释放前的询问钩子
This commit is contained in:
milimoe 2026-01-14 19:03:52 +08:00 committed by GitHub
parent d72ccb2dd3
commit 6325e9a956
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 437 additions and 153 deletions

View File

@ -307,6 +307,17 @@ namespace Milimoe.FunGame.Core.Entity
}
}
/// <summary>
/// 在选取目标前向角色(玩家)发起询问
/// </summary>
/// <param name="character"></param>
/// <param name="item"></param>
/// <returns></returns>
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, Item item)
{
return null;
}
/// <summary>
/// 局内使用物品触发
/// </summary>

View File

@ -546,7 +546,8 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="killer"></param>
/// <param name="continuousKilling"></param>
/// <param name="earnedMoney"></param>
public virtual void AfterDeathCalculation(Character death, Character? killer, Dictionary<Character, int> continuousKilling, Dictionary<Character, int> earnedMoney)
/// <param name="assists"></param>
public virtual void AfterDeathCalculation(Character death, Character? killer, Dictionary<Character, int> continuousKilling, Dictionary<Character, int> earnedMoney, Character[] assists)
{
}
@ -865,6 +866,20 @@ namespace Milimoe.FunGame.Core.Entity
return true;
}
/// <summary>
/// 在特效豁免检定时
/// </summary>
/// <param name="character"></param>
/// <param name="source"></param>
/// <param name="effect"></param>
/// <param name="isEvade"></param>
/// <param name="throwingBonus"></param>
/// <returns>false跳过豁免检定</returns>
public virtual bool OnExemptionCheck(Character character, Character? source, Effect effect, bool isEvade, ref double throwingBonus)
{
return true;
}
/// <summary>
/// 在角色行动后触发
/// </summary>

View File

@ -478,6 +478,24 @@ namespace Milimoe.FunGame.Core.Entity
/// <returns></returns>
public override string ToString() => GetInfo(true);
/// <summary>
/// 在选取目标前向角色(玩家)发起询问的事件
/// </summary>
/// <param name="character"></param>
/// <param name="normalAttack"></param>
/// <returns></returns>
public delegate InquiryOptions? NormalAttackInquiryOptionsDelegate(Character character, NormalAttack normalAttack);
public event NormalAttackInquiryOptionsDelegate? InquiryBeforeTargetSelectionEvent;
/// <summary>
/// 触发选择目标前的询问事件
/// </summary>
/// <param name="character"></param>
/// <param name="normalAttack"></param>
public InquiryOptions? OnInquiryBeforeTargetSelection(Character character, NormalAttack normalAttack)
{
return InquiryBeforeTargetSelectionEvent?.Invoke(character, normalAttack);
}
/// <summary>
/// 等级
/// </summary>

View File

@ -4,6 +4,7 @@ using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Entity
{
@ -379,6 +380,17 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <summary>
/// 在选取目标前向角色(玩家)发起询问
/// </summary>
/// <param name="character"></param>
/// <param name="skill"></param>
/// <returns></returns>
public virtual InquiryOptions? InquiryBeforeTargetSelection(Character character, Skill skill)
{
return null;
}
/// <summary>
/// 获取可选择的目标列表
/// </summary>

View File

@ -26,6 +26,10 @@
public bool IsOnThisTeam(Character character)
{
if (character.Master != null)
{
character = character.Master;
}
return Members.Contains(character);
}

View File

@ -258,9 +258,9 @@ namespace Milimoe.FunGame.Core.Model
protected readonly Dictionary<Character, double> _respawnCountdown = [];
/// <summary>
/// 当前回合死亡角色
/// 当前回合死亡角色和参与击杀的人
/// </summary>
protected readonly List<Character> _roundDeaths = [];
protected readonly Dictionary<Character, Character[]> _roundDeaths = [];
/// <summary>
/// 回合奖励
@ -352,6 +352,45 @@ namespace Milimoe.FunGame.Core.Model
_map = map.InitGamingQueue(this);
}
/// <summary>
/// 将角色从地图上移除
/// </summary>
public void RemoveCharacterFromMap(params IEnumerable<Character> characters)
{
if (Map is null)
{
return;
}
foreach (Character character in characters)
{
RemoveCharacterFromQueue(character);
Map.Characters.Remove(character);
Grid[] grids = [.. Map.Grids.Values.Where(g => g.Characters.Contains(character))];
foreach (Grid grid in grids)
{
grid.Characters.Remove(character);
}
}
}
/// <summary>
/// 将角色拥有的单位从地图上移除
/// </summary>
public void RemoveCharactersUnitFromMap(params IEnumerable<Character> characters)
{
if (Map is null)
{
return;
}
foreach (Character character in characters)
{
Character[] willRemove = [.. _queue.Where(c => c.Master == character)];
RemoveCharacterFromMap(willRemove);
}
}
#endregion
#region
@ -365,6 +404,7 @@ namespace Milimoe.FunGame.Core.Model
// 保存原始的角色信息。用于复活时还原状态
foreach (Character character in characters)
{
if (character.IsUnit) continue;
// 添加角色引用到所有角色列表
_allCharacters.Add(character);
// 复制原始角色对象
@ -384,7 +424,7 @@ namespace Milimoe.FunGame.Core.Model
}
// 获取 HP 小于等于 0 的角色
List<Character> deadCharacters = [.. characters.Where(c => c.HP <= 0)];
List<Character> deadCharacters = [.. characters.Where(c => c.HP <= 0 && !c.IsUnit)];
foreach (Character death in deadCharacters)
{
_eliminated.Add(death);
@ -588,6 +628,9 @@ namespace Milimoe.FunGame.Core.Model
}
}
}
// 重新排序
_queue.Sort((a, b) => _hardnessTimes[a].CompareTo(_hardnessTimes[b]));
}
/// <summary>
@ -615,6 +658,19 @@ namespace Milimoe.FunGame.Core.Model
_charactersInAIByUser.Clear();
}
/// <summary>
/// 将角色彻底移出行动顺序表
/// </summary>
/// <param name="characters"></param>
public void RemoveCharacterFromQueue(params IEnumerable<Character> characters)
{
foreach (Character character in characters)
{
_queue.Remove(character);
_hardnessTimes.Remove(character);
}
}
#endregion
#region
@ -696,11 +752,19 @@ namespace Milimoe.FunGame.Core.Model
}
// 统计
_stats[character].LiveRound += 1;
_stats[character].LiveTime += timeToReduce;
_stats[character].DamagePerRound = _stats[character].LiveRound == 0 ? 0 : _stats[character].TotalDamage / _stats[character].LiveRound;
_stats[character].DamagePerTurn = _stats[character].ActionTurn == 0 ? 0 : _stats[character].TotalDamage / _stats[character].ActionTurn;
_stats[character].DamagePerSecond = _stats[character].LiveTime == 0 ? 0 : _stats[character].TotalDamage / _stats[character].LiveTime;
Character statsCharacter = character;
if (character.Master != null)
{
statsCharacter = character.Master;
}
if (character.Master is null)
{
_stats[statsCharacter].LiveRound += 1;
_stats[statsCharacter].LiveTime += timeToReduce;
}
_stats[statsCharacter].DamagePerRound = _stats[statsCharacter].LiveRound == 0 ? 0 : _stats[statsCharacter].TotalDamage / _stats[statsCharacter].LiveRound;
_stats[statsCharacter].DamagePerTurn = _stats[statsCharacter].ActionTurn == 0 ? 0 : _stats[statsCharacter].TotalDamage / _stats[statsCharacter].ActionTurn;
_stats[statsCharacter].DamagePerSecond = _stats[statsCharacter].LiveTime == 0 ? 0 : _stats[statsCharacter].TotalDamage / _stats[statsCharacter].LiveTime;
// 回血回蓝
double recoveryHP = character.HR * timeToReduce;
@ -725,19 +789,19 @@ namespace Milimoe.FunGame.Core.Model
{
character.HP += reallyReHP;
character.MP += reallyReMP;
if (IsDebug) WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}");
if (IsDebug) WriteLine($"角色 {character} 回血:{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;
if (IsDebug) WriteLine($"角色 {character.Name} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 当前能量:{character.EP:0.##}");
if (IsDebug) WriteLine($"角色 {character} 回血:{recoveryHP:0.##} [{character.HP:0.##} / {character.MaxHP:0.##}] / 当前能量:{character.EP:0.##}");
}
if (reallyReMP > 0)
{
character.MP += reallyReMP;
if (IsDebug) WriteLine($"角色 {character.Name} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}");
if (IsDebug) WriteLine($"角色 {character} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}");
}
}
}
@ -798,8 +862,16 @@ namespace Milimoe.FunGame.Core.Model
// 统计控制时长
if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable)
{
_stats[effect.Source].ControlTime += timeToReduce;
_assistDetail[effect.Source][character, TotalTime] += 1;
Character source = effect.Source;
if (effect.Source.Master != null)
{
source = effect.Source.Master;
}
_stats[source].ControlTime += timeToReduce;
if (character.Master is null)
{
_assistDetail[source][character, TotalTime] += 1;
}
}
if (effect.Durative)
@ -878,6 +950,11 @@ namespace Milimoe.FunGame.Core.Model
_isInRound = true;
LastRound.Actor = character;
_roundDeaths.Clear();
Character statsCharacter = character;
if (character.Master != null)
{
statsCharacter = character.Master;
}
if (!BeforeTurn(character))
{
@ -899,7 +976,7 @@ namespace Milimoe.FunGame.Core.Model
List<Character> allTeammates = GetTeammates(character);
// 敌人列表
List<Character> allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c))];
List<Character> allEnemys = [.. _allCharacters.Union(_queue).Distinct().Where(c => c != character && !allTeammates.Contains(c) && !_eliminated.Contains(c) && c.Master != character && character.Master != c)];
// 取得可选列表
(List<Character> selectableTeammates, List<Character> selectableEnemys, List<Skill> skills, List<Item> items) = GetTurnStartNeedyList(character, allTeammates, allEnemys);
@ -941,7 +1018,7 @@ namespace Milimoe.FunGame.Core.Model
// 是否结束回合
bool endTurn = false;
bool isAI = CharactersInAI.Contains(character);
bool isAI = IsCharacterInAIControlling(character);
// 循环条件未结束回合、决策点大于0AI控制下为0时自动结束或角色处于吟唱态
while (!endTurn && (!isAI || dp.CurrentDecisionPoints > 0 || character.CharacterState == CharacterState.Casting || character.CharacterState == CharacterState.PreCastSuperSkill))
@ -975,7 +1052,7 @@ namespace Milimoe.FunGame.Core.Model
// 循环条件:
// AI 控制下未决策、取消次数大于0
// 手动控制下:未决策
isAI = CharactersInAI.Contains(character);
isAI = IsCharacterInAIControlling(character);
while (!decided && (!isAI || cancelTimes > 0))
{
// 根据当前位置,更新可选取角色列表
@ -1245,6 +1322,13 @@ namespace Milimoe.FunGame.Core.Model
else
{
// 使用普通攻击逻辑
// 如果有询问,先进行询问
character.NormalAttack.GamingQueue = this;
if (character.NormalAttack.OnInquiryBeforeTargetSelection(character, character.NormalAttack) is InquiryOptions inquiry)
{
Inquiry(character, inquiry);
}
// 选择目标
List<Character> targets;
if (aiDecision != null)
{
@ -1265,8 +1349,8 @@ namespace Milimoe.FunGame.Core.Model
{
LastRound.Targets[CharacterActionType.NormalAttack] = [.. targets];
LastRound.ActionTypes.Add(CharacterActionType.NormalAttack);
_stats[character].UseDecisionPoints += costDP;
_stats[character].TurnDecisions++;
_stats[statsCharacter].UseDecisionPoints += costDP;
_stats[statsCharacter].TurnDecisions++;
dp.AddActionType(CharacterActionType.NormalAttack);
dp.CurrentDecisionPoints -= costDP;
decided = true;
@ -1304,7 +1388,7 @@ namespace Milimoe.FunGame.Core.Model
{
skill = OnSelectSkillEvent(character, skills);
}
if (skill is null && CharactersInAI.Contains(character) && skills.Count > 0)
if (skill is null && IsCharacterInAIControlling(character) && skills.Count > 0)
{
skill = skills[Random.Shared.Next(skills.Count)];
}
@ -1320,6 +1404,14 @@ namespace Milimoe.FunGame.Core.Model
}
else if (skill.SkillType == SkillType.Magic)
{
if (CheckCanCast(character, skill, out double cost))
{
// 如果有询问,先进行询问
if (skill.InquiryBeforeTargetSelection(character, skill) is InquiryOptions inquiry)
{
Inquiry(character, inquiry);
}
// 吟唱前需要先选取目标
List<Grid> castRange = [];
if (_map != null && realGrid != null)
@ -1341,8 +1433,8 @@ namespace Milimoe.FunGame.Core.Model
LastRound.Skills[CharacterActionType.PreCastSkill] = skill;
LastRound.Targets[CharacterActionType.PreCastSkill] = [.. targets];
LastRound.ActionTypes.Add(CharacterActionType.PreCastSkill);
_stats[character].UseDecisionPoints += costDP;
_stats[character].TurnDecisions++;
_stats[statsCharacter].UseDecisionPoints += costDP;
_stats[statsCharacter].TurnDecisions++;
dp.AddActionType(CharacterActionType.PreCastSkill);
dp.CurrentDecisionPoints -= costDP;
decided = true;
@ -1362,6 +1454,7 @@ namespace Milimoe.FunGame.Core.Model
if (IsDebug) WriteLine($"[ {character} ] 想要吟唱 [ {skill.Name} ],但是没有目标!");
}
}
}
else if (skill is CourageCommandSkill && dp.CourageCommandSkill)
{
if (IsDebug) WriteLine($"角色 [ {character} ] 该回合已经使用过勇气指令,无法再次使用勇气指令!");
@ -1379,6 +1472,12 @@ namespace Milimoe.FunGame.Core.Model
// 只有魔法需要吟唱,战技和爆发技直接释放
if (CheckCanCast(character, skill, out double cost))
{
// 如果有询问,先进行询问
if (skill.InquiryBeforeTargetSelection(character, skill) is InquiryOptions inquiry)
{
Inquiry(character, inquiry);
}
List<Grid> castRange = [];
if (_map != null && realGrid != null)
{
@ -1402,8 +1501,8 @@ namespace Milimoe.FunGame.Core.Model
LastRound.ActionTypes.Add(skillType);
if (skill is not CourageCommandSkill)
{
_stats[character].UseDecisionPoints += costDP;
_stats[character].TurnDecisions++;
_stats[statsCharacter].UseDecisionPoints += costDP;
_stats[statsCharacter].TurnDecisions++;
dp.AddActionType(skillType);
dp.CurrentDecisionPoints -= costDP;
}
@ -1513,6 +1612,8 @@ namespace Milimoe.FunGame.Core.Model
WriteLine($"[ {character} ] 想要释放 [ {skill.Name} ],但是没有目标!");
}
WriteLine($"[ {character} ] 放弃释放技能!");
character.CharacterState = CharacterState.Actionable;
character.UpdateCharacterState();
// 放弃释放技能会获得3的硬直时间
if (baseTime == 0) baseTime = 3;
decided = true;
@ -1528,7 +1629,7 @@ namespace Milimoe.FunGame.Core.Model
}
else if (type == CharacterActionType.CastSuperSkill)
{
_stats[character].TurnDecisions++;
_stats[statsCharacter].TurnDecisions++;
dp.AddActionType(CharacterActionType.CastSuperSkill);
LastRound.ActionTypes.Add(CharacterActionType.CastSuperSkill);
decided = true;
@ -1573,6 +1674,8 @@ namespace Milimoe.FunGame.Core.Model
else
{
WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
character.CharacterState = CharacterState.Actionable;
character.UpdateCharacterState();
// 放弃释放技能会获得3的硬直时间
if (baseTime == 0) baseTime = 3;
decided = true;
@ -1591,7 +1694,7 @@ namespace Milimoe.FunGame.Core.Model
{
item = OnSelectItemEvent(character, items);
}
if (item is null && CharactersInAI.Contains(character) && items.Count > 0)
if (item is null && IsCharacterInAIControlling(character) && items.Count > 0)
{
// AI 控制下随机选取一个物品
item = items[Random.Shared.Next(items.Count)];
@ -1599,6 +1702,18 @@ namespace Milimoe.FunGame.Core.Model
if (item != null && item.Skills.Active != null)
{
Skill skill = item.Skills.Active;
// 如果有询问,先进行询问
if (item.InquiryBeforeTargetSelection(character, item) is InquiryOptions inquiry)
{
Inquiry(character, inquiry);
}
if (skill.InquiryBeforeTargetSelection(character, skill) is InquiryOptions inquiry2)
{
Inquiry(character, inquiry2);
}
List<Grid> castRange = [];
if (_map != null && realGrid != null)
{
@ -1616,8 +1731,8 @@ namespace Milimoe.FunGame.Core.Model
}
else if (UseItem(item, character, dp, enemys, teammates, castRange, allEnemys, allTeammates, aiDecision))
{
_stats[character].UseDecisionPoints += costDP;
_stats[character].TurnDecisions++;
_stats[statsCharacter].UseDecisionPoints += costDP;
_stats[statsCharacter].TurnDecisions++;
dp.AddActionType(CharacterActionType.UseItem);
dp.CurrentDecisionPoints -= costDP;
LastRound.ActionTypes.Add(CharacterActionType.UseItem);
@ -1634,7 +1749,7 @@ namespace Milimoe.FunGame.Core.Model
}
else if (type == CharacterActionType.EndTurn)
{
_stats[character].TurnDecisions++;
_stats[statsCharacter].TurnDecisions++;
SetOnlyMoveHardnessTime(character, dp, ref baseTime);
decided = true;
endTurn = true;
@ -1690,7 +1805,10 @@ namespace Milimoe.FunGame.Core.Model
baseTime = dp.ActionsTaken > 1 ? (dp.ActionsHardnessTime.Max() + dp.ActionsTaken) : dp.ActionsHardnessTime.Max();
}
if (character.Master is null)
{
_stats[character].ActionTurn += 1;
}
AfterCharacterDecision(character, dp);
OnCharacterDecisionCompletedEvent(character, dp, LastRound);
@ -1784,9 +1902,11 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="character"></param>
protected void ProcessCharacterDeath(Character character)
{
foreach (Character death in _roundDeaths)
foreach (Character death in _roundDeaths.Keys)
{
if (!OnCharacterDeathEvent(character, death))
Character[] assists = _roundDeaths[death];
if (!OnCharacterDeathEvent(character, death, assists))
{
continue;
}
@ -1795,12 +1915,12 @@ namespace Milimoe.FunGame.Core.Model
List<Effect> effects = [.. _queue.SelectMany(c => c.Effects.Where(e => e.IsInEffect))];
foreach (Effect effect in effects)
{
effect.AfterDeathCalculation(death, character, _continuousKilling, _earnedMoney);
effect.AfterDeathCalculation(death, character, _continuousKilling, _earnedMoney, assists);
}
// 将死者移出队列
_queue.Remove(death);
AfterDeathCalculation(death, character);
AfterDeathCalculation(death, character, assists);
}
}
@ -1813,7 +1933,7 @@ namespace Milimoe.FunGame.Core.Model
protected virtual bool AfterCharacterAction(Character character, CharacterActionType type)
{
List<Character> allTeammates = GetTeammates(character);
Character[] allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c) && !_eliminated.Contains(c))];
Character[] allEnemys = [.. _allCharacters.Union(_queue).Distinct().Where(c => c != character && !allTeammates.Contains(c) && !_eliminated.Contains(c) && c.Master != character && character.Master != c)];
if (!allEnemys.Any(c => c.HP > 0))
{
return false;
@ -1846,9 +1966,10 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
/// <param name="death"></param>
/// <param name="killer"></param>
protected virtual void AfterDeathCalculation(Character death, Character killer)
/// <param name="assists"></param>
protected virtual void AfterDeathCalculation(Character death, Character killer, Character[] assists)
{
if (!_queue.Where(c => c != killer).Any())
if (!_queue.Any(c => c != killer && c.Master != killer && killer.Master != c))
{
// 没有其他的角色了,游戏结束
WriteLine("[ " + killer + " ] 是胜利者。");
@ -2173,11 +2294,19 @@ namespace Milimoe.FunGame.Core.Model
}
// 统计护盾
if (damage > actualDamage && _stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null)
if (damage > actualDamage)
{
Character statsCharacter = actor;
if (statsCharacter.Master != null)
{
statsCharacter = statsCharacter.Master;
}
if (_stats.TryGetValue(statsCharacter, out CharacterStatistics? stats) && stats != null)
{
stats.TotalShield += damage - actualDamage;
}
}
}
enemy.HP -= actualDamage;
string strDamageMessage = $"[ {enemy} ] 受到了 {actualDamage:0.##} 点{damageTypeString}{shieldMsg}";
@ -2224,11 +2353,22 @@ namespace Milimoe.FunGame.Core.Model
// 计算助攻
if (actor != enemy && !IsTeammate(actor, enemy))
{
if (actor.Master != null)
{
actor = actor.Master;
}
if (enemy.Master != null)
{
enemy = enemy.Master;
}
if (actor != enemy)
{
_assistDetail[actor][enemy, TotalTime] += damage;
}
}
}
}
else
{
LastRound.IsEvaded[enemy] = true;
@ -2249,7 +2389,7 @@ namespace Milimoe.FunGame.Core.Model
if (enemy.HP <= 0 && !_eliminated.Contains(enemy) && !_respawnCountdown.ContainsKey(enemy))
{
LastRound.HasKill = true;
_roundDeaths.Add(enemy);
_roundDeaths.Add(enemy, []);
DeathCalculation(actor, enemy);
}
}
@ -2261,6 +2401,26 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="death"></param>
public void DeathCalculation(Character killer, Character death)
{
if (killer == death)
{
if (!OnDeathCalculationEvent(killer, death))
{
return;
}
if (killer.Master is null)
{
_stats[death].Deaths += 1;
}
WriteLine($"[ {death} ] 自杀了!");
DealWithCharacterDied(killer, death);
return;
}
if (killer.Master != null)
{
killer = killer.Master;
}
if (IsTeammate(killer, death))
{
DeathCalculationByTeammate(killer, death);
@ -2272,11 +2432,8 @@ namespace Milimoe.FunGame.Core.Model
return;
}
if (killer == death)
if (death.Master != null)
{
_stats[death].Deaths += 1;
WriteLine($"[ {death} ] 自杀了!");
DealWithCharacterDied(killer, death);
return;
}
@ -2387,6 +2544,7 @@ namespace Milimoe.FunGame.Core.Model
}
WriteLine(msg);
}
_roundDeaths[death] = assists;
if (FirstKiller is null)
{
@ -2443,6 +2601,8 @@ namespace Milimoe.FunGame.Core.Model
return;
}
if (death.Master is null)
{
_stats[death].Deaths += 1;
string msg = $"[ {killer} ] 反补了 [ {death} ][ {killer} ] 受到了击杀队友惩罚,扣除 200 {GameplayEquilibriumConstant.InGameCurrency}并且当前连杀计数减少一次!!";
if (!_earnedMoney.TryAdd(killer, -200))
@ -2455,6 +2615,7 @@ namespace Milimoe.FunGame.Core.Model
}
LastRound.DeathContinuousKilling.Add(msg);
WriteLine(msg);
}
DealWithCharacterDied(killer, death);
}
@ -2537,7 +2698,12 @@ namespace Milimoe.FunGame.Core.Model
SetNotDamageAssistTime(actor, target);
// 统计数据
if (_stats.TryGetValue(actor, out CharacterStatistics? stats) && stats != null)
Character statsCharacter = actor;
if (statsCharacter.Master != null)
{
statsCharacter = statsCharacter.Master;
}
if (_stats.TryGetValue(statsCharacter, out CharacterStatistics? stats) && stats != null)
{
stats.TotalHeal += heal;
}
@ -2629,6 +2795,8 @@ namespace Milimoe.FunGame.Core.Model
death.EP = 0;
if (death.Master is null)
{
// 清除对死者的助攻数据
List<AssistDetail> ads = [.. _assistDetail.Values.Where(ad => ad.Character != death)];
foreach (AssistDetail ad in ads)
@ -2654,6 +2822,7 @@ namespace Milimoe.FunGame.Core.Model
LastRound.RespawnCountdowns.TryAdd(death, respawnTime);
WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} {GameplayEquilibriumConstant.InGameTime}");
}
}
// 移除死者的施法
_castingSkills.Remove(death);
@ -2890,7 +3059,7 @@ namespace Milimoe.FunGame.Core.Model
effect.AlterSelectListBeforeSelection(caster, skill, enemys, teammates);
}
List<Character> targets = OnSelectSkillTargetsEvent(caster, skill, enemys, teammates, castRange);
if (targets.Count == 0 && CharactersInAI.Contains(caster))
if (targets.Count == 0 && IsCharacterInAIControlling(caster))
{
targets = skill.SelectTargets(caster, enemys, teammates);
}
@ -2909,7 +3078,7 @@ namespace Milimoe.FunGame.Core.Model
public List<Grid> SelectNonDirectionalSkillTargetGrid(Character caster, Skill skill, List<Character> enemys, List<Character> teammates, List<Grid> castRange)
{
List<Grid> targets = OnSelectNonDirectionalSkillTargetsEvent(caster, skill, enemys, teammates, castRange);
if (targets.Count == 0 && CharactersInAI.Contains(caster) && castRange.Count > 0)
if (targets.Count == 0 && IsCharacterInAIControlling(caster) && castRange.Count > 0)
{
targets = skill.SelectNonDirectionalTargets(caster, castRange.OrderBy(r => Random.Shared.Next()).FirstOrDefault(r => r.Characters.Count > 0) ?? castRange.First(), skill.SelectIncludeCharacterGrid);
}
@ -2933,7 +3102,7 @@ namespace Milimoe.FunGame.Core.Model
effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates);
}
List<Character> targets = OnSelectNormalAttackTargetsEvent(character, attack, enemys, teammates, attackRange);
if (targets.Count == 0 && CharactersInAI.Contains(character))
if (targets.Count == 0 && IsCharacterInAIControlling(character))
{
targets = character.NormalAttack.SelectTargets(character, enemys, teammates);
}
@ -3291,7 +3460,7 @@ namespace Milimoe.FunGame.Core.Model
public List<Character> GetEnemies(Character character)
{
List<Character> teammates = GetTeammates(character);
return [.. _allCharacters.Where(c => c != character && !teammates.Contains(c))];
return [.. _allCharacters.Union(_queue).Distinct().Where(c => c != character && !teammates.Contains(c) && !_eliminated.Contains(c) && c.Master != character && character.Master != c)];
}
/// <summary>
@ -3301,7 +3470,7 @@ namespace Milimoe.FunGame.Core.Model
/// <returns></returns>
public virtual List<Character> GetTeammates(Character character)
{
return [];
return [.. _queue.Where(c => c != character && (character.Master == c || c == character.Master || (c.Master != null && character.Master != null && c.Master == character.Master)))];
}
/// <summary>
@ -3313,7 +3482,7 @@ namespace Milimoe.FunGame.Core.Model
public bool IsTeammate(Character character, Character target)
{
List<Character> teammates = GetTeammates(character);
return teammates.Contains(target);
return teammates.Contains(target) && (character.Master == target || target == character.Master || (target.Master != null && character.Master != null && target.Master == character.Master));
}
/// <summary>
@ -3524,7 +3693,7 @@ namespace Milimoe.FunGame.Core.Model
protected void 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 && IsCharacterInAIControlling(c)).ToList())
{
if (!_decisionPoints.TryGetValue(other, out DecisionPoints? dp) || dp is null || dp.CurrentDecisionPoints < dp.GetActionPointCost(CharacterActionType.CastSuperSkill))
{
@ -3595,13 +3764,14 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="interrupter"></param>
public void InterruptCasting(Character interrupter)
{
WriteLine($"[ {interrupter} ] 人间蒸发了。");
foreach (Character caster in _castingSkills.Keys)
{
SkillTarget skillTarget = _castingSkills[caster];
if (skillTarget.Targets.Contains(interrupter))
{
Skill skill = skillTarget.Skill;
WriteLine($"[ {interrupter} ] 人间蒸发了。[ {caster} ] 丢失了施法目标!!");
WriteLine($"[ {caster} ] 丢失了施法目标!!");
List<Effect> effects = [.. caster.Effects.Union(interrupter.Effects).Distinct().Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
@ -3617,6 +3787,8 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
/// <param name="character"></param>
public void SetCharacterRespawn(Character character)
{
if (_original.TryGetValue(character.Guid, out Character? original) && original != null)
{
double hardnessTime = 5;
character.Respawn(_original[character.Guid]);
@ -3636,6 +3808,7 @@ namespace Milimoe.FunGame.Core.Model
}
OnQueueUpdatedEvent(_queue, character, dp, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。");
}
}
/// <summary>
/// 设置角色将预释放爆发技
@ -3667,8 +3840,11 @@ namespace Milimoe.FunGame.Core.Model
if (character.CharacterState == CharacterState.Actionable)
{
dp.CurrentDecisionPoints -= GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn;
_stats[character].UseDecisionPoints += GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn;
_stats[character].TurnDecisions++;
if (character.Master is Character statsCharacter)
{
_stats[statsCharacter].UseDecisionPoints += GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn;
_stats[statsCharacter].TurnDecisions++;
}
_castingSuperSkills[character] = skill;
character.CharacterState = CharacterState.PreCastSuperSkill;
_queue.Remove(character);
@ -3719,7 +3895,17 @@ namespace Milimoe.FunGame.Core.Model
foreach (Character target in targets)
{
if (character == target || IsTeammate(character, target)) continue;
_assistDetail[character].NotDamageAssistLastTime[target] = TotalTime;
Character c = character, t = target;
if (character.Master != null)
{
c = character.Master;
}
if (target.Master != null)
{
t = target.Master;
}
if (c == t) continue;
_assistDetail[c].NotDamageAssistLastTime[t] = TotalTime;
}
}
@ -3736,7 +3922,10 @@ namespace Milimoe.FunGame.Core.Model
{
return;
}
double hardnessTime = _hardnessTimes[character];
if (!_hardnessTimes.TryGetValue(character, out double hardnessTime))
{
hardnessTime = 0;
}
if (isPercentage)
{
addValue = hardnessTime * addValue;
@ -3802,6 +3991,14 @@ namespace Milimoe.FunGame.Core.Model
/// <returns></returns>
public bool IsCharacterInAIControlling(Character character)
{
if (character.Master != null)
{
if (_charactersInAIBySystem.Contains(character))
{
return true;
}
character = character.Master;
}
return CharactersInAI.Contains(character);
}
@ -3916,8 +4113,27 @@ namespace Milimoe.FunGame.Core.Model
PrimaryAttribute.INT => character.INTExemption,
_ => 0
};
bool exempted = false;
bool checkExempted = true;
double throwingBonus = 0;
Character[] characters = source != null ? [character, source] : [character];
Effect[] effects = [.. characters.SelectMany(c => c.Effects.Where(e => e.IsInEffect)).Distinct()];
foreach (Effect e in effects)
{
if (!e.OnExemptionCheck(character, source, effect, isEvade, ref throwingBonus))
{
checkExempted = false;
}
}
if (checkExempted)
{
double dice = Random.Shared.NextDouble();
if (dice < exemption)
if (dice < (exemption + throwingBonus))
{
exempted = true;
}
}
if (exempted)
{
if (isEvade)
{
@ -3949,9 +4165,8 @@ namespace Milimoe.FunGame.Core.Model
WriteLine($"[ {character} ] 的{CharacterSet.GetPrimaryAttributeName(effect.ExemptionType)}豁免检定通过!{description}");
}
OnCharacterExemptionEvent(character, source, effect.Skill, effect.Skill.Item, isEvade);
return true;
}
return false;
return exempted;
}
#endregion
@ -3964,6 +4179,10 @@ namespace Milimoe.FunGame.Core.Model
public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, DamageType damageType, double takenDamage = -1)
{
if (takenDamage == -1) takenDamage = damage;
if (character.Master != null)
{
character = character.Master;
}
if (_stats.TryGetValue(character, out CharacterStatistics? stats) && stats != null)
{
if (damageType == DamageType.True)
@ -4294,7 +4513,7 @@ namespace Milimoe.FunGame.Core.Model
return DeathCalculationByTeammateEvent?.Invoke(this, killer, death) ?? true;
}
public delegate bool CharacterDeathEventHandler(GamingQueue queue, Character current, Character death);
public delegate bool CharacterDeathEventHandler(GamingQueue queue, Character current, Character death, Character[] assists);
/// <summary>
/// 角色死亡事件,此事件位于 <see cref="DeathCalculation"/> 之后
/// </summary>
@ -4304,10 +4523,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
/// <param name="current"></param>
/// <param name="death"></param>
/// <param name="assists"></param>
/// <returns></returns>
protected bool OnCharacterDeathEvent(Character current, Character death)
protected bool OnCharacterDeathEvent(Character current, Character death, Character[] assists)
{
return CharacterDeathEvent?.Invoke(this, current, death) ?? true;
return CharacterDeathEvent?.Invoke(this, current, death, assists) ?? true;
}
public delegate void HealToTargetEventHandler(GamingQueue queue, Character actor, Character target, double heal, bool isRespawn);
@ -4596,7 +4816,7 @@ namespace Milimoe.FunGame.Core.Model
CharacterDecisionCompletedEvent?.Invoke(this, actor, dp, record);
}
public delegate InquiryResponse CharacterInquiryEventHandler(GamingQueue character, Character actor, DecisionPoints dp, InquiryOptions options);
public delegate InquiryResponse CharacterInquiryEventHandler(GamingQueue queue, Character character, DecisionPoints dp, InquiryOptions options);
/// <summary>
/// 角色询问反应事件
/// </summary>

View File

@ -13,8 +13,9 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
/// <param name="death"></param>
/// <param name="killer"></param>
/// <param name="assists"></param>
/// <returns></returns>
protected override void AfterDeathCalculation(Character death, Character killer)
protected override void AfterDeathCalculation(Character death, Character killer, Character[] assists)
{
if (MaxRespawnTimes != 0 && MaxScoreToWin > 0)
{
@ -22,7 +23,7 @@ namespace Milimoe.FunGame.Core.Model
.Select(kv => $"[ {kv.Key} ] {kv.Value.Kills} 分"))}\r\n剩余存活人数{_queue.Count}");
}
if (!_queue.Where(c => c != killer).Any())
if (!_queue.Any(c => c != killer && c.Master != killer && killer.Master != c))
{
// 没有其他的角色了,游戏结束
EndGameInfo(killer);

View File

@ -83,7 +83,9 @@ namespace Milimoe.FunGame.Core.Model
{
if (_teams[team].IsOnThisTeam(character))
{
return _teams[team].GetTeammates(character);
List<Character> list = _teams[team].GetTeammates(character);
list.AddRange(_queue.Where(c => c.Master != null && _teams[team].IsOnThisTeam(c.Master)));
return list;
}
}
return [];
@ -155,8 +157,9 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
/// <param name="death"></param>
/// <param name="killer"></param>
/// <param name="assists"></param>
/// <returns></returns>
protected override void AfterDeathCalculation(Character death, Character killer)
protected override void AfterDeathCalculation(Character death, Character killer, Character[] assists)
{
Team? killTeam = GetTeam(killer);
Team? deathTeam = GetTeam(death);