diff --git a/Controller/AIController.cs b/Controller/AIController.cs
index ea453f2..7588ba7 100644
--- a/Controller/AIController.cs
+++ b/Controller/AIController.cs
@@ -12,19 +12,20 @@ namespace Milimoe.FunGame.Core.Controller
private readonly GameMap _map = map;
///
- /// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动。
+ /// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动
///
- /// 当前行动的AI角色。
- /// 角色的起始格子。
- /// 从起始格子可达的所有移动格子(包括起始格子本身)。
- /// 角色所有可用的技能(已过滤CD和EP/MP)。
- /// 角色所有可用的物品(已过滤CD和EP/MP)。
- /// 场上所有敌人。
- /// 场上所有队友。
- /// 场上能够选取的敌人。
- /// 场上能够选取的队友。
- /// 包含最佳行动的AIDecision对象。
- public async Task DecideAIActionAsync(Character character, Grid startGrid, List allPossibleMoveGrids,
+ /// 当前行动的AI角色
+ /// 角色的决策点
+ /// 角色的起始格子
+ /// 从起始格子可达的所有移动格子(包括起始格子本身)
+ /// 角色所有可用的技能(已过滤CD和EP/MP)
+ /// 角色所有可用的物品(已过滤CD和EP/MP)
+ /// 场上所有敌人
+ /// 场上所有队友
+ /// 场上能够选取的敌人
+ /// 场上能够选取的队友
+ /// 包含最佳行动的AIDecision对象
+ public async Task DecideAIActionAsync(Character character, DecisionPoints dp, Grid startGrid, List allPossibleMoveGrids,
List availableSkills, List- availableItems, List allEnemysInGame, List allTeammatesInGame,
List selectableEnemys, List selectableTeammates)
{
@@ -44,7 +45,7 @@ namespace Milimoe.FunGame.Core.Controller
int moveDistance = GameMap.CalculateManhattanDistance(startGrid, potentialMoveGrid);
double movePenalty = moveDistance * 0.5; // 每移动一步扣0.5分
- if (CanCharacterNormalAttack(character))
+ if (CanCharacterNormalAttack(character, dp))
{
// 计算普通攻击的可达格子
List normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
@@ -80,7 +81,7 @@ namespace Milimoe.FunGame.Core.Controller
foreach (Skill skill in availableSkills)
{
- if (CanCharacterUseSkill(character) && _queue.CheckCanCast(character, skill, out double cost))
+ if (CanCharacterUseSkill(character, skill, dp) && _queue.CheckCanCast(character, skill, out double cost))
{
// 计算当前技能的可达格子
List skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
@@ -118,7 +119,7 @@ namespace Milimoe.FunGame.Core.Controller
foreach (Item item in availableItems)
{
- if (item.Skills.Active != null && CanCharacterUseItem(character, item) && _queue.CheckCanCast(character, item.Skills.Active, out double cost))
+ if (item.Skills.Active != null && CanCharacterUseItem(character, item, dp) && _queue.CheckCanCast(character, item.Skills.Active, out double cost))
{
Skill itemSkill = item.Skills.Active;
@@ -158,7 +159,7 @@ namespace Milimoe.FunGame.Core.Controller
}
// 如果从该格子没有更好的行动,但移动本身有价值
- // 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动。
+ // 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动
if (potentialMoveGrid != startGrid && bestDecision.Score < 0) // 如果当前最佳决策是负分(即什么都不做)
{
double pureMoveScore = -movePenalty; // 移动本身有代价
@@ -221,27 +222,32 @@ namespace Milimoe.FunGame.Core.Controller
// --- AI 决策辅助方法 ---
// 检查角色是否能进行普通攻击(基于状态)
- private static bool CanCharacterNormalAttack(Character character)
+ private static bool CanCharacterNormalAttack(Character character, DecisionPoints dp)
{
- return character.CharacterState != CharacterState.NotActionable &&
+ return dp.CheckActionTypeQuota(CharacterActionType.NormalAttack) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostNormalAttack &&
+ character.CharacterState != CharacterState.NotActionable &&
character.CharacterState != CharacterState.ActionRestricted &&
character.CharacterState != CharacterState.BattleRestricted &&
character.CharacterState != CharacterState.AttackRestricted;
}
// 检查角色是否能使用某个技能(基于状态)
- private static bool CanCharacterUseSkill(Character character)
+ private static bool CanCharacterUseSkill(Character character, Skill skill, DecisionPoints dp)
{
- return character.CharacterState != CharacterState.NotActionable &&
+ return ((skill.SkillType == SkillType.Magic && dp.CheckActionTypeQuota(CharacterActionType.PreCastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostMagic) ||
+ (skill.SkillType == SkillType.Skill && dp.CheckActionTypeQuota(CharacterActionType.CastSkill) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSkill) ||
+ (skill.SkillType == SkillType.SuperSkill && dp.CheckActionTypeQuota(CharacterActionType.CastSuperSkill)) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostSuperSkill) &&
+ character.CharacterState != CharacterState.NotActionable &&
character.CharacterState != CharacterState.ActionRestricted &&
character.CharacterState != CharacterState.BattleRestricted &&
character.CharacterState != CharacterState.SkillRestricted;
}
// 检查角色是否能使用某个物品(基于状态)
- private static bool CanCharacterUseItem(Character character, Item item)
+ private static bool CanCharacterUseItem(Character character, Item item, DecisionPoints dp)
{
- return character.CharacterState != CharacterState.NotActionable &&
+ return dp.CheckActionTypeQuota(CharacterActionType.UseItem) && dp.CurrentDecisionPoints > dp.GameplayEquilibriumConstant.DecisionPointsCostItem &&
+ character.CharacterState != CharacterState.NotActionable &&
(character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
character.CharacterState != CharacterState.BattleRestricted;
}
diff --git a/Entity/Item/Item.cs b/Entity/Item/Item.cs
index 81fbe3c..932266f 100644
--- a/Entity/Item/Item.cs
+++ b/Entity/Item/Item.cs
@@ -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
{
@@ -310,7 +311,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 局内使用物品触发
///
///
- public async Task UseItem(IGamingQueue queue, Character character, List enemys, List teammates)
+ public async Task UseItem(IGamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates)
{
bool cancel = false;
bool used = false;
@@ -327,7 +328,7 @@ namespace Milimoe.FunGame.Core.Entity
Grid? grid = Skills.Active.GamingQueue.Map.GetCharacterCurrentGrid(character);
castRange = grid is null ? [] : Skills.Active.GamingQueue.Map.GetGridsByRange(grid, Skills.Active.CastRange, true);
}
- used = await queue.UseItemAsync(this, character, enemys, teammates, castRange);
+ used = await queue.UseItemAsync(this, character, dp, enemys, teammates, castRange);
}
if (used)
{
diff --git a/Entity/Skill/Effect.cs b/Entity/Skill/Effect.cs
index 9292f49..b4f20a1 100644
--- a/Entity/Skill/Effect.cs
+++ b/Entity/Skill/Effect.cs
@@ -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
{
@@ -583,6 +584,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 行动开始前,指定角色的行动,而不是使用顺序表自带的逻辑;或者修改对应的操作触发概率
///
///
+ ///
///
///
///
@@ -591,7 +593,7 @@ namespace Milimoe.FunGame.Core.Entity
///
///
///
- public virtual CharacterActionType AlterActionTypeBeforeAction(Character character, CharacterState state, ref bool canUseItem, ref bool canCastSkill, ref double pUseItem, ref double pCastSkill, ref double pNormalAttack, ref bool forceAction)
+ public virtual CharacterActionType AlterActionTypeBeforeAction(Character character, DecisionPoints dp, CharacterState state, ref bool canUseItem, ref bool canCastSkill, ref double pUseItem, ref double pCastSkill, ref double pNormalAttack, ref bool forceAction)
{
return CharacterActionType.None;
}
@@ -795,6 +797,27 @@ namespace Milimoe.FunGame.Core.Entity
return true;
}
+ ///
+ /// 在角色行动后触发
+ ///
+ ///
+ ///
+ ///
+ public virtual void OnCharacterActionTaken(Character actor, DecisionPoints dp, CharacterActionType type)
+ {
+
+ }
+
+ ///
+ /// 在角色回合决策结束后触发
+ ///
+ ///
+ ///
+ public virtual void OnCharacterDecisionCompleted(Character actor, DecisionPoints dp)
+ {
+
+ }
+
///
/// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 ]
///
@@ -1073,13 +1096,7 @@ namespace Milimoe.FunGame.Core.Entity
///
///
///
- public void RecordCharacterApplyEffects(Character character, params List types)
- {
- if (GamingQueue?.LastRound.ApplyEffects.TryAdd(character, types) ?? false)
- {
- GamingQueue?.LastRound.ApplyEffects[character].AddRange(types);
- }
- }
+ public void RecordCharacterApplyEffects(Character character, params List types) => GamingQueue?.LastRound.AddApplyEffects(character, types);
///
/// 返回特效详情
diff --git a/Entity/Skill/NormalAttack.cs b/Entity/Skill/NormalAttack.cs
index a0c873f..cea38e5 100644
--- a/Entity/Skill/NormalAttack.cs
+++ b/Entity/Skill/NormalAttack.cs
@@ -16,7 +16,7 @@ namespace Milimoe.FunGame.Core.Entity
///
/// 普通攻击说明
///
- public string Description => $"对目标敌人造成 {BaseDamageMultiplier * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "物理伤害")}。";
+ public string Description => $"{(_isMagicByWeapon ? "已由武器附魔。" : "")}对目标敌人造成 {BaseDamageMultiplier * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "物理伤害")}。";
///
/// 普通攻击的通用说明
@@ -425,7 +425,16 @@ namespace Milimoe.FunGame.Core.Entity
if (type == WeaponType.Talisman || type == WeaponType.Staff)
{
_isMagic = true;
+ _isMagicByWeapon = true;
}
+ else
+ {
+ _isMagicByWeapon = false;
+ }
+ }
+ else
+ {
+ _isMagicByWeapon = false;
}
}
if (queue != null && (past != _isMagic || pastType != _magicType))
@@ -477,6 +486,11 @@ namespace Milimoe.FunGame.Core.Entity
///
private bool _isMagic = isMagic;
+ ///
+ /// 指示普通攻击是否由武器附魔
+ ///
+ private bool _isMagicByWeapon = false;
+
///
/// 魔法类型 [ 生效型 ]
///
diff --git a/Interface/Base/IGamingQueue.cs b/Interface/Base/IGamingQueue.cs
index ea4b6c0..c441786 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 CharacterDecisionPoints { get; }
+
///
/// 游戏运行的时间
///
@@ -153,22 +158,24 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// 使用物品
///
///
- ///
+ ///
+ ///
///
///
///
///
///
- public Task UseItemAsync(Item item, Character caster, List enemys, List teammates, List castRange, List? desiredTargets = null);
+ public Task UseItemAsync(Item item, Character character, DecisionPoints dp, List enemys, List teammates, List castRange, List? desiredTargets = null);
///
/// 角色移动
///
///
+ ///
///
///
///
- public Task CharacterMoveAsync(Character character, Grid target, Grid? startGrid);
+ public Task CharacterMoveAsync(Character character, DecisionPoints dp, Grid target, Grid? startGrid);
///
/// 选取移动目标
diff --git a/Library/Common/JsonConverter/RoundRecordConverter.cs b/Library/Common/JsonConverter/RoundRecordConverter.cs
index d760343..ee175f5 100644
--- a/Library/Common/JsonConverter/RoundRecordConverter.cs
+++ b/Library/Common/JsonConverter/RoundRecordConverter.cs
@@ -3,7 +3,6 @@ using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
-using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
@@ -25,8 +24,11 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
result.Actor = NetworkUtility.JsonDeserialize(ref reader, options) ?? Factory.GetCharacter();
break;
case nameof(RoundRecord.Targets):
- List targets = NetworkUtility.JsonDeserialize
>(ref reader, options) ?? [];
- result.Targets.AddRange(targets);
+ Dictionary> targets = NetworkUtility.JsonDeserialize>>(ref reader, options) ?? [];
+ foreach (CharacterActionType type in targets.Keys)
+ {
+ result.Targets[type] = targets[type];
+ }
break;
case nameof(RoundRecord.Damages):
Dictionary damagesGuid = NetworkUtility.JsonDeserialize>(ref reader, options) ?? [];
@@ -40,17 +42,40 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
}
break;
- case nameof(RoundRecord.ActionType):
- result.ActionType = (CharacterActionType)reader.GetInt32();
+ case nameof(RoundRecord.ActionTypes):
+ List types = NetworkUtility.JsonDeserialize>(ref reader, options) ?? [];
+ foreach (CharacterActionType type in types)
+ {
+ result.ActionTypes.Add(type);
+ }
break;
- case nameof(RoundRecord.Skill):
- result.Skill = NetworkUtility.JsonDeserialize(ref reader, options);
+ case nameof(RoundRecord.Skills):
+ Dictionary skills = NetworkUtility.JsonDeserialize>(ref reader, options) ?? [];
+ foreach (CharacterActionType type in skills.Keys)
+ {
+ result.Skills[type] = skills[type];
+ }
break;
- case nameof(RoundRecord.SkillCost):
- result.SkillCost = reader.GetString() ?? "";
+ case nameof(RoundRecord.SkillsCost):
+ Dictionary skillsCost = NetworkUtility.JsonDeserialize>(ref reader, options) ?? [];
+ foreach (Skill skill in skillsCost.Keys)
+ {
+ result.SkillsCost[skill] = skillsCost[skill];
+ }
break;
- case nameof(RoundRecord.Item):
- result.Item = NetworkUtility.JsonDeserialize- (ref reader, options);
+ case nameof(RoundRecord.Items):
+ Dictionary items = NetworkUtility.JsonDeserialize>(ref reader, options) ?? [];
+ foreach (CharacterActionType type in items.Keys)
+ {
+ result.Items[type] = items[type];
+ }
+ break;
+ case nameof(RoundRecord.ItemsCost):
+ Dictionary
- itemsCost = NetworkUtility.JsonDeserialize>(ref reader, options) ?? [];
+ foreach (Item item in itemsCost.Keys)
+ {
+ result.ItemsCost[item] = itemsCost[item];
+ }
break;
case nameof(RoundRecord.HasKill):
result.HasKill = reader.GetBoolean();
@@ -181,12 +206,16 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
JsonSerializer.Serialize(writer, value.Targets, options);
writer.WritePropertyName(nameof(RoundRecord.Damages));
JsonSerializer.Serialize(writer, value.Damages.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
- writer.WriteNumber(nameof(RoundRecord.ActionType), (int)value.ActionType);
- writer.WritePropertyName(nameof(RoundRecord.Skill));
- JsonSerializer.Serialize(writer, value.Skill, options);
- writer.WriteString(nameof(RoundRecord.SkillCost), value.SkillCost);
- writer.WritePropertyName(nameof(RoundRecord.Item));
- JsonSerializer.Serialize(writer, value.Item, options);
+ writer.WritePropertyName(nameof(RoundRecord.ActionTypes));
+ JsonSerializer.Serialize(writer, value.ActionTypes.Select(type => (int)type), options);
+ writer.WritePropertyName(nameof(RoundRecord.Skills));
+ JsonSerializer.Serialize(writer, value.Skills.ToDictionary(kv => kv.Key.ToString(), kv => kv.Value), options);
+ writer.WritePropertyName(nameof(RoundRecord.SkillsCost));
+ JsonSerializer.Serialize(writer, value.SkillsCost.ToDictionary(kv => kv.Key.GetIdName(), kv => kv.Value), options);
+ writer.WritePropertyName(nameof(RoundRecord.Items));
+ JsonSerializer.Serialize(writer, value.Items.ToDictionary(kv => kv.Key.ToString(), kv => kv.Value), options);
+ writer.WritePropertyName(nameof(RoundRecord.ItemsCost));
+ JsonSerializer.Serialize(writer, value.ItemsCost.ToDictionary(kv => kv.Key.GetIdName(), kv => kv.Value), options);
writer.WriteBoolean(nameof(RoundRecord.HasKill), value.HasKill);
writer.WritePropertyName(nameof(RoundRecord.Assists));
JsonSerializer.Serialize(writer, value.Assists, options);
@@ -221,7 +250,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
private static Character? FindCharacterByGuid(Guid guid, RoundRecord record)
{
- Character? character = record.Targets.FirstOrDefault(c => c.Guid == guid);
+ Character? character = record.Targets.Values.SelectMany(c => c).FirstOrDefault(c => c.Guid == guid);
if (character != null) return character;
if (record.Actor != null && record.Actor.Guid == guid) return record.Actor;
character = record.Assists.FirstOrDefault(c => c.Guid == guid);
diff --git a/Model/DecisionPoints.cs b/Model/DecisionPoints.cs
new file mode 100644
index 0000000..25c112e
--- /dev/null
+++ b/Model/DecisionPoints.cs
@@ -0,0 +1,169 @@
+using System.Text.Json.Serialization;
+using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Library.Constant;
+
+namespace Milimoe.FunGame.Core.Model
+{
+ public class DecisionPoints
+ {
+ ///
+ /// 所用的游戏平衡常数
+ ///
+ [JsonIgnore]
+ public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant;
+
+ ///
+ /// 当前决策点
+ ///
+ public int CurrentDecisionPoints { get; set; } = General.GameplayEquilibriumConstant.InitialDecisionPoints;
+
+ ///
+ /// 决策点上限
+ ///
+ public int MaxDecisionPoints { get; set; } = General.GameplayEquilibriumConstant.InitialDecisionPoints;
+
+ ///
+ /// 每回合决策点补充数量
+ ///
+ public int RecoverDecisionPointsPerRound { get; set; } = General.GameplayEquilibriumConstant.RecoverDecisionPointsPerRound;
+
+ ///
+ /// 本回合已补充的决策点
+ ///
+ public int DecisionPointsRecovery { get; set; } = 0;
+
+ ///
+ /// 是否释放过勇气指令
+ ///
+ public bool CourageCommandSkill { get; set; } = false;
+
+ ///
+ /// 记录本回合已使用的行动类型和次数
+ ///
+ public Dictionary ActionTypes { get; } = [];
+
+ ///
+ /// 记录本回合行动的硬直时间
+ ///
+ public List ActionsHardnessTime { get; } = [];
+
+ ///
+ /// 本回合已进行的行动次数
+ ///
+ public int ActionsTaken { get; set; } = 0;
+
+ // 回合内的临时决策点配额加成
+ private int _tempActionQuotaNormalAttack = 0;
+ private int _tempActionQuotaSuperSkill = 0;
+ private int _tempActionQuotaSkill = 0;
+ private int _tempActionQuotaItem = 0;
+ private int _tempActionQuotaOther = 0;
+
+ ///
+ /// 获取当前决策点配额
+ ///
+ ///
+ ///
+ public int this[CharacterActionType type]
+ {
+ get
+ {
+ return type switch
+ {
+ CharacterActionType.NormalAttack => GameplayEquilibriumConstant.ActionQuotaNormalAttack + _tempActionQuotaNormalAttack,
+ CharacterActionType.CastSuperSkill => GameplayEquilibriumConstant.ActionQuotaSuperSkill + _tempActionQuotaSuperSkill,
+ CharacterActionType.CastSkill => GameplayEquilibriumConstant.ActionQuotaSkill + _tempActionQuotaSkill,
+ CharacterActionType.PreCastSkill => GameplayEquilibriumConstant.ActionQuotaMagic,
+ CharacterActionType.UseItem => GameplayEquilibriumConstant.ActionQuotaItem + _tempActionQuotaItem,
+ _ => GameplayEquilibriumConstant.ActionQuotaOther + _tempActionQuotaOther,
+ };
+ }
+ }
+
+ ///
+ /// 添加临时决策点配额 [ 回合结束时清除 ]
+ ///
+ ///
+ ///
+ public void AddTempActionQuota(CharacterActionType type, int add = 1)
+ {
+ switch (type)
+ {
+ case CharacterActionType.NormalAttack:
+ _tempActionQuotaNormalAttack += add;
+ break;
+ case CharacterActionType.CastSkill:
+ _tempActionQuotaSkill += add;
+ break;
+ case CharacterActionType.CastSuperSkill:
+ _tempActionQuotaSuperSkill += add;
+ break;
+ case CharacterActionType.UseItem:
+ _tempActionQuotaItem += add;
+ break;
+ default:
+ _tempActionQuotaOther += add;
+ break;
+ }
+ }
+
+ ///
+ /// 清除临时决策点配额
+ ///
+ public void ClearTempActionQuota()
+ {
+ _tempActionQuotaNormalAttack = 0;
+ _tempActionQuotaSuperSkill = 0;
+ _tempActionQuotaSkill = 0;
+ _tempActionQuotaItem = 0;
+ _tempActionQuotaOther = 0;
+ }
+
+ ///
+ /// 累计行动类型和次数
+ ///
+ ///
+ ///
+ public void AddActionType(CharacterActionType type, bool addActionTaken = true)
+ {
+ if (addActionTaken) ActionsTaken++;
+ if (!ActionTypes.TryAdd(type, 1))
+ {
+ ActionTypes[type]++;
+ }
+ }
+
+ ///
+ /// 判断行动类型是否达到配额
+ ///
+ ///
+ public bool CheckActionTypeQuota(CharacterActionType type)
+ {
+ if (ActionTypes.TryGetValue(type, out int times))
+ {
+ return times < this[type];
+ }
+ return true;
+ }
+
+ ///
+ /// 获取决策点消耗
+ ///
+ ///
+ ///
+ ///
+ public int GetActionPointCost(CharacterActionType type, Skill? skill = null)
+ {
+ return type switch
+ {
+ CharacterActionType.NormalAttack => GameplayEquilibriumConstant.DecisionPointsCostNormalAttack,
+ CharacterActionType.PreCastSkill when skill?.SkillType == SkillType.SuperSkill => GameplayEquilibriumConstant.DecisionPointsCostSuperSkill,
+ CharacterActionType.PreCastSkill when skill?.SkillType == SkillType.Skill => GameplayEquilibriumConstant.DecisionPointsCostSkill,
+ CharacterActionType.PreCastSkill when skill?.SkillType == SkillType.Magic => GameplayEquilibriumConstant.DecisionPointsCostMagic,
+ CharacterActionType.UseItem => GameplayEquilibriumConstant.DecisionPointsCostItem,
+ CharacterActionType.CastSuperSkill => GameplayEquilibriumConstant.DecisionPointsCostSuperSkillOutOfTurn, // 回合外使用爆发技
+ _ => GameplayEquilibriumConstant.DecisionPointsCostOther
+ };
+ }
+ }
+}
diff --git a/Model/EquilibriumConstant.cs b/Model/EquilibriumConstant.cs
index 5769138..7fb8646 100644
--- a/Model/EquilibriumConstant.cs
+++ b/Model/EquilibriumConstant.cs
@@ -540,6 +540,86 @@ namespace Milimoe.FunGame.Core.Model
///
public int RoleMOV_Medic { get; set; } = 3;
+ ///
+ /// 初始决策点数
+ ///
+ public int InitialDecisionPoints { get; set; } = 1;
+
+ ///
+ /// 决策点上限
+ ///
+ public int MaxDecisionPoints { get; set; } = 7;
+
+ ///
+ /// 每回合决策点补充数量
+ ///
+ public int RecoverDecisionPointsPerRound { get; set; } = 1;
+
+ ///
+ /// 每回合普通攻击决策配额
+ ///
+ public int ActionQuotaNormalAttack { get; set; } = 1;
+
+ ///
+ /// 每回合战技决策配额
+ ///
+ public int ActionQuotaSkill { get; set; } = 1;
+
+ ///
+ /// 每回合爆发技决策配额
+ ///
+ public int ActionQuotaSuperSkill { get; set; } = 1;
+
+ ///
+ /// 每回合魔法决策配额 [ 使用魔法因为会进入吟唱态,所以无论是否设置都没意义 ]
+ ///
+ public int ActionQuotaMagic { get; set; } = 1;
+
+ ///
+ /// 每回合使用物品决策配额
+ ///
+ public int ActionQuotaItem { get; set; } = 1;
+
+ ///
+ /// 每回合其他决策配额
+ ///
+ public int ActionQuotaOther { get; set; } = 0;
+
+ ///
+ /// 普通攻击决策点消耗
+ ///
+ public int DecisionPointsCostNormalAttack { get; set; } = 1;
+
+ ///
+ /// 战技决策点消耗
+ ///
+ public int DecisionPointsCostSkill { get; set; } = 2;
+
+ ///
+ /// 爆发技决策点消耗(回合内)
+ ///
+ public int DecisionPointsCostSuperSkill { get; set; } = 2;
+
+ ///
+ /// 爆发技决策点消耗(回合外)
+ ///
+ public int DecisionPointsCostSuperSkillOutOfTurn { get; set; } = 3;
+
+ ///
+ /// 魔法决策点消耗
+ ///
+ public int DecisionPointsCostMagic { get; set; } = 2;
+
+ ///
+ /// 使用物品决策点消耗
+ ///
+ public int DecisionPointsCostItem { get; set; } = 1;
+
+ ///
+ /// 其他决策点消耗
+ ///
+ public int DecisionPointsCostOther { get; set; } = 1;
+
///
/// 应用此游戏平衡常数给实体
///
diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs
index 75a44af..abc34d2 100644
--- a/Model/GamingQueue.cs
+++ b/Model/GamingQueue.cs
@@ -5,6 +5,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.PrefabricatedEntity;
namespace Milimoe.FunGame.Core.Model
{
@@ -26,6 +27,11 @@ namespace Milimoe.FunGame.Core.Model
///
public Action WriteLine { get; }
+ ///
+ /// 调试模式
+ ///
+ public bool IsDebug { get; set; } = false;
+
///
/// 参与本次游戏的所有角色列表
///
@@ -152,6 +158,11 @@ namespace Milimoe.FunGame.Core.Model
///
public GameMap? Map => _map;
+ ///
+ /// 角色的决策点
+ ///
+ public Dictionary CharacterDecisionPoints => _decisionPoints;
+
#endregion
#region 保护变量
@@ -261,6 +272,11 @@ namespace Milimoe.FunGame.Core.Model
///
protected Func> _factoryRoundRewardEffects = id => [];
+ ///
+ /// 角色的决策点
+ ///
+ protected readonly Dictionary _decisionPoints = [];
+
///
/// 最多被插队次数,-1 为默认,即队列长度,最少为 5
///
@@ -691,19 +707,19 @@ namespace Milimoe.FunGame.Core.Model
{
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.##}");
+ if (IsDebug) 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 (IsDebug) 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.##}");
+ if (IsDebug) WriteLine($"角色 {character.Name} 回蓝:{recoveryMP:0.##} [{character.MP:0.##} / {character.MaxMP:0.##}] / 当前能量:{character.EP:0.##}");
}
}
@@ -748,12 +764,6 @@ namespace Milimoe.FunGame.Core.Model
effect.OnEffectGained(character);
}
- // 如果特效具备临时驱散或者持续性驱散的功能
- if (effect.Source != null && (effect.EffectType == EffectType.WeakDispelling || effect.EffectType == EffectType.StrongDispelling))
- {
- effect.Dispel(effect.Source, character, !IsTeammate(character, effect.Source) && character != effect.Source);
- }
-
// 自身被动不会考虑
if (effect.EffectType == EffectType.None && effect.Skill.SkillType == SkillType.Passive)
{
@@ -785,6 +795,14 @@ namespace Milimoe.FunGame.Core.Model
}
}
+ // 如果特效具备临时驱散或者持续性驱散的功能
+ effects = [.. character.Effects.Where(e => e.Source != null && (e.EffectType == EffectType.WeakDispelling || e.EffectType == EffectType.StrongDispelling))];
+ foreach (Effect effect in effects)
+ {
+ if (effect.Source is null) continue;
+ effect.Dispel(effect.Source, character, !IsTeammate(character, effect.Source) && character != effect.Source);
+ }
+
_eliminated.Remove(character);
}
@@ -836,6 +854,9 @@ namespace Milimoe.FunGame.Core.Model
return _isGameEnd;
}
+ // 决策点补充
+ DecisionPoints dp = DecisionPointsRecovery(character);
+
// 获取回合奖励
List rewards = GetRoundRewards(TotalRound, character);
@@ -845,23 +866,16 @@ namespace Milimoe.FunGame.Core.Model
// 队友列表
List allTeammates = GetTeammates(character);
- List selectableTeammates = [.. allTeammates.Where(_queue.Contains)];
// 敌人列表
List allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c))];
- List selectableEnemys = [.. allEnemys.Where(c => _queue.Contains(c) && !c.IsUnselectable)];
- // 技能列表
- List 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- 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)];
+ // 取得可选列表
+ (List selectableTeammates, List selectableEnemys, List skills, List
- items) = GetTurnStartNeedyList(character, allTeammates, allEnemys);
// 回合开始事件,允许事件返回 false 接管回合操作
// 如果事件全程接管回合操作,需要注意触发特效
- if (!await OnTurnStartAsync(character, selectableEnemys, selectableTeammates, skills, items))
+ if (!await OnTurnStartAsync(character, dp, selectableEnemys, selectableTeammates, skills, items))
{
_isInRound = false;
return _isGameEnd;
@@ -878,400 +892,398 @@ namespace Milimoe.FunGame.Core.Model
effect.OnTurnStart(character, selectableEnemys, selectableTeammates, skills, items);
}
- // 此变量用于在取消选择时,能够重新行动
- bool decided = false;
- // 最大取消次数
- int cancelTimes = 3;
- // 此变量指示角色是否移动
- bool moved = false;
-
- // AI 决策控制器,适用于启用战棋地图的情况
- AIController? ai = null;
-
// 角色的起始地点,确保角色该回合移动的范围不超过 MOV
Grid? startGrid = null;
+ // 可移动的格子列表
List canMoveGrids = [];
- // 并且要筛选最远可选取角色
- List canAttackGridsByStartGrid = [];
- List canCastGridsByStartGrid = [];
if (_map != null)
{
startGrid = _map.GetCharacterCurrentGrid(character);
-
if (startGrid != null)
{
canMoveGrids = _map.GetGridsByRange(startGrid, character.MOV, false);
+ }
+ }
+
+ // 作出了什么行动
+ CharacterActionType type = CharacterActionType.None;
+
+ // 是否结束回合
+ bool endTurn = false;
+ bool isAI = CharactersInAI.Contains(character);
+
+ // 循环条件:未结束回合、决策点大于0(AI控制下为0时自动结束)或角色处于吟唱态
+ while (!endTurn && (!isAI || dp.CurrentDecisionPoints > 0 || character.CharacterState == CharacterState.Casting || character.CharacterState == CharacterState.PreCastSuperSkill))
+ {
+ // 刷新可选列表
+ (selectableTeammates, selectableEnemys, skills, items) = GetTurnStartNeedyList(character, allTeammates, allEnemys);
+
+ // 并且要筛选最远可选取角色
+ List canAttackGridsByStartGrid = [];
+ List canCastGridsByStartGrid = [];
+ if (_map != null && startGrid != null)
+ {
canAttackGridsByStartGrid = _map.GetGridsByRange(startGrid, character.ATR, true);
Skill[] canCastSkills = [.. skills, .. items.Select(i => i.Skills.Active!)];
foreach (Skill skill in canCastSkills)
{
canCastGridsByStartGrid.AddRange(_map.GetGridsByRange(startGrid, skill.CastRange, true));
}
+ allEnemys = [.. allEnemys.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)];
+ allTeammates = [.. allTeammates.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)];
}
- allEnemys = [.. allEnemys.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)];
- allTeammates = [.. allTeammates.Where(canAttackGridsByStartGrid.Union(canCastGridsByStartGrid).SelectMany(g => g.Characters).Contains)];
- }
+ // 此变量用于在取消选择时,能够重新行动
+ bool decided = false;
+ // 最大取消次数
+ int cancelTimes = 3;
+ // 此变量指示角色是否移动
+ bool moved = false;
- // 作出了什么行动
- CharacterActionType type = CharacterActionType.None;
+ // AI 决策控制器,适用于启用战棋地图的情况
+ AIController? ai = null;
- // 循环条件:
- // AI 控制下:未决策、取消次数大于0
- // 手动控制下:未决策
- bool isAI = CharactersInAI.Contains(character);
- while (!decided && (!isAI || cancelTimes > 0))
- {
- // 根据当前位置,更新可选取角色列表
- Grid? realGrid = null;
- List canAttackGrids = [];
- List canCastGrids = [];
- List willMoveGridWithSkill = [];
- List enemys = [];
- List teammates = [];
- if (_map != null)
+ // 循环条件:
+ // AI 控制下:未决策、取消次数大于0
+ // 手动控制下:未决策
+ isAI = CharactersInAI.Contains(character);
+ while (!decided && (!isAI || cancelTimes > 0))
{
- if (isAI)
+ // 根据当前位置,更新可选取角色列表
+ Grid? realGrid = null;
+ List canAttackGrids = [];
+ List canCastGrids = [];
+ List willMoveGridWithSkill = [];
+ List enemys = [];
+ List teammates = [];
+ if (_map != null)
{
- ai ??= new(this, _map);
- }
-
- realGrid = _map.GetCharacterCurrentGrid(character);
-
- if (realGrid != null)
- {
- canAttackGrids = _map.GetGridsByRange(realGrid, character.ATR, true);
- Skill[] canCastSkills = [.. skills, .. items.Select(i => i.Skills.Active!)];
- foreach (Skill skill in canCastSkills)
+ if (isAI)
{
- canCastGrids.AddRange(_map.GetGridsByRange(realGrid, skill.CastRange, true));
+ ai ??= new(this, _map);
}
+
+ realGrid = _map.GetCharacterCurrentGrid(character);
+
+ if (realGrid != null)
+ {
+ canAttackGrids = _map.GetGridsByRange(realGrid, character.ATR, true);
+ Skill[] canCastSkills = [.. skills, .. items.Select(i => i.Skills.Active!)];
+ foreach (Skill skill in canCastSkills)
+ {
+ canCastGrids.AddRange(_map.GetGridsByRange(realGrid, skill.CastRange, true));
+ }
+ }
+
+ enemys = [.. selectableEnemys.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
+ teammates = [.. selectableTeammates.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
+ willMoveGridWithSkill = [.. canMoveGrids.Where(g => canAttackGrids.Union(canCastGrids).Contains(g))];
+ }
+ else
+ {
+ enemys = selectableEnemys;
+ teammates = selectableTeammates;
}
- enemys = [.. selectableEnemys.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
- teammates = [.. selectableTeammates.Where(canAttackGrids.Union(canCastGrids).SelectMany(g => g.Characters).Contains)];
- willMoveGridWithSkill = [.. canMoveGrids.Where(g => canAttackGrids.Union(canCastGrids).Contains(g))];
- }
- else
- {
- enemys = selectableEnemys;
- teammates = selectableTeammates;
- }
+ // AI 决策结果(适用于启用战棋地图的情况)
+ AIDecision? aiDecision = null;
- // AI 决策结果(适用于启用战棋地图的情况)
- AIDecision? aiDecision = null;
-
- // 行动开始前,可以修改可选取的角色列表
- 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);
- }
-
- // 这里筛掉重复角色
- enemys = [.. enemys.Distinct()];
- teammates = [.. teammates.Distinct()];
-
- if (moved) moved = false;
- else cancelTimes--;
- 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;
-
- // 是否强制执行(跳过状态检查等)
- bool forceAction = false;
-
- // 不允许在吟唱和预释放状态下,修改角色的行动
- if (character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill)
- {
- CharacterActionType actionTypeTemp = CharacterActionType.None;
+ // 行动开始前,可以修改可选取的角色列表
+ Dictionary continuousKillingTemp = new(_continuousKilling);
+ Dictionary earnedMoneyTemp = new(_earnedMoney);
effects = [.. character.Effects.Where(e => e.IsInEffect)];
foreach (Effect effect in effects)
{
- actionTypeTemp = effect.AlterActionTypeBeforeAction(character, character.CharacterState, ref canUseItem, ref canCastSkill, ref pUseItem, ref pCastSkill, ref pNormalAttack, ref forceAction);
+ effect.AlterSelectListBeforeAction(character, enemys, teammates, skills, continuousKillingTemp, earnedMoneyTemp);
}
- 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)
- {
- // 行动受限,只能使用消耗品
- items = [.. items.Where(i => i.ItemType == ItemType.Consumable)];
- canUseItem = items.Count > 0;
- 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;
- }
- }
+ // 这里筛掉重复角色
+ enemys = [.. enemys.Distinct()];
+ teammates = [.. teammates.Distinct()];
- // 启用战棋地图时的专属 AI 决策方法
- if (isAI && ai != null && startGrid != null)
- {
- aiDecision = await ai.DecideAIActionAsync(character, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates, enemys, teammates);
- type = aiDecision.ActionType;
- }
- else
- {
- // 模组可以通过此事件来决定角色的行动
- type = await OnDecideActionAsync(character, enemys, teammates, skills, items);
- }
- // 若事件未完成决策,则将通过概率对角色进行自动化决策
- if (type == CharacterActionType.None)
- {
- type = GetActionType(pUseItem, pCastSkill, pNormalAttack);
- }
- }
- else if (character.CharacterState == CharacterState.Casting)
- {
- // 如果角色上一次吟唱了魔法,这次的行动则是结算这个魔法
- type = CharacterActionType.CastSkill;
- }
- else if (character.CharacterState == CharacterState.PreCastSuperSkill)
- {
- // 角色使用回合外爆发技插队
- type = CharacterActionType.CastSuperSkill;
- }
- else
- {
- // 完全行动不能
- type = CharacterActionType.None;
- }
- }
+ baseTime = 0;
+ if (moved) moved = false;
+ else cancelTimes--;
+ type = CharacterActionType.None;
- if (aiDecision != null && aiDecision.ActionType != CharacterActionType.Move && aiDecision.TargetMoveGrid != null)
- {
- // 不是纯粹移动的情况,需要手动移动
- moved = await CharacterMoveAsync(character, aiDecision.TargetMoveGrid, startGrid);
- }
+ // 是否能使用物品和释放技能
+ bool canUseItem = items.Count > 0;
+ bool canCastSkill = skills.Count > 0;
- if (type == CharacterActionType.Move)
- {
- if (_map != null)
+ // 使用物品和释放技能、使用普通攻击的概率
+ double pUseItem = 0.33;
+ double pCastSkill = 0.33;
+ double pNormalAttack = 0.34;
+
+ // 是否强制执行(跳过状态检查等)
+ bool forceAction = false;
+
+ // 不允许在吟唱和预释放状态下,修改角色的行动
+ if (character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill)
{
- Grid target;
- if (aiDecision != null && aiDecision.TargetMoveGrid != null)
+ CharacterActionType actionTypeTemp = CharacterActionType.None;
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
{
- target = aiDecision.TargetMoveGrid;
- }
- else
- {
- target = await SelectTargetGridAsync(character, enemys, teammates, _map, canMoveGrids);
- }
- moved = await CharacterMoveAsync(character, target, startGrid);
- }
- if (isAI && (aiDecision?.IsPureMove ?? false))
- {
- // 取消 AI 的移动
- SetOnlyMoveHardnessTime(character, ref baseTime);
- type = CharacterActionType.EndTurn;
- decided = true;
- WriteLine($"[ {character} ] 结束了回合!");
- await OnCharacterDoNothingAsync(character);
- }
- }
- else if (type == CharacterActionType.NormalAttack)
- {
- if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
- character.CharacterState == CharacterState.ActionRestricted ||
- character.CharacterState == CharacterState.BattleRestricted ||
- character.CharacterState == CharacterState.AttackRestricted))
- {
- WriteLine($"角色 [ {character} ] 状态为:{CharacterSet.GetCharacterState(character.CharacterState)},无法使用普通攻击!");
- }
- else
- {
- // 使用普通攻击逻辑
- List targets;
- if (aiDecision != null)
- {
- targets = aiDecision.Targets;
- }
- else
- {
- List attackRange = [];
- if (_map != null && realGrid != null)
+ bool force = false;
+ CharacterActionType forceType = effect.AlterActionTypeBeforeAction(character, dp, character.CharacterState, ref canUseItem, ref canCastSkill, ref pUseItem, ref pCastSkill, ref pNormalAttack, ref force);
+ if (force && forceType != CharacterActionType.None)
{
- attackRange = _map.GetGridsByRange(realGrid, character.ATR, true);
- enemys = [.. enemys.Where(attackRange.SelectMany(g => g.Characters).Contains)];
- teammates = [.. teammates.Where(attackRange.SelectMany(g => g.Characters).Contains)];
- }
- targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates, attackRange);
- }
- if (targets.Count > 0)
- {
- LastRound.Targets = [.. targets];
- decided = true;
-
- await OnCharacterNormalAttackAsync(character, targets);
-
- character.NormalAttack.Attack(this, character, targets);
- baseTime += character.NormalAttack.RealHardnessTime;
- effects = [.. character.Effects.Where(e => e.IsInEffect)];
- foreach (Effect effect in effects)
- {
- effect.AlterHardnessTimeAfterNormalAttack(character, ref baseTime, ref isCheckProtected);
+ forceAction = true;
+ actionTypeTemp = forceType;
+ break;
}
}
+ if (actionTypeTemp != CharacterActionType.None && actionTypeTemp != CharacterActionType.CastSkill && actionTypeTemp != CharacterActionType.CastSuperSkill)
+ {
+ type = actionTypeTemp;
+ }
}
- }
- else if (type == CharacterActionType.PreCastSkill)
- {
- if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
- character.CharacterState == CharacterState.ActionRestricted ||
- character.CharacterState == CharacterState.BattleRestricted ||
- character.CharacterState == CharacterState.SkillRestricted))
+
+ if (type == CharacterActionType.None)
{
- WriteLine($"角色 [ {character} ] 状态为:{CharacterSet.GetCharacterState(character.CharacterState)},无法释放技能!");
- }
- else
- {
- // 预使用技能,即开始吟唱逻辑
- Skill? skill;
- if (aiDecision != null && aiDecision.SkillToUse is Skill s)
+ if (character.CharacterState != CharacterState.NotActionable && character.CharacterState != CharacterState.Casting && character.CharacterState != CharacterState.PreCastSuperSkill)
{
- skill = s;
- }
- else
- {
- 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)
+ // 根据角色状态,设置一些参数
+ if (character.CharacterState == CharacterState.Actionable)
{
- List targets;
- if (aiDecision != null)
+ // 可以任意行动
+ if (canUseItem && canCastSkill)
{
- targets = aiDecision.Targets;
+ // 不做任何处理
+ }
+ else if (canUseItem && !canCastSkill)
+ {
+ pCastSkill = 0;
+ }
+ else if (!canUseItem && canCastSkill)
+ {
+ pUseItem = 0;
}
else
{
- List castRange = [];
- if (_map != null && realGrid != null)
- {
- castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
- enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
- teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
- }
- targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
+ pUseItem = 0;
+ pCastSkill = 0;
}
- if (targets.Count > 0)
+ }
+ else if (character.CharacterState == CharacterState.ActionRestricted)
+ {
+ // 行动受限,只能使用消耗品
+ items = [.. items.Where(i => i.ItemType == ItemType.Consumable)];
+ canUseItem = items.Count > 0;
+ if (canUseItem)
{
- // 免疫检定
- await CheckSkilledImmuneAsync(character, targets, skill);
-
- 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.RealCastTime;
- isCheckProtected = false;
- skill.OnSkillCasting(this, character, targets);
- }
+ 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;
+ }
+ }
+
+ // 启用战棋地图时的专属 AI 决策方法
+ if (isAI && ai != null && startGrid != null)
+ {
+ aiDecision = await ai.DecideAIActionAsync(character, dp, startGrid, canMoveGrids, skills, items, allEnemys, allTeammates, enemys, teammates);
+ type = aiDecision.ActionType;
}
else
{
- // 只有魔法需要吟唱,战技和爆发技直接释放
- if (CheckCanCast(character, skill, out double cost))
+ // 模组可以通过此事件来决定角色的行动
+ type = await OnDecideActionAsync(character, dp, enemys, teammates, skills, items);
+ }
+ // 若事件未完成决策,则将通过概率对角色进行自动化决策
+ if (type == CharacterActionType.None)
+ {
+ type = GetActionType(dp, pUseItem, pCastSkill, pNormalAttack);
+ }
+ }
+ else if (character.CharacterState == CharacterState.Casting)
+ {
+ // 如果角色上一次吟唱了魔法,这次的行动则是结算这个魔法
+ type = CharacterActionType.CastSkill;
+ }
+ else if (character.CharacterState == CharacterState.PreCastSuperSkill)
+ {
+ // 角色使用回合外爆发技插队
+ type = CharacterActionType.CastSuperSkill;
+ }
+ else
+ {
+ // 完全行动不能
+ type = CharacterActionType.None;
+ }
+ }
+
+ if (aiDecision != null && aiDecision.ActionType != CharacterActionType.Move && aiDecision.TargetMoveGrid != null)
+ {
+ // 不是纯粹移动的情况,需要手动移动
+ moved = await CharacterMoveAsync(character, dp, aiDecision.TargetMoveGrid, startGrid);
+ }
+
+ int costDP = dp.GetActionPointCost(type);
+
+ if (type == CharacterActionType.Move)
+ {
+ if (_map != null)
+ {
+ Grid target;
+ if (aiDecision != null && aiDecision.TargetMoveGrid != null)
+ {
+ target = aiDecision.TargetMoveGrid;
+ }
+ else
+ {
+ target = await SelectTargetGridAsync(character, enemys, teammates, _map, canMoveGrids);
+ }
+ moved = await CharacterMoveAsync(character, dp, target, startGrid);
+ }
+ if (isAI && (aiDecision?.IsPureMove ?? false))
+ {
+ // 取消 AI 的移动
+ SetOnlyMoveHardnessTime(character, dp, ref baseTime);
+ type = CharacterActionType.EndTurn;
+ decided = true;
+ endTurn = true;
+ WriteLine($"[ {character} ] 结束了回合!");
+ await OnCharacterDoNothingAsync(character, dp);
+ }
+ }
+ else if (type == CharacterActionType.NormalAttack)
+ {
+ if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
+ character.CharacterState == CharacterState.ActionRestricted ||
+ character.CharacterState == CharacterState.BattleRestricted ||
+ character.CharacterState == CharacterState.AttackRestricted))
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 状态为:{CharacterSet.GetCharacterState(character.CharacterState)},无法使用普通攻击!");
+ }
+ else if (dp.CurrentDecisionPoints < costDP)
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 决策点不足,无法使用普通攻击!");
+ }
+ else if (!dp.CheckActionTypeQuota(CharacterActionType.NormalAttack))
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 该回合使用普通攻击的次数已超过决策点配额,无法再次使用普通攻击!");
+ }
+ else
+ {
+ // 使用普通攻击逻辑
+ List targets;
+ if (aiDecision != null)
+ {
+ targets = aiDecision.Targets;
+ }
+ else
+ {
+ List attackRange = [];
+ if (_map != null && realGrid != null)
{
+ attackRange = _map.GetGridsByRange(realGrid, character.ATR, true);
+ enemys = [.. enemys.Where(attackRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(attackRange.SelectMany(g => g.Characters).Contains)];
+ }
+ targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates, attackRange);
+ }
+ if (targets.Count > 0)
+ {
+ LastRound.Targets[CharacterActionType.NormalAttack] = [.. targets];
+ LastRound.ActionTypes.Add(CharacterActionType.NormalAttack);
+ dp.AddActionType(CharacterActionType.NormalAttack);
+ dp.CurrentDecisionPoints -= costDP;
+ decided = true;
+
+ await OnCharacterNormalAttackAsync(character, dp, targets);
+
+ character.NormalAttack.Attack(this, character, targets);
+ baseTime += character.NormalAttack.RealHardnessTime;
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
+ {
+ effect.AlterHardnessTimeAfterNormalAttack(character, ref baseTime, ref isCheckProtected);
+ }
+ }
+ }
+ }
+ else if (type == CharacterActionType.PreCastSkill)
+ {
+ if (!forceAction && (character.CharacterState == CharacterState.NotActionable ||
+ character.CharacterState == CharacterState.ActionRestricted ||
+ character.CharacterState == CharacterState.BattleRestricted ||
+ character.CharacterState == CharacterState.SkillRestricted))
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 状态为:{CharacterSet.GetCharacterState(character.CharacterState)},无法释放技能!");
+ }
+ else
+ {
+ // 预使用技能,即开始吟唱逻辑
+ Skill? skill;
+ if (aiDecision != null && aiDecision.SkillToUse is Skill s)
+ {
+ skill = s;
+ }
+ else
+ {
+ 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)
+ {
+ costDP = dp.GetActionPointCost(type, skill);
+ if (dp.CurrentDecisionPoints < costDP)
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 决策点不足,无法释放技能!");
+ }
+ else if (skill.SkillType == SkillType.Magic)
+ {
+ // 吟唱前需要先选取目标
List targets;
if (aiDecision != null)
{
@@ -1295,78 +1307,208 @@ namespace Milimoe.FunGame.Core.Model
if (targets.Count > 0)
{
- LastRound.Targets = [.. targets];
+ LastRound.Skills[CharacterActionType.PreCastSkill] = skill;
+ LastRound.Targets[CharacterActionType.PreCastSkill] = [.. targets];
+ LastRound.ActionTypes.Add(CharacterActionType.PreCastSkill);
+ dp.AddActionType(CharacterActionType.PreCastSkill);
+ dp.CurrentDecisionPoints -= costDP;
decided = true;
+ endTurn = true;
+ character.CharacterState = CharacterState.Casting;
SkillTarget skillTarget = new(skill, targets);
- await OnCharacterPreCastSkillAsync(character, skillTarget);
+ await OnCharacterPreCastSkillAsync(character, dp, skillTarget);
+ _castingSkills[character] = skillTarget;
+ baseTime += skill.RealCastTime;
+ isCheckProtected = false;
skill.OnSkillCasting(this, character, targets);
- skill.BeforeSkillCasted();
-
- character.EP -= cost;
- baseTime += skill.RealHardnessTime;
- 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.IsInEffect)];
- foreach (Effect effect in effects)
+ }
+ }
+ }
+ else if (skill is CourageCommandSkill && dp.CourageCommandSkill)
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 该回合已经使用过勇气指令,无法再次使用勇气指令!");
+ }
+ else if (skill is not CourageCommandSkill && !skill.IsSuperSkill && !dp.CheckActionTypeQuota(CharacterActionType.CastSkill))
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 该回合使用战技的次数已超过决策点配额,无法再次使用战技!");
+ }
+ else if (skill is not CourageCommandSkill && skill.IsSuperSkill && !dp.CheckActionTypeQuota(CharacterActionType.CastSuperSkill))
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 该回合使用爆发技的次数已超过决策点配额,无法再次使用爆发技!");
+ }
+ else
+ {
+ // 只有魔法需要吟唱,战技和爆发技直接释放
+ if (CheckCanCast(character, skill, out double cost))
+ {
+ List targets;
+ if (aiDecision != null)
+ {
+ targets = aiDecision.Targets;
+ }
+ else
+ {
+ List castRange = [];
+ if (_map != null && realGrid != null)
{
- effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
+ castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
+ enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ }
+ targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
+ }
+ if (targets.Count > 0)
+ {
+ // 免疫检定
+ await CheckSkilledImmuneAsync(character, targets, skill);
+
+ if (targets.Count > 0)
+ {
+ CharacterActionType skillType = skill.SkillType == SkillType.SuperSkill ? CharacterActionType.CastSuperSkill : CharacterActionType.CastSkill;
+ LastRound.Skills[skillType] = skill;
+ LastRound.Targets[skillType] = [.. targets];
+ LastRound.ActionTypes.Add(skillType);
+ if (skill is not CourageCommandSkill)
+ {
+ dp.AddActionType(skillType);
+ dp.CurrentDecisionPoints -= costDP;
+ }
+ else
+ {
+ // 勇气指令不消耗决策点,但是有标记
+ dp.CourageCommandSkill = true;
+ }
+ decided = true;
+
+ SkillTarget skillTarget = new(skill, targets);
+ await OnCharacterPreCastSkillAsync(character, dp, skillTarget);
+
+ skill.OnSkillCasting(this, character, targets);
+ skill.BeforeSkillCasted();
+
+ character.EP -= cost;
+ baseTime += skill.RealHardnessTime;
+ skill.CurrentCD = skill.RealCD;
+ skill.Enable = false;
+ LastRound.SkillsCost[skill] = $"{-cost:0.##} EP";
+ WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "爆发技" : "战技")} [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}");
+
+ await OnCharacterCastSkillAsync(character, dp, skillTarget, cost);
+
+ skill.OnSkillCasted(this, character, targets);
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ 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))
+ else if (type == CharacterActionType.CastSkill)
{
- // 使用技能逻辑,结束吟唱状态
- character.CharacterState = CharacterState.Actionable;
- character.UpdateCharacterState();
- Skill skill = skillTarget.Skill;
- List targets = [.. skillTarget.Targets.Where(c => c == character || !c.IsUnselectable)];
-
- // 判断是否能够释放技能
- if (targets.Count > 0 && CheckCanCast(character, skill, out double cost))
+ if (_castingSkills.TryGetValue(character, out SkillTarget skillTarget))
{
- // 免疫检定
- await CheckSkilledImmuneAsync(character, targets, skill);
+ // 使用技能逻辑,结束吟唱状态
+ character.CharacterState = CharacterState.Actionable;
+ character.UpdateCharacterState();
+ Skill skill = skillTarget.Skill;
+ List targets = [.. skillTarget.Targets.Where(c => c == character || !c.IsUnselectable)];
- if (targets.Count > 0)
+ // 判断是否能够释放技能
+ if (targets.Count > 0 && CheckCanCast(character, skill, out double cost))
{
- decided = true;
- LastRound.Targets = [.. targets];
- LastRound.Skill = skill;
- _castingSkills.Remove(character);
+ // 免疫检定
+ await CheckSkilledImmuneAsync(character, targets, skill);
- skill.BeforeSkillCasted();
+ if (targets.Count > 0)
+ {
+ decided = true;
+ endTurn = true;
+ LastRound.Targets[CharacterActionType.CastSkill] = [.. targets];
+ LastRound.Skills[CharacterActionType.CastSkill] = skill;
+ LastRound.ActionTypes.Add(CharacterActionType.CastSkill);
+ _castingSkills.Remove(character);
- character.MP -= cost;
- baseTime += skill.RealHardnessTime;
- skill.CurrentCD = skill.RealCD;
- skill.Enable = false;
- LastRound.SkillCost = $"{-cost:0.##} MP";
- WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}");
+ skill.BeforeSkillCasted();
- await OnCharacterCastSkillAsync(character, skillTarget, cost);
+ character.MP -= cost;
+ baseTime += skill.RealHardnessTime;
+ skill.CurrentCD = skill.RealCD;
+ skill.Enable = false;
+ LastRound.SkillsCost[skill] = $"{-cost:0.##} MP";
+ WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}");
- skill.OnSkillCasted(this, character, targets);
+ await OnCharacterCastSkillAsync(character, dp, skillTarget, cost);
+
+ skill.OnSkillCasted(this, character, targets);
+ }
+ }
+ else
+ {
+ WriteLine($"[ {character} ] 放弃释放技能!");
+ // 放弃释放技能会获得3的硬直时间
+ if (baseTime == 0) baseTime = 3;
+ }
+
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
+ {
+ effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
}
}
else
{
- WriteLine($"[ {character} ] 放弃释放技能!");
+ // 原吟唱的技能丢失(被打断或者被取消),允许角色再次决策
+ character.CharacterState = CharacterState.Actionable;
+ character.UpdateCharacterState();
+ }
+ }
+ else if (type == CharacterActionType.CastSuperSkill)
+ {
+ dp.AddActionType(CharacterActionType.CastSuperSkill);
+ LastRound.ActionTypes.Add(CharacterActionType.CastSuperSkill);
+ decided = true;
+ endTurn = true;
+ // 结束预释放爆发技的状态
+ character.CharacterState = CharacterState.Actionable;
+ character.UpdateCharacterState();
+ Skill skill = _castingSuperSkills[character];
+ LastRound.Skills[CharacterActionType.CastSuperSkill] = skill;
+ _castingSuperSkills.Remove(character);
+
+ // 判断是否能够释放技能
+ if (CheckCanCast(character, skill, out double cost))
+ {
+ // 预释放的爆发技不可取消
+ List castRange = _map != null && realGrid != null ? _map.GetGridsByRange(realGrid, skill.CastRange, true) : [];
+ List targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
+ // 免疫检定
+ await CheckSkilledImmuneAsync(character, targets, skill);
+ LastRound.Targets[CharacterActionType.CastSuperSkill] = [.. targets];
+
+ skill.BeforeSkillCasted();
+
+ character.EP -= cost;
+ baseTime += skill.RealHardnessTime;
+ skill.CurrentCD = skill.RealCD;
+ skill.Enable = false;
+ LastRound.SkillsCost[skill] = $"{-cost:0.##} EP";
+ WriteLine($"[ {character} ] 消耗了 {cost:0.##} 点能量值,释放了爆发技 [ {skill.Name} ]!{(skill.Slogan != "" ? skill.Slogan : "")}");
+
+ SkillTarget skillTarget = new(skill, targets);
+ await OnCharacterCastSkillAsync(character, dp, skillTarget, cost);
+
+ skill.OnSkillCasted(this, character, targets);
+ }
+ else
+ {
+ WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
// 放弃释放技能会获得3的硬直时间
if (baseTime == 0) baseTime = 3;
}
@@ -1377,131 +1519,123 @@ namespace Milimoe.FunGame.Core.Model
effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
}
}
- else
+ else if (type == CharacterActionType.UseItem)
{
- // 原吟唱的技能丢失(被打断或者被取消),允许角色再次决策
- character.CharacterState = CharacterState.Actionable;
- character.UpdateCharacterState();
- }
- }
- else if (type == CharacterActionType.CastSuperSkill)
- {
- decided = true;
- // 结束预释放爆发技的状态
- character.CharacterState = CharacterState.Actionable;
- character.UpdateCharacterState();
- Skill skill = _castingSuperSkills[character];
- LastRound.Skill = skill;
- _castingSuperSkills.Remove(character);
-
- // 判断是否能够释放技能
- if (CheckCanCast(character, skill, out double cost))
- {
- // 预释放的爆发技不可取消
- List castRange = _map != null && realGrid != null ? _map.GetGridsByRange(realGrid, skill.CastRange, true) : [];
- List targets = await SelectTargetsAsync(character, skill, enemys, teammates, castRange);
- // 免疫检定
- await CheckSkilledImmuneAsync(character, targets, skill);
- LastRound.Targets = [.. targets];
-
- skill.BeforeSkillCasted();
-
- character.EP -= cost;
- baseTime += skill.RealHardnessTime;
- 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的硬直时间
- if (baseTime == 0) baseTime = 3;
- }
-
- effects = [.. character.Effects.Where(e => e.IsInEffect)];
- foreach (Effect effect in effects)
- {
- effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
- }
- }
- else if (type == CharacterActionType.UseItem)
- {
- // 使用物品逻辑
- Item? item;
- if (aiDecision != null && aiDecision.ItemToUse != null)
- {
- item = aiDecision.ItemToUse;
- }
- else
- {
- 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;
- List castRange = [];
- if (_map != null && realGrid != null)
+ // 使用物品逻辑
+ Item? item;
+ if (aiDecision != null && aiDecision.ItemToUse != null)
{
- castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
- enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
- teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ item = aiDecision.ItemToUse;
}
- if (await UseItemAsync(item, character, enemys, teammates, castRange, aiDecision?.Targets))
+ else
{
- decided = true;
- LastRound.Item = item;
- baseTime += skill.RealHardnessTime > 0 ? skill.RealHardnessTime : 5;
- effects = [.. character.Effects.Where(e => e.IsInEffect)];
- foreach (Effect effect in effects)
+ 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;
+ List castRange = [];
+ if (_map != null && realGrid != null)
{
- effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
+ castRange = _map.GetGridsByRange(realGrid, skill.CastRange, true);
+ enemys = [.. enemys.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ teammates = [.. teammates.Where(castRange.SelectMany(g => g.Characters).Contains)];
+ }
+ if (dp.CurrentDecisionPoints < costDP)
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 决策点不足,无法使用物品!");
+ }
+ else if (!dp.CheckActionTypeQuota(CharacterActionType.UseItem))
+ {
+ if (IsDebug) WriteLine($"角色 [ {character} ] 该回合使用物品的次数已超过决策点配额,无法再使用物品!");
+ }
+ else if (await UseItemAsync(item, character, dp, enemys, teammates, castRange, aiDecision?.Targets))
+ {
+ dp.AddActionType(CharacterActionType.UseItem);
+ dp.CurrentDecisionPoints -= costDP;
+ LastRound.ActionTypes.Add(CharacterActionType.UseItem);
+ LastRound.Items[CharacterActionType.UseItem] = item;
+ decided = true;
+ baseTime += skill.RealHardnessTime > 0 ? skill.RealHardnessTime : 5;
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
+ {
+ effect.AlterHardnessTimeAfterCastSkill(character, skill, ref baseTime, ref isCheckProtected);
+ }
}
}
}
+ else if (type == CharacterActionType.EndTurn)
+ {
+ SetOnlyMoveHardnessTime(character, dp, ref baseTime);
+ decided = true;
+ endTurn = true;
+ WriteLine($"[ {character} ] 结束了回合!");
+ await OnCharacterDoNothingAsync(character, dp);
+ }
+ else
+ {
+ if (baseTime == 0) baseTime += 8;
+ decided = true;
+ endTurn = true;
+ WriteLine($"[ {character} ] 完全行动不能!");
+ }
+
+ if (forceAction)
+ {
+ endTurn = true;
+ }
}
- else if (type == CharacterActionType.EndTurn)
+
+ if (!decided && (isAI || cancelTimes == 0))
{
- SetOnlyMoveHardnessTime(character, ref baseTime);
- decided = true;
- WriteLine($"[ {character} ] 结束了回合!");
- await OnCharacterDoNothingAsync(character);
+ endTurn = true;
+ baseTime += 5;
+ type = CharacterActionType.EndTurn;
}
- else
+
+ if (type == CharacterActionType.None)
{
- if (baseTime == 0) baseTime += 8;
- decided = true;
- WriteLine($"[ {character} ] 完全行动不能!");
+ endTurn = true;
+ WriteLine($"[ {character} ] 放弃了行动!");
+ await OnCharacterGiveUpAsync(character, dp);
+ }
+
+ if (character.CharacterState != CharacterState.Casting) dp.ActionsHardnessTime.Add(baseTime);
+
+ await OnCharacterActionTakenAsync(character, dp, type, LastRound);
+
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
+ {
+ effect.OnCharacterActionTaken(character, dp, type);
+ }
+
+ if (!await AfterCharacterAction(character, type))
+ {
+ endTurn = true;
}
}
- if (!decided && (isAI || cancelTimes == 0))
+ if (character.CharacterState != CharacterState.Casting)
{
- baseTime += 5;
- type = CharacterActionType.EndTurn;
- }
-
- if (type == CharacterActionType.None)
- {
- WriteLine($"[ {character} ] 放弃了行动!");
- await OnCharacterGiveUpAsync(character);
+ baseTime = dp.ActionsTaken > 1 ? (dp.ActionsHardnessTime.Max() + dp.ActionsTaken) : dp.ActionsHardnessTime.Max();
}
_stats[character].ActionTurn += 1;
- LastRound.ActionType = type;
- await AfterCharacterAction(character, type);
+ await AfterCharacterDecision(character, dp);
+ await OnCharacterDecisionCompletedAsync(character, dp, LastRound);
+ effects = [.. character.Effects.Where(e => e.IsInEffect)];
+ foreach (Effect effect in effects)
+ {
+ effect.OnCharacterDecisionCompleted(character, dp);
+ }
// 统一在回合结束时处理角色的死亡
await ProcessCharacterDeathAsync(character);
@@ -1512,7 +1646,7 @@ namespace Milimoe.FunGame.Core.Model
if (_isGameEnd)
{
// 回合结束事件
- await OnTurnEndAsync(character);
+ await OnTurnEndAsync(character, dp);
await AfterTurnAsync(character);
@@ -1535,7 +1669,7 @@ namespace Milimoe.FunGame.Core.Model
}
AddCharacter(character, newHardnessTime, isCheckProtected);
LastRound.HardnessTime = newHardnessTime;
- await OnQueueUpdatedAsync(_queue, character, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。");
+ await OnQueueUpdatedAsync(_queue, character, dp, newHardnessTime, QueueUpdatedReason.Action, "设置角色行动后的硬直时间。");
effects = [.. character.Effects];
foreach (Effect effect in effects)
@@ -1565,11 +1699,14 @@ namespace Milimoe.FunGame.Core.Model
}
}
+ // 清空临时决策点
+ dp.ClearTempActionQuota();
+
// 有人想要插队吗?
await WillPreCastSuperSkill();
// 回合结束事件
- await OnTurnEndAsync(character);
+ await OnTurnEndAsync(character, dp);
await AfterTurnAsync(character);
@@ -1619,7 +1756,24 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
- protected virtual async Task AfterCharacterAction(Character character, CharacterActionType type)
+ /// 返回 false 结束回合
+ protected virtual async Task AfterCharacterAction(Character character, CharacterActionType type)
+ {
+ List allTeammates = GetTeammates(character);
+ Character[] allEnemys = [.. _allCharacters.Where(c => c != character && !allTeammates.Contains(c) && !_eliminated.Contains(c))];
+ if (!allEnemys.Any(c => c.HP > 0))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// 角色完成回合决策后触发
+ ///
+ ///
+ ///
+ protected virtual async Task AfterCharacterDecision(Character character, DecisionPoints dp)
{
await Task.CompletedTask;
}
@@ -2331,6 +2485,29 @@ namespace Milimoe.FunGame.Core.Model
#region 回合内-辅助方法
+ ///
+ /// 取得回合开始时必需的列表
+ ///
+ ///
+ public (List, List, List, List
- ) GetTurnStartNeedyList(Character character, List allTeammates, List allEnemys)
+ {
+ // 可选队友列表
+ List selectableTeammates = [.. allTeammates.Where(_queue.Contains)];
+
+ // 可选敌人列表
+ List selectableEnemys = [.. allEnemys.Where(c => _queue.Contains(c) && !c.IsUnselectable)];
+
+ // 技能列表
+ List 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
- 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)];
+
+ return (selectableTeammates, selectableEnemys, skills, items);
+ }
+
///
/// 需要处理复活和解除施法等
///
@@ -2399,12 +2576,13 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
///
///
///
///
- public async Task UseItemAsync(Item item, Character character, List enemys, List teammates, List castRange, List? desiredTargets = null)
+ public async Task UseItemAsync(Item item, Character character, DecisionPoints dp, List enemys, List teammates, List castRange, List? desiredTargets = null)
{
if (CheckCanCast(character, item, out double costMP, out double costEP))
{
@@ -2428,7 +2606,7 @@ namespace Milimoe.FunGame.Core.Model
if (targets.Count > 0)
{
- LastRound.Targets = [.. targets];
+ LastRound.Targets[CharacterActionType.UseItem] = [.. targets];
WriteLine($"[ {character} ] 使用了物品 [ {item.Name} ]!");
item.ReduceTimesAndRemove();
@@ -2436,7 +2614,7 @@ namespace Milimoe.FunGame.Core.Model
{
character.Items.Remove(item);
}
- await OnCharacterUseItemAsync(character, item, targets);
+ await OnCharacterUseItemAsync(character, dp, item, targets);
skill.OnSkillCasting(this, character, targets);
skill.BeforeSkillCasted();
@@ -2448,15 +2626,15 @@ namespace Milimoe.FunGame.Core.Model
if (costMP > 0)
{
character.MP -= costMP;
- LastRound.SkillCost = $"{-costMP:0.##} MP";
+ LastRound.ItemsCost[item] = $"{-costMP:0.##} MP";
line += $"消耗了 {costMP:0.##} 点魔法值,";
}
if (costEP > 0)
{
character.EP -= costEP;
- if (LastRound.SkillCost != "") LastRound.SkillCost += " / ";
- LastRound.SkillCost += $"{-costEP:0.##} EP";
+ if (LastRound.ItemsCost[item] != "") LastRound.ItemsCost[item] += " / ";
+ LastRound.ItemsCost[item] += $"{-costEP:0.##} EP";
line += $"消耗了 {costEP:0.##} 点能量,";
}
@@ -2464,7 +2642,7 @@ namespace Milimoe.FunGame.Core.Model
WriteLine(line);
SkillTarget skillTarget = new(skill, targets);
- await OnCharacterCastItemSkillAsync(character, item, skillTarget, costMP, costEP);
+ await OnCharacterCastItemSkillAsync(character, dp, item, skillTarget, costMP, costEP);
skill.OnSkillCasted(this, character, targets);
return true;
@@ -2479,10 +2657,11 @@ namespace Milimoe.FunGame.Core.Model
/// 角色移动实际逻辑
///
///
+ ///
///
///
///
- public async Task CharacterMoveAsync(Character character, Grid target, Grid? startGrid)
+ public async Task CharacterMoveAsync(Character character, DecisionPoints dp, Grid target, Grid? startGrid)
{
if (target.Id != -1)
{
@@ -2490,7 +2669,7 @@ namespace Milimoe.FunGame.Core.Model
if (steps > 0)
{
WriteLine($"[ {character} ] 移动了 {steps} 步!");
- await OnCharacterMoveAsync(character, target);
+ await OnCharacterMoveAsync(character, dp, target);
return true;
}
}
@@ -2500,12 +2679,30 @@ namespace Milimoe.FunGame.Core.Model
///
/// 通过概率计算角色要干嘛
///
+ ///
///
///
///
///
- public static CharacterActionType GetActionType(double pUseItem, double pCastSkill, double pNormalAttack)
+ public static CharacterActionType GetActionType(DecisionPoints dp, double pUseItem, double pCastSkill, double pNormalAttack)
{
+ if (!dp.CheckActionTypeQuota(CharacterActionType.NormalAttack) || dp.CurrentDecisionPoints < dp.GameplayEquilibriumConstant.DecisionPointsCostNormalAttack)
+ {
+ pNormalAttack = 0;
+ }
+
+ if (!dp.CheckActionTypeQuota(CharacterActionType.UseItem) || dp.CurrentDecisionPoints < dp.GameplayEquilibriumConstant.DecisionPointsCostItem)
+ {
+ pUseItem = 0;
+ }
+
+ if (dp.CurrentDecisionPoints < dp.GameplayEquilibriumConstant.DecisionPointsCostSkill &&
+ dp.CurrentDecisionPoints < dp.GameplayEquilibriumConstant.DecisionPointsCostSuperSkill &&
+ dp.CurrentDecisionPoints < dp.GameplayEquilibriumConstant.DecisionPointsCostMagic)
+ {
+ pCastSkill = 0;
+ }
+
if (pUseItem == 0 && pCastSkill == 0 && pNormalAttack == 0)
{
return CharacterActionType.EndTurn;
@@ -2974,9 +3171,12 @@ namespace Milimoe.FunGame.Core.Model
/// 对角色设置仅移动的硬直时间
///
///
+ ///
///
- public void SetOnlyMoveHardnessTime(Character character, ref double baseTime)
+ public void SetOnlyMoveHardnessTime(Character character, DecisionPoints dp, ref double baseTime)
{
+ if (dp.ActionsTaken > 0) return;
+
baseTime += 3;
if (character.CharacterState == CharacterState.NotActionable ||
character.CharacterState == CharacterState.ActionRestricted ||
@@ -2987,6 +3187,63 @@ namespace Milimoe.FunGame.Core.Model
}
}
+ ///
+ /// 决策点补充
+ ///
+ public DecisionPoints DecisionPointsRecovery(Character character)
+ {
+ DecisionPoints dp;
+ if (!_decisionPoints.TryGetValue(character, out DecisionPoints? value) || value is null)
+ {
+ value = new();
+ _decisionPoints[character] = value;
+ }
+ dp = value;
+
+ // 吟唱态不做处理
+ if (character.CharacterState == CharacterState.Casting || character.CharacterState == CharacterState.PreCastSuperSkill)
+ {
+ return dp;
+ }
+
+ // 清空上回合的记录
+ dp.CourageCommandSkill = false;
+ dp.ActionsHardnessTime.Clear();
+ dp.ActionTypes.Clear();
+ dp.ActionsTaken = 0;
+
+ // 根据角色状态补充决策点
+ int pointsToAdd;
+
+ // 每回合提升决策点上限
+ if (dp.MaxDecisionPoints < dp.GameplayEquilibriumConstant.MaxDecisionPoints)
+ {
+ dp.MaxDecisionPoints++;
+ }
+ else if (dp.MaxDecisionPoints > dp.GameplayEquilibriumConstant.MaxDecisionPoints)
+ {
+ dp.MaxDecisionPoints = dp.GameplayEquilibriumConstant.MaxDecisionPoints;
+ }
+
+ if (character.CharacterState == CharacterState.NotActionable || character.CharacterState == CharacterState.ActionRestricted)
+ {
+ // 完全行动不能/行动受限:补充上限1/4
+ pointsToAdd = Math.Max(1, dp.MaxDecisionPoints / 4);
+ dp.CurrentDecisionPoints = Math.Min(dp.CurrentDecisionPoints + pointsToAdd, dp.MaxDecisionPoints);
+ }
+ else
+ {
+ // 正常状态:补充上限一半
+ pointsToAdd = Math.Max(1, dp.MaxDecisionPoints / 2);
+ dp.CurrentDecisionPoints = Math.Min(dp.CurrentDecisionPoints + pointsToAdd, dp.MaxDecisionPoints);
+ }
+
+ dp.DecisionPointsRecovery = pointsToAdd;
+
+ if (IsDebug) WriteLine($"[ {character} ] 回合开始,补充 {pointsToAdd} 决策点,当前 {dp.CurrentDecisionPoints}/{dp.MaxDecisionPoints} 决策点。");
+ return dp;
+ }
+
#endregion
#region 回合奖励
@@ -3059,7 +3316,6 @@ namespace Milimoe.FunGame.Core.Model
WriteLine($"[ {character} ] 获得了回合奖励!{skill.Description}".Trim());
if (skill.IsActive)
{
- LastRound.Targets.Add(character);
skill.OnSkillCasted(this, character, [character]);
}
else
@@ -3103,6 +3359,11 @@ namespace Milimoe.FunGame.Core.Model
// 选取所有 AI 控制角色
foreach (Character other in _queue.Where(c => c.CharacterState == CharacterState.Actionable && CharactersInAI.Contains(c)).ToList())
{
+ if (_decisionPoints.TryGetValue(other, out DecisionPoints? dp) && dp != null && dp.CurrentDecisionPoints < dp.GetActionPointCost(CharacterActionType.CastSuperSkill))
+ {
+ continue;
+ }
+
// 有 65% 欲望插队
if (Random.Shared.NextDouble() < 0.65)
{
@@ -3189,7 +3450,12 @@ namespace Milimoe.FunGame.Core.Model
{
_respawnTimes[character] += 1;
}
- await OnQueueUpdatedAsync(_queue, character, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。");
+ if (!_decisionPoints.TryGetValue(character, out DecisionPoints? dp) || dp is null)
+ {
+ dp = new();
+ _decisionPoints[character] = dp;
+ }
+ await OnQueueUpdatedAsync(_queue, character, dp, hardnessTime, QueueUpdatedReason.Respawn, "设置角色复活后的硬直时间。");
}
///
@@ -3199,6 +3465,19 @@ namespace Milimoe.FunGame.Core.Model
///
public async Task SetCharacterPreCastSuperSkill(Character character, Skill skill)
{
+ if (_decisionPoints.TryGetValue(character, out DecisionPoints? dp) && dp != null)
+ {
+ if (dp.CurrentDecisionPoints < 3)
+ {
+ WriteLine("[ " + character + " ] 决策点不足,无法预释放爆发技。");
+ return;
+ }
+ }
+ else
+ {
+ dp = new();
+ _decisionPoints[character] = dp;
+ }
if (character.CharacterState == CharacterState.Casting)
{
_castingSkills.Remove(character);
@@ -3244,7 +3523,7 @@ namespace Milimoe.FunGame.Core.Model
AddCharacter(character, newHardnessTime, false);
skill.OnSkillCasting(this, character, []);
- await OnQueueUpdatedAsync(_queue, character, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。");
+ await OnQueueUpdatedAsync(_queue, character, dp, 0, QueueUpdatedReason.PreCastSuperSkill, "设置角色预释放爆发技的硬直时间。");
}
}
@@ -3513,7 +3792,7 @@ namespace Milimoe.FunGame.Core.Model
#region 事件
- public delegate Task TurnStartEventHandler(GamingQueue queue, Character character, List enemys, List teammates, List skills, List
- items);
+ public delegate Task TurnStartEventHandler(GamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List skills, List
- items);
///
/// 回合开始事件
///
@@ -3522,17 +3801,18 @@ namespace Milimoe.FunGame.Core.Model
/// 回合开始事件
///
///
+ ///
///
///
///
///
///
- protected async Task OnTurnStartAsync(Character character, List enemys, List teammates, List skills, List- items)
+ protected async Task OnTurnStartAsync(Character character, DecisionPoints dp, List enemys, List teammates, List skills, List
- items)
{
- return await (TurnStart?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(true));
+ return await (TurnStart?.Invoke(this, character, dp, enemys, teammates, skills, items) ?? Task.FromResult(true));
}
- public delegate Task TurnEndEventHandler(GamingQueue queue, Character character);
+ public delegate Task TurnEndEventHandler(GamingQueue queue, Character character, DecisionPoints dp);
///
/// 回合结束事件
///
@@ -3541,13 +3821,14 @@ namespace Milimoe.FunGame.Core.Model
/// 回合结束事件
///
///
+ ///
///
- protected async Task OnTurnEndAsync(Character character)
+ protected async Task OnTurnEndAsync(Character character, DecisionPoints dp)
{
- await (TurnEnd?.Invoke(this, character) ?? Task.CompletedTask);
+ await (TurnEnd?.Invoke(this, character, dp) ?? Task.CompletedTask);
}
- public delegate Task DecideActionEventHandler(GamingQueue queue, Character character, List enemys, List teammates, List skills, List
- items);
+ public delegate Task DecideActionEventHandler(GamingQueue queue, Character character, DecisionPoints dp, List enemys, List teammates, List skills, List
- items);
///
/// 决定角色的行动事件
///
@@ -3556,14 +3837,15 @@ namespace Milimoe.FunGame.Core.Model
/// 决定角色的行动事件
///
///
+ ///
///
///
///
///
///
- protected async Task OnDecideActionAsync(Character character, List enemys, List teammates, List skills, List
- items)
+ protected async Task OnDecideActionAsync(Character character, DecisionPoints dp, List enemys, List teammates, List skills, List
- items)
{
- return await (DecideAction?.Invoke(this, character, enemys, teammates, skills, items) ?? Task.FromResult(CharacterActionType.None));
+ return await (DecideAction?.Invoke(this, character, dp, enemys, teammates, skills, items) ?? Task.FromResult(CharacterActionType.None));
}
public delegate Task SelectSkillEventHandler(GamingQueue queue, Character character, List skills);
@@ -3760,7 +4042,7 @@ namespace Milimoe.FunGame.Core.Model
await (DamageToEnemy?.Invoke(this, actor, enemy, damage, actualDamage, isNormalAttack, damageType, magicType, damageResult) ?? Task.CompletedTask);
}
- public delegate Task CharacterNormalAttackEventHandler(GamingQueue queue, Character actor, List targets);
+ public delegate Task CharacterNormalAttackEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, List targets);
///
/// 角色普通攻击事件
///
@@ -3769,14 +4051,15 @@ namespace Milimoe.FunGame.Core.Model
/// 角色普通攻击事件
///
///
+ ///
///
///
- protected async Task OnCharacterNormalAttackAsync(Character actor, List targets)
+ protected async Task OnCharacterNormalAttackAsync(Character actor, DecisionPoints dp, List targets)
{
- await (CharacterNormalAttack?.Invoke(this, actor, targets) ?? Task.CompletedTask);
+ await (CharacterNormalAttack?.Invoke(this, actor, dp, targets) ?? Task.CompletedTask);
}
- public delegate Task CharacterPreCastSkillEventHandler(GamingQueue queue, Character actor, SkillTarget skillTarget);
+ public delegate Task CharacterPreCastSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, SkillTarget skillTarget);
///
/// 角色吟唱技能事件(包括直接释放战技)
///
@@ -3785,14 +4068,15 @@ namespace Milimoe.FunGame.Core.Model
/// 角色吟唱技能事件(包括直接释放战技)
///
///
+ ///
///
///
- protected async Task OnCharacterPreCastSkillAsync(Character actor, SkillTarget skillTarget)
+ protected async Task OnCharacterPreCastSkillAsync(Character actor, DecisionPoints dp, SkillTarget skillTarget)
{
- await (CharacterPreCastSkill?.Invoke(this, actor, skillTarget) ?? Task.CompletedTask);
+ await (CharacterPreCastSkill?.Invoke(this, actor, dp, skillTarget) ?? Task.CompletedTask);
}
- public delegate Task CharacterCastSkillEventHandler(GamingQueue queue, Character actor, SkillTarget skillTarget, double cost);
+ public delegate Task CharacterCastSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, SkillTarget skillTarget, double cost);
///
/// 角色释放技能事件
///
@@ -3801,15 +4085,16 @@ namespace Milimoe.FunGame.Core.Model
/// 角色释放技能事件
///
///
+ ///
///
///
///
- protected async Task OnCharacterCastSkillAsync(Character actor, SkillTarget skillTarget, double cost)
+ protected async Task OnCharacterCastSkillAsync(Character actor, DecisionPoints dp, SkillTarget skillTarget, double cost)
{
- await (CharacterCastSkill?.Invoke(this, actor, skillTarget, cost) ?? Task.CompletedTask);
+ await (CharacterCastSkill?.Invoke(this, actor, dp, skillTarget, cost) ?? Task.CompletedTask);
}
- public delegate Task CharacterUseItemEventHandler(GamingQueue queue, Character actor, Item item, List targets);
+ public delegate Task CharacterUseItemEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Item item, List targets);
///
/// 角色使用物品事件
///
@@ -3818,15 +4103,16 @@ namespace Milimoe.FunGame.Core.Model
/// 角色使用物品事件
///
///
+ ///
///
///
///
- protected async Task OnCharacterUseItemAsync(Character actor, Item item, List targets)
+ protected async Task OnCharacterUseItemAsync(Character actor, DecisionPoints dp, Item item, List targets)
{
- await (CharacterUseItem?.Invoke(this, actor, item, targets) ?? Task.CompletedTask);
+ await (CharacterUseItem?.Invoke(this, actor, dp, item, targets) ?? Task.CompletedTask);
}
- public delegate Task CharacterCastItemSkillEventHandler(GamingQueue queue, Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP);
+ public delegate Task CharacterCastItemSkillEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Item item, SkillTarget skillTarget, double costMP, double costEP);
///
/// 角色释放物品的技能事件
///
@@ -3835,14 +4121,15 @@ namespace Milimoe.FunGame.Core.Model
/// 角色释放物品的技能事件
///
///
+ ///
///
///
///
///
///
- protected async Task OnCharacterCastItemSkillAsync(Character actor, Item item, SkillTarget skillTarget, double costMP, double costEP)
+ protected async Task OnCharacterCastItemSkillAsync(Character actor, DecisionPoints dp, Item item, SkillTarget skillTarget, double costMP, double costEP)
{
- await (CharacterCastItemSkill?.Invoke(this, actor, item, skillTarget, costMP, costEP) ?? Task.CompletedTask);
+ await (CharacterCastItemSkill?.Invoke(this, actor, dp, item, skillTarget, costMP, costEP) ?? Task.CompletedTask);
}
public delegate Task CharacterImmunedEventHandler(GamingQueue queue, Character character, Character immune, ISkill skill, Item? item = null);
@@ -3863,7 +4150,7 @@ namespace Milimoe.FunGame.Core.Model
await (CharacterImmuned?.Invoke(this, character, immune, skill, item) ?? Task.CompletedTask);
}
- public delegate Task CharacterDoNothingEventHandler(GamingQueue queue, Character actor);
+ public delegate Task CharacterDoNothingEventHandler(GamingQueue queue, Character actor, DecisionPoints dp);
///
/// 角色主动结束回合事件(区别于放弃行动,这个是主动的)
///
@@ -3872,13 +4159,14 @@ namespace Milimoe.FunGame.Core.Model
/// 角色主动结束回合事件(区别于放弃行动,这个是主动的)
///
///
+ ///
///
- protected async Task OnCharacterDoNothingAsync(Character actor)
+ protected async Task OnCharacterDoNothingAsync(Character actor, DecisionPoints dp)
{
- await (CharacterDoNothing?.Invoke(this, actor) ?? Task.CompletedTask);
+ await (CharacterDoNothing?.Invoke(this, actor, dp) ?? Task.CompletedTask);
}
- public delegate Task CharacterGiveUpEventHandler(GamingQueue queue, Character actor);
+ public delegate Task CharacterGiveUpEventHandler(GamingQueue queue, Character actor, DecisionPoints dp);
///
/// 角色放弃行动事件
///
@@ -3887,13 +4175,14 @@ namespace Milimoe.FunGame.Core.Model
/// 角色放弃行动事件
///
///
+ ///
///
- protected async Task OnCharacterGiveUpAsync(Character actor)
+ protected async Task OnCharacterGiveUpAsync(Character actor, DecisionPoints dp)
{
- await (CharacterGiveUp?.Invoke(this, actor) ?? Task.CompletedTask);
+ await (CharacterGiveUp?.Invoke(this, actor, dp) ?? Task.CompletedTask);
}
- public delegate Task CharacterMoveEventHandler(GamingQueue queue, Character actor, Grid grid);
+ public delegate Task CharacterMoveEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, Grid grid);
///
/// 角色移动事件
///
@@ -3902,11 +4191,12 @@ namespace Milimoe.FunGame.Core.Model
/// 角色移动事件
///
///
+ ///
///
///
- protected async Task OnCharacterMoveAsync(Character actor, Grid grid)
+ protected async Task OnCharacterMoveAsync(Character actor, DecisionPoints dp, Grid grid)
{
- await (CharacterMove?.Invoke(this, actor, grid) ?? Task.CompletedTask);
+ await (CharacterMove?.Invoke(this, actor, dp, grid) ?? Task.CompletedTask);
}
public delegate Task GameEndEventHandler(GamingQueue queue, Character winner);
@@ -3924,7 +4214,7 @@ namespace Milimoe.FunGame.Core.Model
return await (GameEnd?.Invoke(this, winner) ?? Task.FromResult(true));
}
- public delegate Task QueueUpdatedEventHandler(GamingQueue queue, List characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg);
+ public delegate Task QueueUpdatedEventHandler(GamingQueue queue, List characters, Character character, DecisionPoints dp, double hardnessTime, QueueUpdatedReason reason, string msg);
///
/// 行动顺序表更新事件
///
@@ -3934,13 +4224,49 @@ namespace Milimoe.FunGame.Core.Model
///
///
///
+ ///
///
///
///
///
- protected async Task OnQueueUpdatedAsync(List characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg = "")
+ protected async Task OnQueueUpdatedAsync(List characters, Character character, DecisionPoints dp, double hardnessTime, QueueUpdatedReason reason, string msg = "")
{
- await (QueueUpdated?.Invoke(this, characters, character, hardnessTime, reason, msg) ?? Task.CompletedTask);
+ await (QueueUpdated?.Invoke(this, characters, character, dp, hardnessTime, reason, msg) ?? Task.CompletedTask);
+ }
+
+ public delegate Task CharacterActionTakenEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record);
+ ///
+ /// 角色完成行动事件
+ ///
+ public event CharacterActionTakenEventHandler? CharacterActionTaken;
+ ///
+ /// 角色完成行动事件
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async Task OnCharacterActionTakenAsync(Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record)
+ {
+ await (CharacterActionTaken?.Invoke(this, actor, dp, type, record) ?? Task.CompletedTask);
+ }
+
+ public delegate Task CharacterDecisionCompletedEventHandler(GamingQueue queue, Character actor, DecisionPoints dp, RoundRecord record);
+ ///
+ /// 角色完成决策事件
+ ///
+ public event CharacterDecisionCompletedEventHandler? CharacterDecisionCompleted;
+ ///
+ /// 角色完成决策事件
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async Task OnCharacterDecisionCompletedAsync(Character actor, DecisionPoints dp, RoundRecord record)
+ {
+ await (CharacterDecisionCompleted?.Invoke(this, actor, dp, record) ?? Task.CompletedTask);
}
#endregion
diff --git a/Model/MixGamingQueue.cs b/Model/MixGamingQueue.cs
index 01259c6..294b740 100644
--- a/Model/MixGamingQueue.cs
+++ b/Model/MixGamingQueue.cs
@@ -35,6 +35,25 @@ namespace Milimoe.FunGame.Core.Model
}
}
+ ///
+ /// 角色行动后,进行死亡竞赛幸存者检定
+ ///
+ ///
+ ///
+ ///
+ protected override async Task AfterCharacterAction(Character character, CharacterActionType type)
+ {
+ bool result = await base.AfterCharacterAction(character, type);
+ if (result)
+ {
+ if (MaxRespawnTimes != 0 && MaxScoreToWin > 0 && _stats[character].Kills >= MaxScoreToWin)
+ {
+ return false;
+ }
+ }
+ return result;
+ }
+
///
/// 游戏结束信息
///
diff --git a/Model/RoundRecord.cs b/Model/RoundRecord.cs
index a8ad3c3..b875b52 100644
--- a/Model/RoundRecord.cs
+++ b/Model/RoundRecord.cs
@@ -8,11 +8,12 @@ namespace Milimoe.FunGame.Core.Entity
{
public int Round { get; set; } = round;
public Character Actor { get; set; } = Factory.GetCharacter();
- public CharacterActionType ActionType { get; set; } = CharacterActionType.None;
- public List Targets { get; set; } = [];
- public Skill? Skill { get; set; } = null;
- public string SkillCost { get; set; } = "";
- public Item? Item { get; set; } = null;
+ public HashSet ActionTypes { get; } = [];
+ public Dictionary> Targets { get; } = [];
+ public Dictionary Skills { get; } = [];
+ public Dictionary SkillsCost { get; set; } = [];
+ public Dictionary Items { get; set; } = [];
+ public Dictionary
- ItemsCost { get; set; } = [];
public bool HasKill { get; set; } = false;
public List Assists { get; set; } = [];
public Dictionary Damages { get; set; } = [];
@@ -31,6 +32,18 @@ namespace Milimoe.FunGame.Core.Entity
public List RoundRewards { get; set; } = [];
public List OtherMessages { get; set; } = [];
+ public void AddApplyEffects(Character character, params IEnumerable types)
+ {
+ if (ApplyEffects.TryGetValue(character, out List? list) && list != null)
+ {
+ list.AddRange(types);
+ }
+ else
+ {
+ ApplyEffects.TryAdd(character, [.. types]);
+ }
+ }
+
public override string ToString()
{
StringBuilder builder = new();
@@ -46,47 +59,56 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"[ {Actor} ] 发动了技能:{string.Join(",", Effects.Where(kv => kv.Key == Actor).Select(e => e.Value.Name))}");
}
- if (ActionType == CharacterActionType.NormalAttack || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)
+ foreach (CharacterActionType type in ActionTypes)
{
- if (ActionType == CharacterActionType.NormalAttack)
+ if (type == CharacterActionType.PreCastSkill)
+ {
+ continue;
+ }
+
+ if (!Targets.TryGetValue(type, out List? targets) || targets is null)
+ {
+ targets = [];
+ }
+
+ if (type == CharacterActionType.NormalAttack)
{
builder.Append($"[ {Actor} ] {Actor.NormalAttack.Name} -> ");
}
- else if (ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)
+ else if (type == CharacterActionType.CastSkill || type == CharacterActionType.CastSuperSkill)
{
- if (Skill != null)
+ if (Skills.TryGetValue(type, out Skill? skill) && skill != null)
{
- builder.Append($"[ {Actor} ] {Skill.Name}({SkillCost})-> ");
+ string skillCost = SkillsCost.TryGetValue(skill, out string? cost) ? $"({cost})" : "";
+ builder.Append($"[ {Actor} ] {skill.Name}{skillCost} -> ");
}
else
{
- builder.Append($"释放魔法 -> ");
+ builder.Append($"技能 -> ");
}
}
- builder.AppendLine(string.Join(" / ", GetTargetsState()));
- if (DeathContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", DeathContinuousKilling)}");
- if (ActorContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", ActorContinuousKilling)}");
- if (Assists.Count > 0) builder.AppendLine($"本回合助攻:[ {string.Join(" ] / [ ", Assists)} ]");
+ else if (type == CharacterActionType.UseItem)
+ {
+ if (Items.TryGetValue(type, out Item? item) && item != null)
+ {
+ string itemCost = ItemsCost.TryGetValue(item, out string? cost) ? $"({cost})" : "";
+ builder.Append($"[ {Actor} ] {item.Name}{itemCost} -> ");
+ }
+ else
+ {
+ builder.Append($"技能 -> ");
+ }
+ }
+ builder.AppendLine(string.Join(" / ", GetTargetsState(type, targets)));
}
- if (ActionType == CharacterActionType.PreCastSkill && Skill != null)
+ if (DeathContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", DeathContinuousKilling)}");
+ if (ActorContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", ActorContinuousKilling)}");
+ if (Assists.Count > 0) builder.AppendLine($"本回合助攻:[ {string.Join(" ] / [ ", Assists)} ]");
+
+ if (ActionTypes.Any(type => type == CharacterActionType.PreCastSkill) && Skills.TryGetValue(CharacterActionType.PreCastSkill, out Skill? magic) && magic != null)
{
- if (Skill.IsMagic)
- {
- builder.AppendLine($"[ {Actor} ] 吟唱 [ {Skill.Name} ],持续时间:{CastTime:0.##}");
- }
- else
- {
- builder.Append($"[ {Actor} ] {Skill.Name}({SkillCost})-> ");
- builder.AppendLine(string.Join(" / ", GetTargetsState()));
- builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
- }
- }
- else if (ActionType == CharacterActionType.UseItem && Item != null)
- {
- builder.Append($"[ {Actor} ] {Item.Name}{(SkillCost != "" ? $"({SkillCost})" : " ")}-> ");
- builder.AppendLine(string.Join(" / ", GetTargetsState()));
- builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
+ builder.AppendLine($"[ {Actor} ] 吟唱 [ {magic.Name} ],持续时间:{CastTime:0.##}");
}
else
{
@@ -106,10 +128,10 @@ namespace Milimoe.FunGame.Core.Entity
return builder.ToString();
}
- private List GetTargetsState()
+ private List GetTargetsState(CharacterActionType type, List targets)
{
List strings = [];
- foreach (Character target in Targets.Distinct())
+ foreach (Character target in targets.Distinct())
{
string hasDamage = "";
string hasHeal = "";
@@ -133,11 +155,11 @@ namespace Milimoe.FunGame.Core.Entity
}
if (IsEvaded.ContainsKey(target))
{
- if (ActionType == CharacterActionType.NormalAttack)
+ if (type == CharacterActionType.NormalAttack)
{
hasEvaded = hasDamage == "" ? "完美闪避" : "闪避";
}
- else if ((ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill))
+ else if ((type == CharacterActionType.PreCastSkill || type == CharacterActionType.CastSkill || type == CharacterActionType.CastSuperSkill))
{
hasEvaded = "技能免疫";
}
diff --git a/Model/TeamGamingQueue.cs b/Model/TeamGamingQueue.cs
index 17b0acd..a2cbd1a 100644
--- a/Model/TeamGamingQueue.cs
+++ b/Model/TeamGamingQueue.cs
@@ -90,22 +90,42 @@ namespace Milimoe.FunGame.Core.Model
}
///
- /// 角色行动后
+ /// 当角色完成决策后
///
///
- ///
+ ///
///
- protected override async Task AfterCharacterAction(Character character, CharacterActionType type)
+ protected override async Task AfterCharacterDecision(Character character, DecisionPoints dp)
{
// 如果目标都是队友,会考虑非伤害型助攻
Team? team = GetTeam(character);
if (team != null)
{
- SetNotDamageAssistTime(character, LastRound.Targets.Where(team.IsOnThisTeam));
+ SetNotDamageAssistTime(character, LastRound.Targets.Values.SelectMany(c => c).Where(team.IsOnThisTeam));
}
else await Task.CompletedTask;
}
+ ///
+ /// 角色行动后,进行死亡竞赛幸存者检定
+ ///
+ ///
+ ///
+ ///
+ protected override async Task AfterCharacterAction(Character character, CharacterActionType type)
+ {
+ bool result = await base.AfterCharacterAction(character, type);
+ if (result)
+ {
+ Team? team = GetTeam(character);
+ if ((!_teams.Keys.Where(str => str != team?.Name).Any()) || (MaxScoreToWin > 0 && (team?.Score ?? 0) >= MaxScoreToWin))
+ {
+ return false;
+ }
+ }
+ return result;
+ }
+
///
/// 死亡结算时
///
@@ -148,10 +168,6 @@ namespace Milimoe.FunGame.Core.Model
string[] teamActive = [.. Teams.OrderByDescending(kv => kv.Value.Score).Select(kv =>
{
int activeCount = kv.Value.GetActiveCharacters().Count;
- if (kv.Value == killTeam)
- {
- activeCount += 1;
- }
return kv.Key + ":" + kv.Value.Score + "(剩余存活人数:" + activeCount + ")";
})];
WriteLine($"\r\n=== 当前死亡竞赛比分 ===\r\n{string.Join("\r\n", teamActive)}");