This commit is contained in:
milimoe 2025-09-01 20:59:45 +08:00
parent 6299259b96
commit e119aa3b69
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
10 changed files with 2965 additions and 3 deletions

View File

@ -1,7 +1,7 @@
<Application x:Class="Milimoe.FunGame.Testing.Desktop.App" <Application x:Class="Milimoe.FunGame.Testing.Desktop.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Solutions/NovelEditor/NovelEditor.xaml"> StartupUri="GameMapTesting/GameMapViewer.xaml">
<Application.Resources> <Application.Resources>
</Application.Resources> </Application.Resources>

View File

@ -1,4 +1,6 @@
using Application = System.Windows.Application; using Oshima.FunGame.OshimaModules;
using Oshima.FunGame.OshimaServers.Service;
using Application = System.Windows.Application;
namespace Milimoe.FunGame.Testing.Desktop namespace Milimoe.FunGame.Testing.Desktop
{ {
@ -6,7 +8,14 @@ namespace Milimoe.FunGame.Testing.Desktop
{ {
public App() public App()
{ {
CharacterModule cm = new();
cm.Load();
SkillModule sm = new();
sm.Load();
ItemModule im = new();
im.Load();
FunGameService.InitFunGame();
FunGameSimulation.InitFunGameSimulation();
} }
} }
} }

View File

@ -27,6 +27,11 @@
<Page Include="App.xaml" /> <Page Include="App.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\OshimaGameModule\OshimaModules\OshimaModules.csproj" />
<ProjectReference Include="..\..\OshimaGameModule\OshimaServers\OshimaServers.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="FunGame.Core"> <Reference Include="FunGame.Core">
<HintPath>..\..\FunGame.Core\bin\Debug\net9.0\FunGame.Core.dll</HintPath> <HintPath>..\..\FunGame.Core\bin\Debug\net9.0\FunGame.Core.dll</HintPath>

View File

@ -0,0 +1,216 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{
public class GameMapController(GameMapViewer ui)
{
public GameMapViewer UI => ui;
private GameMapTesting? _game;
// 输入请求器实例
private readonly UserInputRequester<long> _characterSelectionRequester = new();
private readonly UserInputRequester<CharacterActionType> _actionTypeRequester = new();
private readonly UserInputRequester<List<Character>> _targetSelectionRequester = new();
private readonly UserInputRequester<long> _skillSelectionRequester = new();
private readonly UserInputRequester<long> _itemSelectionRequester = new();
private readonly UserInputRequester<bool> _continuePromptRequester = new(); // 用于“按任意键继续”提示
public void WriteLine(string str = "") => UI.AppendDebugLog(str);
public async Task Start()
{
_game = new(this);
await _game.StartGame(false);
}
public async Task<long> RequestCharacterSelection(List<Character> availableCharacters)
{
WriteLine("请选择你想玩的角色。");
return await _characterSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowCharacterSelectionPrompt(availableCharacters, callback))
);
}
public async Task<CharacterActionType> RequestActionType(Character character, List<Skill> availableSkills, List<Item> availableItems)
{
WriteLine($"现在是 {character.NickName} 的回合,请选择行动。");
return await _actionTypeRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowActionButtons(character, availableSkills, availableItems, callback))
);
}
public async Task<List<Character>> RequestTargetSelection(Character actor, List<Character> potentialTargets, long maxTargets, bool canSelectSelf, bool canSelectEnemy, bool canSelectTeammate)
{
WriteLine($"请为 {actor.NickName} 选择目标 (最多 {maxTargets} 个)。");
List<Character> targetIds = await _targetSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowTargetSelectionUI(actor, potentialTargets, maxTargets, canSelectSelf, canSelectEnemy, canSelectTeammate, callback))
) ?? [];
if (targetIds == null) return [];
return [.. potentialTargets.Where(targetIds.Contains)];
}
public async Task<Skill?> RequestSkillSelection(Character character, List<Skill> availableSkills)
{
WriteLine($"请为 {character.NickName} 选择一个技能。");
long? skillId = await _skillSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowSkillSelectionUI(character, availableSkills, callback))
);
return skillId.HasValue ? availableSkills.FirstOrDefault(s => s.Id == skillId.Value) : null;
}
public async Task<Item?> RequestItemSelection(Character character, List<Item> availableItems)
{
WriteLine($"请为 {character.NickName} 选择一个物品。");
long? itemId = await _itemSelectionRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowItemSelectionUI(availableItems, callback))
);
return itemId.HasValue ? availableItems.FirstOrDefault(i => i.Id == itemId.Value) : null;
}
public async Task RequestContinuePrompt(string message)
{
WriteLine(message);
await _continuePromptRequester.RequestInput(
(callback) => UI.Invoke(() => UI.ShowContinuePrompt(message, callback))
);
}
// --- GameMapViewer 调用这些方法来解决 UI 输入 ---
public void ResolveCharacterSelection(long characterId)
{
_characterSelectionRequester.ResolveInput(characterId);
UI.Invoke(() => UI.HideCharacterSelectionPrompt());
}
public void ResolveActionType(CharacterActionType actionType)
{
_actionTypeRequester.ResolveInput(actionType);
UI.Invoke(() => UI.HideActionButtons());
}
public void ResolveTargetSelection(List<Character> targetIds)
{
_targetSelectionRequester.ResolveInput(targetIds);
UI.Invoke(() => UI.HideTargetSelectionUI());
}
public void ResolveSkillSelection(long skillId)
{
_skillSelectionRequester.ResolveInput(skillId);
UI.Invoke(() => UI.HideSkillSelectionUI());
}
public void ResolveItemSelection(long itemId)
{
_itemSelectionRequester.ResolveInput(itemId);
UI.Invoke(() => UI.HideItemSelectionUI());
}
public void ResolveContinuePrompt()
{
_continuePromptRequester.ResolveInput(true); // 任何值都可以只要完成Task
UI.Invoke(() => UI.HideContinuePrompt());
}
public bool IsTeammate(Character actor, Character target)
{
if (actor == target) return true;
if (_game != null && _game.GamingQueue != null)
{
return _game.GamingQueue.IsTeammate(actor, target);
}
return false;
}
public void UpdateBottomInfoPanel()
{
UI.Invoke(UI.UpdateBottomInfoPanel);
}
public void UpdateQueue()
{
UI.Invoke(UI.UpdateLeftQueuePanel);
}
public void UpdateCharacterPositionsOnMap()
{
UI.Invoke(UI.UpdateCharacterPositionsOnMap);
}
public void SetQueue(Dictionary<Character, double> dict)
{
UI.Invoke(() =>
{
UI.CharacterQueueData = dict;
});
}
public void SetGameMap(GameMap map)
{
UI.Invoke(() =>
{
UI.CurrentGameMap = map;
});
}
public void SetPlayerCharacter(Character character)
{
UI.Invoke(() =>
{
UI.PlayerCharacter = character;
});
}
public void SetCurrentCharacter(Character character)
{
UI.Invoke(() =>
{
UI.CurrentCharacter = character;
});
}
public void SetCharacterStatistics(Dictionary<Character, CharacterStatistics> stats)
{
UI.Invoke(() =>
{
UI.CharacterStatistics = stats;
});
}
}
/// <summary>
/// 辅助类,用于管理异步的用户输入请求。
/// </summary>
/// <typeparam name="T">期望的用户输入类型。</typeparam>
public class UserInputRequester<T>
{
private TaskCompletionSource<T?>? _tcs;
/// <summary>
/// 请求用户输入,并等待结果。
/// </summary>
/// <param name="uiPromptAction">一个Action用于通知UI显示提示并传入一个回调函数供UI完成输入后调用。</param>
/// <returns>用户输入的结果如果用户取消则为null。</returns>
public async Task<T?> RequestInput(Action<Action<T?>> uiPromptAction)
{
_tcs = new TaskCompletionSource<T?>();
// 调用UI动作并传入我们的ResolveInput方法作为回调
// UI将在获取输入后调用此回调
uiPromptAction(ResolveInput);
return await _tcs.Task;
}
/// <summary>
/// 解决用户输入请求将结果传递给等待的Task。
/// </summary>
/// <param name="result">用户输入的结果。</param>
public void ResolveInput(T? result)
{
_tcs?.TrySetResult(result);
_tcs = null; // 清除以防止意外重用
}
}
}

