移动不再计算硬直

This commit is contained in:
milimoe 2025-09-03 00:07:29 +08:00
parent ba70102207
commit 86e4611656
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
6 changed files with 693 additions and 198 deletions

View File

@ -0,0 +1,75 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Milimoe.FunGame.Core.Entity;
namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{
public class CharacterQueueItemViewModel(CharacterQueueItem model, Func<Dictionary<int, List<Skill>>> getTurnRewards) : INotifyPropertyChanged
{
public CharacterQueueItem Model { get; } = model ?? throw new ArgumentNullException(nameof(model));
public Character Character => Model.Character;
public double ATDelay => Model.ATDelay;
private int _predictedTurnNumber;
public int PredictedTurnNumber
{
get => _predictedTurnNumber;
set
{
if (_predictedTurnNumber != value)
{
_predictedTurnNumber = value;
OnPropertyChanged();
// 当回合数变化时,奖励信息可能也变化,因此需要更新
UpdateRewardProperties();
}
}
}
private string _turnRewardSkillName = "";
public string TurnRewardSkillName
{
get => _turnRewardSkillName;
set
{
if (_turnRewardSkillName != value)
{
_turnRewardSkillName = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasTurnReward)); // 奖励名称变化时,通知可见性也可能变化
}
}
}
public bool HasTurnReward => !string.IsNullOrEmpty(TurnRewardSkillName);
// 用于获取 TurnRewards 字典的委托,避免直接依赖 GameMapViewer
private readonly Func<Dictionary<int, List<Skill>>> _getTurnRewards = getTurnRewards ?? throw new ArgumentNullException(nameof(getTurnRewards));
// 当 PredictedTurnNumber 或 TurnRewards 变化时调用此方法
public void UpdateRewardProperties()
{
Dictionary<int, List<Skill>> turnRewards = _getTurnRewards();
if (turnRewards != null && turnRewards.TryGetValue(PredictedTurnNumber, out List<Skill>? rewardSkills))
{
TurnRewardSkillName = string.Join("", rewardSkills.Select(s => s.Name.Replace("[R]", "").Trim()));
}
else
{
TurnRewardSkillName = "";
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CharacterQueueItem(Character character, double atDelay)
{
public Character Character { get; set; } = character;
public double ATDelay { get; set; } = atDelay;
}
}

View File

@ -0,0 +1,129 @@
using System.Globalization;
using System.Windows.Data;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{
public class FirstCharConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string s && s.Length > 0)
{
return s[0].ToString().ToUpper();
}
return "?";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class CharacterToStringWithLevelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Character character)
{
string name = character.ToString();
if (character.CharacterState == CharacterState.Casting)
{
name += " - 吟唱";
}
else if (character.CharacterState == CharacterState.PreCastSuperSkill)
{
name += " - 爆发技";
}
return name;
}
return "[未知角色]";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class SkillItemFormatterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string name = "";
if (value is Skill skill)
{
Character? character = skill.Character;
name = $"【{(skill.IsSuperSkill ? "" : (skill.IsMagic ? "" : ""))}】{skill.Name}";
if (skill.CurrentCD > 0)
{
name += $" - 冷却剩余 {skill.CurrentCD:0.##} 秒";
}
else if (skill.RealEPCost > 0 && skill.RealMPCost > 0 && character != null && character.EP < skill.RealEPCost && character.MP < skill.RealMPCost)
{
name += $" - 能量/魔法要求 {skill.RealEPCost:0.##} / {skill.RealMPCost:0.##} 点";
}
else if (skill.RealEPCost > 0 && character != null && character.EP < skill.RealEPCost)
{
name += $" - 能量不足,要求 {skill.RealEPCost:0.##} 点";
}
else if (skill.RealMPCost > 0 && character != null && character.MP < skill.RealMPCost)
{
name += $" - 魔法不足,要求 {skill.RealMPCost:0.##} 点";
}
}
else if (value is Item item)
{
name = item.Name;
}
else
{
name = value?.ToString() ?? name;
}
return name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 组合转换器:判断技能或物品是否可用。
/// 接收 Skill/Item 对象和当前 Character 对象。
/// </summary>
public class SkillUsabilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// values[0] 应该是 Skill 或 Item 对象
// values[1] 应该是 PlayerCharacter 对象
if (values.Length < 2 || values[1] is not Character character)
{
return false;
}
if (values[0] is Skill s)
{
return 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);
}
else if (values[0] is Item i)
{
return 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 false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
@ -17,7 +18,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
private readonly UserInputRequester<long> _itemSelectionRequester = new(); private readonly UserInputRequester<long> _itemSelectionRequester = new();
private readonly UserInputRequester<bool> _continuePromptRequester = new(); // 用于“按任意键继续”提示 private readonly UserInputRequester<bool> _continuePromptRequester = new(); // 用于“按任意键继续”提示
public void WriteLine(string str = "") => UI.AppendDebugLog(str); public async Task WriteLine(string str = "") => await UI.AppendDebugLog(str);
public async Task Start() public async Task Start()
{ {
@ -25,9 +26,22 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
await _game.StartGame(false); await _game.StartGame(false);
} }
public async Task SetPreCastSuperSkill(Character character, Skill skill)
{
if (_game != null)
{
await _game.SetPreCastSuperSkill(character, skill);
}
}
public void SetPredictCharacter(string name, double ht)
{
UI.Invoke(() => UI.SetPredictCharacter(name, ht));
}
public async Task<long> RequestCharacterSelection(List<Character> availableCharacters) public async Task<long> RequestCharacterSelection(List<Character> availableCharacters)
{ {
WriteLine("请选择你想玩的角色。"); await WriteLine("请选择你想玩的角色。");
return await _characterSelectionRequester.RequestInput( return await _characterSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowCharacterSelectionPrompt(availableCharacters, callback)) (callback) => UI.Invoke(() => UI.ShowCharacterSelectionPrompt(availableCharacters, callback))
); );
@ -35,17 +49,17 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
public async Task<CharacterActionType> RequestActionType(Character character, List<Item> availableItems) public async Task<CharacterActionType> RequestActionType(Character character, List<Item> availableItems)
{ {
WriteLine($"现在是 {character.NickName} 的回合,请选择行动。"); await WriteLine($"现在是 {character.NickName} 的回合,请选择行动。");
return await _actionTypeRequester.RequestInput( return await _actionTypeRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowActionButtons(character, availableItems, callback)) (callback) => UI.Invoke(() => UI.ShowActionButtons(character, availableItems, callback))
); );
} }
public async Task<List<Character>> RequestTargetSelection(Character actor, List<Character> potentialTargets, long maxTargets, bool canSelectSelf, bool canSelectEnemy, bool canSelectTeammate) public async Task<List<Character>> RequestTargetSelection(Character actor, List<Character> potentialTargets, ISkill skill, long maxTargets, bool canSelectSelf, bool canSelectEnemy, bool canSelectTeammate)
{ {
WriteLine($"请为 {actor.NickName} 选择目标 (最多 {maxTargets} 个)。"); await WriteLine($"请为 {actor.NickName} 选择目标 (最多 {maxTargets} 个)。");
List<Character> targetIds = await _targetSelectionRequester.RequestInput( List<Character> targetIds = await _targetSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowTargetSelectionUI(actor, potentialTargets, maxTargets, canSelectSelf, canSelectEnemy, canSelectTeammate, callback)) (callback) => UI.Invoke(() => UI.ShowTargetSelectionUI(actor, potentialTargets, skill, maxTargets, canSelectSelf, canSelectEnemy, canSelectTeammate, callback))
) ?? []; ) ?? [];
if (targetIds == null) return []; if (targetIds == null) return [];
return [.. potentialTargets.Where(targetIds.Contains)]; return [.. potentialTargets.Where(targetIds.Contains)];
@ -53,7 +67,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
public async Task<Skill?> RequestSkillSelection(Character character, List<Skill> availableSkills) public async Task<Skill?> RequestSkillSelection(Character character, List<Skill> availableSkills)
{ {
WriteLine($"请为 {character.NickName} 选择一个技能。"); await WriteLine($"请为 {character.NickName} 选择一个技能。");
long? skillId = await _skillSelectionRequester.RequestInput( long? skillId = await _skillSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowSkillSelectionUI(character, callback)) (callback) => UI.Invoke(() => UI.ShowSkillSelectionUI(character, callback))
); );
@ -62,7 +76,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
public async Task<Item?> RequestItemSelection(Character character, List<Item> availableItems) public async Task<Item?> RequestItemSelection(Character character, List<Item> availableItems)
{ {
WriteLine($"请为 {character.NickName} 选择一个物品。"); await WriteLine($"请为 {character.NickName} 选择一个物品。");
long? itemId = await _itemSelectionRequester.RequestInput( long? itemId = await _itemSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowItemSelectionUI(character, callback)) (callback) => UI.Invoke(() => UI.ShowItemSelectionUI(character, callback))
); );
@ -71,12 +85,21 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
public async Task RequestContinuePrompt(string message) public async Task RequestContinuePrompt(string message)
{ {
WriteLine(message); await WriteLine(message);
await _continuePromptRequester.RequestInput( await _continuePromptRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowContinuePrompt(message, callback)) (callback) => UI.Invoke(() => UI.ShowContinuePrompt(message, callback))
); );
} }
public async Task RequestCountDownContinuePrompt(string message, int countdownSeconds = 2)
{
await WriteLine(message);
// 调用 _continuePromptRequester 的 RequestInput 方法,它会等待回调被触发
await _continuePromptRequester.RequestInput(
(callback) => UI.Invoke(() => UI.StartCountdownForContinue(countdownSeconds, callback))
);
}
// --- GameMapViewer 调用这些方法来解决 UI 输入 --- // --- GameMapViewer 调用这些方法来解决 UI 输入 ---
public void ResolveCharacterSelection(long characterId) public void ResolveCharacterSelection(long characterId)
@ -115,6 +138,12 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
UI.Invoke(() => UI.HideContinuePrompt()); UI.Invoke(() => UI.HideContinuePrompt());
} }
public void ResolveCountDownContinuePrompt()
{
_continuePromptRequester.ResolveInput(true);
UI.Invoke(() => UI.HideContinuePrompt());
}
public bool IsTeammate(Character actor, Character target) public bool IsTeammate(Character actor, Character target)
{ {
if (actor == target) return true; if (actor == target) return true;
@ -156,6 +185,22 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
}); });
} }
public void SetCurrentRound(int round)
{
UI.Invoke(() =>
{
UI.CurrentRound = round;
});
}
public void SetTurnRewards(Dictionary<int, List<Skill>> rewards)
{
UI.Invoke(() =>
{
UI.TurnRewards = rewards;
});
}
public void SetPlayerCharacter(Character character) public void SetPlayerCharacter(Character character)
{ {
UI.Invoke(() => UI.Invoke(() =>

View File

@ -39,7 +39,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
List<string> result = []; List<string> result = [];
Msg = ""; Msg = "";
List<Character> allCharactersInGame = [.. FunGameConstant.Characters]; // 使用不同的名称以避免与后面的 `characters` 冲突 List<Character> allCharactersInGame = [.. FunGameConstant.Characters]; // 使用不同的名称以避免与后面的 `characters` 冲突
Controller.WriteLine("--- 游戏开始 ---"); await Controller.WriteLine("--- 游戏开始 ---");
int clevel = 60; int clevel = 60;
int slevel = 6; int slevel = 6;
@ -62,7 +62,10 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
// 显示角色信息 // 显示角色信息
characters.ForEach(c => Controller.WriteLine($"角色编号:{c.Id}\r\n{c.GetInfo()}")); foreach (Character c in characters)
{
await Controller.WriteLine($"角色编号:{c.Id}\r\n{c.GetInfo()}");
}
// 询问玩家需要选择哪个角色 (通过UI界面选择) // 询问玩家需要选择哪个角色 (通过UI界面选择)
Character? player = null; Character? player = null;
@ -73,7 +76,8 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
player = characters.FirstOrDefault(c => c.Id == selectedPlayerId); player = characters.FirstOrDefault(c => c.Id == selectedPlayerId);
if (player != null) if (player != null)
{ {
Controller.WriteLine($"选择了 [ {player} ]"); await Controller.WriteLine($"选择了 [ {player} ]");
player.Promotion = 200;
Controller.SetCurrentCharacter(player); Controller.SetCurrentCharacter(player);
Controller.SetPlayerCharacter(player); Controller.SetPlayerCharacter(player);
@ -137,9 +141,9 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
int qArmor = 5; int qArmor = 5;
int qShoes = 5; int qShoes = 5;
int qAccessory = 5; int qAccessory = 5;
WriteLine($"社区送温暖了,现在随机发放空投!!"); await Controller.WriteLine($"社区送温暖了,现在随机发放空投!!");
DropItems(_gamingQueue, qMagicCardPack, qWeapon, qArmor, qShoes, qAccessory); DropItems(_gamingQueue, qMagicCardPack, qWeapon, qArmor, qShoes, qAccessory);
WriteLine(""); await Controller.WriteLine("");
if (isWeb) result.Add("=== 空投 ===\r\n" + Msg); if (isWeb) result.Add("=== 空投 ===\r\n" + Msg);
double nextDropItemTime = 40; double nextDropItemTime = 40;
if (qMagicCardPack < 5) qMagicCardPack++; if (qMagicCardPack < 5) qMagicCardPack++;
@ -149,20 +153,23 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (qAccessory < 5) qAccessory++; if (qAccessory < 5) qAccessory++;
// 显示角色信息 // 显示角色信息
characters.ForEach(c => Controller.WriteLine(c.GetInfo())); foreach (Character c in characters)
{
await Controller.WriteLine(c.GetInfo());
}
// 初始化队列,准备开始游戏 // 初始化队列,准备开始游戏
_gamingQueue.InitActionQueue(); _gamingQueue.InitActionQueue();
_gamingQueue.SetCharactersToAIControl(false, characters); _gamingQueue.SetCharactersToAIControl(false, characters);
_gamingQueue.SetCharactersToAIControl(true, player); _gamingQueue.SetCharactersToAIControl(true, player);
_gamingQueue.CustomData.Add("player", player); _gamingQueue.CustomData.Add("player", player);
Controller.WriteLine(); await Controller.WriteLine();
// 显示初始顺序表 // 显示初始顺序表
_gamingQueue.DisplayQueue(); _gamingQueue.DisplayQueue();
Controller.WriteLine(); await Controller.WriteLine();
Controller.WriteLine($"你的角色是 [ {player} ],详细信息:{player.GetInfo()}"); await Controller.WriteLine($"你的角色是 [ {player} ],详细信息:{player.GetInfo()}");
// 总回合数 // 总回合数
int maxRound = 999; int maxRound = 999;
@ -180,6 +187,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
effects.Add(effectID, isActive); effects.Add(effectID, isActive);
} }
_gamingQueue.InitRoundRewards(maxRound, 1, effects, id => FunGameConstant.RoundRewards[(EffectID)id]); _gamingQueue.InitRoundRewards(maxRound, 1, effects, id => FunGameConstant.RoundRewards[(EffectID)id]);
Controller.SetTurnRewards(_gamingQueue.RoundRewards);
int i = 1; int i = 1;
while (i < maxRound) while (i < maxRound)
@ -187,7 +195,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
Msg = ""; Msg = "";
if (i == maxRound - 1) if (i == maxRound - 1)
{ {
WriteLine($"=== 终局审判 ==="); await Controller.WriteLine($"=== 终局审判 ===");
Dictionary<Character, double> hpPercentage = []; Dictionary<Character, double> hpPercentage = [];
foreach (Character c in characters) foreach (Character c in characters)
{ {
@ -195,10 +203,10 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
double max = hpPercentage.Values.Max(); double max = hpPercentage.Values.Max();
Character winner = hpPercentage.Keys.Where(c => hpPercentage[c] == max).First(); Character winner = hpPercentage.Keys.Where(c => hpPercentage[c] == max).First();
WriteLine("[ " + winner + " ] 成为了天选之人!!"); await Controller.WriteLine("[ " + winner + " ] 成为了天选之人!!");
foreach (Character c in characters.Where(c => c != winner && c.HP > 0)) foreach (Character c in characters.Where(c => c != winner && c.HP > 0))
{ {
WriteLine("[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。"); await Controller.WriteLine("[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。");
await _gamingQueue.DeathCalculationAsync(winner, c); await _gamingQueue.DeathCalculationAsync(winner, c);
} }
if (_gamingQueue is MixGamingQueue mix) if (_gamingQueue is MixGamingQueue mix)
@ -217,8 +225,9 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
// 处理回合 // 处理回合
if (characterToAct != null) if (characterToAct != null)
{ {
WriteLine($"=== 回合 {i++} ==="); Controller.SetCurrentRound(i);
WriteLine("现在是 [ " + characterToAct + " ] 的回合!"); await Controller.WriteLine($"=== 回合 {i++} ===");
await Controller.WriteLine("现在是 [ " + characterToAct + " ] 的回合!");
bool isGameEnd = await _gamingQueue.ProcessTurnAsync(characterToAct); bool isGameEnd = await _gamingQueue.ProcessTurnAsync(characterToAct);
@ -229,7 +238,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
if (isWeb) _gamingQueue.DisplayQueue(); if (isWeb) _gamingQueue.DisplayQueue();
WriteLine(""); await Controller.WriteLine("");
} }
string roundMsg = ""; string roundMsg = "";
@ -243,6 +252,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
double timeLapse = await _gamingQueue.TimeLapse(); double timeLapse = await _gamingQueue.TimeLapse();
totalTime += timeLapse; totalTime += timeLapse;
nextDropItemTime -= timeLapse; nextDropItemTime -= timeLapse;
Controller.UpdateBottomInfoPanel();
Controller.UpdateQueue(); Controller.UpdateQueue();
Controller.UpdateCharacterPositionsOnMap(); Controller.UpdateCharacterPositionsOnMap();
@ -259,9 +269,9 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{ {
// 空投 // 空投
Msg = ""; Msg = "";
WriteLine($"社区送温暖了,现在随机发放空投!!"); await Controller.WriteLine($"社区送温暖了,现在随机发放空投!!");
DropItems(_gamingQueue, qMagicCardPack, qWeapon, qArmor, qShoes, qAccessory); DropItems(_gamingQueue, qMagicCardPack, qWeapon, qArmor, qShoes, qAccessory);
WriteLine(""); await Controller.WriteLine("");
if (isWeb) result.Add("=== 空投 ===\r\n" + Msg); if (isWeb) result.Add("=== 空投 ===\r\n" + Msg);
nextDropItemTime = 40; nextDropItemTime = 40;
if (qMagicCardPack < 5) qMagicCardPack++; if (qMagicCardPack < 5) qMagicCardPack++;
@ -272,8 +282,8 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
} }
Controller.WriteLine("--- 游戏结束 ---"); await Controller.WriteLine("--- 游戏结束 ---");
Controller.WriteLine($"总游戏时长:{totalTime:0.##} {_gamingQueue.GameplayEquilibriumConstant.InGameTime}"); await Controller.WriteLine($"总游戏时长:{totalTime:0.##} {_gamingQueue.GameplayEquilibriumConstant.InGameTime}");
// 赛后统计 // 赛后统计
FunGameService.GetCharacterRating(_gamingQueue.CharacterStatistics, false, []); FunGameService.GetCharacterRating(_gamingQueue.CharacterStatistics, false, []);
@ -299,18 +309,18 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
int count = 1; int count = 1;
if (isWeb) if (isWeb)
{ {
WriteLine("=== 技术得分排行榜 ==="); await Controller.WriteLine("=== 技术得分排行榜 ===");
Msg = $"=== 技术得分排行榜 TOP{top} ===\r\n"; Msg = $"=== 技术得分排行榜 TOP{top} ===\r\n";
} }
else else
{ {
StringBuilder ratingBuilder = new(); StringBuilder ratingBuilder = new();
WriteLine("=== 本场比赛最佳角色 ==="); await Controller.WriteLine("=== 本场比赛最佳角色 ===");
Msg = $"=== 本场比赛最佳角色 ===\r\n"; Msg = $"=== 本场比赛最佳角色 ===\r\n";
WriteLine(mvpBuilder.ToString() + "\r\n\r\n" + ratingBuilder.ToString()); await Controller.WriteLine(mvpBuilder.ToString() + "\r\n\r\n" + ratingBuilder.ToString());
Controller.WriteLine(); await Controller.WriteLine();
Controller.WriteLine("=== 技术得分排行榜 ==="); await Controller.WriteLine("=== 技术得分排行榜 ===");
} }
foreach (Character character in _gamingQueue.CharacterStatistics.OrderByDescending(d => d.Value.Rating).Select(d => d.Key)) foreach (Character character in _gamingQueue.CharacterStatistics.OrderByDescending(d => d.Value.Rating).Select(d => d.Key))
@ -327,11 +337,11 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
builder.Append($"每秒伤害:{stats.DamagePerSecond:0.##} / 每回合伤害:{stats.DamagePerTurn:0.##}"); builder.Append($"每秒伤害:{stats.DamagePerSecond:0.##} / 每回合伤害:{stats.DamagePerTurn:0.##}");
if (count++ <= top) if (count++ <= top)
{ {
WriteLine(builder.ToString()); await Controller.WriteLine(builder.ToString());
} }
else else
{ {
Controller.WriteLine(builder.ToString()); await Controller.WriteLine(builder.ToString());
} }
CharacterStatistics? totalStats = CharacterStatistics.Where(kv => kv.Key.GetName() == character.GetName()).Select(kv => kv.Value).FirstOrDefault(); CharacterStatistics? totalStats = CharacterStatistics.Where(kv => kv.Key.GetName() == character.GetName()).Select(kv => kv.Value).FirstOrDefault();
@ -364,7 +374,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
catch (Exception ex) catch (Exception ex)
{ {
Controller.WriteLine(ex.ToString()); await Controller.WriteLine(ex.ToString());
return [ex.ToString()]; return [ex.ToString()];
} }
} }
@ -380,26 +390,29 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
private async Task GamingQueue_CharacterMove(GamingQueue queue, Character actor, Grid grid) private async Task GamingQueue_CharacterMove(GamingQueue queue, Character actor, Grid grid)
{ {
Controller.UpdateCharacterPositionsOnMap();
await Task.CompletedTask; await Task.CompletedTask;
} }
private async Task GamingQueue_QueueUpdated(GamingQueue queue, List<Character> characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg) private async Task GamingQueue_QueueUpdated(GamingQueue queue, List<Character> characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg)
{ {
if (reason != QueueUpdatedReason.Action)
{
Controller.UpdateQueue();
}
if (IsPlayer_OnlyTest(queue, character)) if (IsPlayer_OnlyTest(queue, character))
{ {
if (reason == QueueUpdatedReason.Action) //if (reason == QueueUpdatedReason.Action)
{ //{
queue.SetCharactersToAIControl(false, character); // queue.SetCharactersToAIControl(false, character);
//}
//if (reason == QueueUpdatedReason.PreCastSuperSkill)
//{
// // 玩家释放爆发技后,需要等待玩家确认
// await Controller.RequestContinuePrompt("你的下一回合需要选择爆发技目标,知晓请点击继续. . .");
// Controller.ResolveContinuePrompt();
//}
} }
if (reason == QueueUpdatedReason.PreCastSuperSkill)
{
// 玩家释放爆发技后,需要等待玩家确认
await Controller.RequestContinuePrompt("你的下一回合需要选择爆发技目标,知晓请点击继续. . .");
Controller.ResolveContinuePrompt();
}
}
Controller.UpdateQueue();
Controller.UpdateCharacterPositionsOnMap();
await Task.CompletedTask; await Task.CompletedTask;
} }
@ -409,7 +422,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (IsPlayer_OnlyTest(queue, character)) if (IsPlayer_OnlyTest(queue, character))
{ {
// 确保玩家角色在回合开始时取消AI托管以便玩家可以控制 // 确保玩家角色在回合开始时取消AI托管以便玩家可以控制
queue.SetCharactersToAIControl(cancel: true, character); //queue.SetCharactersToAIControl(cancel: true, character);
} }
await Task.CompletedTask; await Task.CompletedTask;
return true; return true;
@ -428,6 +441,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
List<Character> selectedTargets = await Controller.RequestTargetSelection( List<Character> selectedTargets = await Controller.RequestTargetSelection(
character, character,
potentialTargets, potentialTargets,
attack,
attack.CanSelectTargetCount, attack.CanSelectTargetCount,
attack.CanSelectSelf, attack.CanSelectSelf,
attack.CanSelectEnemy, attack.CanSelectEnemy,
@ -461,6 +475,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
List<Character>? selectedTargets = await Controller.RequestTargetSelection( List<Character>? selectedTargets = await Controller.RequestTargetSelection(
caster, caster,
potentialTargets, potentialTargets,
skill,
skill.CanSelectTargetCount, skill.CanSelectTargetCount,
skill.CanSelectSelf, skill.CanSelectSelf,
skill.CanSelectEnemy, skill.CanSelectEnemy,
@ -483,6 +498,8 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
private async Task GamingQueue_TurnEnd(GamingQueue queue, Character character) private async Task GamingQueue_TurnEnd(GamingQueue queue, Character character)
{ {
double ht = queue.HardnessTime[character];
Controller.SetPredictCharacter(character.NickName, ht);
Controller.UpdateBottomInfoPanel(); Controller.UpdateBottomInfoPanel();
if (IsRoundHasPlayer_OnlyTest(queue, character)) if (IsRoundHasPlayer_OnlyTest(queue, character))
{ {
@ -490,6 +507,11 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
await Controller.RequestContinuePrompt("你的回合(或与你相关的回合)已结束,请查看本回合日志,然后点击继续. . ."); await Controller.RequestContinuePrompt("你的回合(或与你相关的回合)已结束,请查看本回合日志,然后点击继续. . .");
Controller.ResolveContinuePrompt(); Controller.ResolveContinuePrompt();
} }
else
{
await Controller.RequestCountDownContinuePrompt("该角色的回合已结束. . .");
Controller.ResolveCountDownContinuePrompt();
}
await Task.CompletedTask; await Task.CompletedTask;
} }
@ -507,7 +529,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
private static bool IsPlayer_OnlyTest(GamingQueue queue, Character current) private static bool IsPlayer_OnlyTest(GamingQueue queue, Character current)
{ {
return queue.CustomData.TryGetValue("player", out object? value) && value is Character player && player == current; return queue.CustomData.TryGetValue("player", out object? value) && value is Character player && player == current && !queue.IsCharacterInAIControlling(current);
} }
private static bool IsRoundHasPlayer_OnlyTest(GamingQueue queue, Character current) private static bool IsRoundHasPlayer_OnlyTest(GamingQueue queue, Character current)
@ -515,10 +537,18 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
return queue.CustomData.TryGetValue("player", out object? value) && value is Character player && (player == current || (current.CharacterState != CharacterState.Casting && queue.LastRound.Targets.Any(c => c == player))); return queue.CustomData.TryGetValue("player", out object? value) && value is Character player && (player == current || (current.CharacterState != CharacterState.Casting && queue.LastRound.Targets.Any(c => c == player)));
} }
public async Task SetPreCastSuperSkill(Character character, Skill skill)
{
if (_gamingQueue is GamingQueue queue)
{
await queue.SetCharacterPreCastSuperSkill(character, skill);
}
}
public void WriteLine(string str) public void WriteLine(string str)
{ {
Msg += str + "\r\n"; Msg += str + "\r\n";
Controller.WriteLine(str); _ = Controller.WriteLine(str);
} }
public static void DropItems(GamingQueue queue, int mQuality, int wQuality, int aQuality, int sQuality, int acQuality) public static void DropItems(GamingQueue queue, int mQuality, int wQuality, int aQuality, int sQuality, int acQuality)
@ -561,6 +591,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
Item consumable = consumables[Random.Shared.Next(consumables.Length)].Copy(); Item consumable = consumables[Random.Shared.Next(consumables.Length)].Copy();
consumable.Character = character;
character.Items.Add(consumable); character.Items.Add(consumable);
} }
} }

View File

@ -137,20 +137,35 @@
<Border Grid.Column="0" Grid.Row="0" BorderBrush="LightGray" BorderThickness="1" Margin="5" Padding="5"> <Border Grid.Column="0" Grid.Row="0" BorderBrush="LightGray" BorderThickness="1" Margin="5" Padding="5">
<StackPanel x:Name="LeftQueuePanel" MinWidth="180" Background="#FFF0F8FF"> <StackPanel x:Name="LeftQueuePanel" MinWidth="180" Background="#FFF0F8FF">
<!-- AliceBlue --> <!-- AliceBlue -->
<TextBlock Text="行动顺序表" Margin="0,0,0,10" FontWeight="Bold" FontSize="14"/> <TextBlock x:Name="QueueTitle" Text="行动顺序表" Margin="0,0,0,10" FontWeight="Bold" FontSize="14"/>
<!-- 动态内容将在此处添加 --> <!-- 动态内容将在此处添加 -->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Height="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Height="Auto">
<ItemsControl x:Name="CharacterQueueItemsControl" ItemsSource="{Binding CharacterQueueItems, RelativeSource={RelativeSource AncestorType=UserControl}}"> <ItemsControl x:Name="CharacterQueueItemsControl" ItemsSource="{Binding CharacterQueueDisplayItems, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Margin="0,2,0,2" Padding="2"> <Border BorderThickness="0,0,0,1" Margin="0,2,0,2" Padding="2">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="LightGray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Character.Promotion}" Value="200">
<Setter Property="Background" Value="Bisque"/>
</DataTrigger>
<DataTrigger Binding="{Binding Character.Promotion}" Value="1800">
<Setter Property="Background" Value="#FFDDA0DD"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- 图标列 --> <!-- 图标列 -->
<ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/>
<!-- 文本信息列 --> <!-- 文本信息列 -->
<ColumnDefinition Width="*"/>
<!-- 新增:回合奖励列 -->
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@ -158,9 +173,19 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- 角色图标 (用大字代替) --> <!-- 角色图标 (用大字代替) -->
<Border Grid.Column="0" Grid.RowSpan="2" Width="25" Height="25" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Margin="0,0,5,0" <Border Grid.Column="0" Grid.RowSpan="2" Width="25" Height="25" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Margin="0,0,5,0">
Background="#FF6A5ACD">
<!-- 默认背景色,可根据角色动态改变 --> <!-- 默认背景色,可根据角色动态改变 -->
<Border.Style>
<Style TargetType="Border">
<!-- 默认背景色 -->
<Setter Property="Background" Value="#FF6A5ACD"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Character.Promotion}" Value="1800">
<Setter Property="Background" Value="#FF228B22"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Character.NickName, Converter={StaticResource FirstCharConverter}}" <TextBlock Text="{Binding Character.NickName, Converter={StaticResource FirstCharConverter}}"
Foreground="White" Foreground="White"
FontSize="10" FontSize="10"
@ -176,6 +201,17 @@
<!-- ATDelay --> <!-- ATDelay -->
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ATDelay, StringFormat=AT Delay: {0:0.##}}" <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ATDelay, StringFormat=AT Delay: {0:0.##}}"
FontSize="11" Foreground="DimGray"/> FontSize="11" Foreground="DimGray"/>
<!-- 新增:回合奖励显示 -->
<StackPanel Grid.Column="2" Grid.RowSpan="2" VerticalAlignment="Center" Margin="5,0,0,0">
<!-- 直接绑定到 ViewModel 的 PredictedTurnNumber -->
<TextBlock FontSize="9" Foreground="Gray" HorizontalAlignment="Right"
Text="{Binding PredictedTurnNumber, StringFormat=回合: {0}}" />
<!-- 直接绑定到 ViewModel 的 TurnRewardSkillName -->
<TextBlock FontSize="10" Foreground="DarkGreen" FontWeight="SemiBold" HorizontalAlignment="Right"
Text="{Binding TurnRewardSkillName}" />
</StackPanel>
</Grid> </Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>
@ -407,6 +443,19 @@
</StackPanel> </StackPanel>
</Border> </Border>
<TextBlock x:Name="CountdownTextBlock"
Panel.ZIndex="200"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="15"
FontSize="18"
FontWeight="Bold"
Foreground="White"
Background="#88000000"
Padding="8"
Visibility="Collapsed"
Text="0 秒后继续..." />
</Grid> </Grid>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
@ -643,6 +692,7 @@
<Button x:Name="SkillButton" Content="战技/魔法" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.PreCastSkill}" IsEnabled="False"/> <Button x:Name="SkillButton" Content="战技/魔法" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.PreCastSkill}" IsEnabled="False"/>
<Button x:Name="UseItemButton" Content="使用物品" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.UseItem}" IsEnabled="False"/> <Button x:Name="UseItemButton" Content="使用物品" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.UseItem}" IsEnabled="False"/>
<Button x:Name="EndTurnButton" Content="结束回合" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.EndTurn}" IsEnabled="False"/> <Button x:Name="EndTurnButton" Content="结束回合" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.EndTurn}" IsEnabled="False"/>
<Button x:Name="PreCastButton" Content="爆发技插队" Width="100" Height="30" Margin="5" Click="PreCastSkillButton_Click" Tag="" IsEnabled="False"/>
</WrapPanel> </WrapPanel>
<!-- 新增:数据统计 --> <!-- 新增:数据统计 -->

