mirror of
https://github.com/project-redbud/FunGame-Core.git
synced 2026-01-19 14:08:22 +00:00
决策点实现:回合内多次行动 (#144)
* 添加决策点功能 * 修复回合日志记录错误 * 吟唱和结算未正确显示 * 事件传递决策点参数;修复第二次行动相关列表不更新的问题 * 决策点可定制;添加事件;事件和钩子优化;修复BUG
This commit is contained in:
parent
b9fbed9c68
commit
ea22adf9cd
@ -12,19 +12,20 @@ namespace Milimoe.FunGame.Core.Controller
|
||||
private readonly GameMap _map = map;
|
||||
|
||||
/// <summary>
|
||||
/// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动。
|
||||
/// AI的核心决策方法,根据当前游戏状态为角色选择最佳行动
|
||||
/// </summary>
|
||||
/// <param name="character">当前行动的AI角色。</param>
|
||||
/// <param name="startGrid">角色的起始格子。</param>
|
||||
/// <param name="allPossibleMoveGrids">从起始格子可达的所有移动格子(包括起始格子本身)。</param>
|
||||
/// <param name="availableSkills">角色所有可用的技能(已过滤CD和EP/MP)。</param>
|
||||
/// <param name="availableItems">角色所有可用的物品(已过滤CD和EP/MP)。</param>
|
||||
/// <param name="allEnemysInGame">场上所有敌人。</param>
|
||||
/// <param name="allTeammatesInGame">场上所有队友。</param>
|
||||
/// <param name="selectableEnemys">场上能够选取的敌人。</param>
|
||||
/// <param name="selectableTeammates">场上能够选取的队友。</param>
|
||||
/// <returns>包含最佳行动的AIDecision对象。</returns>
|
||||
public async Task<AIDecision> DecideAIActionAsync(Character character, Grid startGrid, List<Grid> allPossibleMoveGrids,
|
||||
/// <param name="character">当前行动的AI角色</param>
|
||||
/// <param name="dp">角色的决策点</param>
|
||||
/// <param name="startGrid">角色的起始格子</param>
|
||||
/// <param name="allPossibleMoveGrids">从起始格子可达的所有移动格子(包括起始格子本身)</param>
|
||||
/// <param name="availableSkills">角色所有可用的技能(已过滤CD和EP/MP)</param>
|
||||
/// <param name="availableItems">角色所有可用的物品(已过滤CD和EP/MP)</param>
|
||||
/// <param name="allEnemysInGame">场上所有敌人</param>
|
||||
/// <param name="allTeammatesInGame">场上所有队友</param>
|
||||
/// <param name="selectableEnemys">场上能够选取的敌人</param>
|
||||
/// <param name="selectableTeammates">场上能够选取的队友</param>
|
||||
/// <returns>包含最佳行动的AIDecision对象</returns>
|
||||
public async Task<AIDecision> DecideAIActionAsync(Character character, DecisionPoints dp, Grid startGrid, List<Grid> allPossibleMoveGrids,
|
||||
List<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
|
||||
List<Character> selectableEnemys, List<Character> 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<Grid> 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<Grid> 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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
/// 局内使用物品触发
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> UseItem(IGamingQueue queue, Character character, List<Character> enemys, List<Character> teammates)
|
||||
public async Task<bool> UseItem(IGamingQueue queue, Character character, DecisionPoints dp, List<Character> enemys, List<Character> 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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
/// 行动开始前,指定角色的行动,而不是使用顺序表自带的逻辑;或者修改对应的操作触发概率
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="canUseItem"></param>
|
||||
/// <param name="canCastSkill"></param>
|
||||
@ -591,7 +593,7 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
/// <param name="pNormalAttack"></param>
|
||||
/// <param name="forceAction"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在角色行动后触发
|
||||
/// </summary>
|
||||
/// <param name="actor"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="type"></param>
|
||||
public virtual void OnCharacterActionTaken(Character actor, DecisionPoints dp, CharacterActionType type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在角色回合决策结束后触发
|
||||
/// </summary>
|
||||
/// <param name="actor"></param>
|
||||
/// <param name="dp"></param>
|
||||
public virtual void OnCharacterDecisionCompleted(Character actor, DecisionPoints dp)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对敌人造成技能伤害 [ 强烈建议使用此方法造成伤害而不是自行调用 <see cref="IGamingQueue.DamageToEnemyAsync"/> ]
|
||||
/// </summary>
|
||||
@ -1073,13 +1096,7 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="types"></param>
|
||||
public void RecordCharacterApplyEffects(Character character, params List<EffectType> types)
|
||||
{
|
||||
if (GamingQueue?.LastRound.ApplyEffects.TryAdd(character, types) ?? false)
|
||||
{
|
||||
GamingQueue?.LastRound.ApplyEffects[character].AddRange(types);
|
||||
}
|
||||
}
|
||||
public void RecordCharacterApplyEffects(Character character, params List<EffectType> types) => GamingQueue?.LastRound.AddApplyEffects(character, types);
|
||||
|
||||
/// <summary>
|
||||
/// 返回特效详情
|
||||
|
||||
@ -16,7 +16,7 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
/// <summary>
|
||||
/// 普通攻击说明
|
||||
/// </summary>
|
||||
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) : "物理伤害")}。";
|
||||
|
||||
/// <summary>
|
||||
/// 普通攻击的通用说明
|
||||
@ -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
|
||||
/// </summary>
|
||||
private bool _isMagic = isMagic;
|
||||
|
||||
/// <summary>
|
||||
/// 指示普通攻击是否由武器附魔
|
||||
/// </summary>
|
||||
private bool _isMagicByWeapon = false;
|
||||
|
||||
/// <summary>
|
||||
/// 魔法类型 [ 生效型 ]
|
||||
/// </summary>
|
||||
|
||||
@ -55,6 +55,11 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
||||
/// </summary>
|
||||
public Dictionary<Character, CharacterStatistics> CharacterStatistics { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色的决策点
|
||||
/// </summary>
|
||||
public Dictionary<Character, DecisionPoints> CharacterDecisionPoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 游戏运行的时间
|
||||
/// </summary>
|
||||
@ -153,22 +158,24 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
||||
/// 使用物品
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="caster"></param>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="enemys"></param>
|
||||
/// <param name="teammates"></param>
|
||||
/// <param name="castRange"></param>
|
||||
/// <param name="desiredTargets"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> UseItemAsync(Item item, Character caster, List<Character> enemys, List<Character> teammates, List<Grid> castRange, List<Character>? desiredTargets = null);
|
||||
public Task<bool> UseItemAsync(Item item, Character character, DecisionPoints dp, List<Character> enemys, List<Character> teammates, List<Grid> castRange, List<Character>? desiredTargets = null);
|
||||
|
||||
/// <summary>
|
||||
/// 角色移动
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="startGrid"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> CharacterMoveAsync(Character character, Grid target, Grid? startGrid);
|
||||
public Task<bool> CharacterMoveAsync(Character character, DecisionPoints dp, Grid target, Grid? startGrid);
|
||||
|
||||
/// <summary>
|
||||
/// 选取移动目标
|
||||
|
||||
@ -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<Character>(ref reader, options) ?? Factory.GetCharacter();
|
||||
break;
|
||||
case nameof(RoundRecord.Targets):
|
||||
List<Character> targets = NetworkUtility.JsonDeserialize<List<Character>>(ref reader, options) ?? [];
|
||||
result.Targets.AddRange(targets);
|
||||
Dictionary<CharacterActionType, List<Character>> targets = NetworkUtility.JsonDeserialize<Dictionary<CharacterActionType, List<Character>>>(ref reader, options) ?? [];
|
||||
foreach (CharacterActionType type in targets.Keys)
|
||||
{
|
||||
result.Targets[type] = targets[type];
|
||||
}
|
||||
break;
|
||||
case nameof(RoundRecord.Damages):
|
||||
Dictionary<Guid, double> damagesGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, double>>(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<CharacterActionType> types = NetworkUtility.JsonDeserialize<List<CharacterActionType>>(ref reader, options) ?? [];
|
||||
foreach (CharacterActionType type in types)
|
||||
{
|
||||
result.ActionTypes.Add(type);
|
||||
}
|
||||
break;
|
||||
case nameof(RoundRecord.Skill):
|
||||
result.Skill = NetworkUtility.JsonDeserialize<Skill>(ref reader, options);
|
||||
case nameof(RoundRecord.Skills):
|
||||
Dictionary<CharacterActionType, Skill> skills = NetworkUtility.JsonDeserialize<Dictionary<CharacterActionType, Skill>>(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<Skill, string> skillsCost = NetworkUtility.JsonDeserialize<Dictionary<Skill, string>>(ref reader, options) ?? [];
|
||||
foreach (Skill skill in skillsCost.Keys)
|
||||
{
|
||||
result.SkillsCost[skill] = skillsCost[skill];
|
||||
}
|
||||
break;
|
||||
case nameof(RoundRecord.Item):
|
||||
result.Item = NetworkUtility.JsonDeserialize<Item>(ref reader, options);
|
||||
case nameof(RoundRecord.Items):
|
||||
Dictionary<CharacterActionType, Item> items = NetworkUtility.JsonDeserialize<Dictionary<CharacterActionType, Item>>(ref reader, options) ?? [];
|
||||
foreach (CharacterActionType type in items.Keys)
|
||||
{
|
||||
result.Items[type] = items[type];
|
||||
}
|
||||
break;
|
||||
case nameof(RoundRecord.ItemsCost):
|
||||
Dictionary<Item, string> itemsCost = NetworkUtility.JsonDeserialize<Dictionary<Item, string>>(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);
|
||||
|
||||
169
Model/DecisionPoints.cs
Normal file
169
Model/DecisionPoints.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 所用的游戏平衡常数
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant;
|
||||
|
||||
/// <summary>
|
||||
/// 当前决策点
|
||||
/// </summary>
|
||||
public int CurrentDecisionPoints { get; set; } = General.GameplayEquilibriumConstant.InitialDecisionPoints;
|
||||
|
||||
/// <summary>
|
||||
/// 决策点上限
|
||||
/// </summary>
|
||||
public int MaxDecisionPoints { get; set; } = General.GameplayEquilibriumConstant.InitialDecisionPoints;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合决策点补充数量
|
||||
/// </summary>
|
||||
public int RecoverDecisionPointsPerRound { get; set; } = General.GameplayEquilibriumConstant.RecoverDecisionPointsPerRound;
|
||||
|
||||
/// <summary>
|
||||
/// 本回合已补充的决策点
|
||||
/// </summary>
|
||||
public int DecisionPointsRecovery { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 是否释放过勇气指令
|
||||
/// </summary>
|
||||
public bool CourageCommandSkill { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 记录本回合已使用的行动类型和次数
|
||||
/// </summary>
|
||||
public Dictionary<CharacterActionType, int> ActionTypes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 记录本回合行动的硬直时间
|
||||
/// </summary>
|
||||
public List<double> ActionsHardnessTime { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 本回合已进行的行动次数
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前决策点配额
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加临时决策点配额 [ 回合结束时清除 ]
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="add"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除临时决策点配额
|
||||
/// </summary>
|
||||
public void ClearTempActionQuota()
|
||||
{
|
||||
_tempActionQuotaNormalAttack = 0;
|
||||
_tempActionQuotaSuperSkill = 0;
|
||||
_tempActionQuotaSkill = 0;
|
||||
_tempActionQuotaItem = 0;
|
||||
_tempActionQuotaOther = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 累计行动类型和次数
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="addActionTaken"></param>
|
||||
public void AddActionType(CharacterActionType type, bool addActionTaken = true)
|
||||
{
|
||||
if (addActionTaken) ActionsTaken++;
|
||||
if (!ActionTypes.TryAdd(type, 1))
|
||||
{
|
||||
ActionTypes[type]++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断行动类型是否达到配额
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
public bool CheckActionTypeQuota(CharacterActionType type)
|
||||
{
|
||||
if (ActionTypes.TryGetValue(type, out int times))
|
||||
{
|
||||
return times < this[type];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取决策点消耗
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="skill"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -540,6 +540,86 @@ namespace Milimoe.FunGame.Core.Model
|
||||
/// </summary>
|
||||
public int RoleMOV_Medic { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 初始决策点数
|
||||
/// </summary>
|
||||
public int InitialDecisionPoints { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 决策点上限
|
||||
/// </summary>
|
||||
public int MaxDecisionPoints { get; set; } = 7;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合决策点补充数量
|
||||
/// </summary>
|
||||
public int RecoverDecisionPointsPerRound { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合普通攻击决策配额
|
||||
/// </summary>
|
||||
public int ActionQuotaNormalAttack { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合战技决策配额
|
||||
/// </summary>
|
||||
public int ActionQuotaSkill { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合爆发技决策配额
|
||||
/// </summary>
|
||||
public int ActionQuotaSuperSkill { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合魔法决策配额 [ 使用魔法因为会进入吟唱态,所以无论是否设置都没意义 ]
|
||||
/// </summary>
|
||||
public int ActionQuotaMagic { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合使用物品决策配额
|
||||
/// </summary>
|
||||
public int ActionQuotaItem { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每回合其他决策配额
|
||||
/// </summary>
|
||||
public int ActionQuotaOther { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 普通攻击决策点消耗
|
||||
/// </summary>
|
||||
public int DecisionPointsCostNormalAttack { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 战技决策点消耗
|
||||
/// </summary>
|
||||
public int DecisionPointsCostSkill { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// 爆发技决策点消耗(回合内)
|
||||
/// </summary>
|
||||
public int DecisionPointsCostSuperSkill { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// 爆发技决策点消耗(回合外)
|
||||
/// </summary>
|
||||
public int DecisionPointsCostSuperSkillOutOfTurn { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 魔法决策点消耗
|
||||
/// </summary>
|
||||
public int DecisionPointsCostMagic { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// 使用物品决策点消耗
|
||||
/// </summary>
|
||||
public int DecisionPointsCostItem { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 其他决策点消耗
|
||||
/// </summary>
|
||||
public int DecisionPointsCostOther { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 应用此游戏平衡常数给实体
|
||||
/// </summary>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,25 @@ namespace Milimoe.FunGame.Core.Model
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色行动后,进行死亡竞赛幸存者检定
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<bool> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏结束信息
|
||||
/// </summary>
|
||||
|
||||
@ -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<Character> Targets { get; set; } = [];
|
||||
public Skill? Skill { get; set; } = null;
|
||||
public string SkillCost { get; set; } = "";
|
||||
public Item? Item { get; set; } = null;
|
||||
public HashSet<CharacterActionType> ActionTypes { get; } = [];
|
||||
public Dictionary<CharacterActionType, List<Character>> Targets { get; } = [];
|
||||
public Dictionary<CharacterActionType, Skill> Skills { get; } = [];
|
||||
public Dictionary<Skill, string> SkillsCost { get; set; } = [];
|
||||
public Dictionary<CharacterActionType, Item> Items { get; set; } = [];
|
||||
public Dictionary<Item, string> ItemsCost { get; set; } = [];
|
||||
public bool HasKill { get; set; } = false;
|
||||
public List<Character> Assists { get; set; } = [];
|
||||
public Dictionary<Character, double> Damages { get; set; } = [];
|
||||
@ -31,6 +32,18 @@ namespace Milimoe.FunGame.Core.Entity
|
||||
public List<Skill> RoundRewards { get; set; } = [];
|
||||
public List<string> OtherMessages { get; set; } = [];
|
||||
|
||||
public void AddApplyEffects(Character character, params IEnumerable<EffectType> types)
|
||||
{
|
||||
if (ApplyEffects.TryGetValue(character, out List<EffectType>? 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<Character>? 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()));
|
||||
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 (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 (ActionType == CharacterActionType.PreCastSkill && Skill != null)
|
||||
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<string> GetTargetsState()
|
||||
private List<string> GetTargetsState(CharacterActionType type, List<Character> targets)
|
||||
{
|
||||
List<string> 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 = "技能免疫";
|
||||
}
|
||||
|
||||
@ -90,22 +90,42 @@ namespace Milimoe.FunGame.Core.Model
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色行动后
|
||||
/// 当角色完成决策后
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="dp"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色行动后,进行死亡竞赛幸存者检定
|
||||
/// </summary>
|
||||
/// <param name="character"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
protected override async Task<bool> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 死亡结算时
|
||||
/// </summary>
|
||||
@ -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)}");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user