View File

@ -0,0 +1,648 @@
using System.Text;
// using System.Windows; // 不再需要,因为移除了 InputDialog
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
using Oshima.Core.Constant;
using Oshima.FunGame.OshimaModules.Effects.OpenEffects;
using Oshima.FunGame.OshimaModules.Models;
using Oshima.FunGame.OshimaModules.Skills;
using Oshima.FunGame.OshimaServers.Service;
namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{
public class GameMapTesting
{
public GamingQueue? GamingQueue => _gamingQueue;
public GameMapController Controller { get; }
public Dictionary<Character, CharacterStatistics> CharacterStatistics { get; } = [];
public PluginConfig StatsConfig { get; } = new(nameof(FunGameSimulation), nameof(CharacterStatistics));
public GameMap GameMap { get; } = new TestMap();
public bool IsWeb { get; set; } = false;
public string Msg { get; set; } = "";
private GamingQueue? _gamingQueue = null;
public GameMapTesting(GameMapController controller)
{
Controller = controller;
InitCharacter(); // 初始化角色统计数据
}
public async Task<List<string>> StartGame(bool isWeb = false)
{
IsWeb = isWeb;
try
{
List<string> result = [];
Msg = "";
List<Character> allCharactersInGame = [.. FunGameConstant.Characters]; // 使用不同的名称以避免与后面的 `characters` 冲突
Controller.WriteLine("--- 游戏开始 ---");
int clevel = 60;
int slevel = 6;
int mlevel = 8;
// 升级和赋能
List<Character> characters = [];
for (int index = 0; index < FunGameConstant.Characters.Count; index++)
{
Character c = FunGameConstant.Characters[index];
c.Level = clevel;
c.NormalAttack.Level = mlevel;
FunGameService.AddCharacterSkills(c, 1, slevel, slevel);
Skill = new (c)
{
Level = slevel
};
c.Skills.Add();
characters.Add(c);
}
// 显示角色信息
characters.ForEach(c => Controller.WriteLine($"角色编号:{c.Id}\r\n{c.GetInfo()}"));
// 询问玩家需要选择哪个角色 (通过UI界面选择)
Character? player = null;
long selectedPlayerId = await Controller.RequestCharacterSelection(characters); // 异步等待UI选择
Controller.ResolveCharacterSelection(selectedPlayerId);
if (selectedPlayerId != -1)
{
player = characters.FirstOrDefault(c => c.Id == selectedPlayerId);
if (player != null)
{
Controller.WriteLine($"选择了 [ {player} ]");
Controller.SetCurrentCharacter(player);
Controller.SetPlayerCharacter(player);
}
}
if (player is null)
{
throw new Exception("没有选择角色,游戏结束。");
}
// 创建顺序表并排序
_gamingQueue = new MixGamingQueue(characters, WriteLine)
{
GameplayEquilibriumConstant = OshimaGameModuleConstant.GameplayEquilibriumConstant
};
// 加载地图和绑定事件
_gamingQueue.LoadGameMap(GameMap);
if (_gamingQueue.Map != null)
{
GameMap map = _gamingQueue.Map;
// 随机放置角色
HashSet<Grid> allocated = [];
List<Grid> grids = [.. map.Grids.Values];
foreach (Character character in characters)
{
Grid grid = Grid.Empty;
do
{
grid = grids[Random.Shared.Next(grids.Count)];
}
while (allocated.Contains(grid));
allocated.Add(grid);
map.SetCharacterCurrentGrid(character, grid);
}
Controller.SetGameMap(map);
_gamingQueue.SelectTargetGrid += GamingQueue_SelectTargetGrid;
_gamingQueue.CharacterMove += GamingQueue_CharacterMove;
}
// 绑定事件
Controller.SetQueue(_gamingQueue.HardnessTime);
Controller.SetCharacterStatistics(_gamingQueue.CharacterStatistics);
_gamingQueue.TurnStart += GamingQueue_TurnStart;
_gamingQueue.DecideAction += GamingQueue_DecideAction;
_gamingQueue.SelectNormalAttackTargets += GamingQueue_SelectNormalAttackTargets;
_gamingQueue.SelectSkill += GamingQueue_SelectSkill;
_gamingQueue.SelectSkillTargets += GamingQueue_SelectSkillTargets;
_gamingQueue.SelectItem += GamingQueue_SelectItem;
_gamingQueue.QueueUpdated += GamingQueue_QueueUpdated;
_gamingQueue.TurnEnd += GamingQueue_TurnEnd;
// 总游戏时长
double totalTime = 0;
// 开始空投
Msg = "";
int qMagicCardPack = 5;
int qWeapon = 5;
int qArmor = 5;
int qShoes = 5;
int qAccessory = 4;
WriteLine($"社区送温暖了,现在随机发放空投!!");
DropItems(_gamingQueue, qMagicCardPack, qWeapon, qArmor, qShoes, qAccessory);
WriteLine("");
if (isWeb) result.Add("=== 空投 ===\r\n" + Msg);
double nextDropItemTime = 40;
if (qMagicCardPack < 5) qMagicCardPack++;
if (qWeapon < 5) qWeapon++;
if (qArmor < 5) qArmor++;
if (qShoes < 5) qShoes++;
if (qAccessory < 5) qAccessory++;
// 显示角色信息
characters.ForEach(c => Controller.WriteLine(c.GetInfo()));
// 初始化队列,准备开始游戏
_gamingQueue.InitActionQueue();
_gamingQueue.SetCharactersToAIControl(false, characters);
_gamingQueue.SetCharactersToAIControl(true, player);
_gamingQueue.CustomData.Add("player", player);
Controller.WriteLine();
// 显示初始顺序表
_gamingQueue.DisplayQueue();
Controller.WriteLine();
Controller.WriteLine($"你的角色是 [ {player} ],详细信息:{player.GetInfo()}");
// 总回合数
int maxRound = 999;
// 随机回合奖励
Dictionary<long, bool> effects = [];
foreach (EffectID id in FunGameConstant.RoundRewards.Keys)
{
long effectID = (long)id;
bool isActive = false;
if (effectID > (long)EffectID.Active_Start)
{
isActive = true;
}
effects.Add(effectID, isActive);
}
_gamingQueue.InitRoundRewards(maxRound, 1, effects, id => FunGameConstant.RoundRewards[(EffectID)id]);
int i = 1;
while (i < maxRound)
{
Msg = "";
if (i == maxRound - 1)
{
WriteLine($"=== 终局审判 ===");
Dictionary<Character, double> hpPercentage = [];
foreach (Character c in characters)
{
hpPercentage.TryAdd(c, c.HP / c.MaxHP);
}
double max = hpPercentage.Values.Max();
Character winner = hpPercentage.Keys.Where(c => hpPercentage[c] == max).First();
WriteLine("[ " + winner + " ] 成为了天选之人!!");
foreach (Character c in characters.Where(c => c != winner && c.HP > 0))
{
WriteLine("[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。");
await _gamingQueue.DeathCalculationAsync(winner, c);
}
if (_gamingQueue is MixGamingQueue mix)
{
await mix.EndGameInfo(winner);
}
result.Add(Msg);
break;
}
// 检查是否有角色可以行动
Character? characterToAct = await _gamingQueue.NextCharacterAsync();
Controller.UpdateQueue();
Controller.UpdateCharacterPositionsOnMap();
// 处理回合
if (characterToAct != null)
{
WriteLine($"=== 回合 {i++} ===");
WriteLine("现在是 [ " + characterToAct + " ] 的回合!");
bool isGameEnd = await _gamingQueue.ProcessTurnAsync(characterToAct);
if (isGameEnd)
{
result.Add(Msg);
break;
}
if (isWeb) _gamingQueue.DisplayQueue();
WriteLine("");
}
string roundMsg = "";
if (_gamingQueue.LastRound.HasKill)
{
roundMsg = Msg;
Msg = "";
}
// 模拟时间流逝
double timeLapse = await _gamingQueue.TimeLapse();
totalTime += timeLapse;
nextDropItemTime -= timeLapse;
Controller.UpdateQueue();
Controller.UpdateCharacterPositionsOnMap();
if (roundMsg != "")
{
if (isWeb)
{
roundMsg += "\r\n" + Msg;
}
result.Add(roundMsg);
}
if (nextDropItemTime <= 0)
{
// 空投
Msg = "";
WriteLine($"社区送温暖了,现在随机发放空投!!");
DropItems(_gamingQueue, qMagicCardPack, qWeapon, qArmor, qShoes, qAccessory);
WriteLine("");
if (isWeb) result.Add("=== 空投 ===\r\n" + Msg);
nextDropItemTime = 40;
if (qMagicCardPack < 5) qMagicCardPack++;
if (qWeapon < 5) qWeapon++;
if (qArmor < 5) qArmor++;
if (qShoes < 5) qShoes++;
if (qAccessory < 5) qAccessory++;
}
}
Controller.WriteLine("--- 游戏结束 ---");
Controller.WriteLine($"总游戏时长:{totalTime:0.##} {_gamingQueue.GameplayEquilibriumConstant.InGameTime}");
// 赛后统计
FunGameService.GetCharacterRating(_gamingQueue.CharacterStatistics, false, []);
// 统计技术得分,评选 MVP
Character? mvp = _gamingQueue.CharacterStatistics.OrderByDescending(d => d.Value.Rating).Select(d => d.Key).FirstOrDefault();
StringBuilder mvpBuilder = new();
if (mvp != null)
{
CharacterStatistics stats = _gamingQueue.CharacterStatistics[mvp];
stats.MVPs++;
mvpBuilder.AppendLine($"[ {mvp.ToStringWithLevel()} ]");
mvpBuilder.AppendLine($"技术得分:{stats.Rating:0.0#} / 击杀数:{stats.Kills} / 助攻数:{stats.Assists}{(_gamingQueue.MaxRespawnTimes != 0 ? " / " + stats.Deaths : "")}");
mvpBuilder.AppendLine($"存活时长:{stats.LiveTime:0.##} / 存活回合数:{stats.LiveRound} / 行动回合数:{stats.ActionTurn}");
mvpBuilder.AppendLine($"控制时长:{stats.ControlTime:0.##} / 总计治疗:{stats.TotalHeal:0.##} / 护盾抵消:{stats.TotalShield:0.##}");
mvpBuilder.AppendLine($"总计伤害:{stats.TotalDamage:0.##} / 总计物理伤害:{stats.TotalPhysicalDamage:0.##} / 总计魔法伤害:{stats.TotalMagicDamage:0.##}");
mvpBuilder.AppendLine($"总承受伤害:{stats.TotalTakenDamage:0.##} / 总承受物理伤害:{stats.TotalTakenPhysicalDamage:0.##} / 总承受魔法伤害:{stats.TotalTakenMagicDamage:0.##}");
if (stats.TotalTrueDamage > 0 || stats.TotalTakenTrueDamage > 0) mvpBuilder.AppendLine($"总计真实伤害:{stats.TotalTrueDamage:0.##} / 总承受真实伤害:{stats.TotalTakenTrueDamage:0.##}");
mvpBuilder.Append($"每秒伤害:{stats.DamagePerSecond:0.##} / 每回合伤害:{stats.DamagePerTurn:0.##}");
}
int top = isWeb ? _gamingQueue.CharacterStatistics.Count : 0;
int count = 1;
if (isWeb)
{
WriteLine("=== 技术得分排行榜 ===");
Msg = $"=== 技术得分排行榜 TOP{top} ===\r\n";
}
else
{
StringBuilder ratingBuilder = new();
WriteLine("=== 本场比赛最佳角色 ===");
Msg = $"=== 本场比赛最佳角色 ===\r\n";
WriteLine(mvpBuilder.ToString() + "\r\n\r\n" + ratingBuilder.ToString());
Controller.WriteLine();
Controller.WriteLine("=== 技术得分排行榜 ===");
}
foreach (Character character in _gamingQueue.CharacterStatistics.OrderByDescending(d => d.Value.Rating).Select(d => d.Key))
{
StringBuilder builder = new();
CharacterStatistics stats = _gamingQueue.CharacterStatistics[character];
builder.AppendLine($"{(isWeb ? count + ". " : "")}[ {character.ToStringWithLevel()} ]");
builder.AppendLine($"技术得分:{stats.Rating:0.0#} / 击杀数:{stats.Kills} / 助攻数:{stats.Assists}{(_gamingQueue.MaxRespawnTimes != 0 ? " / " + stats.Deaths : "")}");
builder.AppendLine($"存活时长:{stats.LiveTime:0.##} / 存活回合数:{stats.LiveRound} / 行动回合数:{stats.ActionTurn}");
builder.AppendLine($"控制时长:{stats.ControlTime:0.##} / 总计治疗:{stats.TotalHeal:0.##} / 护盾抵消:{stats.TotalShield:0.##}");
builder.AppendLine($"总计伤害:{stats.TotalDamage:0.##} / 总计物理伤害:{stats.TotalPhysicalDamage:0.##} / 总计魔法伤害:{stats.TotalMagicDamage:0.##}");
builder.AppendLine($"总承受伤害:{stats.TotalTakenDamage:0.##} / 总承受物理伤害:{stats.TotalTakenPhysicalDamage:0.##} / 总承受魔法伤害:{stats.TotalTakenMagicDamage:0.##}");
if (stats.TotalTrueDamage > 0 || stats.TotalTakenTrueDamage > 0) builder.AppendLine($"总计真实伤害:{stats.TotalTrueDamage:0.##} / 总承受真实伤害:{stats.TotalTakenTrueDamage:0.##}");
builder.Append($"每秒伤害:{stats.DamagePerSecond:0.##} / 每回合伤害:{stats.DamagePerTurn:0.##}");
if (count++ <= top)
{
WriteLine(builder.ToString());
}
else
{
Controller.WriteLine(builder.ToString());
}
CharacterStatistics? totalStats = CharacterStatistics.Where(kv => kv.Key.GetName() == character.GetName()).Select(kv => kv.Value).FirstOrDefault();
if (totalStats != null)
{
UpdateStatistics(totalStats, stats);
}
}
result.Add(Msg);
if (isWeb)
{
for (i = _gamingQueue.Eliminated.Count - 1; i >= 0; i--)
{
Character character = _gamingQueue.Eliminated[i];
result.Add($"=== 角色 [ {character} ] ===\r\n{character.GetInfo()}");
}
}
lock (StatsConfig)
{
foreach (Character c in CharacterStatistics.Keys)
{
StatsConfig.Add(c.ToStringWithOutUser(), CharacterStatistics[c]);
}
StatsConfig.SaveConfig();
}
return result;
}
catch (Exception ex)
{
Controller.WriteLine(ex.ToString());
return [ex.ToString()];
}
}
private async Task<Grid> GamingQueue_SelectTargetGrid(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, GameMap map)
{
// 目前格子选择未直接绑定到UI按钮。
// 如果“移动”动作被完全实现这里需要一个UI提示来选择目标格子。
// 为简化目前返回一个空Grid。
await Task.CompletedTask; // 模拟异步操作
return Grid.Empty;
}
private async Task GamingQueue_CharacterMove(GamingQueue queue, Character actor, Grid grid)
{
await Task.CompletedTask;
}
private async Task GamingQueue_QueueUpdated(GamingQueue queue, List<Character> characters, Character character, double hardnessTime, QueueUpdatedReason reason, string msg)
{
if (IsPlayer_OnlyTest(queue, character))
{
if (reason == QueueUpdatedReason.Action)
{
queue.SetCharactersToAIControl(false, character);
}
if (reason == QueueUpdatedReason.PreCastSuperSkill)
{
// 玩家释放爆发技后,需要等待玩家确认
await Controller.RequestContinuePrompt("你的下一回合需要选择爆发技目标,知晓请点击继续. . .");
Controller.ResolveContinuePrompt();
}
}
Controller.UpdateQueue();
Controller.UpdateCharacterPositionsOnMap();
await Task.CompletedTask;
}
private async Task<bool> GamingQueue_TurnStart(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items)
{
Controller.UpdateBottomInfoPanel();
if (IsPlayer_OnlyTest(queue, character))
{
// 确保玩家角色在回合开始时取消AI托管以便玩家可以控制
queue.SetCharactersToAIControl(cancel: true, character);
}
await Task.CompletedTask;
return true;
}
private async Task<List<Character>> GamingQueue_SelectNormalAttackTargets(GamingQueue queue, Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates)
{
if (!IsPlayer_OnlyTest(queue, character)) return [];
List<Character> potentialTargets = [];
if (attack.CanSelectEnemy) potentialTargets.AddRange(enemys);
if (attack.CanSelectTeammate) potentialTargets.AddRange(teammates);
if (attack.CanSelectSelf) potentialTargets.Add(character);
// 通过UI请求目标选择
List<Character> selectedTargets = await Controller.RequestTargetSelection(
character,
potentialTargets,
attack.CanSelectTargetCount,
attack.CanSelectSelf,
attack.CanSelectEnemy,
attack.CanSelectTeammate
);
Controller.ResolveTargetSelection(selectedTargets);
return selectedTargets ?? []; // 如果取消,返回空列表
}
private async Task<Item?> GamingQueue_SelectItem(GamingQueue queue, Character character, List<Item> items)
{
if (!IsPlayer_OnlyTest(queue, character)) return null;
// 通过UI请求物品选择
Item? selectedItem = await Controller.RequestItemSelection(character, items);
Controller.ResolveItemSelection(selectedItem?.Id ?? 0);
return selectedItem;
}
private async Task<List<Character>> GamingQueue_SelectSkillTargets(GamingQueue queue, Character caster, Skill skill, List<Character> enemys, List<Character> teammates)
{
if (!IsPlayer_OnlyTest(queue, caster)) return [];
List<Character> potentialTargets = [];
if (skill.CanSelectEnemy) potentialTargets.AddRange(enemys);
if (skill.CanSelectTeammate) potentialTargets.AddRange(teammates);
if (skill.CanSelectSelf) potentialTargets.Add(caster);
// 通过UI请求目标选择
List<Character>? selectedTargets = await Controller.RequestTargetSelection(
caster,
potentialTargets,
skill.CanSelectTargetCount,
skill.CanSelectSelf,
skill.CanSelectEnemy,
skill.CanSelectTeammate
);
Controller.ResolveTargetSelection(selectedTargets);
return selectedTargets ?? []; // 如果取消,返回空列表
}
private async Task<Skill?> GamingQueue_SelectSkill(GamingQueue queue, Character character, List<Skill> skills)
{
if (!IsPlayer_OnlyTest(queue, character)) return null;
// 通过UI请求技能选择
Skill? selectedSkill = await Controller.RequestSkillSelection(character, skills);
Controller.ResolveSkillSelection(selectedSkill?.Id ?? 0);
return selectedSkill;
}
private async Task GamingQueue_TurnEnd(GamingQueue queue, Character character)
{
Controller.UpdateBottomInfoPanel();
if (IsRoundHasPlayer_OnlyTest(queue, character))
{
// 玩家回合结束,等待玩家确认
await Controller.RequestContinuePrompt("你的回合(或与你相关的回合)已结束,请查看本回合日志,然后点击继续. . .");
Controller.ResolveContinuePrompt();
}
await Task.CompletedTask;
}
private async Task<CharacterActionType> GamingQueue_DecideAction(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, List<Skill> skills, List<Item> items)
{
if (IsPlayer_OnlyTest(queue, character))
{
// 通过UI按钮请求行动类型
CharacterActionType actionType = await Controller.RequestActionType(character, skills, items);
Controller.ResolveActionType(actionType);
return actionType;
}
return CharacterActionType.None; // 非玩家角色由AI处理或默认None
}
private static bool IsPlayer_OnlyTest(GamingQueue queue, Character current)
{
return queue.CustomData.TryGetValue("player", out object? value) && value is Character player && player == current;
}
private static bool IsRoundHasPlayer_OnlyTest(GamingQueue queue, Character current)
{
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 void WriteLine(string str)
{
Msg += str + "\r\n";
Controller.WriteLine(str);
}
public static void DropItems(GamingQueue queue, int mQuality, int wQuality, int aQuality, int sQuality, int acQuality)
{
Item[] weapons = [.. FunGameConstant.Equipment.Where(i => i.Id.ToString().StartsWith("11") && (int)i.QualityType == wQuality)];
Item[] armors = [.. FunGameConstant.Equipment.Where(i => i.Id.ToString().StartsWith("12") && (int)i.QualityType == aQuality)];
Item[] shoes = [.. FunGameConstant.Equipment.Where(i => i.Id.ToString().StartsWith("13") && (int)i.QualityType == sQuality)];
Item[] accessorys = [.. FunGameConstant.Equipment.Where(i => i.Id.ToString().StartsWith("14") && (int)i.QualityType == acQuality)];
Item[] consumables = [.. FunGameConstant.AllItems.Where(i => i.ItemType == ItemType.Consumable && i.IsInGameItem)];
foreach (Character character in queue.AllCharacters)
{
Item? a = null, b = null, c = null, d = null;
if (weapons.Length > 0) a = weapons[Random.Shared.Next(weapons.Length)];
if (armors.Length > 0) b = armors[Random.Shared.Next(armors.Length)];
if (shoes.Length > 0) c = shoes[Random.Shared.Next(shoes.Length)];
if (accessorys.Length > 0) d = accessorys[Random.Shared.Next(accessorys.Length)];
List<Item> drops = [];
if (a != null) drops.Add(a);
if (b != null) drops.Add(b);
if (c != null) drops.Add(c);
if (d != null) drops.Add(d);
Item? magicCardPack = FunGameService.GenerateMagicCardPack(3, (QualityType)mQuality);
if (magicCardPack != null)
{
foreach (Skill magic in magicCardPack.Skills.Magics)
{
magic.Level = 8;
}
magicCardPack.SetGamingQueue(queue);
queue.Equip(character, magicCardPack);
}
foreach (Item item in drops)
{
Item realItem = item.Copy();
realItem.SetGamingQueue(queue);
queue.Equip(character, realItem);
}
if (consumables.Length > 0 && character.Items.Count < 5)
{
for (int i = 0; i < 2; i++)
{
Item consumable = consumables[Random.Shared.Next(consumables.Length)].Copy();
character.Items.Add(consumable);
}
}
}
}
public void InitCharacter()
{
foreach (Character c in FunGameConstant.Characters)
{
CharacterStatistics.Add(c, new());
}
StatsConfig.LoadConfig();
foreach (Character character in CharacterStatistics.Keys)
{
if (StatsConfig.ContainsKey(character.ToStringWithOutUser()))
{
CharacterStatistics[character] = StatsConfig.Get<CharacterStatistics>(character.ToStringWithOutUser()) ?? CharacterStatistics[character];
}
}
}
public static void UpdateStatistics(CharacterStatistics totalStats, CharacterStatistics stats)
{
// 统计此角色的所有数据
totalStats.TotalDamage = Calculation.Round2Digits(totalStats.TotalDamage + stats.TotalDamage);
totalStats.TotalPhysicalDamage = Calculation.Round2Digits(totalStats.TotalPhysicalDamage + stats.TotalPhysicalDamage);
totalStats.TotalMagicDamage = Calculation.Round2Digits(totalStats.TotalMagicDamage + stats.TotalMagicDamage);
totalStats.TotalTrueDamage = Calculation.Round2Digits(totalStats.TotalTrueDamage + stats.TotalTrueDamage);
totalStats.TotalTakenDamage = Calculation.Round2Digits(totalStats.TotalTakenDamage + stats.TotalTakenDamage);
totalStats.TotalTakenPhysicalDamage = Calculation.Round2Digits(totalStats.TotalTakenPhysicalDamage + stats.TotalTakenPhysicalDamage);
totalStats.TotalTakenMagicDamage = Calculation.Round2Digits(totalStats.TotalTakenMagicDamage + stats.TotalTakenMagicDamage);
totalStats.TotalTakenTrueDamage = Calculation.Round2Digits(totalStats.TotalTakenTrueDamage + stats.TotalTakenTrueDamage);
totalStats.TotalHeal = Calculation.Round2Digits(totalStats.TotalHeal + stats.TotalHeal);
totalStats.LiveRound += stats.LiveRound;
totalStats.ActionTurn += stats.ActionTurn;
totalStats.LiveTime = Calculation.Round2Digits(totalStats.LiveTime + stats.LiveTime);
totalStats.ControlTime = Calculation.Round2Digits(totalStats.ControlTime + stats.ControlTime);
totalStats.TotalShield = Calculation.Round2Digits(totalStats.TotalShield + stats.TotalShield);
totalStats.TotalEarnedMoney += stats.TotalEarnedMoney;
totalStats.Kills += stats.Kills;
totalStats.Deaths += stats.Deaths;
totalStats.Assists += stats.Assists;
totalStats.FirstKills += stats.FirstKills;
totalStats.FirstDeaths += stats.FirstDeaths;
totalStats.LastRank = stats.LastRank;
double totalRank = totalStats.AvgRank * totalStats.Plays + totalStats.LastRank;
double totalRating = totalStats.Rating * totalStats.Plays + stats.Rating;
totalStats.Plays += stats.Plays;
if (totalStats.Plays != 0) totalStats.AvgRank = Calculation.Round2Digits(totalRank / totalStats.Plays);
else totalStats.AvgRank = stats.LastRank;
if (totalStats.Plays != 0) totalStats.Rating = Calculation.Round4Digits(totalRating / totalStats.Plays);
else totalStats.Rating = stats.Rating;
totalStats.Wins += stats.Wins;
totalStats.Top3s += stats.Top3s;
totalStats.Loses += stats.Loses;
totalStats.MVPs += stats.MVPs;
if (totalStats.Plays != 0)
{
totalStats.AvgDamage = Calculation.Round2Digits(totalStats.TotalDamage / totalStats.Plays);
totalStats.AvgPhysicalDamage = Calculation.Round2Digits(totalStats.TotalPhysicalDamage / totalStats.Plays);
totalStats.AvgMagicDamage = Calculation.Round2Digits(totalStats.TotalMagicDamage / totalStats.Plays);
totalStats.AvgTrueDamage = Calculation.Round2Digits(totalStats.TotalTrueDamage / totalStats.Plays);
totalStats.AvgTakenDamage = Calculation.Round2Digits(totalStats.TotalTakenDamage / totalStats.Plays);
totalStats.AvgTakenPhysicalDamage = Calculation.Round2Digits(totalStats.TotalTakenPhysicalDamage / totalStats.Plays);
totalStats.AvgTakenMagicDamage = Calculation.Round2Digits(totalStats.TotalTakenMagicDamage / totalStats.Plays);
totalStats.AvgTakenTrueDamage = Calculation.Round2Digits(totalStats.TotalTakenTrueDamage / totalStats.Plays);
totalStats.AvgHeal = Calculation.Round2Digits(totalStats.TotalHeal / totalStats.Plays);
totalStats.AvgLiveRound = totalStats.LiveRound / totalStats.Plays;
totalStats.AvgActionTurn = totalStats.ActionTurn / totalStats.Plays;
totalStats.AvgLiveTime = Calculation.Round2Digits(totalStats.LiveTime / totalStats.Plays);
totalStats.AvgControlTime = Calculation.Round2Digits(totalStats.ControlTime / totalStats.Plays);
totalStats.AvgShield = Calculation.Round2Digits(totalStats.TotalShield / totalStats.Plays);
totalStats.AvgEarnedMoney = totalStats.TotalEarnedMoney / totalStats.Plays;
totalStats.Winrates = Calculation.Round4Digits(Convert.ToDouble(totalStats.Wins) / Convert.ToDouble(totalStats.Plays));
totalStats.Top3rates = Calculation.Round4Digits(Convert.ToDouble(totalStats.Top3s) / Convert.ToDouble(totalStats.Plays));
}
if (totalStats.LiveRound != 0) totalStats.DamagePerRound = Calculation.Round2Digits(totalStats.TotalDamage / totalStats.LiveRound);
if (totalStats.ActionTurn != 0) totalStats.DamagePerTurn = Calculation.Round2Digits(totalStats.TotalDamage / totalStats.ActionTurn);
if (totalStats.LiveTime != 0) totalStats.DamagePerSecond = Calculation.Round2Digits(totalStats.TotalDamage / totalStats.LiveTime);
}
}
}

View File

@ -0,0 +1,588 @@
<UserControl x:Class="Milimoe.FunGame.Testing.Desktop.GameMapTesting.GameMapViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:model="clr-namespace:Milimoe.FunGame.Core.Model;assembly=FunGame.Core"
xmlns:local="clr-namespace:Milimoe.FunGame.Testing.Desktop.GameMapTesting"
xmlns:constant="clr-namespace:Milimoe.FunGame.Core.Library.Constant;assembly=FunGame.Core"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">
<UserControl.Resources>
<!-- ... 现有资源样式 ... -->
<!-- 装备槽位的样式 -->
<Style x:Key="EquipSlotStyle" TargetType="Border">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/>
<Setter Property="BorderBrush" Value="DarkGray"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="3"/>
<Setter Property="Margin" Value="3"/>
<Setter Property="Background" Value="LightGray"/>
<!-- 默认背景色 -->
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
</Style>
<!-- 装备槽位中文本的样式 -->
<Style x:Key="EquipSlotTextStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="DimGray"/>
<!-- 默认文本颜色 -->
</Style>
<!-- 状态图标的样式 -->
<Style x:Key="StatusIconStyle" TargetType="Border">
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="30"/>
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="BorderThickness" Value="0.5"/>
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Background" Value="LightBlue"/>
<!-- 默认背景色 -->
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
</Style>
<!-- 状态图标中文本的样式 -->
<Style x:Key="StatusIconTextStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="Navy"/>
<!-- 默认文本颜色 -->
</Style>
<!-- 角色属性文本的样式 -->
<Style x:Key="CharacterAttributeTextStyle" TargetType="TextBlock">
<Setter Property="Margin" Value="0,1"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Foreground" Value="#FF333333"/>
</Style>
<!-- 新增:队列项中角色头像文本的转换器 -->
<local:FirstCharConverter x:Key="FirstCharConverter"/>
<!-- 新增:队列项中角色名称和等级文本的转换器 -->
<local:CharacterToStringWithLevelConverter x:Key="CharacterToStringWithLevelConverter"/>
<!-- 新增:角色图标的样式 (圆形) -->
<Style x:Key="CharacterIconStyle" TargetType="Border">
<Setter Property="Width" Value="28"/>
<Setter Property="Height" Value="28"/>
<Setter Property="CornerRadius" Value="14"/>
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1.5"/>
<Setter Property="Background" Value="#FF6A5ACD"/>
<!-- 默认背景色,可根据角色动态改变 -->
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
</Style>
<!-- 新增:角色图标中文本的样式 -->
<Style x:Key="CharacterIconTextStyle" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<!-- 新增:选择列表项的样式 -->
<Style x:Key="SelectionItemStyle" TargetType="Border">
<Setter Property="Background" Value="LightCyan"/>
<Setter Property="BorderBrush" Value="DarkCyan"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="3"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Margin" Value="3"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Cyan"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- 左侧动态队列 -->
<ColumnDefinition Width="*"/>
<!-- 地图区域 -->
<ColumnDefinition Width="250"/>
<!-- 新增:右侧调试日志 -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<!-- 地图、左侧队列、右侧日志占据的行 -->
<RowDefinition Height="Auto"/>
<!-- 底部信息界面占据的行 -->
</Grid.RowDefinitions>
<!-- 左侧动态更新队列面板 -->
<Border Grid.Column="0" Grid.Row="0" BorderBrush="LightGray" BorderThickness="1" Margin="5" Padding="5">
<StackPanel x:Name="LeftQueuePanel" MinWidth="180" Background="#FFF0F8FF">
<!-- AliceBlue -->
<TextBlock Text="行动顺序表" Margin="0,0,0,10" FontWeight="Bold" FontSize="14"/>
<!-- 动态内容将在此处添加 -->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Height="Auto">
<ItemsControl x:Name="CharacterQueueItemsControl" ItemsSource="{Binding CharacterQueueItems, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Margin="0,2,0,2" Padding="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- 图标列 -->
<ColumnDefinition Width="*"/>
<!-- 文本信息列 -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 角色图标 (用大字代替) -->
<Border Grid.Column="0" Grid.RowSpan="2" Width="25" Height="25" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Margin="0,0,5,0"
Background="#FF6A5ACD">
<!-- 默认背景色,可根据角色动态改变 -->
<TextBlock Text="{Binding Character.NickName, Converter={StaticResource FirstCharConverter}}"
Foreground="White"
FontSize="10"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<!-- 角色名称和等级 -->
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Character, Converter={StaticResource CharacterToStringWithLevelConverter}}"
FontWeight="SemiBold" FontSize="10" Margin="0,0,0,2"/>
<!-- ATDelay -->
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ATDelay, StringFormat=AT Delay: {0:0.##}}"
FontSize="11" Foreground="DimGray"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Border>
<!-- 地图区域 (使用ScrollViewer支持大地图滚动) -->
<ScrollViewer Grid.Column="1" Grid.Row="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
Background="#FFE0FFFF">
<Border BorderBrush="DarkGray" BorderThickness="1" Margin="5">
<!-- 新增一个Grid来容纳Canvas和调试文本框实现叠加效果 -->
<Grid>
<!-- 地图Canvas占据Grid的全部空间 -->
<!-- 添加 MouseLeftButtonDown 事件处理器 -->
<Canvas x:Name="GameMapCanvas" Background="White" MouseLeftButtonDown="GameMapCanvas_MouseLeftButtonDown">
<!-- 地图格子和角色图标将在此处动态绘制 -->
</Canvas>
<!-- 格子信息面板叠加在Canvas之上位于左侧 -->
<Border x:Name="GridInfoPanel"
Panel.ZIndex="100"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10"
Width="250"
MaxHeight="300"
Background="#CCFFFFFF"
BorderBrush="DarkGray"
BorderThickness="1"
CornerRadius="5"
Padding="10"
Visibility="Collapsed">
<!-- 初始状态为隐藏 -->
<StackPanel>
<TextBlock Text="格子信息" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="250">
<StackPanel>
<TextBlock x:Name="GridIdTextBlock" Text="ID: " Margin="0,2"/>
<TextBlock x:Name="GridCoordTextBlock" Text="坐标: " Margin="0,2"/>
<TextBlock x:Name="GridColorTextBlock" Text="颜色: " Margin="0,2"/>
<!-- 新增:格子上的角色列表 -->
<TextBlock Text="格子上的角色:" FontWeight="SemiBold" Margin="0,5,0,2"/>
<ItemsControl x:Name="GridCharactersInfoItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Padding="2" Margin="0,2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NickName}" FontWeight="SemiBold" Margin="0,0,5,0"/>
<TextBlock Text="{Binding HP, StringFormat=HP:{0:0.##}}"/>
<TextBlock Text="{Binding MaxHP, StringFormat=/{0:0.##}}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding MP, StringFormat=MP:{0:0.##}}"/>
<TextBlock Text="{Binding MaxMP, StringFormat=/{0:0.##}}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 原有的 GridCharactersTextBlock 已移除 -->
<TextBlock x:Name="GridEffectsTextBlock" Text="效果: " Margin="0,2" TextWrapping="Wrap"/>
<!-- 可以根据需要添加更多信息 -->
<Button x:Name="CloseGridInfoButton" Content="关闭" Margin="0,10,0,0" HorizontalAlignment="Right" Click="CloseGridInfoButton_Click"/>
</StackPanel>
</ScrollViewer>
</StackPanel>
</Border>
<!-- 新增:角色选择面板 (Overlay) -->
<Border x:Name="CharacterSelectionOverlay"
Panel.ZIndex="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="400"
MaxHeight="400"
Background="#DDFFFFFF"
BorderBrush="DarkBlue"
BorderThickness="2"
CornerRadius="10"
Padding="20"
Visibility="Collapsed">
<StackPanel>
<TextBlock Text="请选择你的角色" FontWeight="Bold" FontSize="16" Margin="0,0,0,10" HorizontalAlignment="Center"/>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="CharacterSelectionItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource SelectionItemStyle}"
Tag="{Binding Id}"
MouseLeftButtonDown="CharacterSelectionItem_MouseLeftButtonDown">
<TextBlock Text="{Binding NickName}" FontSize="14" FontWeight="SemiBold"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Border>
<!-- 新增:技能/物品选择面板 (Overlay) -->
<Border x:Name="SkillItemSelectionOverlay"
Panel.ZIndex="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="400"
MaxHeight="400"
Background="#DDFFFFFF"
BorderBrush="DarkGreen"
BorderThickness="2"
CornerRadius="10"
Padding="20"
Visibility="Collapsed">
<StackPanel>
<TextBlock x:Name="SkillItemSelectionTitle" Text="请选择技能/物品" FontWeight="Bold" FontSize="16" Margin="0,0,0,10" HorizontalAlignment="Center"/>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="SkillItemSelectionItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource SelectionItemStyle}"
Tag="{Binding Id}"
MouseLeftButtonDown="SkillItemSelectionItem_MouseLeftButtonDown">
<TextBlock Text="{Binding Name}" FontSize="14" FontWeight="SemiBold"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button Content="取消" Margin="0,10,0,0" HorizontalAlignment="Right" Click="CancelSkillItemSelection_Click"/>
</StackPanel>
</Border>
<!-- 新增:目标选择确认面板 (Overlay) -->
<Border x:Name="TargetSelectionOverlay"
Panel.ZIndex="200"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="10"
Width="250"
MaxHeight="300"
Background="#CCFFFFFF"
BorderBrush="DarkRed"
BorderThickness="1"
CornerRadius="5"
Padding="10"
Visibility="Collapsed">
<StackPanel>
<TextBlock x:Name="TargetSelectionTitle" Text="选择目标" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="200">
<ItemsControl x:Name="SelectedTargetsItemsControl" ItemsSource="{Binding SelectedTargets, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Padding="2" Margin="0,2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NickName}" FontWeight="SemiBold" Margin="0,0,5,0"/>
<Button Content="X" Margin="5,0,0,0" Tag="{Binding Id}" Click="RemoveTarget_Click"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"/>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Content="确认" Margin="0,0,5,0" Click="ConfirmTargets_Click"/>
<Button Content="取消" Click="CancelTargetSelection_Click"/>
</StackPanel>
</StackPanel>
</Border>
<!-- 新增:继续提示面板 (Overlay) -->
<Border x:Name="ContinuePromptOverlay"
Panel.ZIndex="200"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="300"
Height="150"
Background="#DDFFFFFF"
BorderBrush="DarkOrange"
BorderThickness="2"
CornerRadius="10"
Padding="20"
Visibility="Collapsed">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock x:Name="ContinuePromptTextBlock" Text="请点击继续..." TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="14" Margin="0,0,0,15"/>
<Button Content="继续" Width="80" Click="ContinuePromptButton_Click"/>
</StackPanel>
</Border>
</Grid>
</Border>
</ScrollViewer>
<!-- 新增:右侧调试日志面板 -->
<Border Grid.Column="2" Grid.Row="0" BorderBrush="LightGray" BorderThickness="1" Margin="5" Padding="5">
<StackPanel Background="#FFF0F8FF">
<!-- AliceBlue -->
<TextBlock Text="调试日志" Margin="0,0,0,10" FontWeight="Bold" FontSize="14"/>
<ScrollViewer x:Name="DebugLogScrollViewer"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Height="Auto">
<!-- 移除MaxHeight让它填充可用空间 -->
<TextBlock x:Name="DebugLogTextBlock"
ScrollViewer.CanContentScroll="True"
Background="Transparent"
Foreground="DimGray"
TextWrapping="Wrap"
Text="调试日志:" />
</ScrollViewer>
</StackPanel>
</Border>
<!-- 底部信息界面面板 (横向填充) -->
<Border Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" BorderBrush="LightGray" BorderThickness="1" Margin="5" Padding="5">
<!-- 将StackPanel替换为Grid实现左右两部分的布局 -->
<Grid x:Name="BottomInfoPanelGrid" Background="#FFF0FFFF">
<!-- Azure -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<!-- 左半部分:角色信息 -->
<ColumnDefinition Width="2*"/>
<!-- 中间部分:装备和状态 (新增) -->
<ColumnDefinition Width="3*"/>
<!-- 右半部分:操作按钮 -->
</Grid.ColumnDefinitions>
<!-- 左半部分:角色信息 -->
<Border Grid.Column="0" BorderBrush="LightGray" BorderThickness="0,0,1,0" Margin="0,0,5,0" Padding="5">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Orientation="Vertical">
<TextBlock Text="当前角色信息" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<!-- 头像 -->
<ColumnDefinition Width="*"/>
<!-- 名称和进度条 -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 头像框 - 显示字符 -->
<Border x:Name="CharacterAvatarBorder" Grid.RowSpan="4" Grid.Column="0" Width="60" Height="60" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="5" Margin="0,0,10,0"
Background="#FF6A5ACD">
<!-- 默认背景色,可以根据角色动态改变 -->
<Grid>
<TextBlock x:Name="CharacterAvatarTextBlock"
Text="?"
Foreground="White"
FontSize="28"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Border>
<!-- 角色名称 -->
<TextBlock x:Name="CharacterNameTextBlock" Grid.Row="0" Grid.Column="1" Text="角色名称: [未选择]" FontWeight="SemiBold" FontSize="13" Margin="0,0,0,5"/>
<!-- HP条 (包含护盾) -->
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,2">
<TextBlock Text="HP:" VerticalAlignment="Center" Width="30"/>
<!-- 内部Grid用于叠加HP和护盾Rectangle -->
<Grid Height="10" Width="120" HorizontalAlignment="Left">
<!-- 背景条 -->
<Border Background="LightGray" BorderBrush="Gray" BorderThickness="0.5" CornerRadius="2"/>
<!-- 实际HP进度 -->
<Rectangle x:Name="HpFillRectangle" HorizontalAlignment="Left" Fill="Green" Height="10" RadiusX="2" RadiusY="2"/>
<!-- 护盾进度叠加在HP之上 -->
<Rectangle x:Name="ShieldFillRectangle" HorizontalAlignment="Left" Fill="Transparent" Height="10" RadiusX="2" RadiusY="2"
Visibility="Collapsed" Panel.ZIndex="1"/>
</Grid>
<TextBlock x:Name="HpValueTextBlock" Text="0/0" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="10"/>
</StackPanel>
<!-- MP条 -->
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,2">
<TextBlock Text="MP:" Width="30" VerticalAlignment="Center"/>
<ProgressBar x:Name="MpProgressBar" Value="0" Maximum="100" Height="10" Width="120" Background="LightGray" BorderBrush="Gray" BorderThickness="0.5" Foreground="Blue"/>
<TextBlock x:Name="MpValueTextBlock" Text="0/0" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="10"/>
</StackPanel>
<!-- EP条 -->
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,2">
<TextBlock Text="EP:" Width="30" VerticalAlignment="Center"/>
<ProgressBar x:Name="EpProgressBar" Value="0" Maximum="100" Height="10" Width="120" Background="LightGray" BorderBrush="Gray" BorderThickness="0.5" Foreground="Orange"/>
<TextBlock x:Name="EpValueTextBlock" Text="0/0" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="10"/>
</StackPanel>
</Grid>
<!-- 新增:其他角色属性 -->
<Border BorderBrush="LightGray" BorderThickness="0,1,0,0" Margin="0,10,0,0" Padding="0,10,0,0">
<!-- 将StackPanel替换为Grid实现横向两列排布 -->
<Grid x:Name="CharacterAttributesGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1.4*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" x:Name="AttackTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="攻击力:"/>
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="PhysicalDefTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="物理护甲:"/>
<TextBlock Grid.Row="1" Grid.Column="0" x:Name="MagicResTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="魔法抗性:"/>
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="SpeedTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="行动速度:"/>
<TextBlock Grid.Row="2" Grid.Column="0" x:Name="PrimaryAttrTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="核心属性:"/>
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="StrengthTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="力量:"/>
<TextBlock Grid.Row="3" Grid.Column="0" x:Name="AgilityTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="敏捷:"/>
<TextBlock Grid.Row="3" Grid.Column="1" x:Name="IntellectTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="智力:"/>
<TextBlock Grid.Row="4" Grid.Column="0" x:Name="HpRegenTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="生命回复:"/>
<TextBlock Grid.Row="4" Grid.Column="1" x:Name="MpRegenTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="魔法回复:"/>
<TextBlock Grid.Row="5" Grid.Column="0" x:Name="CritRateTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="暴击率:"/>
<TextBlock Grid.Row="5" Grid.Column="1" x:Name="CritDmgTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="暴击伤害:"/>
<TextBlock Grid.Row="6" Grid.Column="0" x:Name="EvadeRateTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="闪避率:"/>
<TextBlock Grid.Row="6" Grid.Column="1" x:Name="LifestealTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="生命偷取:"/>
<TextBlock Grid.Row="7" Grid.Column="0" x:Name="CdrTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="冷却缩减:"/>
<TextBlock Grid.Row="7" Grid.Column="1" x:Name="AccelCoeffTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="加速系数:"/>
<TextBlock Grid.Row="8" Grid.Column="0" x:Name="PhysPenTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="物理穿透:"/>
<TextBlock Grid.Row="8" Grid.Column="1" x:Name="MagicPenTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Text="魔法穿透:"/>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
<!-- 新增中间部分:装备和状态 -->
<Border Grid.Column="1" BorderBrush="LightGray" BorderThickness="0,0,1,0" Margin="0,0,5,0" Padding="5">
<StackPanel Orientation="Vertical">
<!-- 装备栏 -->
<TextBlock Text="装备" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
<WrapPanel x:Name="EquipSlotsPanel" HorizontalAlignment="Center" Margin="0,0,0,10">
<!-- 魔法卡包 -->
<Border x:Name="MagicCardPackBorder" Style="{StaticResource EquipSlotStyle}" MouseLeftButtonDown="EquipSlot_MouseLeftButtonDown">
<TextBlock x:Name="MagicCardPackSlotText" Text="魔" Style="{StaticResource EquipSlotTextStyle}"/>
</Border>
<!-- 武器 -->
<Border x:Name="WeaponBorder" Style="{StaticResource EquipSlotStyle}" MouseLeftButtonDown="EquipSlot_MouseLeftButtonDown">
<TextBlock x:Name="WeaponSlotText" Text="武" Style="{StaticResource EquipSlotTextStyle}"/>
</Border>
<!-- 防具 -->
<Border x:Name="ArmorBorder" Style="{StaticResource EquipSlotStyle}" MouseLeftButtonDown="EquipSlot_MouseLeftButtonDown">
<TextBlock x:Name="ArmorSlotText" Text="防" Style="{StaticResource EquipSlotTextStyle}"/>
</Border>
<!-- 鞋子 -->
<Border x:Name="ShoesBorder" Style="{StaticResource EquipSlotStyle}" MouseLeftButtonDown="EquipSlot_MouseLeftButtonDown">
<TextBlock x:Name="ShoesSlotText" Text="鞋" Style="{StaticResource EquipSlotTextStyle}"/>
</Border>
<!-- 饰品1 -->
<Border x:Name="Accessory1Border" Style="{StaticResource EquipSlotStyle}" MouseLeftButtonDown="EquipSlot_MouseLeftButtonDown">
<TextBlock x:Name="Accessory1SlotText" Text="饰1" Style="{StaticResource EquipSlotTextStyle}"/>
</Border>
<!-- 饰品2 -->
<Border x:Name="Accessory2Border" Style="{StaticResource EquipSlotStyle}" MouseLeftButtonDown="EquipSlot_MouseLeftButtonDown">
<TextBlock x:Name="Accessory2SlotText" Text="饰2" Style="{StaticResource EquipSlotTextStyle}"/>
</Border>
</WrapPanel>
<!-- 状态条 -->
<TextBlock Text="状态" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" MaxHeight="80">
<WrapPanel x:Name="CharacterEffectsPanel" HorizontalAlignment="Left">
<!-- 状态图标将在此处动态添加 -->
</WrapPanel>
</ScrollViewer>
<!-- 新增:装备和状态描述 -->
<Border BorderBrush="LightGray" BorderThickness="0,1,0,0" Margin="0,10,0,0" Padding="0,10,0,0">
<StackPanel>
<TextBlock Text="详情描述" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="80">
<TextBlock x:Name="DescriptionTextBlock" Text="点击装备或状态图标查看详情。" TextWrapping="Wrap" FontSize="11" Foreground="DimGray"/>
</ScrollViewer>
</StackPanel>
</Border>
</StackPanel>
</Border>
<!-- 右半部分:操作按钮 -->
<Border Grid.Column="2" Padding="5">
<StackPanel Orientation="Vertical">
<TextBlock Text="操作" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Center">
<!-- IsEnabled 默认设置为 False由代码控制 -->
<!-- Tag 属性用于在 Click 事件中识别是哪个动作 -->
<Button x:Name="MoveButton" Content="移动" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.Move}" IsEnabled="False"/>
<Button x:Name="AttackButton" Content="普通攻击" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.NormalAttack}" 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="CastButton" 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="EndTurnButton" Content="结束回合" Width="100" Height="30" Margin="5" Click="ActionButton_Click" Tag="{x:Static constant:CharacterActionType.EndTurn}" IsEnabled="False"/>
</WrapPanel>
<!-- 新增:数据统计 -->
<Border BorderBrush="LightGray" BorderThickness="0,1,0,0" Margin="0,10,0,0" Padding="0,10,0,0">
<StackPanel>
<TextBlock Text="数据统计" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
<TextBlock x:Name="StatsRatingKillsAssistsDeathsTextBlock" Style="{StaticResource CharacterAttributeTextStyle}"/>
<TextBlock x:Name="StatsLiveTimeRoundTurnTextBlock" Style="{StaticResource CharacterAttributeTextStyle}"/>
<TextBlock x:Name="StatsControlHealShieldTextBlock" Style="{StaticResource CharacterAttributeTextStyle}"/>
<TextBlock x:Name="StatsTotalDamageTextBlock" Style="{StaticResource CharacterAttributeTextStyle}"/>
<TextBlock x:Name="StatsTotalTakenDamageTextBlock" Style="{StaticResource CharacterAttributeTextStyle}"/>
<TextBlock x:Name="StatsTrueDamageTextBlock" Style="{StaticResource CharacterAttributeTextStyle}" Visibility="Collapsed"/>
<!-- 初始隐藏,有真实伤害时显示 -->
<TextBlock x:Name="StatsDamagePerSecondTurnTextBlock" Style="{StaticResource CharacterAttributeTextStyle}"/>
</StackPanel>
</Border>
</StackPanel>
</Border>
</Grid>
</Border>
</Grid>
</UserControl>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Testing.Desktop.GameMapTesting
{
public class TestMap : GameMap
{
public override string Name => "TestMap";
public override string Description => "TestMap";
public override string Version => "1.0.0";
public override string Author => "TestUser";
public override int Length => 12;
public override int Width => 12;
public override int Height => 1;
public override float Size => 32;
public override GameMap InitGamingQueue(IGamingQueue queue)
{
GameMap map = new TestMap();
map.Load();
if (queue is GamingQueue gq)
{
gq.WriteLine($"地图 {map.Name} 已加载。");
}
return map;
}
protected override void AfterTimeElapsed(ref double timeToReduce)
{
}
}
}