View File

@ -1,13 +1,15 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Globalization; using System.Collections.Specialized;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading;
using System.Xml.Linq;
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
using Oshima.FunGame.OshimaServers.Service; using Oshima.FunGame.OshimaServers.Service;
@ -23,125 +25,6 @@ using UserControl = System.Windows.Controls.UserControl;
namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{ {
public class CharacterQueueItem(Character character, double atDelay)
{
public Character Character { get; set; } = character;
public double ATDelay { get; set; } = atDelay;
}
public class FirstCharConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string s && s.Length > 0)
{
return s[0].ToString().ToUpper();
}
return "?";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class CharacterToStringWithLevelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Character character)
{
return character.ToString();
}
return "[未知角色]";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class SkillItemFormatterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string name = "";
if (value is Skill skill)
{
Character? character = skill.Character;
name = $"【{(skill.IsSuperSkill ? "" : (skill.IsMagic ? "" : ""))}】{skill.Name}";
if (skill.CurrentCD > 0)
{
name += $" - 冷却剩余 {skill.CurrentCD:0.##} 秒";
}
else if (skill.RealEPCost > 0 && skill.RealMPCost > 0 && character != null && character.EP < skill.RealEPCost && character.MP < skill.RealMPCost)
{
name += $" - 能量/魔法要求 {skill.RealEPCost:0.##} / {skill.RealMPCost:0.##} 点";
}
else if (skill.RealEPCost > 0 && character != null && character.EP < skill.RealEPCost)
{
name += $" - 能量不足,要求 {skill.RealEPCost:0.##} 点";
}
else if (skill.RealMPCost > 0 && character != null && character.MP < skill.RealMPCost)
{
name += $" - 魔法不足,要求 {skill.RealMPCost:0.##} 点";
}
}
else if (value is Item item)
{
name = item.Name;
}
else
{
name = value?.ToString() ?? name;
}
return name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 组合转换器:判断技能或物品是否可用。
/// 接收 Skill/Item 对象和当前 Character 对象。
/// </summary>
public class SkillUsabilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// values[0] 应该是 Skill 或 Item 对象
// values[1] 应该是 CurrentCharacter 对象
if (values.Length < 2 || values[1] is not Character character)
{
return false;
}
if (values[0] is Skill s)
{
return 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);
}
else if (values[0] is Item i)
{
return 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 false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary> /// <summary>
/// GameMapViewer.xaml 的交互逻辑 /// GameMapViewer.xaml 的交互逻辑
/// </summary> /// </summary>
@ -159,12 +42,14 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
// 新增存储UI元素Border到Character对象的反向关联 // 新增存储UI元素Border到Character对象的反向关联
private readonly Dictionary<Border, Character> _uiElementToCharacter = []; private readonly Dictionary<Border, Character> _uiElementToCharacter = [];
// 新增用于左侧动态队列的ObservableCollection
public ObservableCollection<CharacterQueueItem> CharacterQueueItems { get; set; }
// 新增用于目标选择UI的ObservableCollection // 新增用于目标选择UI的ObservableCollection
public ObservableCollection<Character> SelectedTargets { get; set; } = []; public ObservableCollection<Character> SelectedTargets { get; set; } = [];
// 新增:倒计时相关的字段
private readonly DispatcherTimer _countdownTimer;
private int _remainingCountdownSeconds;
private Action<bool>? _currentContinueCallback;
// 回调Action用于将UI选择结果传递给Controller // 回调Action用于将UI选择结果传递给Controller
private Action<long>? _resolveCharacterSelection; private Action<long>? _resolveCharacterSelection;
private Action<CharacterActionType>? _resolveActionType; private Action<CharacterActionType>? _resolveActionType;
@ -179,13 +64,21 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
private long _maxTargetsForSelection; private long _maxTargetsForSelection;
private bool _canSelectSelf, _canSelectEnemy, _canSelectTeammate; private bool _canSelectSelf, _canSelectEnemy, _canSelectTeammate;
private bool _isSelectingTargets = false; // 标记当前是否处于目标选择模式 private bool _isSelectingTargets = false; // 标记当前是否处于目标选择模式
private readonly CharacterQueueItem _selectionPredictCharacter = new(Factory.GetCharacter(), 0); // 选择时进行下轮预测(用于行动顺序表显示)
public GameMapViewer() public GameMapViewer()
{ {
InitializeComponent(); InitializeComponent();
CharacterQueueItems = []; CharacterQueueItems = [];
_selectionPredictCharacter.Character.Promotion = 1800;
this.DataContext = this; // 将UserControl自身设置为DataContext以便ItemsControl可以绑定到CharacterQueueItems和SelectedTargets属性 this.DataContext = this; // 将UserControl自身设置为DataContext以便ItemsControl可以绑定到CharacterQueueItems和SelectedTargets属性
_countdownTimer = new()
{
Interval = TimeSpan.FromSeconds(1) // 每秒触发一次
};
_countdownTimer.Tick += CountdownTimer_Tick; // 绑定事件处理器
// 初始化 SelectedTargetsItemsControl 的 ItemsSource // 初始化 SelectedTargetsItemsControl 的 ItemsSource
SelectedTargetsItemsControl.ItemsSource = SelectedTargets; SelectedTargetsItemsControl.ItemsSource = SelectedTargets;
@ -208,11 +101,47 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
set { SetValue(CurrentGameMapProperty, value); } set { SetValue(CurrentGameMapProperty, value); }
} }
// 当前回合
public static readonly DependencyProperty CurrentRoundProperty =
DependencyProperty.Register("CurrentRound", typeof(int), typeof(GameMapViewer),
new PropertyMetadata(0, OnCurrentRoundChanged));
public int CurrentRound
{
get { return (int)GetValue(CurrentRoundProperty); }
set { SetValue(CurrentRoundProperty, value); }
}
// 回合奖励
public static readonly DependencyProperty TurnRewardsProperty =
DependencyProperty.Register("TurnRewards", typeof(Dictionary<int, List<Skill>>), typeof(GameMapViewer),
new PropertyMetadata(new Dictionary<int, List<Skill>>(), OnTurnRewardsChanged));
public Dictionary<int, List<Skill>> TurnRewards
{
get { return (Dictionary<int, List<Skill>>)GetValue(TurnRewardsProperty); }
set { SetValue(TurnRewardsProperty, value); }
}
// 新增 CurrentCharacter 依赖属性:用于显示当前玩家角色的信息 // 新增 CurrentCharacter 依赖属性:用于显示当前玩家角色的信息
public static readonly DependencyProperty CurrentCharacterProperty = public static readonly DependencyProperty CurrentCharacterProperty =
DependencyProperty.Register("CurrentCharacter", typeof(Character), typeof(GameMapViewer), DependencyProperty.Register("CurrentCharacter", typeof(Character), typeof(GameMapViewer),
new PropertyMetadata(null, OnCurrentCharacterChanged)); new PropertyMetadata(null, OnCurrentCharacterChanged));
public static readonly DependencyProperty CharacterQueueItemsProperty =
DependencyProperty.Register("CharacterQueueItems", typeof(ObservableCollection<CharacterQueueItem>), typeof(GameMapViewer),
new PropertyMetadata(null, OnCharacterQueueItemsChanged));
// 用于左侧动态队列的ObservableCollection
public ObservableCollection<CharacterQueueItem> CharacterQueueItems
{
get { return (ObservableCollection<CharacterQueueItem>)GetValue(CharacterQueueItemsProperty); }
set { SetValue(CharacterQueueItemsProperty, value); }
}
// 用于 UI 绑定的 ViewModel 集合
public ObservableCollection<CharacterQueueItemViewModel> CharacterQueueDisplayItems { get; } = [];
public Character? CurrentCharacter public Character? CurrentCharacter
{ {
get { return (Character)GetValue(CurrentCharacterProperty); } get { return (Character)GetValue(CurrentCharacterProperty); }
@ -307,6 +236,51 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
viewer.UpdateCharacterPositionsOnMap(); viewer.UpdateCharacterPositionsOnMap();
} }
// CurrentRound
private static void OnCurrentRoundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
GameMapViewer viewer = (GameMapViewer)d;
viewer.CurrentRoundChanged();
viewer.UpdateCharacterQueueDisplayItems();
}
private static void OnCharacterQueueItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is GameMapViewer viewer)
{
// 解除旧集合的事件订阅
if (e.OldValue is ObservableCollection<CharacterQueueItem> oldCollection)
{
oldCollection.CollectionChanged -= viewer.CharacterQueueItems_CollectionChanged;
}
// 订阅新集合的事件
if (e.NewValue is ObservableCollection<CharacterQueueItem> newCollection)
{
newCollection.CollectionChanged += viewer.CharacterQueueItems_CollectionChanged;
}
// 立即更新显示队列
viewer.UpdateCharacterQueueDisplayItems();
}
}
// 当原始队列 CharacterQueueItems 内部发生变化时 (添加、删除、移动、替换)
private void CharacterQueueItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// 任何集合内容的变动或排序变动,都需要重新计算 PredictedTurnNumber 和奖励
// 因为索引变了PredictedTurnNumber 就会变,进而可能影响奖励
UpdateCharacterQueueDisplayItems();
}
// TurnRewards
private static void OnTurnRewardsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
GameMapViewer viewer = (GameMapViewer)d;
foreach (CharacterQueueItemViewModel vm in viewer.CharacterQueueDisplayItems)
{
vm.UpdateRewardProperties();
}
}
// 当CurrentCharacter属性改变时更新底部信息面板 // 当CurrentCharacter属性改变时更新底部信息面板
private static void OnCurrentCharacterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void OnCurrentCharacterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
@ -344,16 +318,16 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
/// 向调试日志文本框添加一条消息。 /// 向调试日志文本框添加一条消息。
/// </summary> /// </summary>
/// <param name="message">要添加的日志消息。</param> /// <param name="message">要添加的日志消息。</param>
public void AppendDebugLog(string message) public async Task AppendDebugLog(string message)
{ {
// 检查当前线程是否是UI线程 // 检查当前线程是否是UI线程
if (!this.Dispatcher.CheckAccess()) if (!this.Dispatcher.CheckAccess())
{ {
this.Dispatcher.BeginInvoke(new Action(() => AppendDebugLog(message))); await this.Dispatcher.BeginInvoke(new Action(async () => await AppendDebugLog(message)));
return; return;
} }
int maxLines = 1000; int maxLines = 300;
// 获取 FlowDocument // 获取 FlowDocument
FlowDocument doc = DebugLogRichTextBox.Document; FlowDocument doc = DebugLogRichTextBox.Document;
@ -387,6 +361,11 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
DebugLogScrollViewer.ScrollToEnd(); DebugLogScrollViewer.ScrollToEnd();
} }
private void CurrentRoundChanged()
{
QueueTitle.Text = $"行动顺序表{(CurrentRound > 0 ? $" - {CurrentRound} " : "")}";
}
/// <summary> /// <summary>
/// 渲染地图根据CurrentGameMap对象在Canvas上绘制所有格子 /// 渲染地图根据CurrentGameMap对象在Canvas上绘制所有格子
/// </summary> /// </summary>
@ -401,7 +380,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (CurrentGameMap == null) if (CurrentGameMap == null)
{ {
AppendDebugLog("地图未加载。"); _ = AppendDebugLog("地图未加载。");
return; return;
} }
@ -480,7 +459,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
Border characterBorder = new() Border characterBorder = new()
{ {
Style = (Style)this.FindResource("CharacterIconStyle"), Style = (Style)this.FindResource("CharacterIconStyle"),
ToolTip = character.ToStringWithLevel(), ToolTip = character.GetInfo(),
IsHitTestVisible = true // 确保角色图标可以被点击 IsHitTestVisible = true // 确保角色图标可以被点击
}; };
@ -614,6 +593,8 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
EpProgressBar.Value = character.EP; EpProgressBar.Value = character.EP;
EpProgressBar.Maximum = GameplayEquilibriumConstant.MaxEP; EpProgressBar.Maximum = GameplayEquilibriumConstant.MaxEP;
EpValueTextBlock.Text = $"{character.EP:0.##}/{GameplayEquilibriumConstant.MaxEP}"; EpValueTextBlock.Text = $"{character.EP:0.##}/{GameplayEquilibriumConstant.MaxEP}";
PreCastButton.IsEnabled = character.CharacterState == CharacterState.Actionable || character.CharacterState == CharacterState.AttackRestricted ||
character.CharacterState == CharacterState.Casting || character.HP > 0 || character.EP >= 100;
// --- 更新装备槽位 --- // --- 更新装备槽位 ---
EquipSlot equipSlot = character.EquipSlot; EquipSlot equipSlot = character.EquipSlot;
@ -888,7 +869,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
this.CurrentCharacter = this.PlayerCharacter; this.CurrentCharacter = this.PlayerCharacter;
} }
AppendDebugLog($"选中格子: ID={grid.Id}, 坐标=({grid.X},{grid.Y},{grid.Z})"); _ = AppendDebugLog($"选中格子: ID={grid.Id}, 坐标=({grid.X},{grid.Y},{grid.Z})");
e.Handled = true; // 标记事件已处理防止冒泡到Canvas e.Handled = true; // 标记事件已处理防止冒泡到Canvas
} }
} }
@ -922,7 +903,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
UpdateGridInfoPanel(grid); UpdateGridInfoPanel(grid);
} }
AppendDebugLog($"选中角色: {character.ToStringWithLevel()} (通过点击图标)"); _ = AppendDebugLog($"选中角色: {character.ToStringWithLevel()} (通过点击图标)");
} }
e.Handled = true; // 阻止事件冒泡到下方的Grid_MouseLeftButtonDown 或 GameMapCanvas_MouseLeftButtonDown e.Handled = true; // 阻止事件冒泡到下方的Grid_MouseLeftButtonDown 或 GameMapCanvas_MouseLeftButtonDown
} }
@ -939,7 +920,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
// 只有当点击事件的原始源是Canvas本身时才处理这意味着没有点击到任何子元素如格子或角色图标 // 只有当点击事件的原始源是Canvas本身时才处理这意味着没有点击到任何子元素如格子或角色图标
if (e.OriginalSource == GameMapCanvas) if (e.OriginalSource == GameMapCanvas)
{ {
AppendDebugLog("点击了地图空白区域。"); _ = AppendDebugLog("点击了地图空白区域。");
// 调用关闭格子信息面板的逻辑,它现在也会重置描述和高亮 // 调用关闭格子信息面板的逻辑,它现在也会重置描述和高亮
CloseGridInfoButton_Click(new(), new()); CloseGridInfoButton_Click(new(), new());
// 将当前角色设置回玩家角色 // 将当前角色设置回玩家角色
@ -999,12 +980,12 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (item != null) if (item != null)
{ {
SetRichTextBoxText(DescriptionRichTextBox, item.ToString()); SetRichTextBoxText(DescriptionRichTextBox, item.ToString());
AppendDebugLog($"查看装备: {item.Name}"); _ = AppendDebugLog($"查看装备: {item.Name}");
} }
else else
{ {
SetRichTextBoxText(DescriptionRichTextBox, "此槽位未装备物品。"); SetRichTextBoxText(DescriptionRichTextBox, "此槽位未装备物品。");
AppendDebugLog("查看空装备槽位。"); _ = AppendDebugLog("查看空装备槽位。");
} }
} }
} }
@ -1039,7 +1020,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
clickedBorder.BorderThickness = new Thickness(1.5); clickedBorder.BorderThickness = new Thickness(1.5);
SetRichTextBoxText(DescriptionRichTextBox, effect.ToString()); SetRichTextBoxText(DescriptionRichTextBox, effect.ToString());
AppendDebugLog($"查看状态: {effect.GetType().Name}"); _ = AppendDebugLog($"查看状态: {effect.GetType().Name}");
} }
} }
@ -1081,6 +1062,29 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
} }
/// <summary>
/// 预释放爆发技的特殊处理。
/// </summary>
private async void PreCastSkillButton_Click(object sender, RoutedEventArgs e)
{
if (PlayerCharacter != null)
{
Skill? skill = PlayerCharacter.Skills.FirstOrDefault(s => s.IsSuperSkill && s.Enable && s.CurrentCD == 0 && s.RealEPCost <= PlayerCharacter.EP);
if (skill != null)
{
await _controller.SetPreCastSuperSkill(PlayerCharacter, skill);
}
else
{
await AppendDebugLog("当前无法预释放爆发技,因为找不到可用的爆发技。");
}
}
else
{
await AppendDebugLog("找不到角色。");
}
}
// --- UI 提示方法 (由 GameMapController 调用) --- // --- UI 提示方法 (由 GameMapController 调用) ---
/// <summary> /// <summary>
@ -1220,15 +1224,30 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (sender is Border border) if (sender is Border border)
{ {
string details = ""; string details = "";
double hardnessTime = 0;
string name = "";
if (border.Tag is Skill hoveredSkill) if (border.Tag is Skill hoveredSkill)
{ {
details = hoveredSkill.ToString(); details = hoveredSkill.ToString();
if (!hoveredSkill.IsMagic) hardnessTime = hoveredSkill.RealHardnessTime;
else hardnessTime = hoveredSkill.RealCastTime;
name = hoveredSkill.Character?.NickName ?? "未知角色";
} }
else if (border.Tag is Item hoveredItem) else if (border.Tag is Item hoveredItem)
{ {
details = hoveredItem.ToString(); details = hoveredItem.ToString();
if (hoveredItem.Skills.Active != null)
{
hardnessTime = hoveredItem.Skills.Active.RealHardnessTime;
}
if (hardnessTime == 0)
{
hardnessTime = 5;
}
name = hoveredItem.Character?.NickName ?? "未知角色";
} }
SetRichTextBoxText(SkillItemDetailsRichTextBox, details); SetRichTextBoxText(SkillItemDetailsRichTextBox, details);
SetPredictCharacter(name, hardnessTime);
} }
} }
@ -1247,6 +1266,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
SkillItemDescription.Text = ""; SkillItemDescription.Text = "";
_resolveItemSelection?.Invoke(-1); _resolveItemSelection?.Invoke(-1);
} }
CharacterQueueItems.Remove(_selectionPredictCharacter);
} }
/// <summary> /// <summary>
@ -1274,12 +1294,13 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
/// </summary> /// </summary>
/// <param name="actor">发起行动的角色。</param> /// <param name="actor">发起行动的角色。</param>
/// <param name="potentialTargets">所有潜在的可选目标列表。</param> /// <param name="potentialTargets">所有潜在的可选目标列表。</param>
/// <param name="skill">请求选择目标的技能。</param>
/// <param name="maxTargets">最大可选目标数量。</param> /// <param name="maxTargets">最大可选目标数量。</param>
/// <param name="canSelectSelf">是否可选择自身。</param> /// <param name="canSelectSelf">是否可选择自身。</param>
/// <param name="canSelectEnemy">是否可选择敌方。</param> /// <param name="canSelectEnemy">是否可选择敌方。</param>
/// <param name="canSelectTeammate">是否可选择友方。</param> /// <param name="canSelectTeammate">是否可选择友方。</param>
/// <param name="callback">选择完成后调用的回调函数。</param> /// <param name="callback">选择完成后调用的回调函数。</param>
public void ShowTargetSelectionUI(Character actor, List<Character> potentialTargets, long maxTargets, bool canSelectSelf, bool canSelectEnemy, bool canSelectTeammate, Action<List<Character>> callback) public void ShowTargetSelectionUI(Character actor, List<Character> potentialTargets, ISkill skill, long maxTargets, bool canSelectSelf, bool canSelectEnemy, bool canSelectTeammate, Action<List<Character>> callback)
{ {
_resolveTargetSelection = callback; _resolveTargetSelection = callback;
_actingCharacterForTargetSelection = actor; _actingCharacterForTargetSelection = actor;
@ -1294,6 +1315,11 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
TargetSelectionTitle.Text = $"选择 {actor.NickName} 的目标 (最多 {maxTargets} 个)"; TargetSelectionTitle.Text = $"选择 {actor.NickName} 的目标 (最多 {maxTargets} 个)";
TargetSelectionOverlay.Visibility = Visibility.Visible; TargetSelectionOverlay.Visibility = Visibility.Visible;
if (!CharacterQueueItems.Contains(_selectionPredictCharacter))
{
SetPredictCharacter(actor.NickName, skill.RealHardnessTime);
}
// 更新地图上角色的高亮,以显示潜在目标和已选目标 // 更新地图上角色的高亮,以显示潜在目标和已选目标
UpdateCharacterHighlights(); UpdateCharacterHighlights();
} }
@ -1307,7 +1333,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
// 检查是否是潜在目标 // 检查是否是潜在目标
if (_potentialTargetsForSelection == null || !_potentialTargetsForSelection.Contains(clickedCharacter)) if (_potentialTargetsForSelection == null || !_potentialTargetsForSelection.Contains(clickedCharacter))
{ {
AppendDebugLog($"无法选择 {clickedCharacter.NickName}:不是潜在目标。"); _ = AppendDebugLog($"无法选择 {clickedCharacter.NickName}:不是潜在目标。");
return; return;
} }
@ -1332,7 +1358,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (!isValidTarget) if (!isValidTarget)
{ {
AppendDebugLog($"无法选择 {clickedCharacter.NickName}:不符合目标选择规则。"); _ = AppendDebugLog($"无法选择 {clickedCharacter.NickName}:不符合目标选择规则。");
return; return;
} }
@ -1344,7 +1370,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
} }
else else
{ {
AppendDebugLog($"已达到最大目标数量 ({_maxTargetsForSelection})。"); _ = AppendDebugLog($"已达到最大目标数量 ({_maxTargetsForSelection})。");
} }
} }
UpdateCharacterHighlights(); // 更新地图上的高亮显示 UpdateCharacterHighlights(); // 更新地图上的高亮显示
@ -1416,6 +1442,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
private void CancelTargetSelection_Click(object sender, RoutedEventArgs e) private void CancelTargetSelection_Click(object sender, RoutedEventArgs e)
{ {
_resolveTargetSelection?.Invoke([]); // 返回空表示取消 _resolveTargetSelection?.Invoke([]); // 返回空表示取消
CharacterQueueItems.Remove(_selectionPredictCharacter);
} }
/// <summary> /// <summary>
@ -1461,6 +1488,45 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
_resolveContinuePrompt = null; _resolveContinuePrompt = null;
} }
private void CountdownTimer_Tick(object? sender, EventArgs e)
{
_remainingCountdownSeconds--;
if (_remainingCountdownSeconds > 0)
{
// 更新倒计时文本
CountdownTextBlock.Text = $"{_remainingCountdownSeconds} 秒后继续...";
}
else
{
// 倒计时结束
_countdownTimer.Stop(); // 停止计时器
CountdownTextBlock.Visibility = Visibility.Collapsed; // 隐藏倒计时文本
// 触发继续回调
_currentContinueCallback?.Invoke(true);
_currentContinueCallback = null; // 清除回调,防止重复触发
}
}
/// <summary>
/// 启动倒计时,并在倒计时结束后自动触发继续。
/// </summary>
/// <param name="seconds">倒计时秒数。</param>
/// <param name="callback">倒计时结束后调用的回调函数。</param>
public void StartCountdownForContinue(int seconds, Action<bool> callback)
{
_remainingCountdownSeconds = seconds;
_currentContinueCallback = callback;
// 显示倒计时文本并设置初始值
CountdownTextBlock.Text = $"{_remainingCountdownSeconds} 秒后继续...";
CountdownTextBlock.Visibility = Visibility.Visible;
// 启动计时器
_countdownTimer.Start();
}
/// <summary> /// <summary>
/// 辅助方法:将 System.Drawing.Color 转换为 System.Windows.Media.SolidColorBrush /// 辅助方法:将 System.Drawing.Color 转换为 System.Windows.Media.SolidColorBrush
/// WPF UI元素使用System.Windows.Media.Brush而Grid类使用System.Drawing.Color /// WPF UI元素使用System.Windows.Media.Brush而Grid类使用System.Drawing.Color
@ -1478,7 +1544,7 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
if (sender is Border clickedBorder && e.OriginalSource == clickedBorder) if (sender is Border clickedBorder && e.OriginalSource == clickedBorder)
{ {
ResetDescriptionAndHighlights(); ResetDescriptionAndHighlights();
AppendDebugLog("点击了装备/状态区域空白处。"); _ = AppendDebugLog("点击了装备/状态区域空白处。");
e.Handled = true; // 标记事件已处理 e.Handled = true; // 标记事件已处理
} }
} }
@ -1500,5 +1566,104 @@ namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
richTextBox.Document.Blocks.Clear(); richTextBox.Document.Blocks.Clear();
richTextBox.Document.Blocks.Add(new Paragraph(new Run(text)) { Margin = new Thickness(0) }); richTextBox.Document.Blocks.Add(new Paragraph(new Run(text)) { Margin = new Thickness(0) });
} }
public static void InsertSorted<T>(ObservableCollection<T> collection, T item, Func<T, double> keySelector)
{
// 处理空集合情况
if (collection.Count == 0)
{
collection.Add(item);
return;
}
// 二分查找插入位置
int low = 0;
int high = collection.Count - 1;
int index = 0;
while (low <= high)
{
int mid = (low + high) / 2;
double midValue = keySelector(collection[mid]);
double newValue = keySelector(item);
if (Math.Abs(midValue - newValue) < double.Epsilon) // 处理浮点精度
{
index = mid + 1; // 相同值插入后面
break;
}
else if (midValue < newValue)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
if (low > high) index = low;
}
collection.Insert(index, item);
}
private void UpdateCharacterQueueDisplayItems()
{
if (CharacterQueueItems == null)
{
CharacterQueueDisplayItems.Clear();
return;
}
// 1. 创建一个新的 ViewModel 列表,同时尝试重用现有实例
List<CharacterQueueItemViewModel> newDisplayItems = [];
// 使用字典快速查找现有 ViewModel
Dictionary<CharacterQueueItem, CharacterQueueItemViewModel> existingVmMap = CharacterQueueDisplayItems.ToDictionary(vm => vm.Model);
for (int i = 0; i < CharacterQueueItems.Count; i++)
{
CharacterQueueItem rawItem = CharacterQueueItems[i];
CharacterQueueItemViewModel vm;
// 尝试从现有 ViewModel 映射中获取
if (existingVmMap.TryGetValue(rawItem, out CharacterQueueItemViewModel? existingVm))
{
vm = existingVm;
}
else
{
// 如果没有,则创建新的 ViewModel
vm = new CharacterQueueItemViewModel(rawItem, () => TurnRewards);
}
// 2. 更新 ViewModel 的派生属性
// 预测回合数会因为队列顺序或 CurrentRound 变化而变化
int predictedTurn = CurrentRound + i;
if (vm.PredictedTurnNumber != predictedTurn) // 只有当实际变化时才设置,触发 INPC
{
vm.PredictedTurnNumber = predictedTurn;
}
// 奖励信息可能因为 PredictedTurnNumber 变化而变化 (即使 TurnRewards 字典本身不变)
vm.UpdateRewardProperties(); // 会在内部检查 TurnRewardSkillName 是否变化并触发 INPC
newDisplayItems.Add(vm);
}
// 3. 高效同步 CharacterQueueDisplayItems
// 这是一个简单的同步策略:清空并重新添加。
CharacterQueueDisplayItems.Clear();
foreach (CharacterQueueItemViewModel vm in newDisplayItems)
{
CharacterQueueDisplayItems.Add(vm);
}
}
public void SetPredictCharacter(string name, double ht)
{
CharacterQueueItems.Remove(_selectionPredictCharacter);
_selectionPredictCharacter.Character.NickName = $"{name} [ 下轮预测 ]";
_selectionPredictCharacter.ATDelay = ht;
InsertSorted(CharacterQueueItems, _selectionPredictCharacter, cq => cq.ATDelay);
}
} }
} }