View File

@ -0,0 +1,32 @@
<Window x:Class="Milimoe.FunGame.Testing.Desktop.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="输入" Height="180" Width="350"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
ShowInTaskbar="False">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 提示文本 -->
<TextBlock x:Name="PromptTextBlock" Grid.Row="0" Margin="0,0,0,10" TextWrapping="Wrap"
Text="请输入文本:"/>
<!-- 输入框 -->
<TextBox x:Name="InputTextBox" Grid.Row="1" Height="30" VerticalContentAlignment="Center"
AcceptsReturn="False" TextWrapping="NoWrap" />
<!-- 按钮区域 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
<Button Content="确定" Width="75" Margin="0,0,10,0" Click="OkButton_Click" IsDefault="True"/>
<Button Content="取消" Width="75" Click="CancelButton_Click" IsCancel="True"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,75 @@
using System.Windows;
namespace Milimoe.FunGame.Testing.Desktop
{
/// <summary>
/// InputDialog.xaml 的交互逻辑
/// </summary>
public partial class InputDialog : Window
{
/// <summary>
/// 获取或设置用户输入的文本。
/// </summary>
public string InputText
{
get { return InputTextBox.Text; }
set { InputTextBox.Text = value; }
}
/// <summary>
/// 构造函数,允许设置提示文本和初始值。
/// </summary>
/// <param name="prompt">显示给用户的提示信息。</param>
/// <param name="initialValue">输入框的初始值。</param>
public InputDialog(string prompt, string initialValue = "")
{
InitializeComponent();
PromptTextBlock.Text = prompt;
InputTextBox.Text = initialValue;
InputTextBox.Focus(); // 自动聚焦到输入框
InputTextBox.SelectAll(); // 选中所有文本,方便用户直接输入覆盖
}
/// <summary>
/// "确定" 按钮点击事件。
/// </summary>
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true; // 设置对话框结果为 true表示用户点击了确定
Close();
}
/// <summary>
/// "取消" 按钮点击事件。
/// </summary>
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false; // 设置对话框结果为 false表示用户点击了取消
Close();
}
/// <summary>
/// 静态方法,方便外部调用显示输入弹窗。
/// </summary>
/// <param name="prompt">显示给用户的提示信息。</param>
/// <param name="initialValue">输入框的初始值。</param>
/// <param name="owner">弹窗的拥有者窗口,用于居中显示。</param>
/// <returns>如果用户点击确定,返回输入的字符串;否则返回 null。</returns>
public static string? Show(string prompt, string initialValue = "", Window? owner = null)
{
InputDialog dialog = new(prompt, initialValue);
if (owner != null)
{
dialog.Owner = owner;
}
bool? result = dialog.ShowDialog();
if (result == true)
{
return dialog.InputText;
}
return null;
}
}
}