Compare commits

...

8 Commits

Author SHA1 Message Date
Mistmoon cffa1c879f
Merge pull request #156 from Jack15678/dev
debug_interface---update Server Communicate
2025-04-05 17:21:19 +08:00
Mistmoon 331594278a
Update logic/ClientTest/Program.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-05 17:21:13 +08:00
JackPWY 8cc0401124 Debug_interface - Added Legend to Map 2025-04-05 13:38:35 +08:00
JackPWY 45751d88d4 Debug_interface - Spectator Mode try 2025-04-04 16:16:35 +08:00
JackPWY 0f8ae51306 Debug_interface - update Server Communicate 2025-04-04 15:04:02 +08:00
JackPWY d54b51538d Debug_interface - old Server Communicate 2025-04-03 16:07:12 +08:00
JackPWY 2b61d9f9c1 Debug_interface - old Server Communicate 2025-04-03 15:46:57 +08:00
JackPWY ccc933d4f5 Debug_interface - success Server Communicate 2025-04-03 12:19:36 +08:00
18 changed files with 1960 additions and 1556 deletions

0
114514 Normal file
View File

View File

@ -52,7 +52,7 @@ namespace installer.Data
public int TeamID { get; set; } = 0;
public int PlayerID { get; set; } = 2024;
public int PlayerID { get; set; } = 2025;
public int CharacterType { get; set; } = 0;
}

View File

@ -1,7 +1,8 @@
//App.axaml.cs
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using debug_interface.Services;
using debug_interface.ViewModels;
using debug_interface.Views;
using System;
@ -22,39 +23,41 @@ namespace debug_interface
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// 加载配置
var config = new ConfigData();
// 设置日志目录
string logDir = Path.Combine(config.InstallPath, "Logs");
Directory.CreateDirectory(logDir);
// 创建日志记录器
var logFilePath = Path.Combine(logDir, $"debug_interface_{DateTime.Now:yyyyMMdd_HHmmss}.log");
var logger = new FileLogger(logFilePath);
// 创建主窗口视图模型
var mainWindowViewModel = new MainWindowViewModel(logger, config);
// 创建服务器通信服务
var serverCommunicationService = new ServerCommunicationService(mainWindowViewModel, logger, config);
// 设置主窗口
desktop.MainWindow = new MainWindow
try
{
DataContext = mainWindowViewModel
};
// 加载配置
var config = new ConfigData();
// 尝试连接服务器或启动回放
if (string.IsNullOrEmpty(config.Commands.PlaybackFile))
{
// 连接到服务器
_ = serverCommunicationService.ConnectToServer();
// 设置日志目录
string logDir = Path.Combine(config.InstallPath, "Logs");
Directory.CreateDirectory(logDir);
// 创建日志记录器
var logFilePath = Path.Combine(logDir, $"debug_interface_{DateTime.Now:yyyyMMdd_HHmmss}.log");
var logger = LoggerProvider.FromFile(logFilePath);
// 创建主窗口视图模型
var mainWindowViewModel = new MainWindowViewModel();
mainWindowViewModel.myLogger = logger;
// 设置主窗口
desktop.MainWindow = new MainWindow
{
DataContext = mainWindowViewModel
};
// 注意连接服务器的逻辑已经移到了ViewModelBase的构造函数中
// 不需要手动调用ConnectToServer或StartPlaybackMode
}
else
catch (Exception ex)
{
// 启动回放模式
serverCommunicationService.StartPlaybackMode();
// 如果初始化过程中出现错误,至少尝试创建一个基本的窗口
Console.WriteLine($"初始化出错: {ex.Message}");
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel()
};
}
}

View File

@ -2,6 +2,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Media;
using debug_interface.Models;
using debug_interface.ViewModels;
@ -12,8 +13,11 @@ namespace debug_interface.Controls
{
public static class MapHelper
{
// CellRectangles 存储矩形引用
private static Dictionary<int, Rectangle> cellRectangles = new Dictionary<int, Rectangle>();
private static Grid? gridContainer;
// CellTextBlocks 存储文本引用
private static Dictionary<int, TextBlock> cellTextBlocks = new Dictionary<int, TextBlock>();
private static Grid? gridContainer; // Grid 容器引用
/// <summary>
/// 初始化地图网格
@ -56,7 +60,9 @@ namespace debug_interface.Controls
Fill = cell.DisplayColor,
Margin = new Thickness(0),
[Grid.RowProperty] = i,
[Grid.ColumnProperty] = j
[Grid.ColumnProperty] = j,
IsHitTestVisible = true, // 确保它参与命中测试
ZIndex = 0 // 确保它在 Z 轴上有明确的层级 (低于 TextBlock 和网格线)
};
// 为单元格添加文本(如果有)
@ -84,12 +90,12 @@ namespace debug_interface.Controls
}
// 添加网格线(在单元格上方)
AddGridLines(grid);
//AddGridLines(grid);
return grid;
}
/// <summary>
/// 初始化地图网格
/// 初始化地图网格,包括单元格、文本和 Tooltip
/// </summary>
public static void InitializeMapGrid(Grid grid, MapViewModel mapViewModel)
{
@ -97,22 +103,24 @@ namespace debug_interface.Controls
grid.Children.Clear();
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
cellRectangles.Clear();
//cellRectangles.Clear();
//cellTextBlocks.Clear(); // 清空文本引用
gridContainer = grid;
// 定义列
// 定义列和行 (50x50)
for (int i = 0; i < 50; i++)
{
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
}
// 定义行
for (int i = 0; i < 50; i++)
{
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
}
// 绘制单元格
//for (int i = 0; i < 50; i++)
//{
// grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
// grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
//}
// 绘制单元格、文本和设置Tooltip
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 50; j++)
@ -123,41 +131,69 @@ namespace debug_interface.Controls
{
var cell = mapViewModel.MapCells[index];
// 创建矩形单元格
// Create Rectangle
var rectangle = new Rectangle
{
Fill = cell.DisplayColor,
Margin = new Thickness(0),
[Grid.RowProperty] = i,
[Grid.ColumnProperty] = j
[Grid.ColumnProperty] = j,
Tag = index,
IsHitTestVisible = true,
ZIndex = 0
};
// 为单元格添加文本(如果有)
if (!string.IsNullOrEmpty(cell.DisplayText))
// *** Bind Fill property ***
rectangle[!Shape.FillProperty] = new Binding(nameof(MapCell.DisplayColor))
{
var textBlock = new TextBlock
{
Text = cell.DisplayText,
FontSize = 6,
TextAlignment = TextAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
[Grid.RowProperty] = i,
[Grid.ColumnProperty] = j,
ZIndex = 1 // 确保文本在矩形上方
};
grid.Children.Add(textBlock);
}
Source = cell,
Mode = BindingMode.OneWay
};
// 存储矩形引用以便后续更新
cellRectangles[index] = rectangle;
// *** Bind ToolTip property ***
rectangle[!ToolTip.TipProperty] = new Binding(nameof(MapCell.ToolTipText))
{
Source = cell,
Mode = BindingMode.OneWay
};
// Create TextBlock
var textBlock = new TextBlock
{
FontSize = 8,
TextAlignment = TextAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
Foreground = Brushes.Black, // Consider binding this too if needed
[Grid.RowProperty] = i,
[Grid.ColumnProperty] = j,
IsHitTestVisible = false,
ZIndex = 1
};
// *** Bind Text property ***
textBlock[!TextBlock.TextProperty] = new Binding(nameof(MapCell.DisplayText))
{
Source = cell,
Mode = BindingMode.OneWay
};
// Add to Grid
grid.Children.Add(rectangle);
grid.Children.Add(textBlock);
// --- *** REMOVE PropertyChanged Handler Attachment *** ---
// cell.PropertyChanged -= Cell_PropertyChanged; // Ensure removed if re-initializing
// cell.PropertyChanged += Cell_PropertyChanged; // Remove this line!
// Optional: Store refs if still needed, but binding makes them less necessary for updates
// cellRectangles[index] = rectangle;
// cellTextBlocks[index] = textBlock;
}
}
}
// 添加网格线(在单元格上方)
AddGridLines(grid);
// 添加网格线(在单元格和文本上方)
//AddGridLines(grid);
}
/// <summary>
@ -173,20 +209,23 @@ namespace debug_interface.Controls
StartPoint = new Point(0, 0),
EndPoint = new Point(1, 0),
Stroke = Brushes.Gray,
StrokeThickness = 1,
StrokeThickness = 0.5, // 可以细一点
Stretch = Stretch.Fill,
ZIndex = 2 // 确保网格线在最上层
};
if (i < 50) // 最后一行不需要添加
if (i < 50) // 行线画在单元格底部
{
line.SetValue(Grid.RowProperty, i);
line.SetValue(Grid.ColumnSpanProperty, 50);
line.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Bottom;
grid.Children.Add(line);
}
// (可选) 画最后一条边框线
// else if (i == 50) { ... }
}
// 添加垂直网格线
for (int j = 0; j <= 50; j++)
{
@ -195,68 +234,86 @@ namespace debug_interface.Controls
StartPoint = new Point(0, 0),
EndPoint = new Point(0, 1),
Stroke = Brushes.Gray,
StrokeThickness = 1,
StrokeThickness = 0.5, // 可以细一点
Stretch = Stretch.Fill,
ZIndex = 2 // 确保网格线在最上层
};
if (j < 50) // 最后一列不需要添加
if (j < 50) // 列线画在单元格右侧
{
line.SetValue(Grid.ColumnProperty, j);
line.SetValue(Grid.RowSpanProperty, 50);
line.HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right;
grid.Children.Add(line);
}
// (可选) 画最后一条边框线
// else if (j == 50) { ... }
}
}
// (可选) 辅助方法判断背景色是否需要深色文本
private static bool NeedsDarkText(IBrush background)
{
if (background is SolidColorBrush solidColor)
{
var color = solidColor.Color;
// 基于亮度简单判断 (这是一个简化的亮度计算)
double luminance = (0.299 * color.R + 0.587 * color.G + 0.114 * color.B) / 255;
return luminance > 0.5; // 如果背景亮,用深色文本
}
return true; // 默认用深色
}
/// <summary>
/// 更新单元格颜色
/// </summary>
public static void UpdateCellColor(int x, int y, IBrush color)
{
int index = x * 50 + y;
if (cellRectangles.ContainsKey(index))
{
cellRectangles[index].Fill = color;
}
}
//public static void UpdateCellColor(int x, int y, IBrush color)
//{
// int index = x * 50 + y;
// if (cellRectangles.ContainsKey(index))
// {
// cellRectangles[index].Fill = color;
// }
//}
/// <summary>
/// 更新单元格文本
/// </summary>
public static void UpdateCellText(int x, int y, string text)
{
if (gridContainer == null) return;
//public static void UpdateCellText(int x, int y, string text)
//{
// if (gridContainer == null) return;
// 查找对应位置的TextBlock并更新
foreach (var child in gridContainer.Children)
{
if (child is TextBlock textBlock &&
Grid.GetRow(textBlock) == x &&
Grid.GetColumn(textBlock) == y)
{
textBlock.Text = text;
return;
}
}
// // 查找对应位置的TextBlock并更新
// foreach (var child in gridContainer.Children)
// {
// if (child is TextBlock textBlock &&
// Grid.GetRow(textBlock) == x &&
// Grid.GetColumn(textBlock) == y)
// {
// textBlock.Text = text;
// return;
// }
// }
// 如果没有找到现有的TextBlock创建新的
if (!string.IsNullOrEmpty(text))
{
var textBlock = new TextBlock
{
Text = text,
FontSize = 6,
TextAlignment = TextAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
[Grid.RowProperty] = x,
[Grid.ColumnProperty] = y,
ZIndex = 1
};
gridContainer.Children.Add(textBlock);
}
}
// // 如果没有找到现有的TextBlock创建新的
// if (!string.IsNullOrEmpty(text))
// {
// var textBlock = new TextBlock
// {
// Text = text,
// FontSize = 6,
// TextAlignment = TextAlignment.Center,
// VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
// HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
// [Grid.RowProperty] = x,
// [Grid.ColumnProperty] = y,
// ZIndex = 1
// };
// gridContainer.Children.Add(textBlock);
// }
//}
}
}

View File

@ -0,0 +1,13 @@
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace debug_interface.Models
{
// 使用 record 更简洁,自动实现相等性比较等
public record LegendItem(IBrush Color, string Description, IBrush? Stroke = null, double StrokeThickness = 0);
// 添加了可选的 Stroke 和 StrokeThickness 用于绘制边框 (例如给白色方块)
}

View File

@ -1,4 +1,4 @@
//MapCell.cs
// MapCell.cs
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using debug_interface.ViewModels;
@ -10,52 +10,40 @@ using System.Threading.Tasks;
namespace debug_interface.Models
{
//internal class MapCell
//{
//}
// 定义地图单元格的类型,根据游戏规则可包含障碍物、空地、草丛、资源、建筑等
public enum MapCellType
{
Empty,
Obstacle,
Building,
Resource,
Character
Obstacle, // 障碍物
OpenLand, // 空地
Grass, // 草丛
Resource, // 资源点
Building, // 建筑
Trap // 陷阱
}
public partial class MapCell : ViewModelBase
public partial class MapCell : ObservableObject
{
[ObservableProperty]
private int row;
private int cellX;
[ObservableProperty]
private int col;
public int CellX
{
get => Col;
set => Col = value;
}
public int CellY
{
get => Row;
set => Row = value;
}
private int cellY;
[ObservableProperty]
private MapCellType cellType;
[ObservableProperty]
private string displayText = "";
private SolidColorBrush displayColor = new SolidColorBrush(Colors.White); // 默认白色
[ObservableProperty]
private IBrush displayColor = new SolidColorBrush(Colors.Black);
private string displayText = ""; // 用于显示血量等
[ObservableProperty]
private IBrush backgroundColor = new SolidColorBrush(Colors.White);
private string toolTipText = ""; // 用于鼠标悬浮提示
// 可以选择性地添加血量信息,如果需要更复杂的绑定
// [ObservableProperty]
// private int currentHp;
// [ObservableProperty]
// private int maxHp;
}
}
}

View File

@ -1,776 +0,0 @@
using System;
using System.Threading.Tasks;
using Avalonia.Threading;
using debug_interface.ViewModels;
using Grpc.Core;
using installer.Model;
using installer.Data;
namespace debug_interface.Services
{
public class ServerCommunicationService
{
private readonly MainWindowViewModel _viewModel;
private readonly Logger _logger;
private readonly ConfigData _config;
private bool _isConnected = false;
private bool _isPlaybackMode = false;
public ServerCommunicationService(MainWindowViewModel viewModel, Logger logger, ConfigData config)
{
_viewModel = viewModel;
_logger = logger;
_config = config;
}
public async Task ConnectToServer()
{
try
{
string ip = _config.Commands.IP;
string port = _config.Commands.Port;
_logger.LogInfo($"尝试连接服务器 {ip}:{port}");
// 由于没有正确的Proto引用这里暂时只模拟连接行为
await Task.Delay(1000); // 模拟连接延迟
await Dispatcher.UIThread.InvokeAsync(() => {
_viewModel.GameLog = $"已连接到服务器 {ip}:{port}";
});
_isConnected = true;
_logger.LogInfo("已模拟连接到服务器");
}
catch (Exception ex)
{
_logger.LogError($"连接失败: {ex.Message}");
await Dispatcher.UIThread.InvokeAsync(() => {
_viewModel.GameLog = $"连接失败: {ex.Message}";
});
}
}
public void StartPlaybackMode()
{
try
{
_isPlaybackMode = true;
string playbackFile = _config.Commands.PlaybackFile ?? "";
_logger.LogInfo($"模拟回放: {playbackFile}");
Dispatcher.UIThread.InvokeAsync(() => {
_viewModel.GameLog = $"模拟回放: {playbackFile}";
});
}
catch (Exception ex)
{
_logger.LogError($"回放错误: {ex.Message}");
Dispatcher.UIThread.InvokeAsync(() => {
_viewModel.GameLog = $"回放错误: {ex.Message}";
});
}
}
}
}
//using System;
//using System.Linq;
//using System.Threading.Tasks;
//using Avalonia.Threading;
//using debug_interface.ViewModels;
//using Grpc.Core;
//using Google.Protobuf; // 假设这是proto文件生成的命名空间
//using installer.Model; // Logger的命名空间
//using installer.Data; // ConfigData的命名空间
//using System.Collections.Generic;
//using Avalonia.Data;
//using System.Threading.Channels;
//namespace debug_interface.Services
//{
// public class ServerCommunicationService
// {
// private readonly MainWindowViewModel _viewModel;
// private readonly Logger _logger;
// private readonly ConfigData _config;
// private AvailableService.AvailableServiceClient? _client;
// private AsyncServerStreamingCall<MessageToClient>? _responseStream;
// private bool _isConnected = false;
// private bool _isPlaybackMode = false;
// public ServerCommunicationService(MainWindowViewModel viewModel, Logger logger, ConfigData config)
// {
// _viewModel = viewModel;
// _logger = logger;
// _config = config;
// }
// public async Task ConnectToServer()
// {
// try
// {
// if (_isPlaybackMode)
// return;
// string ip = _config.Commands.IP;
// string port = _config.Commands.Port;
// long playerId = _config.Commands.PlayerID;
// long teamId = _config.Commands.TeamID;
// int characterType = _config.Commands.CharacterType;
// string connectionString = $"{ip}:{port}";
// _logger.LogInfo($"正在连接到服务器 {connectionString}");
// // 创建gRPC通道和客户端
// var channel = new Channel(connectionString, ChannelCredentials.Insecure);
// _client = new AvailableService.AvailableServiceClient(channel);
// // 创建连接消息
// var characterMsg = new CharacterMsg
// {
// CharacterId = playerId,
// TeamId = teamId,
// CharacterType = (CharacterType)characterType
// };
// // 连接到服务器
// _responseStream = _client.AddCharacter(characterMsg);
// _isConnected = true;
// // 开始接收消息
// _ = StartReceivingMessagesAsync();
// _logger.LogInfo("成功连接到服务器");
// }
// catch (Exception ex)
// {
// _isConnected = false;
// _logger.LogError($"连接服务器失败: {ex.Message}");
// // 在UI中显示错误
// await Dispatcher.UIThread.InvokeAsync(() => {
// _viewModel.GameLog = $"连接服务器错误: {ex.Message}";
// });
// throw;
// }
// }
// public void StartPlaybackMode()
// {
// _isPlaybackMode = true;
// string playbackFile = _config.Commands.PlaybackFile ?? "";
// double playbackSpeed = _config.Commands.PlaybackSpeed;
// _logger.LogInfo($"正在从{playbackFile}以{playbackSpeed}倍速开始回放模式");
// // 这里可以实现回放逻辑,读取文件并模拟服务器消息
// // 例如启动一个单独的任务来读取和处理文件
// _ = Task.Run(async () => {
// try
// {
// // 简单的回放逻辑示例
// if (System.IO.File.Exists(playbackFile))
// {
// // 读取文件内容...
// _logger.LogInfo("回放文件存在,开始读取数据");
// // 这里是简化实现,实际应根据文件格式处理
// var fileBytes = System.IO.File.ReadAllBytes(playbackFile);
// // 通知UI
// await Dispatcher.UIThread.InvokeAsync(() => {
// _viewModel.GameLog = "正在回放...";
// });
// }
// else
// {
// _logger.LogError($"回放文件不存在: {playbackFile}");
// await Dispatcher.UIThread.InvokeAsync(() => {
// _viewModel.GameLog = "回放文件不存在!";
// });
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"回放过程中出错: {ex.Message}");
// await Dispatcher.UIThread.InvokeAsync(() => {
// _viewModel.GameLog = $"回放错误: {ex.Message}";
// });
// }
// });
// }
// private async Task StartReceivingMessagesAsync()
// {
// if (_responseStream == null)
// return;
// try
// {
// _logger.LogInfo("开始接收服务器消息");
// while (await _responseStream.ResponseStream.MoveNext(default))
// {
// var message = _responseStream.ResponseStream.Current;
// // 在UI线程上处理消息以安全地更新UI
// await Dispatcher.UIThread.InvokeAsync(() => {
// ProcessServerMessage(message);
// });
// }
// }
// catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
// {
// _logger.LogInfo("服务器流已取消");
// }
// catch (Exception ex)
// {
// _logger.LogError($"接收服务器消息错误: {ex.Message}");
// await Dispatcher.UIThread.InvokeAsync(() => {
// _viewModel.GameLog = $"连接错误: {ex.Message}";
// });
// // 尝试重新连接
// await TryReconnectAsync();
// }
// finally
// {
// _isConnected = false;
// }
// }
// private async Task TryReconnectAsync()
// {
// _logger.LogInfo("尝试重新连接服务器...");
// // 等待一段时间后重试
// await Task.Delay(5000);
// if (!_isConnected && !_isPlaybackMode)
// {
// try
// {
// await ConnectToServer();
// }
// catch
// {
// // 重连失败,等待更长时间后再次尝试
// await Task.Delay(10000);
// _ = TryReconnectAsync();
// }
// }
// }
// private void ProcessServerMessage(MessageToClient message)
// {
// try
// {
// // 更新游戏状态
// switch (message.GameState)
// {
// case GameState.GameStart:
// _logger.LogInfo("游戏开始");
// _viewModel.GameLog = "游戏开始...";
// break;
// case GameState.GameRunning:
// // 处理运行中的游戏数据
// _viewModel.GameLog = "游戏运行中...";
// break;
// case GameState.GameEnd:
// _logger.LogInfo("游戏结束");
// _viewModel.GameLog = "游戏结束";
// break;
// }
// // 更新全局游戏信息
// if (message.AllMessage != null)
// {
// UpdateGlobalGameInfo(message.AllMessage);
// }
// // 处理单个对象消息
// foreach (var obj in message.ObjMessage)
// {
// ProcessObjectMessage(obj);
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"处理服务器消息错误: {ex.Message}");
// }
// }
// private void UpdateGlobalGameInfo(MessageOfAll allMessage)
// {
// try
// {
// // 更新游戏时间
// _viewModel.CurrentTime = FormatGameTime(allMessage.GameTime);
// // 更新队伍得分
// _viewModel.RedScore = allMessage.BuddhistsTeamScore;
// _viewModel.BlueScore = allMessage.MonstersTeamScore;
// // 更新队伍经济/资源
// _viewModel.BuddhistTeamEconomy = allMessage.BuddhistsTeamEconomy;
// _viewModel.MonstersTeamEconomy = allMessage.MonstersTeamEconomy;
// // 更新英雄HP (如果ViewModel中有对应属性)
// if (typeof(MainWindowViewModel).GetProperty("BuddhistHeroHp") != null)
// {
// _viewModel.BuddhistHeroHp = allMessage.BuddhistsHeroHp;
// _viewModel.MonstersHeroHp = allMessage.MonstersHeroHp;
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新全局游戏信息失败: {ex.Message}");
// }
// }
// private void ProcessObjectMessage(MessageOfObj objMessage)
// {
// try
// {
// switch (objMessage.MessageOfObjCase)
// {
// case MessageOfObj.MessageOfObjOneofCase.CharacterMessage:
// UpdateCharacter(objMessage.CharacterMessage);
// break;
// case MessageOfObj.MessageOfObjOneofCase.BarracksMessage:
// UpdateBuilding(objMessage.BarracksMessage, "兵营");
// break;
// case MessageOfObj.MessageOfObjOneofCase.SpringMessage:
// UpdateBuilding(objMessage.SpringMessage, "泉水");
// break;
// case MessageOfObj.MessageOfObjOneofCase.FarmMessage:
// UpdateBuilding(objMessage.FarmMessage, "农场");
// break;
// case MessageOfObj.MessageOfObjOneofCase.TrapMessage:
// UpdateTrap(objMessage.TrapMessage);
// break;
// case MessageOfObj.MessageOfObjOneofCase.EconomyResourceMessage:
// UpdateResource(objMessage.EconomyResourceMessage);
// break;
// case MessageOfObj.MessageOfObjOneofCase.AdditionResourceMessage:
// UpdateAdditionResource(objMessage.AdditionResourceMessage);
// break;
// case MessageOfObj.MessageOfObjOneofCase.MapMessage:
// UpdateMap(objMessage.MapMessage);
// break;
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"处理对象消息失败: {ex.Message} (类型: {objMessage.MessageOfObjCase})");
// }
// }
// private void UpdateCharacter(MessageOfCharacter character)
// {
// try
// {
// // 在适当的团队集合中查找角色
// var isRedTeam = character.TeamId == 1; // 1表示取经队
// var collection = isRedTeam
// ? _viewModel.RedTeamCharacters
// : _viewModel.BlueTeamCharacters;
// // 计算网格坐标
// int gridX = character.X / 1000;
// int gridY = character.Y / 1000;
// // 尝试查找现有角色
// var existingCharacter = collection.FirstOrDefault(c => c.CharacterId == character.PlayerId);
// if (existingCharacter != null)
// {
// // 更新现有角色
// existingCharacter.Name = GetCharacterTypeName(character.CharacterType);
// existingCharacter.Hp = character.Hp;
// existingCharacter.PosX = gridX;
// existingCharacter.PosY = gridY;
// // 更新主动状态
// existingCharacter.ActiveState = GetCharacterStateText(character.CharacterActiveState);
// // 更新被动状态
// existingCharacter.PassiveStates.Clear();
// // 根据需要添加被动状态
// if (character.BlindState != CharacterState.NullCharacterState)
// existingCharacter.PassiveStates.Add("致盲");
// if (character.StunnedState != CharacterState.NullCharacterState)
// existingCharacter.PassiveStates.Add("定身");
// if (character.InvisibleState != CharacterState.NullCharacterState)
// existingCharacter.PassiveStates.Add("隐身");
// if (character.BurnedState != CharacterState.NullCharacterState)
// existingCharacter.PassiveStates.Add("灼烧");
// // 更新装备
// existingCharacter.EquipmentInventory.Clear();
// if (character.ShieldEquipment > 0)
// existingCharacter.EquipmentInventory.Add(new EquipmentItem("护盾", character.ShieldEquipment));
// if (character.ShoesEquipment > 0)
// existingCharacter.EquipmentInventory.Add(new EquipmentItem("鞋子", 1));
// if (character.PurificationEquipmentTime > 0)
// existingCharacter.EquipmentInventory.Add(new EquipmentItem("净化药水", 1));
// // 通知UI更新显示的状态和装备文本
// existingCharacter.OnPropertyChanged(nameof(CharacterViewModel.DisplayStates));
// existingCharacter.OnPropertyChanged(nameof(CharacterViewModel.DisplayEquipments));
// }
// else
// {
// // 创建新角色
// var newCharacter = new CharacterViewModel
// {
// CharacterId = character.PlayerId,
// Name = GetCharacterTypeName(character.CharacterType),
// Hp = character.Hp,
// PosX = gridX,
// PosY = gridY,
// ActiveState = GetCharacterStateText(character.CharacterActiveState)
// };
// // 添加被动状态
// if (character.BlindState != CharacterState.NullCharacterState)
// newCharacter.PassiveStates.Add("致盲");
// if (character.StunnedState != CharacterState.NullCharacterState)
// newCharacter.PassiveStates.Add("定身");
// if (character.InvisibleState != CharacterState.NullCharacterState)
// newCharacter.PassiveStates.Add("隐身");
// if (character.BurnedState != CharacterState.NullCharacterState)
// newCharacter.PassiveStates.Add("灼烧");
// // 添加装备
// if (character.ShieldEquipment > 0)
// newCharacter.EquipmentInventory.Add(new EquipmentItem("护盾", character.ShieldEquipment));
// if (character.ShoesEquipment > 0)
// newCharacter.EquipmentInventory.Add(new EquipmentItem("鞋子", 1));
// if (character.PurificationEquipmentTime > 0)
// newCharacter.EquipmentInventory.Add(new EquipmentItem("净化药水", 1));
// // 添加到集合
// collection.Add(newCharacter);
// }
// // 更新地图上的角色位置
// _viewModel.MapVM.UpdateCharacterPosition(
// character.PlayerId,
// character.TeamId,
// gridX,
// gridY,
// GetCharacterTypeName(character.CharacterType)
// );
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新角色失败: {ex.Message}");
// }
// }
// private string GetCharacterTypeName(CharacterType type)
// {
// return type switch
// {
// CharacterType.TangSeng => "唐僧",
// CharacterType.SunWukong => "孙悟空",
// CharacterType.ZhuBajie => "猪八戒",
// CharacterType.ShaWujing => "沙悟净",
// CharacterType.BaiLongma => "白龙马",
// CharacterType.Monkid => "小和尚",
// CharacterType.JiuLing => "九灵元圣",
// CharacterType.HongHaier => "红孩儿",
// CharacterType.NiuMowang => "牛魔王",
// CharacterType.TieShan => "铁扇公主",
// CharacterType.ZhiZhujing => "蜘蛛精",
// CharacterType.Pawn => "小妖",
// _ => "未知角色"
// };
// }
// private string GetCharacterStateText(CharacterState state)
// {
// return state switch
// {
// CharacterState.Idle => "空置",
// CharacterState.Harvesting => "开采",
// CharacterState.Attacking => "攻击",
// CharacterState.SkillCasting => "释放技能",
// CharacterState.Constructing => "建造",
// CharacterState.Moving => "移动",
// _ => "未知状态"
// };
// }
// private void UpdateBuilding(MessageOfBarracks building, string buildingType)
// {
// try
// {
// // 转换为网格坐标
// int gridX = building.X / 1000;
// int gridY = building.Y / 1000;
// // 确保坐标在有效范围内
// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50)
// {
// // 在地图上更新建筑
// _viewModel.MapVM.UpdateBuildingCell(
// gridX,
// gridY,
// building.TeamId == 1 ? "取经队" : "妖怪队",
// buildingType,
// building.Hp
// );
// // 更新建筑信息文本 (假设ViewModel有这些属性)
// string buildingInfo = $"{buildingType} 位置:({gridX},{gridY}) 血量:{building.Hp}";
// if (building.TeamId == 1) // 取经队
// {
// // 尝试更新建筑信息,如果属性存在的话
// if (typeof(MainWindowViewModel).GetProperty("SomeBuildingInfo") != null)
// {
// _viewModel.SomeBuildingInfo = buildingInfo;
// }
// }
// else // 妖怪队
// {
// if (typeof(MainWindowViewModel).GetProperty("AnotherBuildingInfo") != null)
// {
// _viewModel.AnotherBuildingInfo = buildingInfo;
// }
// }
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新建筑(兵营)失败: {ex.Message}");
// }
// }
// private void UpdateBuilding(MessageOfSpring building, string buildingType)
// {
// try
// {
// // 类似于UpdateBuilding(MessageOfBarracks...)的实现
// int gridX = building.X / 1000;
// int gridY = building.Y / 1000;
// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50)
// {
// _viewModel.MapVM.UpdateBuildingCell(
// gridX,
// gridY,
// building.TeamId == 1 ? "取经队" : "妖怪队",
// buildingType,
// building.Hp
// );
// string buildingInfo = $"{buildingType} 位置:({gridX},{gridY}) 血量:{building.Hp}";
// if (building.TeamId == 1 &&
// typeof(MainWindowViewModel).GetProperty("SomeBuildingInfo") != null)
// {
// _viewModel.SomeBuildingInfo += "\n" + buildingInfo;
// }
// else if (building.TeamId != 1 &&
// typeof(MainWindowViewModel).GetProperty("AnotherBuildingInfo") != null)
// {
// _viewModel.AnotherBuildingInfo += "\n" + buildingInfo;
// }
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新建筑(泉水)失败: {ex.Message}");
// }
// }
// private void UpdateBuilding(MessageOfFarm building, string buildingType)
// {
// try
// {
// // 类似于其他建筑更新方法
// int gridX = building.X / 1000;
// int gridY = building.Y / 1000;
// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50)
// {
// _viewModel.MapVM.UpdateBuildingCell(
// gridX,
// gridY,
// building.TeamId == 1 ? "取经队" : "妖怪队",
// buildingType,
// building.Hp
// );
// string buildingInfo = $"{buildingType} 位置:({gridX},{gridY}) 血量:{building.Hp}";
// if (building.TeamId == 1 &&
// typeof(MainWindowViewModel).GetProperty("SomeBuildingInfo") != null)
// {
// _viewModel.SomeBuildingInfo += "\n" + buildingInfo;
// }
// else if (building.TeamId != 1 &&
// typeof(MainWindowViewModel).GetProperty("AnotherBuildingInfo") != null)
// {
// _viewModel.AnotherBuildingInfo += "\n" + buildingInfo;
// }
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新建筑(农场)失败: {ex.Message}");
// }
// }
// private void UpdateTrap(MessageOfTrap trap)
// {
// try
// {
// int gridX = trap.X / 1000;
// int gridY = trap.Y / 1000;
// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50)
// {
// string trapTypeName = trap.TrapType switch
// {
// TrapType.Hole => "陷阱坑",
// TrapType.Cage => "牢笼",
// _ => "未知陷阱"
// };
// _viewModel.MapVM.UpdateTrapCell(
// gridX,
// gridY,
// trap.TeamId == 1 ? "取经队" : "妖怪队",
// trapTypeName
// );
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新陷阱失败: {ex.Message}");
// }
// }
// private void UpdateResource(MessageOfEconomyResource resource)
// {
// try
// {
// int gridX = resource.X / 1000;
// int gridY = resource.Y / 1000;
// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50)
// {
// string resourceTypeName = resource.EconomyResourceType switch
// {
// EconomyResourceType.SmallEconomyResource => "小经济资源",
// EconomyResourceType.MediumEconomyResource => "中经济资源",
// EconomyResourceType.LargeEconomyResource => "大经济资源",
// _ => "未知资源"
// };
// _viewModel.MapVM.UpdateResourceCell(
// gridX,
// gridY,
// resourceTypeName,
// resource.Process
// );
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新经济资源失败: {ex.Message}");
// }
// }
// private void UpdateAdditionResource(MessageOfAdditionResource resource)
// {
// try
// {
// int gridX = resource.X / 1000;
// int gridY = resource.Y / 1000;
// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50)
// {
// string resourceName = GetAdditionResourceName(resource.AdditionResourceType);
// _viewModel.MapVM.UpdateAdditionResourceCell(
// gridX,
// gridY,
// resourceName,
// resource.Hp
// );
// }
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新附加资源失败: {ex.Message}");
// }
// }
// private string GetAdditionResourceName(AdditionResourceType type)
// {
// return type switch
// {
// AdditionResourceType.LifePool1 => "生命池(小)",
// AdditionResourceType.LifePool2 => "生命池(中)",
// AdditionResourceType.LifePool3 => "生命池(大)",
// AdditionResourceType.CrazyMan1 => "疯人(小)",
// AdditionResourceType.CrazyMan2 => "疯人(中)",
// AdditionResourceType.CrazyMan3 => "疯人(大)",
// AdditionResourceType.QuickStep => "神行步",
// AdditionResourceType.WideView => "千里眼",
// _ => "未知加成资源"
// };
// }
// private void UpdateMap(MessageOfMap map)
// {
// try
// {
// // 将地图数据转换为二维数组
// int[,] mapData = new int[50, 50];
// for (int i = 0; i < map.Rows.Count && i < 50; i++)
// {
// for (int j = 0; j < map.Rows[i].Cols.Count && j < 50; j++)
// {
// mapData[i, j] = (int)map.Rows[i].Cols[j];
// }
// }
// // 更新地图视图模型
// _viewModel.MapVM.UpdateMap(mapData);
// _logger.LogInfo("地图数据已更新");
// }
// catch (Exception ex)
// {
// _logger.LogError($"更新地图数据时出错: {ex.Message}");
// }
// }
// private string FormatGameTime(int milliseconds)
// {
// TimeSpan time = TimeSpan.FromMilliseconds(milliseconds);
// return $"{time.Minutes:00}:{time.Seconds:00}";
// }
// }
//}

View File

@ -3,6 +3,13 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using installer.Model;
using installer.Data;
using Protobuf;
using Google.Protobuf;
using System.Linq;
using System.Collections.Generic;
using debug_interface.Models;
using Avalonia.Media;
namespace debug_interface.ViewModels
{
@ -21,11 +28,18 @@ namespace debug_interface.ViewModels
[ObservableProperty]
private int blueScore = 0;
// --- 用于显示建筑摘要的属性 ---
[ObservableProperty]
private string someBuildingInfo = "";
private string buddhistTeamBuildingInfo = "取经队建筑: 无";
[ObservableProperty]
private string anotherBuildingInfo = "";
private string monstersTeamBuildingInfo = "妖怪队建筑: 无";
//[ObservableProperty]
//private string someBuildingInfo = "";
//[ObservableProperty]
//private string anotherBuildingInfo = "";
[ObservableProperty]
private int buddhistTeamEconomy = 0;
@ -37,25 +51,95 @@ namespace debug_interface.ViewModels
private MapViewModel mapVM;
// 团队角色集合
public ObservableCollection<CharacterViewModel> RedTeamCharacters { get; } = new();
public ObservableCollection<CharacterViewModel> BlueTeamCharacters { get; } = new();
public ObservableCollection<CharacterViewModel> BuddhistsTeamCharacters { get; } = new();
public ObservableCollection<CharacterViewModel> MonstersTeamCharacters { get; } = new();
// 构造函数
public MainWindowViewModel(Logger logger, ConfigData config) : base(logger)
//图例项集合
public ObservableCollection<LegendItem> MapLegendItems { get; } = new();
// 默认构造函数
public MainWindowViewModel() : base() // 调用基类构造函数
{
// 初始化MapViewModel
MapVM = new MapViewModel();
// 如果在设计模式下,可以添加一些测试数据
// 初始化占位角色
InitializePlaceholderCharacters();
InitializeMapLegend(); // <--- 调用填充图例的方法
// 如果在设计模式下,可以添加一些测试数据覆盖占位符
if (Avalonia.Controls.Design.IsDesignMode)
{
InitializeDesignTimeData();
InitializeDesignTimeData(); // 设计时数据可以覆盖部分占位符
}
}
// 带参数的构造函数 (如果使用)
// public MainWindowViewModel(Logger? logger, ConfigData? config) : base() // 调用基类构造函数
// {
// if (logger != null)
// myLogger = logger;
// // 初始化MapViewModel
// MapVM = new MapViewModel();
// // 初始化占位角色
// InitializePlaceholderCharacters();
// // 如果在设计模式下,可以添加一些测试数据
// if (Avalonia.Controls.Design.IsDesignMode)
// {
// InitializeDesignTimeData();
// }
// }
// 初始化占位符角色
private void InitializePlaceholderCharacters()
{
BuddhistsTeamCharacters.Clear();
MonstersTeamCharacters.Clear();
// PlayerID 0-5 为取经, 7-12 为妖怪? (规则说 PlayerID=0 是核心)
// 假设ID 0 是核心1-5 是队员
// 或者根据 CharacterType 来创建? 规则里 CharacterType 定义了角色
// 更好的方法是创建固定数量的占位符,然后用服务器数据填充
// 取经队 (唐僧 + 5个队员/猴子)
BuddhistsTeamCharacters.Add(CreatePlaceholderCharacter(0, "唐僧?", Protobuf.CharacterType.TangSeng)); // 核心
for (int i = 1; i <= 5; i++)
{
BuddhistsTeamCharacters.Add(CreatePlaceholderCharacter(i, $"取经队员{i}?", Protobuf.CharacterType.NullCharacterType));
}
// 启动UI更新定时器
StartUiUpdateTimer();
// 妖怪队 (九头元圣 + 5个队员/小妖)
MonstersTeamCharacters.Add(CreatePlaceholderCharacter(7, "九头元圣?", Protobuf.CharacterType.JiuLing)); // 核心 (假设ID=7?)
for (int i = 8; i <= 12; i++) // 假设ID 8-12
{
MonstersTeamCharacters.Add(CreatePlaceholderCharacter(i, $"妖怪队员{i}?", Protobuf.CharacterType.NullCharacterType));
}
}
// 创建单个占位符角色的辅助方法
private CharacterViewModel CreatePlaceholderCharacter(long id, string defaultName, Protobuf.CharacterType type = Protobuf.CharacterType.NullCharacterType)
{
return new CharacterViewModel
{
CharacterId = id, // 临时ID或标识符
Name = defaultName,
Hp = 0,
PosX = -1, // 初始位置无效
PosY = -1,
ActiveState = "未知",
// PassiveStates 和 EquipmentInventory 默认为空
};
}
// 设计时数据
private void InitializeDesignTimeData()
{
@ -63,33 +147,506 @@ namespace debug_interface.ViewModels
CurrentTime = "12:34";
RedScore = 50;
BlueScore = 30;
BuddhistTeamEconomy = 6000;
MonstersTeamEconomy = 10000;
// 添加一些测试角色
for (int i = 0; i < 3; i++)
// 更新部分占位符作为示例
if (BuddhistsTeamCharacters.Count > 0)
{
RedTeamCharacters.Add(new CharacterViewModel
{
CharacterId = i + 1,
Name = $"取经队角色{i + 1}",
Hp = 1000,
ActiveState = "空置"
});
var ts = BuddhistsTeamCharacters[0]; // 唐僧
ts.Name = "唐僧";
ts.Hp = 1000; // 假设最大血量
ts.ActiveState = "空置";
ts.PosX = 5; ts.PosY = 5; // 示例位置
}
if (BuddhistsTeamCharacters.Count > 1)
{
var swk = BuddhistsTeamCharacters[1];
swk.Name = "孙悟空";
swk.Hp = 200;
swk.ActiveState = "移动";
swk.PosX = 10; swk.PosY = 10;
}
if (MonstersTeamCharacters.Count > 0)
{
var jls = MonstersTeamCharacters[0]; // 九头
jls.Name = "九头元圣";
jls.Hp = 1000;
jls.ActiveState = "攻击";
jls.PosX = 40; jls.PosY = 40;
}
// ... 可以添加更多设计时数据
}
BlueTeamCharacters.Add(new CharacterViewModel
//private void InitializeDesignTimeData()
//{
// GameLog = "设计模式 - 模拟数据";
// CurrentTime = "12:34";
// RedScore = 50;
// BlueScore = 30;
// // 添加一些测试角色
// for (int i = 0; i < 3; i++)
// {
// BuddhistsTeamCharacters.Add(new CharacterViewModel
// {
// CharacterId = i + 1,
// Name = $"取经队角色{i + 1}",
// Hp = 1000,
// ActiveState = "空置"
// });
// MonstersTeamCharacters.Add(new CharacterViewModel
// {
// CharacterId = i + 101,
// Name = $"妖怪队角色{i + 1}",
// Hp = 1200,
// ActiveState = "移动"
// });
// }
//}
// 定时器更新方法
//protected override void OnTimerTick(object? sender, EventArgs e)
//{
// // 更新当前时间显示
// CurrentTime = DateTime.Now.ToString("HH:mm:ss");
//}
// // 更新当前时间显示 - 这个逻辑应该在 UpdateGameStatus 里基于服务器时间更新
// // CurrentTime = DateTime.Now.ToString("HH:mm:ss"); // 不再需要
public void UpdateCharacters()
{
// 记录已更新的角色ID以便处理未在消息中出现的角色可能死亡或离开视野
var updatedBuddhistIds = new HashSet<long>();
var updatedMonsterIds = new HashSet<long>();
lock (drawPicLock) // 确保线程安全
{
foreach (var data in listOfCharacters) // listOfCharacters 来自 ViewModelBase
{
CharacterId = i + 101,
Name = $"妖怪队角色{i + 1}",
Hp = 1200,
ActiveState = "移动"
});
CharacterViewModel? targetCharacter = null;
ObservableCollection<CharacterViewModel>? targetList = null;
// 根据 TeamId 选择列表并查找角色
if (data.TeamId == (int)PlayerTeam.BuddhistsTeam)
{
targetList = BuddhistsTeamCharacters;
targetCharacter = targetList.FirstOrDefault(c => c.CharacterId == data.PlayerId);
updatedBuddhistIds.Add(data.PlayerId);
}
else if (data.TeamId == (int)PlayerTeam.MonstersTeam)
{
targetList = MonstersTeamCharacters;
targetCharacter = targetList.FirstOrDefault(c => c.CharacterId == data.PlayerId);
updatedMonsterIds.Add(data.PlayerId);
}
// 如果找到了对应的角色ViewModel (应该总能找到,因为有占位符)
if (targetCharacter != null)
{
// 更新角色信息
targetCharacter.Name = GetCharacterName(data.CharacterType); // 更新名字
targetCharacter.Hp = data.Hp;
targetCharacter.PosX = data.X / 1000; // 转换为网格坐标 X
targetCharacter.PosY = data.Y / 1000; // 转换为网格坐标 Y
targetCharacter.ActiveState = data.CharacterActiveState.ToString(); // 主动状态
// 清空并更新被动状态
targetCharacter.PassiveStates.Clear();
if (data.BlindState != CharacterState.NullCharacterState)
targetCharacter.PassiveStates.Add($"致盲({data.BlindTime}ms)"); // 显示时间
if (data.StunnedState != CharacterState.NullCharacterState)
targetCharacter.PassiveStates.Add($"眩晕({data.StunnedTime}ms)");
if (data.InvisibleState != CharacterState.NullCharacterState)
targetCharacter.PassiveStates.Add($"隐身({data.InvisibleTime}ms)");
if (data.BurnedState != CharacterState.NullCharacterState)
targetCharacter.PassiveStates.Add($"燃烧({data.BurnedTime}ms)");
// 添加其他状态,如击退、定身、死亡等
if (data.CharacterPassiveState == CharacterState.KnockedBack) // 使用 CharacterPassiveState
targetCharacter.PassiveStates.Add("击退"); // 击退通常是瞬时的,没有时间
if (data.CharacterPassiveState == CharacterState.Stunned) // 确认 Stunned 是用 StunnedState 还是 PassiveState
targetCharacter.PassiveStates.Add($"定身({data.StunnedTime}ms)"); // 假设 Stunned 对应定身
if (data.DeceasedState != CharacterState.NullCharacterState) // 死亡状态
targetCharacter.PassiveStates.Add("已死亡");
// 清空并更新装备
targetCharacter.EquipmentInventory.Clear();
if (data.ShieldEquipment > 0) // 护盾显示剩余值
targetCharacter.EquipmentInventory.Add(new EquipmentItem("护盾", data.ShieldEquipment));
if (data.ShoesEquipment > 0) // 鞋子是状态,显示剩余时间
targetCharacter.PassiveStates.Add($"鞋子({~(data.ShoesEquipmentTime / 1000)}s)"); // 显示秒
// 其他装备如净化、隐身、狂暴是 Buff 或一次性效果,看是否需要在装备栏显示
if (data.PurificationEquipmentTime > 0)
targetCharacter.PassiveStates.Add($"净化({~(data.PurificationEquipmentTime / 1000)}s)"); // 显示秒
if (data.AttackBuffTime > 0)
targetCharacter.PassiveStates.Add($"攻击Buff({~(data.AttackBuffTime / 1000)}s)");
if (data.SpeedBuffTime > 0)
targetCharacter.PassiveStates.Add($"移速Buff({~(data.SpeedBuffTime / 1000)}s)");
if (data.VisionBuffTime > 0)
targetCharacter.PassiveStates.Add($"视野Buff({~(data.VisionBuffTime / 1000)}s)");
// 触发 PropertyChanged 以更新UI绑定 (虽然 ObservableObject 会自动做,但显式调用一下没坏处)(不行嘻嘻)
//targetCharacter.OnPropertyChanged(nameof(CharacterViewModel.DisplayStates));
//targetCharacter.OnPropertyChanged(nameof(CharacterViewModel.DisplayEquipments));
}
// else
// {
// // 如果没找到,可能是新的 PlayerID 或逻辑错误,可以记录日志
// myLogger?.LogWarning($"未找到 PlayerID {data.PlayerId} (Team {data.TeamId}) 的占位符 ViewModel。");
// }
}
// 处理未在消息中出现的角色(可能死亡、离开视野或未出生)
// 我们可以选择将他们标记为“未知”或“死亡”(如果之前是活的)
ResetUnseenCharacters(BuddhistsTeamCharacters, updatedBuddhistIds);
ResetUnseenCharacters(MonstersTeamCharacters, updatedMonsterIds);
}
}
// 定时器更新方法
protected override void OnTimerTick(object? sender, EventArgs e)
// 重置未出现在当前帧消息中的角色状态
private void ResetUnseenCharacters(ObservableCollection<CharacterViewModel> teamList, HashSet<long> seenIds)
{
// 更新当前时间显示
CurrentTime = DateTime.Now.ToString("HH:mm:ss");
foreach (var character in teamList)
{
if (!seenIds.Contains(character.CharacterId))
{
// 如果角色之前是“活”的例如有HP不在地图外现在可能死亡或离开视野
// if (character.Hp > 0 && character.PosX >= 0)
// {
// // 这里可以根据游戏逻辑判断是标记为“未知”、“离开视野”还是保留最后状态
// // 简单起见,我们先重置部分状态,或者标记为未知
// character.ActiveState = "未知/离线";
// character.Hp = 0; // 或者保持最后血量?
// character.PosX = -1; // 移出地图
// character.PosY = -1;
// character.PassiveStates.Clear();
// character.EquipmentInventory.Clear();
// }
// 或者,如果角色本来就是占位符,则保持占位符状态
if (character.Name.EndsWith("?")) // 检查是否是初始占位符
{
// 保持占位符状态不变
}
else // 如果是之前更新过的角色,现在看不到了
{
// 决定如何处理,例如标记为死亡或未知
if (!character.PassiveStates.Contains("已死亡")) // 如果之前没死
{
character.ActiveState = "视野丢失/死亡?";
// character.PosX = -1; // 不再地图上显示
// character.PosY = -1;
// 可以不清空血量,显示最后状态
}
}
}
}
}
//不再需要这个方法,更新逻辑合并到 UpdateCharacters
//private void UpdateOrAddCharacter(ObservableCollection<CharacterViewModel> list, CharacterViewModel newCharacter)
//{
// // 尝试找到现有角色并更新
// var existing = list.FirstOrDefault(c => c.CharacterId == newCharacter.CharacterId);
// if (existing != null)
// {
// existing.Hp = newCharacter.Hp;
// existing.PosX = newCharacter.PosX;
// existing.PosY = newCharacter.PosY;
// existing.ActiveState = newCharacter.ActiveState;
// existing.PassiveStates.Clear();
// foreach (var state in newCharacter.PassiveStates)
// existing.PassiveStates.Add(state);
// existing.EquipmentInventory.Clear();
// foreach (var equip in newCharacter.EquipmentInventory)
// existing.EquipmentInventory.Add(equip);
// }
// else
// {
// // 添加新角色
// list.Add(newCharacter);
// }
//}
// 获取角色名称的辅助方法 (根据 Proto MessageType.proto)
private string GetCharacterName(CharacterType type)
{
return type switch
{
CharacterType.TangSeng => "唐僧",
CharacterType.SunWukong => "孙悟空",
CharacterType.ZhuBajie => "猪八戒",
CharacterType.ShaWujing => "沙悟净",
CharacterType.BaiLongma => "白龙马",
CharacterType.Monkid => "猴子猴孙", //
CharacterType.JiuLing => "九头元圣", //
CharacterType.HongHaier => "红孩儿",
CharacterType.NiuMowang => "牛魔王",
CharacterType.TieShan => "铁扇公主", //
CharacterType.ZhiZhujing => "蜘蛛精",
CharacterType.Pawn => "无名小妖", //
CharacterType.NullCharacterType => "未知类型",
_ => $"未知 ({type})" // 处理枚举未定义的值
};
}
//private string GetCharacterName(CharacterType type)
//{
// return type switch
// {
// CharacterType.TangSeng => "唐僧",
// CharacterType.SunWukong => "孙悟空",
// CharacterType.ZhuBajie => "猪八戒",
// CharacterType.ShaWujing => "沙悟净",
// CharacterType.BaiLongma => "白龙马",
// CharacterType.Monkid => "小僧",
// CharacterType.JiuLing => "九灵",
// CharacterType.HongHaier => "红孩儿",
// CharacterType.NiuMowang => "牛魔王",
// CharacterType.TieShan => "铁扇",
// CharacterType.ZhiZhujing => "蜘蛛精",
// CharacterType.Pawn => "小妖",
// _ => "未知角色"
// };
//}
// 地图元素更新方法
public void UpdateMapElements()
{
// 先清除地图上旧的角色标记 (如果 MapViewModel 没有自动处理)
// MapVM.ClearCharacterDisplay(); // 需要在 MapViewModel 实现此方法
lock (drawPicLock)
{
// 更新地图地形 (如果需要,基于 MapMessage)
// MapVM.UpdateMap(receivedMapMessage); // 假设 receivedMapMessage 在某处获得
// 清除动态元素的旧状态 (例如,一个格子之前是建筑,现在不是了)
// 最好在 MapViewModel 中处理:在更新前重置所有动态格子的状态为基础地形
// 更新兵营
foreach (var barracks in listOfBarracks)
{
MapVM.UpdateBuildingCell(
barracks.X / 1000,
barracks.Y / 1000,
barracks.TeamId == (int)PlayerTeam.BuddhistsTeam ? "取经队" : "妖怪队",
"兵营",
barracks.Hp
);
}
// 更新春泉
foreach (var spring in listOfSprings)
{
MapVM.UpdateBuildingCell(
spring.X / 1000,
spring.Y / 1000,
spring.TeamId == (int)PlayerTeam.BuddhistsTeam ? "取经队" : "妖怪队",
"泉水",
spring.Hp
);
}
// 更新农场
foreach (var farm in listOfFarms)
{
MapVM.UpdateBuildingCell(
farm.X / 1000,
farm.Y / 1000,
farm.TeamId == (int)PlayerTeam.BuddhistsTeam ? "取经队" : "妖怪队",
"农场",
farm.Hp
);
}
// 更新陷阱
foreach (var trap in listOfTraps)
{
MapVM.UpdateTrapCell(
trap.X / 1000,
trap.Y / 1000,
trap.TeamId == (int)PlayerTeam.BuddhistsTeam ? "取经队" : "妖怪队",
trap.TrapType == TrapType.Hole ? "陷阱(坑洞)" : "陷阱(牢笼)" // 区分类型
);
}
// 更新经济资源
foreach (var resource in listOfEconomyResources)
{
MapVM.UpdateResourceCell(
resource.X / 1000,
resource.Y / 1000,
GetEconomyResourceType(resource.EconomyResourceType),
resource.Process // 传入剩余量
);
}
// 更新加成资源
foreach (var resource in listOfAdditionResources)
{
MapVM.UpdateAdditionResourceCell(
resource.X / 1000,
resource.Y / 1000,
GetAdditionResourceType(resource.AdditionResourceType),
resource.Hp // 传入Boss血量
);
}
// 更新地图上的角色位置标记 (现在由 MapView 直接处理)
// MapVM.UpdateCharacterPositions(BuddhistsTeamCharacters, MonstersTeamCharacters); // 不再需要 MapViewModel 处理这个
}
}
// 获取经济资源类型名称 (根据 Proto)
private string GetEconomyResourceType(EconomyResourceType type)
{
// 规则中没有区分大小,但 Proto 里有
return type switch
{
EconomyResourceType.SmallEconomyResource => "经济资源(小)",
EconomyResourceType.MediumEconomyResource => "经济资源(中)",
EconomyResourceType.LargeEconomyResource => "经济资源(大)",
_ => "经济资源(未知)"
};
}
// 获取加成资源类型名称
private string GetAdditionResourceType(AdditionResourceType type)
{
return type switch
{
AdditionResourceType.LifePool1 => "生命之泉(1)",
AdditionResourceType.LifePool2 => "生命之泉(2)",
AdditionResourceType.LifePool3 => "生命之泉(3)",
AdditionResourceType.CrazyMan1 => "狂战士之力(1)",
AdditionResourceType.CrazyMan2 => "狂战士之力(2)",
AdditionResourceType.CrazyMan3 => "狂战士之力(3)",
AdditionResourceType.QuickStep => "疾步之灵",
AdditionResourceType.WideView => "视野之灵",
_ => "加成资源(未知)"
};
}
public void UpdateGameStatus()
{
// 清空旧的建筑信息,准备重新生成
BuddhistTeamBuildingInfo = "取经队建筑: ";
MonstersTeamBuildingInfo = "妖怪队建筑: ";
lock (drawPicLock) // 确保访问列表时线程安全
{
if (listOfAll.Count > 0)
{
var data = listOfAll[0]; // 全局状态信息
CurrentTime = FormatGameTime(data.GameTime); // 使用服务器时间
RedScore = data.BuddhistsTeamScore;
BlueScore = data.MonstersTeamScore;
BuddhistTeamEconomy = data.BuddhistsTeamEconomy;
MonstersTeamEconomy = data.MonstersTeamEconomy;
// 更新建筑摘要信息
UpdateBuildingSummary(); // 调用新的方法生成摘要
// // 移除旧的、不准确的建筑信息显示
// SomeBuildingInfo = $"取经队经济: {data.BuddhistsTeamEconomy}, 英雄血量: {data.BuddhistsHeroHp}"; // 这个信息不准确唐僧没有HP字段
// AnotherBuildingInfo = $"妖怪队经济: {data.MonstersTeamEconomy}, 英雄血量: {data.MonstersHeroHp}"; // 九头也没有
}
else
{
// 如果没有全局信息,可能需要显示默认值或“等待数据”
CurrentTime = "00:00";
RedScore = 0;
BlueScore = 0;
BuddhistTeamEconomy = 0;
MonstersTeamEconomy = 0;
BuddhistTeamBuildingInfo += "无";
MonstersTeamBuildingInfo += "无";
}
}
}
// 格式化游戏时间 (毫秒 -> mm:ss)
private string FormatGameTime(int gameTimeInMilliseconds)
{
// ... (保持不变)
int totalSeconds = gameTimeInMilliseconds / 1000;
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
return $"{minutes:D2}:{seconds:D2}";
}
// 辅助方法根据建筑类型获取最大HP(根据游戏规则文档)
public int GetBuildingMaxHp(string buildingType)
{
return buildingType switch
{
"兵营" => 600,
"泉水" => 300,
"农场" => 400,
_ => 0 // 其他或未知类型
};
}
// 更新建筑摘要信息的方法
private void UpdateBuildingSummary()
{
// 使用 Linq 对建筑列表进行分组和计数
var buddhistBuildings = listOfBarracks.Where(b => b.TeamId == (int)PlayerTeam.BuddhistsTeam).Select(b => $"兵营({b.Hp}/{GetBuildingMaxHp("")})")
.Concat(listOfSprings.Where(s => s.TeamId == (int)PlayerTeam.BuddhistsTeam).Select(s => $"泉水({s.Hp}/{GetBuildingMaxHp("")})"))
.Concat(listOfFarms.Where(f => f.TeamId == (int)PlayerTeam.BuddhistsTeam).Select(f => $"农场({f.Hp}/{GetBuildingMaxHp("")})"));
var monsterBuildings = listOfBarracks.Where(b => b.TeamId == (int)PlayerTeam.MonstersTeam).Select(b => $"兵营({b.Hp}/{GetBuildingMaxHp("")})")
.Concat(listOfSprings.Where(s => s.TeamId == (int)PlayerTeam.MonstersTeam).Select(s => $"泉水({s.Hp}/{GetBuildingMaxHp("")})"))
.Concat(listOfFarms.Where(f => f.TeamId == (int)PlayerTeam.MonstersTeam).Select(f => $"农场({f.Hp}/{GetBuildingMaxHp("")})"));
BuddhistTeamBuildingInfo = "取经队建筑: " + (buddhistBuildings.Any() ? string.Join(", ", buddhistBuildings) : "无");
MonstersTeamBuildingInfo = "妖怪队建筑: " + (monsterBuildings.Any() ? string.Join(", ", monsterBuildings) : "无");
// 可以在这里加入陷阱的统计信息(如果需要)
// var buddhistTraps = listOfTraps.Count(t => t.TeamId == (int)PlayerTeam.BuddhistsTeam);
// var monsterTraps = listOfTraps.Count(t => t.TeamId == (int)PlayerTeam.MonstersTeam);
// BuddhistTeamBuildingInfo += $", 陷阱: {buddhistTraps}";
// MonstersTeamBuildingInfo += $", 陷阱: {monsterTraps}";
}
// 填充图例数据的方法
private void InitializeMapLegend()
{
MapLegendItems.Clear(); // 清空旧数据
// 添加基础地形
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.Cyan), "家园"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.White), "空地", Brushes.LightGray, 1)); // 白色加边框
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.DarkGray), "障碍物"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.LightGreen), "草丛"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.LightGray), "未知区域", Brushes.DimGray, 1));
// 添加建筑 (队伍)
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.DarkRed), "取经队建筑"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.DarkBlue), "妖怪队建筑"));
// 添加陷阱 (队伍)
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.IndianRed), "取经队陷阱"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.CornflowerBlue), "妖怪队陷阱"));
// 添加经济资源
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.Gold), "经济资源"));
// 添加加成资源 (根据 MapViewModel 中的颜色)
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.LightPink), "加成 (生命泉)"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.OrangeRed), "加成 (狂战士)"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.LightSkyBlue), "加成 (疾步灵)"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.MediumPurple), "加成 (视野灵)"));
MapLegendItems.Add(new LegendItem(new SolidColorBrush(Colors.Purple), "加成 (未知)"));
// 根据需要添加更多条目
}
}
}

View File

@ -1,7 +1,25 @@
using System.Collections.ObjectModel;
//MapViewModel.cs
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Avalonia.Media;
using Avalonia.Threading;
using Grpc.Core;
using installer.Data;
using installer.Model;
using debug_interface.Models;
using System.Collections.Generic;
using Avalonia.Controls.Shapes;
using Avalonia.Controls;
using Google.Protobuf;
using System.Linq; // 解决Concat错误
using Protobuf;
namespace debug_interface.ViewModels
{
@ -12,190 +30,435 @@ namespace debug_interface.ViewModels
[ObservableProperty]
private ObservableCollection<MapCell> mapCells = new();
// 添加缺失的字段
private Canvas? characterCanvas;
private Dictionary<string, Grid> characterElements = new Dictionary<string, Grid>();
private MainWindowViewModel? viewModel;
public MapViewModel()
{
// 初始化地图单元格
InitializeMapCells();
}
private void InitializeMapCells()
// 设置Canvas和ViewModel引用的方法
public void SetCanvasAndViewModel(Canvas canvas, MainWindowViewModel vm)
{
MapCells.Clear();
for (int i = 0; i < GridSize * GridSize; i++)
characterCanvas = canvas;
viewModel = vm;
RefreshCharacters();
}
// 更新角色位置的方法
private void UpdateCharacterPosition(Grid element, int x, int y)
{
if (characterCanvas == null) return;
// 更新位置
Canvas.SetLeft(element, x);
Canvas.SetTop(element, y);
}
private void InitializeCharacters(IEnumerable<CharacterViewModel> characters, Color color)
{
if (characterCanvas == null || viewModel == null) return;
foreach (var character in characters)
{
MapCells.Add(new MapCell
var id = $"{color.ToString()}_{character.Name}";
if (!characterElements.ContainsKey(id))
{
Row = i / GridSize,
Col = i % GridSize,
CellType = MapCellType.Empty,
DisplayText = "",
DisplayColor = new SolidColorBrush(Colors.White),
BackgroundColor = new SolidColorBrush(Colors.LightGray)
});
var grid = new Grid { Width = 15, Height = 15 };
var ellipse = new Ellipse
{
Width = 15,
Height = 15,
Fill = new SolidColorBrush(Colors.White),
Stroke = new SolidColorBrush(color),
StrokeThickness = 2,
Tag = character.Name
};
grid.Children.Add(ellipse);
ToolTip.SetTip(grid, character.Name);
characterCanvas.Children.Add(grid);
characterElements[id] = grid;
}
var element = characterElements[id];
UpdateCharacterPosition(element, character.PosX, character.PosY);
character.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(CharacterViewModel.PosX) || e.PropertyName == nameof(CharacterViewModel.PosY))
{
UpdateCharacterPosition(element, character.PosX, character.PosY);
}
};
}
}
// 更新整个地图
public void UpdateMap(int[,] mapData)
//初始化地图单元格
public void InitializeMapCells()
{
MapCells.Clear();
for (int i = 0; i < GridSize; i++)
{
for (int j = 0; j < GridSize; j++)
{
MapCells.Add(new MapCell
{
CellX = i,
CellY = j,
CellType = MapCellType.OpenLand,
DisplayColor = new SolidColorBrush(Colors.White),
DisplayText = ""
});
}
}
}
// 更新整个地图
public void UpdateMap(MessageOfMap mapMessage)
{
for (int i = 0; i < mapMessage.Rows.Count && i < GridSize; i++)
{
for (int j = 0; j < mapMessage.Rows[i].Cols.Count && j < GridSize; j++)
{
int index = i * GridSize + j;
if (index < MapCells.Count)
{
int cellType = mapData[i, j];
UpdateCellType(MapCells[index], cellType);
UpdateCellType(i, j, mapMessage.Rows[i].Cols[j]);
}
}
}
OnPropertyChanged(nameof(MapCells));
}
private void UpdateCellType(MapCell cell, int cellType)
private void UpdateCellType(int x, int y, PlaceType placeType)
{
// 根据cellType设置单元格属性
// 这里简化处理
switch (cellType)
{
case 0: // 空地
cell.CellType = MapCellType.Empty;
cell.BackgroundColor = new SolidColorBrush(Colors.White);
break;
case 1: // 障碍物
cell.CellType = MapCellType.Obstacle;
cell.BackgroundColor = new SolidColorBrush(Colors.DarkGray);
break;
case 2: // 资源
cell.CellType = MapCellType.Resource;
cell.BackgroundColor = new SolidColorBrush(Colors.Green);
break;
default:
cell.CellType = MapCellType.Empty;
cell.BackgroundColor = new SolidColorBrush(Colors.LightGray);
break;
}
}
// 更新角色位置
public void UpdateCharacterPosition(long characterId, long teamId, int x, int y, string name)
{
// 简化实现
int index = x * GridSize + y;
if (index >= 0 && index < MapCells.Count)
{
MapCells[index].DisplayText = name.Substring(0, 1);
MapCells[index].DisplayColor = teamId == 1
? new SolidColorBrush(Colors.Red)
: new SolidColorBrush(Colors.Blue);
var cell = MapCells[index];
cell.DisplayText = ""; // 重置文本
cell.ToolTipText = ""; // 重置提示
OnPropertyChanged(nameof(MapCells));
// 根据地形类型设置单元格属性
switch (placeType)
{
case PlaceType.Home: // 出生点通常也是建筑或特殊区域
cell.CellType = MapCellType.Building; // 假设是建筑
cell.DisplayColor = new SolidColorBrush(Colors.Cyan); // 特殊颜色标记出生点
cell.ToolTipText = "家园";
break;
case PlaceType.Space:
cell.CellType = MapCellType.OpenLand;
cell.DisplayColor = new SolidColorBrush(Colors.White);
cell.ToolTipText = "空地";
break;
case PlaceType.Barrier:
cell.CellType = MapCellType.Obstacle;
cell.DisplayColor = new SolidColorBrush(Colors.DarkGray);
cell.ToolTipText = "障碍物";
break;
case PlaceType.Bush:
cell.CellType = MapCellType.Grass;
cell.DisplayColor = new SolidColorBrush(Colors.LightGreen);
cell.ToolTipText = "草丛";
break;
// 资源和建筑类型由特定的 UpdateXXX 方法处理,这里不再覆盖颜色和类型
// case PlaceType.EconomyResource:
// case PlaceType.AdditionResource:
// case PlaceType.Construction:
// case PlaceType.Trap:
// break; // 由其他方法设置详细信息
default:
cell.CellType = MapCellType.OpenLand; // 未知视为OpenLand
cell.DisplayColor = new SolidColorBrush(Colors.LightGray); // 用浅灰标记未知
cell.ToolTipText = "空地";
break;
}
// 如果placeType是资源/建筑/陷阱,后续的 UpdateXXX 会覆盖这里的设置
}
}
//private void UpdateCellType(int x, int y, PlaceType placeType)
//{
// int index = x * GridSize + y;
// if (index >= MapCells.Count) return;
// // 根据地形类型设置单元格属性
// switch (placeType)
// {
// case PlaceType.Home:
// MapCells[index].CellType = MapCellType.Building;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.Gold);
// break;
// case PlaceType.Space:
// MapCells[index].CellType = MapCellType.OpenLand;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.White);
// break;
// case PlaceType.Barrier:
// MapCells[index].CellType = MapCellType.Obstacle;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.DarkGray);
// break;
// case PlaceType.Bush:
// MapCells[index].CellType = MapCellType.Grass;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.LightGreen);
// break;
// case PlaceType.EconomyResource:
// MapCells[index].CellType = MapCellType.Resource;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.Gold);
// break;
// case PlaceType.AdditionResource:
// MapCells[index].CellType = MapCellType.Resource;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.Purple);
// break;
// case PlaceType.Construction:
// MapCells[index].CellType = MapCellType.Building;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.Brown);
// break;
// case PlaceType.Trap:
// MapCells[index].CellType = MapCellType.Obstacle;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.Red);
// break;
// default:
// MapCells[index].CellType = MapCellType.OpenLand;
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.LightGray);
// break;
// }
//}
public void UpdateCharacterPositions(IEnumerable<CharacterViewModel> buddhistsTeam, IEnumerable<CharacterViewModel> monstersTeam)
{
// 使用System.Linq的Concat扩展方法
var allCharacters = buddhistsTeam.Concat(monstersTeam);
foreach (var character in allCharacters)
{
int index = (int)(character.PosX * GridSize + character.PosY);
if (index >= 0 && index < MapCells.Count)
{
MapCells[index].DisplayText = character.Name;
}
}
}
// 更新建筑
public void UpdateBuildingCell(int x, int y, string team, string buildingType, int hp)
{
int index = x * GridSize + y;
if (index >= 0 && index < MapCells.Count)
{
MapCells[index].CellType = MapCellType.Building;
MapCells[index].DisplayText = buildingType.Substring(0, 1);
MapCells[index].DisplayColor = team == "取经队"
var cell = MapCells[index];
cell.CellType = MapCellType.Building;
//cell.DisplayText = buildingType.Substring(0, 1); // 先用文字代替后面修改为显示HP
cell.DisplayColor = team == "取经队"
? new SolidColorBrush(Colors.DarkRed)
: new SolidColorBrush(Colors.DarkBlue);
OnPropertyChanged(nameof(MapCells));
// 更新HP和Tooltip
// 注意游戏规则中未明确建筑的最大血量这里用传入的hp作为当前值假设需要一个最大值才能显示 current/max
// 暂时只显示类型和当前HP
// TODO: 获取建筑的最大HP (可能需要从游戏规则或新消息字段获取)
int maxHp = GetBuildingMaxHp(buildingType); // 需要实现这个辅助方法
cell.DisplayText = $"{hp}/{maxHp}";
cell.ToolTipText = $"类型: {buildingType}\n队伍: {team}\n血量: {hp}/{maxHp}";
// 触发UI更新 (如果MapCell的属性变更没有自动通知)
// OnPropertyChanged(nameof(MapCells)); // ObservableCollection通常会自动处理子项变更通知但如果直接修改子项属性有时需要手动触发
}
}
//public void UpdateBuildingCell(int x, int y, string team, string buildingType, int hp)
//{
// int index = x * GridSize + y;
// if (index >= 0 && index < MapCells.Count)
// {
// MapCells[index].CellType = MapCellType.Building;
// MapCells[index].DisplayText = buildingType.Substring(0, 1);
// MapCells[index].DisplayColor = team == "取经队"
// ? new SolidColorBrush(Colors.DarkRed)
// : new SolidColorBrush(Colors.DarkBlue);
// OnPropertyChanged(nameof(MapCells));
// }
//}
// 更新陷阱
public void UpdateTrapCell(int x, int y, string team, string trapType)
{
int index = x * GridSize + y;
if (index >= 0 && index < MapCells.Count)
{
MapCells[index].CellType = MapCellType.Obstacle;
MapCells[index].DisplayText = trapType.Substring(0, 1);
MapCells[index].DisplayColor = team == "取经队"
? new SolidColorBrush(Colors.IndianRed)
var cell = MapCells[index];
cell.CellType = MapCellType.Trap; // 使用新的类型
//cell.DisplayText = trapType.Substring(0, 1);
cell.DisplayColor = team == "取经队"
? new SolidColorBrush(Colors.IndianRed) // 可以为不同陷阱设置不同颜色
: new SolidColorBrush(Colors.CornflowerBlue);
OnPropertyChanged(nameof(MapCells));
// 更新Tooltip (陷阱没有血量)
cell.DisplayText = ""; // 陷阱通常不显示血量文本
cell.ToolTipText = $"类型: {trapType}\n队伍: {team}";
// OnPropertyChanged(nameof(MapCells));
}
}
//public void UpdateTrapCell(int x, int y, string team, string trapType)
//{
// int index = x * GridSize + y;
// if (index >= 0 && index < MapCells.Count)
// {
// MapCells[index].CellType = MapCellType.Obstacle;
// MapCells[index].DisplayText = trapType.Substring(0, 1);
// MapCells[index].DisplayColor = team == "取经队"
// ? new SolidColorBrush(Colors.IndianRed)
// : new SolidColorBrush(Colors.CornflowerBlue);
// OnPropertyChanged(nameof(MapCells));
// }
//}
// 更新资源
public void UpdateResourceCell(int x, int y, string resourceType, int process)
public void UpdateResourceCell(int x, int y, string resourceType, int process) // process 是采集进度/剩余量
{
int index = x * GridSize + y;
if (index >= 0 && index < MapCells.Count)
{
MapCells[index].CellType = MapCellType.Resource;
MapCells[index].DisplayText = process.ToString();
MapCells[index].DisplayColor = new SolidColorBrush(Colors.DarkGreen);
var cell = MapCells[index];
cell.CellType = MapCellType.Resource;
// cell.DisplayText = process.ToString(); // 显示剩余量
cell.DisplayColor = new SolidColorBrush(Colors.Gold); // 经济资源用金色
OnPropertyChanged(nameof(MapCells));
// 更新HP和Tooltip
int maxResource = 10000; // 根据规则经济资源上限1w
cell.DisplayText = $"{process}/{maxResource}";
cell.ToolTipText = $"类型: {resourceType}\n剩余量: {process}/{maxResource}";
// OnPropertyChanged(nameof(MapCells));
}
}
//public void UpdateResourceCell(int x, int y, string resourceType, int process)
//{
// int index = x * GridSize + y;
// if (index >= 0 && index < MapCells.Count)
// {
// MapCells[index].CellType = MapCellType.Resource;
// MapCells[index].DisplayText = process.ToString();
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.DarkGreen);
// OnPropertyChanged(nameof(MapCells));
// }
//}
// 更新额外资源
public void UpdateAdditionResourceCell(int x, int y, string resourceName, int value)
public void UpdateAdditionResourceCell(int x, int y, string resourceName, int hp) // hp 是 Boss 血量
{
int index = x * GridSize + y;
if (index >= 0 && index < MapCells.Count)
{
MapCells[index].CellType = MapCellType.Resource;
MapCells[index].DisplayText = value.ToString();
var cell = MapCells[index];
cell.CellType = MapCellType.Resource; // 仍然是资源类型
// cell.DisplayText = hp.ToString(); // 显示Boss血量
// 根据资源类型选择颜色
if (resourceName.Contains("生命池"))
if (resourceName.Contains("生命")) // 规则是生命之泉
{
MapCells[index].DisplayColor = new SolidColorBrush(Colors.LightGreen);
cell.DisplayColor = new SolidColorBrush(Colors.LightPink); // 淡粉色
}
else if (resourceName.Contains("疯人"))
else if (resourceName.Contains("狂战") || resourceName.Contains("疯人")) // 规则是狂战士之力
{
MapCells[index].DisplayColor = new SolidColorBrush(Colors.OrangeRed);
cell.DisplayColor = new SolidColorBrush(Colors.OrangeRed); // 橙红色
}
else if (resourceName.Contains("疾步") || resourceName.Contains("快步")) // 规则是疾步之灵
{
cell.DisplayColor = new SolidColorBrush(Colors.LightSkyBlue); // 天蓝色
}
else if (resourceName.Contains("视野") || resourceName.Contains("广视")) // 规则是视野之灵
{
cell.DisplayColor = new SolidColorBrush(Colors.MediumPurple); // 紫色
}
else
{
MapCells[index].DisplayColor = new SolidColorBrush(Colors.Purple);
cell.DisplayColor = new SolidColorBrush(Colors.Purple); // 未知用紫色
}
OnPropertyChanged(nameof(MapCells));
// 更新HP和Tooltip
// TODO: 获取加成资源Boss的最大HP (可能需要从游戏规则或新消息字段获取)
int maxHp = GetAdditionResourceMaxHp(resourceName); // 需要实现此辅助方法
cell.DisplayText = $"{hp}/{maxHp}";
cell.ToolTipText = $"类型: {resourceName} (Boss)\n血量: {hp}/{maxHp}";
// OnPropertyChanged(nameof(MapCells));
}
}
//public void UpdateAdditionResourceCell(int x, int y, string resourceName, int value)
//{
// int index = x * GridSize + y;
// if (index >= 0 && index < MapCells.Count)
// {
// MapCells[index].CellType = MapCellType.Resource;
// MapCells[index].DisplayText = value.ToString();
// // 根据资源类型选择颜色
// if (resourceName.Contains("生命池"))
// {
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.LightGreen);
// }
// else if (resourceName.Contains("疯人"))
// {
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.OrangeRed);
// }
// else
// {
// MapCells[index].DisplayColor = new SolidColorBrush(Colors.Purple);
// }
// OnPropertyChanged(nameof(MapCells));
// }
//}
private void RefreshCharacters()
{
if (characterCanvas == null || viewModel == null) return;
characterCanvas.Children.Clear();
characterElements.Clear();
InitializeCharacters(viewModel.BuddhistsTeamCharacters, Colors.Red);
InitializeCharacters(viewModel.MonstersTeamCharacters, Colors.Blue);
}
// 辅助方法根据建筑类型获取最大HP(根据游戏规则文档)
public int GetBuildingMaxHp(string buildingType)
{
return buildingType switch
{
"兵营" => 600,
"泉水" => 300,
"农场" => 400,
_ => 0 // 其他或未知类型
};
}
// 辅助方法根据资源名称获取Boss最大HP (根据游戏规则文档)
// 注意Boss血量会变化这里可能需要根据当前游戏阶段判断暂时用一个代表值
public int GetAdditionResourceMaxHp(string resourceName)
{
// 简化处理,取最大阶段的值或一个固定值
if (resourceName.Contains("生命")) return 400;
if (resourceName.Contains("狂战")) return 600;
if (resourceName.Contains("疾步")) return 300;
if (resourceName.Contains("视野")) return 300;
return 0; // 未知
}
}
//public enum MapCellType
//{
// Empty,
// Obstacle,
// Building,
// Resource,
// Character
//}
//public partial class MapCell : ViewModelBase
//{
// [ObservableProperty]
// private int row;
// [ObservableProperty]
// private int col;
// [ObservableProperty]
// private MapCellType cellType;
// [ObservableProperty]
// private string displayText = "";
// [ObservableProperty]
// private IBrush displayColor = new SolidColorBrush(Colors.Black);
// [ObservableProperty]
// private IBrush backgroundColor = new SolidColorBrush(Colors.White);
//}
}

View File

@ -1,70 +1,298 @@
using System;
using System;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using installer.Model; // 使用现有的Logger
using Grpc.Core;
using installer.Model;
using installer.Data;
using System.Collections.Generic;
using Google.Protobuf;
using Protobuf;
namespace debug_interface.ViewModels
{
public class ViewModelBase : ObservableObject
public partial class ViewModelBase : ObservableObject
{
// 使用既有的Logger
protected readonly Logger? logger;
// 用于 UI 刷新的定时器Avalonia 的 DispatcherTimer
private DispatcherTimer timerViewModel;
private int counterViewModelTest = 0;
// UI更新定时器
protected DispatcherTimer? dispatcherTimer;
// 使用 CommunityToolkit 的 ObservableProperty 自动实现 INotifyPropertyChanged
[ObservableProperty]
private string title = "THUAI8";
// 与服务器通信相关的字段
private long playerID;
private readonly string ip;
private readonly string port;
public readonly long CharacterIdTypeID;
private long teamID;
// 与服务器通信相关的 gRPC 客户端
private AvailableService.AvailableServiceClient? client;
private AsyncServerStreamingCall<MessageToClient>? responseStream;
private bool isSpectatorMode = false;
private bool isPlaybackMode = false;
// 日志记录
public Logger? myLogger; // ?表示可空
public Logger? lockGenerator;
// 服务器消息相关的字段
public List<MessageOfMonkeySkill> listOfPMonkeySkill = new();
public List<MessageOfCharacter> listOfCharacters = new();
public List<MessageOfBarracks> listOfBarracks = new();
public List<MessageOfTrap> listOfTraps = new();
public List<MessageOfSpring> listOfSprings = new();
public List<MessageOfFarm> listOfFarms = new();
public List<MessageOfEconomyResource> listOfEconomyResources = new();
public List<MessageOfAdditionResource> listOfAdditionResources = new();
public List<MessageOfAll> listOfAll = new();
public readonly object drawPicLock = new();
// 基本构造函数
public ViewModelBase()
{
// 子类可以初始化logger
}
// 带logger的构造函数
public ViewModelBase(Logger logger)
{
this.logger = logger;
}
// 启动UI更新定时器的方法
protected void StartUiUpdateTimer(double intervalMs = 50)
{
if (dispatcherTimer != null)
// 读取配置
try
{
dispatcherTimer.Stop();
var d = new ConfigData();
ip = d.Commands.IP;
port = d.Commands.Port;
playerID = Convert.ToInt64(d.Commands.PlayerID);
teamID = Convert.ToInt64(d.Commands.TeamID);
CharacterIdTypeID = Convert.ToInt64(d.Commands.CharacterType);
string? playbackFile = d.Commands.PlaybackFile;
double playbackSpeed = d.Commands.PlaybackSpeed;
//初始化日志记录器
myLogger = LoggerProvider.FromFile(System.IO.Path.Combine(d.InstallPath, "Logs", $"Client.{teamID}.{playerID}.log"));
lockGenerator = LoggerProvider.FromFile(System.IO.Path.Combine(d.InstallPath, "Logs", $"lock.{teamID}.{playerID}.log"));
// 使用 Avalonia 的 DispatcherTimer 定时刷新 UI
timerViewModel = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) };
timerViewModel.Tick += Refresh;
timerViewModel.Start();
//判断是否走回放模式
if (string.IsNullOrEmpty(d.Commands.PlaybackFile))
{
string[] comInfo = new string[]
{
ip,
port,
Convert.ToString(playerID),
Convert.ToString(teamID),
Convert.ToString(CharacterIdTypeID),
};
ConnectToServer(comInfo);
myLogger?.LogInfo("Trying to connect to server...");
OnReceive();
}
else
{
myLogger?.LogInfo($"PlaybackFile: {d.Commands.PlaybackFile}");
Playback(d.Commands.PlaybackFile, playbackSpeed);
}
}
dispatcherTimer = new DispatcherTimer
catch (Exception ex)
{
Interval = TimeSpan.FromMilliseconds(intervalMs)
};
dispatcherTimer.Tick += OnTimerTick;
dispatcherTimer.Start();
logger?.LogInfo($"UI更新定时器已启动间隔{intervalMs}毫秒");
}
// 停止定时器
protected void StopUiUpdateTimer()
{
if (dispatcherTimer != null)
{
dispatcherTimer.Stop();
dispatcherTimer.Tick -= OnTimerTick;
logger?.LogInfo("UI更新定时器已停止");
Console.WriteLine($"初始化ViewModelBase时出错: {ex.Message}");
}
}
// 子类应覆盖此方法来处理定时器事件
/// <summary>
/// 连接到服务器,初始化 gRPC 客户端
/// </summary>
/// <param name="comInfo">包含 ip、port、playerID、teamID、shipTypeID 的数组</param>
public void ConnectToServer(string[] comInfo)
{
if (isPlaybackMode) return;
if (Convert.ToInt64(comInfo[2]) > 2023)
{myLogger?.LogInfo("isSpectatorMode = true");
isSpectatorMode = true;
myLogger?.LogInfo("isSpectatorMode = true");
}
if (comInfo.Length != 5)
{
throw new Exception("Error Registration Information");
}
string connect = $"{comInfo[0]}:{comInfo[1]}";
Channel channel = new Channel(connect, ChannelCredentials.Insecure);
client = new AvailableService.AvailableServiceClient(channel);
CharacterMsg playerMsg = new CharacterMsg();
playerID = Convert.ToInt64(comInfo[2]);
playerMsg.CharacterId = playerID;
if (!isSpectatorMode)
{
teamID = Convert.ToInt64(comInfo[3]);
playerMsg.TeamId = teamID;
playerMsg.CharacterType = Convert.ToInt64(comInfo[4]) switch
{
1 => CharacterType.TangSeng,
2 => CharacterType.SunWukong,
3 => CharacterType.ZhuBajie,
4 => CharacterType.ShaWujing,
5 => CharacterType.BaiLongma,
6 => CharacterType.Monkid,
// 妖怪团队阵营角色
7 => CharacterType.JiuLing,
8 => CharacterType.HongHaier,
9 => CharacterType.NiuMowang,
10 => CharacterType.TieShan,
12 => CharacterType.Pawn,
_ => CharacterType.NullCharacterType
};
}
responseStream = client.AddCharacter(playerMsg);
myLogger?.LogInfo("ResponseStream created successfully.");
}
/// <summary>
/// 异步接收服务器流消息
/// </summary>
private async void OnReceive()
{
try
{
myLogger?.LogInfo("============= OnReceiving Server Stream ================");
while (responseStream != null && await responseStream.ResponseStream.MoveNext())
{
myLogger?.LogInfo("============= Receiving Server Stream ================");
lock (drawPicLock)
{
// 清除所有列表
listOfCharacters.Clear();
listOfBarracks.Clear();
listOfTraps.Clear();
listOfSprings.Clear();
listOfFarms.Clear();
listOfEconomyResources.Clear();
listOfAdditionResources.Clear();
listOfAll.Clear();
MessageToClient content = responseStream.ResponseStream.Current;
MessageOfMap mapMessage = new MessageOfMap();
bool hasMapMessage = false;
switch (content.GameState)
{
case GameState.GameStart:
myLogger?.LogInfo("============= GameState: Game Start ================");
break;
case GameState.GameRunning:
myLogger?.LogInfo("============= GameState: Game Running ================");
break;
case GameState.GameEnd:
myLogger?.LogInfo("============= GameState: Game End ================");
break;
}
// 处理所有消息
foreach (var obj in content.ObjMessage)
{
switch (obj.MessageOfObjCase)
{
case MessageOfObj.MessageOfObjOneofCase.CharacterMessage:
listOfCharacters.Add(obj.CharacterMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.BarracksMessage:
listOfBarracks.Add(obj.BarracksMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.TrapMessage:
listOfTraps.Add(obj.TrapMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.SpringMessage:
listOfSprings.Add(obj.SpringMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.FarmMessage:
listOfFarms.Add(obj.FarmMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.EconomyResourceMessage:
listOfEconomyResources.Add(obj.EconomyResourceMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.AdditionResourceMessage:
listOfAdditionResources.Add(obj.AdditionResourceMessage);
break;
case MessageOfObj.MessageOfObjOneofCase.MapMessage:
mapMessage = obj.MapMessage;
hasMapMessage = true;
break;
}
}
// 存储全局游戏状态
listOfAll.Add(content.AllMessage);
// 如果有地图消息并且当前ViewModel是MainWindowViewModel
if (hasMapMessage && this is MainWindowViewModel vm)
{
vm.MapVM.UpdateMap(mapMessage);
}
}
}
}
catch (Exception ex)
{
myLogger?.LogError($"接收消息时出错: {ex.Message}");
}
}
/// <summary>
/// 播放回放数据(示例方法)
/// </summary>
/// <param name="fileName">回放文件</param>
/// <param name="pbSpeed">播放速度</param>
private void Playback(string fileName, double pbSpeed = 2.0)
{
//myLogger.LogInfo($"Starting playback with file: {fileName} at speed {pbSpeed}");
// TODO: 实现回放逻辑
isPlaybackMode = true;
}
/// <summary>
/// 可被子类重写的定时器事件处理方法
/// </summary>
protected virtual void OnTimerTick(object? sender, EventArgs e)
{
// 默认实现为空
// 基类实现为空,子类可以重写
}
// 在UI线程上执行操作的便捷方法
protected async void RunOnUiThread(Action action)
/// <summary>
/// 定时器回调方法,用于刷新 UI 与游戏状态
/// </summary>
private void Refresh(object? sender, EventArgs e)
{
await Dispatcher.UIThread.InvokeAsync(action);
try
{
// 调用可被重写的方法
OnTimerTick(sender, e);
// 默认实现更新UI
if (this is MainWindowViewModel vm)
{
// 更新角色信息
vm.UpdateCharacters();
// 更新地图上的各种元素
vm.UpdateMapElements();
// 更新游戏状态信息
vm.UpdateGameStatus();
}
}
catch (Exception ex)
{
if (myLogger != null)
myLogger.LogError($"刷新UI时出错: {ex.Message}");
else
Console.WriteLine($"刷新UI时出错: {ex.Message}");
}
}
}
}

View File

@ -4,8 +4,9 @@
xmlns="https://github.com/avaloniaui"
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"
xmlns:local="clr-namespace:debug_interface.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:debug_interface.Models"
xmlns:vm="using:debug_interface.ViewModels"
x:Name="MainWindowElement"
Title="THUAI8 调试界面"
@ -35,40 +36,46 @@
<!-- 左侧区域 -->
<Grid Background="AliceBlue" RowDefinitions="*,*">
<Grid Grid.Row="0" RowDefinitions="1*,7*,3*">
<!-- 红方队伍标题行 -->
<StackPanel Grid.Row="0" Orientation="Horizontal">
<!-- 取经队区域 -->
<Grid Grid.Row="0" RowDefinitions="Auto,1*,Auto">
<!-- 取经队伍标题行 -->
<StackPanel
Grid.Row="0"
Margin="5"
Orientation="Horizontal">
<TextBlock
Margin="5"
Margin="0,0,10,0"
Background="LightGoldenrodYellow"
FontSize="16"
FontWeight="Bold"
Foreground="Brown"
Text="取经队伍" />
<TextBlock
Margin="5,2"
VerticalAlignment="Center"
FontSize="12"
Text="经济: 60000" />
Text="{Binding BuddhistTeamEconomy, StringFormat='经济: {0}'}" />
</StackPanel>
<!-- 红方团队角色信息 -->
<UniformGrid Grid.Row="1" Margin="2">
<ItemsControl ItemsSource="{Binding RedTeamCharacters}">
<!-- 取经团队角色信息 -->
<ScrollViewer
Grid.Row="1"
Margin="2"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding BuddhistsTeamCharacters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
<!-- 使用 WrapPanel 或 StackPanel 可能比 UniformGrid 好,因为角色卡片大小不一 -->
<WrapPanel ItemWidth="140" Orientation="Horizontal" />
<!-- <UniformGrid Columns="3" /> -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:CharacterViewModel">
<Border
Margin="2"
MinWidth="130"
Margin="3"
Padding="5"
BorderBrush="Gray"
BorderThickness="1">
@ -77,83 +84,93 @@
<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding Name}" />
FontSize="12"
FontWeight="Bold"
Text="{Binding Name}"
ToolTip.Tip="{Binding CharacterId, StringFormat='ID: {0}'}" />
<!-- 血量 -->
<Border Width="130" BorderThickness="1">
<Border Margin="0,2" BorderThickness="1">
<!-- TODO: Max HP for ProgressBar should come from CharacterViewModel -->
<ProgressBar
Height="18"
MaxWidth="80"
Margin="5,2,5,2"
HorizontalAlignment="Center"
FontSize="10"
Foreground="LightGoldenrodYellow"
Maximum="{Binding Hp}"
ProgressTextFormat="{} 血量:{0}/{3} ({1:0}%)"
Height="16"
MinWidth="100"
Margin="2"
FontSize="9"
Foreground="LightGreen"
Maximum="1000"
ProgressTextFormat="{}{0} / {3}"
ShowProgressText="True"
Value="609" />
Value="{Binding Hp}" />
<!-- Maximum 应该绑定到角色的最大HP -->
</Border>
<!--<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding Hp, StringFormat=血量: {0}}" />-->
<!-- 状态显示 (主动/被动) -->
<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding DisplayStates}" />
FontSize="9"
Text="{Binding DisplayStates}"
TextWrapping="Wrap" />
<!-- 装备显示 -->
<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding DisplayEquipments}" />
FontSize="9"
Text="{Binding DisplayEquipments}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
</ScrollViewer>
<!-- 红方建筑信息 -->
<!-- 取经方建筑信息 (绑定到新的属性) -->
<TextBlock
Grid.Row="2"
Margin="5"
FontSize="10"
FontStyle="Italic"
Text="{Binding SomeBuildingInfo}" />
Text="{Binding BuddhistTeamBuildingInfo}"
TextWrapping="Wrap" />
</Grid>
<!-- 蓝方队伍标题 -->
<Grid Grid.Row="1" RowDefinitions="1*,7*,3*">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<!-- 妖怪队区域 -->
<Grid Grid.Row="1" RowDefinitions="Auto,1*,Auto">
<!-- 妖怪队伍标题行 -->
<StackPanel
Grid.Row="0"
Margin="5"
Orientation="Horizontal">
<TextBlock
Margin="5"
Margin="0,0,10,0"
Background="LightBlue"
FontSize="16"
FontWeight="Bold"
Foreground="Brown"
Foreground="DarkBlue"
Text="妖怪队伍" />
<TextBlock
Margin="5,2"
VerticalAlignment="Center"
FontSize="12"
Text="经济: 10000" />
Text="{Binding MonstersTeamEconomy, StringFormat='经济: {0}'}" />
</StackPanel>
<!-- 蓝方团队角色信息 -->
<UniformGrid Grid.Row="1" Margin="5">
<ItemsControl ItemsSource="{Binding BlueTeamCharacters}">
<!-- 妖怪团队角色信息 -->
<ScrollViewer
Grid.Row="1"
Margin="2"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MonstersTeamCharacters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
<WrapPanel ItemWidth="140" Orientation="Horizontal" />
<!-- <UniformGrid Columns="3" /> -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:CharacterViewModel">
<Border
Margin="2"
MinWidth="130"
Margin="3"
Padding="5"
BorderBrush="Gray"
BorderThickness="1">
@ -162,68 +179,113 @@
<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding Name}" />
FontSize="12"
FontWeight="Bold"
Text="{Binding Name}"
ToolTip.Tip="{Binding CharacterId, StringFormat='ID: {0}'}" />
<!-- 血量 -->
<Border Width="130" BorderThickness="1">
<Border Margin="0,2" BorderThickness="1">
<!-- TODO: Max HP for ProgressBar should come from CharacterViewModel -->
<ProgressBar
Height="18"
MaxWidth="80"
Margin="5,2,5,2"
HorizontalAlignment="Center"
FontSize="10"
Foreground="LightBlue"
Maximum="{Binding Hp}"
ProgressTextFormat="{} 血量:{0}/{3} ({1:0}%)"
Height="16"
MinWidth="100"
Margin="2"
FontSize="9"
Foreground="LightCoral"
Maximum="1000"
ProgressTextFormat="{}{0} / {3}"
ShowProgressText="True"
Value="450" />
Value="{Binding Hp}" />
<!-- Maximum 应该绑定到角色的最大HP -->
</Border>
<!-- 状态显示 (主动/被动) -->
<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding DisplayStates}" />
FontSize="9"
Text="{Binding DisplayStates}"
TextWrapping="Wrap" />
<!-- 装备显示 -->
<TextBlock
Margin="5,2"
HorizontalAlignment="Center"
FontSize="10"
Text="{Binding DisplayEquipments}" />
FontSize="9"
Text="{Binding DisplayEquipments}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
</ScrollViewer>
<!-- 蓝方建筑信息 -->
<!-- 妖怪方建筑信息 (绑定到新的属性) -->
<TextBlock
Grid.Row="2"
Margin="5"
FontSize="10"
FontStyle="Italic"
Text="{Binding AnotherBuildingInfo}" />
Text="{Binding MonstersTeamBuildingInfo}"
TextWrapping="Wrap" />
</Grid>
</Grid>
<!-- 右侧区域 -->
<Grid
Grid.Column="1"
Margin="2"
>
<Grid Grid.Column="1" Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- *** 浮动的 Expander *** -->
<Canvas Panel.ZIndex="1">
<!-- 确保 Canvas 在上层 -->
<Expander
Canvas.Top="10"
Canvas.Right="10"
MaxWidth="200"
Padding="5"
Background="#AAFFFFFF"
BorderBrush="Gray"
BorderThickness="1"
ExpandDirection="Down">
<Expander.Header>
<TextBlock FontWeight="Bold" Text="地图图例" />
</Expander.Header>
<ScrollViewer MaxHeight="400">
<!-- *** 使用 ItemsControl 绑定图例数据 *** -->
<ItemsControl ItemsSource="{Binding MapLegendItems}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:LegendItem">
<!-- 设置 DataTemplate 的数据类型 -->
<StackPanel Margin="2" Orientation="Horizontal">
<!-- 色块 -->
<Border
Width="15"
Height="15"
Margin="0,0,5,0"
Background="{Binding Color}"
BorderBrush="{Binding Stroke, FallbackValue={x:Null}}"
BorderThickness="{Binding StrokeThickness, FallbackValue=0}" />
<!-- 描述文本 -->
<TextBlock
VerticalAlignment="Center"
FontSize="10"
Text="{Binding Description}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Expander>
</Canvas>
<!-- 时间、比分 -->
<StackPanel
Margin="2"
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Margin="20,0,10,0"
VerticalAlignment="Center"
@ -232,30 +294,41 @@
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
Text="红方得分:" />
Foreground="Red"
Text="取经队得分:" />
<TextBlock
Margin="5,0"
VerticalAlignment="Center"
Foreground="Red"
Text="{Binding RedScore}" />
<TextBlock
Margin="10,0"
Margin="20,0,10,0"
VerticalAlignment="Center"
Text="蓝方得分:" />
Foreground="Blue"
Text="妖怪队得分:" />
<TextBlock
Margin="5,0"
VerticalAlignment="Center"
Foreground="Blue"
Text="{Binding BlueScore}" />
</StackPanel>
<!-- 地图 -->
<Border
Grid.Row="1"
Margin="5"
BorderBrush="Gray"
BorderThickness="1"
ClipToBounds="True">
<!-- MapView 保持不变 -->
<local:MapView />
</Border>
<!-- ?或使用图标 -->
</Grid>
<!-- 地图 -->
<Border
Grid.Row="1"
Margin="5"
BorderBrush="Gray"
BorderThickness="1">
<local:MapView />
</Border>
</Grid>
</Grid>
</Window>

View File

@ -4,15 +4,8 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace debug_interface.Views
{
//public partial class MainWindow : Window
//{
// public MainWindow()
// {
// InitializeComComponent();
// }
//}
{
/// <summary>
/// 主窗口的后置代码,负责初始化组件。

View File

@ -11,7 +11,7 @@
<Grid>
<!-- 使用空的Grid将在代码中填充 -->
<Grid Name="MapGrid" />
<Grid Name="MapGrid" Background="Transparent" />
<!-- Character Positions Canvas -->
<Canvas Name="CharacterCanvas" Background="Transparent" />

View File

@ -1,396 +1,371 @@
// MapView.axaml.cs - Fixed version
// MapView.axaml.cs
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.VisualTree;
using debug_interface.Controls;
using Avalonia.Threading;
using debug_interface.Controls; // MapHelper
using debug_interface.Models;
using debug_interface.ViewModels;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq; // For Linq
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace debug_interface.Views
{
public partial class MapView : UserControl
{
private Canvas? characterCanvas;
private Grid? mapGrid;
private Dictionary<string, Control> characterElements = new Dictionary<string, Control>();
private Dictionary<long, Control> characterElements = new Dictionary<long, Control>();
private MainWindowViewModel? viewModel;
private bool isMapInitialized = false; // 添加一个标志位
//public MapView()
//{
// InitializeComponent();
// this.AttachedToVisualTree += MapView_AttachedToVisualTree;
// this.DataContextChanged += MapView_DataContextChanged;
// // 监听 Canvas 尺寸变化以更新缩放因子
// // Use a lambda expression here:
// this.GetObservable(BoundsProperty).Subscribe(bounds => UpdateCharacterScaling((Rect)bounds)); // Pass bounds explicitly
//}
public MapView()
{
InitializeComponent();
this.AttachedToVisualTree += MapView_AttachedToVisualTree;
this.DataContextChanged += MapView_DataContextChanged;
BoundsProperty.Changed
.Where(args => args.Sender == this)
.Select(args => args.NewValue.GetValueOrDefault()) // 返回 Rect
.Subscribe(newBounds => // newBounds 是 Rect 类型
{
// 直接使用 newBounds不再需要检查 HasValue 或访问 Value
// Rect currentBounds = newBounds; // 这行是多余的
// 使用之前的 IsEmpty 替代方案
bool isEffectivelyEmpty = newBounds.Width <= 0 || newBounds.Height <= 0;
if (isEffectivelyEmpty)
{
// Console.WriteLine("Bounds are effectively empty, skipping scaling.");
}
else
{
// 直接传递 newBounds
Dispatcher.UIThread.InvokeAsync(() => UpdateCharacterScaling(newBounds));
}
});
}
// 当 Canvas 尺寸变化时,重新计算所有角色的位置
private void UpdateCharacterScaling(Rect bounds)
{
// if (viewModel == null || characterCanvas == null || bounds.IsEmpty == true) return; // 暂时移除检查
if (viewModel == null || characterCanvas == null) return; // 保留这两个检查
// 这里的代码现在保证在 UI 线程执行
foreach (var character in viewModel.BuddhistsTeamCharacters.Concat(viewModel.MonstersTeamCharacters))
{
UpdateCharacterVisual(character); // 重用更新逻辑来重新定位
}
}
private void MapView_DataContextChanged(object? sender, EventArgs e)
{
var mainWindow = this.FindAncestorOfType<MainWindow>();
if (mainWindow != null && mainWindow.DataContext is MainWindowViewModel vm)
// 当 DataContext 变化时,尝试设置 ViewModel 并初始化地图
if (this.DataContext is MainWindowViewModel vm)
{
SetupViewModel(vm);
viewModel = vm; // 获取 ViewModel
TryInitializeMap(); // 尝试初始化
}
else
{
// DataContext 被清空,清理资源
CleanupViewModel();
viewModel = null;
isMapInitialized = false; // 重置标志位
mapGrid?.Children.Clear(); // 清空地图Grid内容
characterCanvas?.Children.Clear(); // 清空角色Canvas内容
}
}
private void MapView_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
// 当附加到可视化树时,查找控件并尝试初始化地图
characterCanvas = this.FindControl<Canvas>("CharacterCanvas");
mapGrid = this.FindControl<Grid>("MapGrid"); // 修改这里对应XAML
var mainWindow = this.FindAncestorOfType<MainWindow>();
if (mainWindow != null && mainWindow.DataContext is MainWindowViewModel vm)
{
SetupViewModel(vm);
}
mapGrid = this.FindControl<Grid>("MapGrid");
TryInitializeMap(); // 尝试初始化
}
private void SetupViewModel(MainWindowViewModel vm)
// 尝试初始化地图和角色(核心逻辑)
private void TryInitializeMap()
{
// 只有当 ViewModel 和 MapGrid 都准备好,并且地图尚未初始化时才执行
if (viewModel != null && mapGrid != null && !isMapInitialized)
{
// 检查 MapVM 是否也准备好了
if (viewModel.MapVM != null)
{
Console.WriteLine("MapView: Initializing Map Grid and Characters..."); // 添加日志
CleanupViewModel(); // 清理旧的订阅(如果之前有的话)
// 设置 ViewModel 的事件监听等
SetupViewModelSubscriptions();
// 初始化地图 Grid
MapHelper.InitializeMapGrid(mapGrid, viewModel.MapVM);
// 初始化角色 Canvas
RefreshCharacters();
isMapInitialized = true; // 标记地图已初始化
}
else
{
Console.WriteLine("MapView: ViewModel is ready, but MapVM is null."); // 日志
}
}
// else
// {
// 可以加日志说明为什么没初始化
// Console.WriteLine($"MapView: Initialize skipped. ViewModel: {viewModel != null}, MapGrid: {mapGrid != null}, Initialized: {isMapInitialized}");
// }
}
private void CleanupViewModel()
{
if (viewModel != null)
{
viewModel.RedTeamCharacters.CollectionChanged -= RedTeamCharacters_CollectionChanged;
viewModel.BlueTeamCharacters.CollectionChanged -= BlueTeamCharacters_CollectionChanged;
// 取消之前的集合和属性变更订阅
viewModel.BuddhistsTeamCharacters.CollectionChanged -= TeamCharacters_CollectionChanged;
viewModel.MonstersTeamCharacters.CollectionChanged -= TeamCharacters_CollectionChanged;
foreach (var character in viewModel.BuddhistsTeamCharacters.Concat(viewModel.MonstersTeamCharacters))
{
character.PropertyChanged -= Character_PropertyChanged;
}
// 注意:这里不清空 viewModel 变量DataContextChanged 会处理
}
// characterElements 和 characterCanvas 的清理移到 RefreshCharacters 或 DataContext 被清空时
// characterElements.Clear();
// characterCanvas?.Children.Clear();
}
private void SetupViewModel(MainWindowViewModel vm)
{
// 防止重复设置或处理旧 ViewModel
if (vm == viewModel) return;
CleanupViewModel(); // 清理旧的 ViewModel 订阅
viewModel = vm;
// 初始化地图网格
if (mapGrid != null && viewModel.MapVM != null)
{
// 直接使用现有的mapGrid
MapHelper.InitializeMapGrid(mapGrid, viewModel.MapVM);
}
// 监听角色集合变化
viewModel.RedTeamCharacters.CollectionChanged += RedTeamCharacters_CollectionChanged;
viewModel.BlueTeamCharacters.CollectionChanged += BlueTeamCharacters_CollectionChanged;
// 初始化角色
RefreshCharacters();
InitializeRandomPositions();
// 监听地图单元格变化(如果模型提供了这种能力)
// 初始化地图网格 (如果尚未初始化或 ViewModel 改变)
if (viewModel.MapVM != null)
{
// 如果MapCell类型实现了INotifyPropertyChanged您可以在这里监听属性变化
foreach (var cell in viewModel.MapVM.MapCells)
{
cell.PropertyChanged += (s, e) => {
if (s is MapCell mapCell)
{
if (e.PropertyName == nameof(MapCell.DisplayColor))
{
MapHelper.UpdateCellColor(mapCell.CellX, mapCell.CellY, mapCell.DisplayColor);
}
else if (e.PropertyName == nameof(MapCell.DisplayText))
{
MapHelper.UpdateCellText(mapCell.CellX, mapCell.CellY, mapCell.DisplayText);
}
}
};
}
if (mapGrid != null) MapHelper.InitializeMapGrid(mapGrid, viewModel.MapVM);
}
// 监听角色集合变化 (添加/删除 - 虽然我们预实例化了,但以防万一)
viewModel.BuddhistsTeamCharacters.CollectionChanged += TeamCharacters_CollectionChanged;
viewModel.MonstersTeamCharacters.CollectionChanged += TeamCharacters_CollectionChanged;
// 初始化角色 UI 元素并监听属性变化
RefreshCharacters();
// InitializeRandomPositions(); // 不再需要随机位置,依赖 ViewModel 的 PosX/PosY
// 地图单元格的 PropertyChanged 已在 MapHelper 中处理
}
// 设置与 ViewModel 相关的订阅
private void SetupViewModelSubscriptions()
{
if (viewModel == null) return;
// 监听角色集合变化
viewModel.BuddhistsTeamCharacters.CollectionChanged += TeamCharacters_CollectionChanged;
viewModel.MonstersTeamCharacters.CollectionChanged += TeamCharacters_CollectionChanged;
// 监听所有现有角色的属性变化
foreach (var character in viewModel.BuddhistsTeamCharacters.Concat(viewModel.MonstersTeamCharacters))
{
// 确保只添加一次监听器
character.PropertyChanged -= Character_PropertyChanged; // 先移除,防止重复添加
character.PropertyChanged += Character_PropertyChanged;
}
}
private void TeamCharacters_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// 集合发生变化时理论上不应频繁发生因为是占位符刷新整个角色UI
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(RefreshCharacters);
}
private void RefreshCharacters()
{
if (characterCanvas == null || viewModel == null) return;
characterCanvas.Children.Clear();
// 清理旧的 UI 元素和事件监听器
foreach (var kvp in characterElements)
{
characterCanvas.Children.Remove(kvp.Value);
// 理论上应该在 CleanupViewModel 中统一处理旧 ViewModel 的事件注销
}
characterElements.Clear();
InitializeCharacters(viewModel.RedTeamCharacters, Colors.Red);
InitializeCharacters(viewModel.BlueTeamCharacters, Colors.Blue);
// 为 ViewModel 中的所有角色(包括占位符)创建或更新 UI 元素
InitializeTeamCharacters(viewModel.BuddhistsTeamCharacters, Colors.Red);
InitializeTeamCharacters(viewModel.MonstersTeamCharacters, Colors.Blue);
// 更新缩放以确保初始位置正确
UpdateCharacterScaling(this.Bounds);
}
private void InitializeRandomPositions()
// 重命名方法以反映其处理单个队伍
private void InitializeTeamCharacters(System.Collections.ObjectModel.ObservableCollection<CharacterViewModel> characters, Color teamColor)
{
if (viewModel == null) return;
if (characterCanvas == null || viewModel == null) return;
Random rnd = new Random();
foreach (var character in viewModel.RedTeamCharacters)
foreach (var character in characters)
{
// Only set position if it's still at default (0,0)
if (character.PosX == 0 && character.PosY == 0)
{
character.PosX = rnd.Next(1, 49);
character.PosY = rnd.Next(1, 49);
}
// 如果该角色的 UI 元素已存在,跳过创建,只需确保事件监听器已附加
if (characterElements.ContainsKey(character.CharacterId)) continue;
// 创建角色视觉元素 (例如一个 Grid 包含 Ellipse 和 TextBlock)
var characterVisual = CreateCharacterVisual(character, teamColor);
// 添加到 Canvas
characterCanvas.Children.Add(characterVisual);
characterElements[character.CharacterId] = characterVisual; // 存入字典
// 监听角色属性变化
character.PropertyChanged += Character_PropertyChanged;
// 设置初始位置和可见性
UpdateCharacterVisual(character);
}
}
foreach (var character in viewModel.BlueTeamCharacters)
// 创建单个角色的视觉元素
private Control CreateCharacterVisual(CharacterViewModel character, Color teamColor)
{
var grid = new Grid
{
// Only set position if it's still at default (0,0)
if (character.PosX == 0 && character.PosY == 0)
Width = 12, // 调整大小
Height = 12,
Tag = character.CharacterId // 存储 ID 以便查找
};
var ellipse = new Ellipse
{
Width = 12,
Height = 12,
Fill = new SolidColorBrush(Colors.White),
Stroke = new SolidColorBrush(teamColor),
StrokeThickness = 2,
};
grid.Children.Add(ellipse);
// 可以添加一个小的 TextBlock 显示编号或类型首字母
var textBlock = new TextBlock
{
//Text = character.CharacterId.ToString(), // 或者用名字首字母
Text = GetCharacterInitial(character.Name),
FontSize = 7,
Foreground = new SolidColorBrush(teamColor),
FontWeight = FontWeight.Bold,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
IsHitTestVisible = false
};
grid.Children.Add(textBlock);
ToolTip.SetTip(grid, $"{character.Name} (ID: {character.CharacterId})\nHP: {character.Hp}\n状态: {character.ActiveState}"); // 初始 Tooltip
return grid;
}
// 获取角色名称首字母或标识符
private string GetCharacterInitial(string name)
{
if (string.IsNullOrEmpty(name) || name.EndsWith("?")) return "?";
if (name == "唐僧") return "T";
if (name == "孙悟空") return "S";
if (name == "猪八戒") return "Z";
if (name == "沙悟净") return "W";
if (name == "白龙马") return "B";
if (name == "猴子猴孙") return "h";
if (name == "九头元圣") return "J";
if (name == "红孩儿") return "H";
if (name == "牛魔王") return "N";
if (name == "铁扇公主") return "F";
if (name == "蜘蛛精") return "P";
if (name == "无名小妖") return "y";
return name.Length > 0 ? name.Substring(0, 1) : "?";
}
// 角色属性变化时的处理
private void Character_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (sender is CharacterViewModel character)
{
// 在 UI 线程上更新视觉元素
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => UpdateCharacterVisual(character));
}
}
// 更新单个角色的视觉状态位置、可见性、Tooltip
private void UpdateCharacterVisual(CharacterViewModel character)
{
if (characterElements.TryGetValue(character.CharacterId, out var element))
{
// 更新 Tooltip
ToolTip.SetTip(element, $"{character.Name} (ID: {character.CharacterId})\nHP: {character.Hp}\n状态: {character.ActiveState}\n位置: ({character.PosX},{character.PosY})");
// 检查角色是否有效且应显示在地图上
bool shouldBeVisible = character.Hp > 0 && // 活着
!character.Name.EndsWith("?") && // 不是纯占位符
!character.PassiveStates.Contains("已死亡") && // 没有死亡状态
character.PosX >= 0 && character.PosX < 50 && // 在地图网格内
character.PosY >= 0 && character.PosY < 50;
element.IsVisible = shouldBeVisible;
if (shouldBeVisible)
{
character.PosX = rnd.Next(1, 49);
character.PosY = rnd.Next(1, 49);
// 计算缩放后的像素位置
double cellWidth = characterCanvas?.Bounds.Width / 50.0 ?? 0;
double cellHeight = characterCanvas?.Bounds.Height / 50.0 ?? 0;
double left = character.PosY * cellWidth + (cellWidth / 2.0) - (element.Bounds.Width / 2.0);
double top = character.PosX * cellHeight + (cellHeight / 2.0) - (element.Bounds.Height / 2.0);
Canvas.SetLeft(element, left);
Canvas.SetTop(element, top);
}
}
}
private void InitializeCharacters<T>(System.Collections.ObjectModel.ObservableCollection<T> characters, Color color) where T : CharacterViewModel
{
if (characterCanvas == null) return;
//bool isRedTeam = color.Equals(Colors.Red);
// --- 旧的或不再需要的方法 ---
// private void InitializeRandomPositions() { ... } // 不需要了
// private void InitializeCharacters<T>(...) { ... } // 已被 InitializeTeamCharacters 替代
// private void UpdateCharacterPosition(Control element, int x, int y) { ... } // 已合并到 UpdateCharacterVisual
// public void UpdateCharacterPosition(long characterId, int x, int y, bool isRedTeam, string name) { ... } // 不再需要,由 ViewModel 驱动
// private Ellipse FindCharacterMarker(long characterId) { ... } // 不再需要
// private TextBlock FindCharacterLabel(long characterId) { ... } // 不再需要
//// 形状选择 - 红队使用圆形,蓝队使用正方形
//for (int i = 0; i < characters.Count; i++)
//{
// var character = characters[i];
// // 创建容器 - 用于定位
// var container = new Canvas
// {
// Width = 16,
// Height = 16,
// Tag = character
// };
// Control characterShape;
// if (isRedTeam)
// {
// // 红队使用圆形
// characterShape = new Ellipse
// {
// Width = 14,
// Height = 14,
// Fill = new SolidColorBrush(color) { Opacity = 0.7 },
// Stroke = Brushes.White,
// StrokeThickness = 1
// };
// }
// else
// {
// // 蓝队使用正方形
// characterShape = new Rectangle
// {
// Width = 14,
// Height = 14,
// Fill = new SolidColorBrush(color) { Opacity = 0.7 },
// Stroke = Brushes.White,
// StrokeThickness = 1
// };
// }
// // 添加形状到容器
// Canvas.SetLeft(characterShape, 1);
// Canvas.SetTop(characterShape, 1);
// container.Children.Add(characterShape);
// // 添加标识符 - 使用不同的方式标识队伍内部的角色
// var identifier = new TextBlock
// {
// Text = (i + 1).ToString(),
// FontSize = 8,
// Foreground = Brushes.White,
// HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
// VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
// };
// // 为标识符添加背景以增强可读性
// var textContainer = new Border
// {
// Child = identifier,
// Width = 14,
// Height = 14,
// Background = Brushes.Transparent,
// HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
// VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
// };
// Canvas.SetLeft(textContainer, 1);
// Canvas.SetTop(textContainer, 1);
// container.Children.Add(textContainer);
// // 添加工具提示,显示详细信息
// var tooltip = new ToolTip
// {
// // 使用角色的泛型属性,确保可以访问
// Content = new TextBlock { Text = $"{(isRedTeam ? "红队" : "蓝队")} 角色 {i + 1}" }
// };
// ToolTip.SetTip(container, tooltip);
// // 将容器添加到画布
// characterCanvas.Children.Add(container);
// characterElements[character.Name] = container;
// // 初始定位 - 稍后会更新
// Canvas.SetLeft(container, i*i * i*i);
// Canvas.SetTop(container, i*i * i * i );
//}
/////////////////////////////////////
/////////////////////////////////////
////////////////////////////////////
for (int i = 0; i < characters.Count; i++)
{
{
var character = characters[i];
var id = color == Colors.Red ? $"red_{i}" : $"blue_{i}";
// 创建一个Grid作为容器包含边框和文本/图标
var grid = new Grid
{
Width = 15,
Height = 15,
};
// 创建带颜色边框的圆形
var borderellipse = new Ellipse
{
Width = 15,
Height = 15,
Fill = new SolidColorBrush(Colors.White), // 白色背景
Stroke = new SolidColorBrush(color), // 队伍颜色边框
StrokeThickness = 2,
Tag = character.Name,
};
grid.Children.Add(borderellipse);
// ===== 选项1: 显示数字编号 =====
// 如果不需要数字编号,注释掉下面这段代码
var textBlock = new TextBlock
{
Text = (i + 1).ToString(), // 使用编号(从1开始)
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
FontSize = 8,
Foreground = new SolidColorBrush(color), // 文本颜色与队伍颜色一致
FontWeight = FontWeight.Bold,
};
//grid.Children.Add(textBlock);
// 设置提示信息
ToolTip.SetTip(grid, character.Name);
// 设置初始位置
Canvas.SetLeft(grid, character.PosY * 15);
Canvas.SetTop(grid, character.PosX * 15);
characterCanvas.Children.Add(grid);
// 存储Grid到字典中
characterElements[id] = grid;
// 设置属性更改处理器
character.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(CharacterViewModel.PosX) || e.PropertyName == nameof(CharacterViewModel.PosY))
{
// 更新Grid的位置
UpdateCharacterPosition(grid, character.PosX, character.PosY);
}
};
}
}
}
// 修改位置更新方法接受任何UIElement
private void UpdateCharacterPosition(Control element, int x, int y)
{
// 转换网格位置为像素
Canvas.SetLeft(element, y * 15);
Canvas.SetTop(element, x * 15);
}
private void RedTeamCharacters_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// When collection changes, refresh all characters for simplicity
RefreshCharacters();
}
private void BlueTeamCharacters_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// When collection changes, refresh all characters for simplicity
RefreshCharacters();
}
public void UpdateCharacterPosition(long characterId, int x, int y, bool isRedTeam, string name)
{
// 查找现有角色标记或创建新标记
var marker = FindCharacterMarker(characterId);
if (marker == null)
{
// 创建新角色标记
marker = new Ellipse
{
Width = 10,
Height = 10,
Fill = new SolidColorBrush(isRedTeam ? Colors.Red : Colors.Blue),
Tag = characterId
};
// 添加文本标签
var label1 = new TextBlock
{
Text = name,
FontSize = 8,
Foreground = new SolidColorBrush(Colors.White)
};
// 添加到画布
CharacterCanvas.Children.Add(marker);
CharacterCanvas.Children.Add(label1);
}
// 更新位置
double cellWidth = CharacterCanvas.Bounds.Width / 50;
double cellHeight = CharacterCanvas.Bounds.Height / 50;
Canvas.SetLeft(marker, y * cellWidth + cellWidth / 2 - marker.Width / 2);
Canvas.SetTop(marker, x * cellHeight + cellHeight / 2 - marker.Height / 2);
// 更新标签位置
var label = FindCharacterLabel(characterId);
if (label != null)
{
Canvas.SetLeft(label, y * cellWidth + cellWidth / 2 - label.Bounds.Width / 2);
Canvas.SetTop(label, x * cellHeight + cellHeight / 2 + marker.Height);
}
}
private Ellipse FindCharacterMarker(long characterId)
{
foreach (var child in CharacterCanvas.Children)
{
if (child is Ellipse ellipse && (long?)ellipse.Tag == characterId)
{
return ellipse;
}
}
return null;
}
private TextBlock FindCharacterLabel(long characterId)
{
var marker = FindCharacterMarker(characterId);
if (marker == null) return null;
int index = CharacterCanvas.Children.IndexOf(marker);
if (index >= 0 && index + 1 < CharacterCanvas.Children.Count &&
CharacterCanvas.Children[index + 1] is TextBlock label)
{
return label;
}
return null;
}
}
}

View File

@ -1,24 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyName>debug_interface</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyName>debug_interface</AssemblyName>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<!-- 添加对 proto 项目的直接引用 -->
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<ProjectReference Include="..\..\..\dependency\proto\proto.csproj" />
</ItemGroup>
<ItemGroup>
<!-- 相对路径 -->
<Protobuf Include="..\..\..\dependency\proto\*.proto" GrpcServices="Client">
<Link>Protos\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Protobuf>
</ItemGroup>
<ItemGroup>
<!-- 使用正斜杠 / 确保跨平台兼容 -->
<Compile Include="..\..\..\installer\Data\ConfigFileData.cs" Link="Interact/ConfigFileData.cs">
@ -30,28 +29,32 @@
<Error Condition="!Exists('../../installer/Model/Logger.cs')" />
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.5" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.5" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.5" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.5">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
<PackageReference Include="Grpc.Net.Client" Version="2.70.0" />
<PackageReference Include="Grpc.Tools" Version="2.71.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.5" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.5" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.5" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.5" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.5">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
<PackageReference Include="Grpc.Net.Client" Version="2.70.0" />
<PackageReference Include="Grpc.Tools" Version="2.71.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Reactive" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Utili\" />
</ItemGroup>
</Project>

View File

@ -5,6 +5,8 @@ VisualStudioVersion = 17.11.35219.272
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "debug_interface", "debug_interface.csproj", "{4F12E13F-A324-4E71-8003-DB2D5C8C4643}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Proto", "..\..\..\dependency\proto\Proto.csproj", "{B6C305C4-ACFA-467E-9408-9617886B7D4D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -15,6 +17,10 @@ Global
{4F12E13F-A324-4E71-8003-DB2D5C8C4643}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F12E13F-A324-4E71-8003-DB2D5C8C4643}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F12E13F-A324-4E71-8003-DB2D5C8C4643}.Release|Any CPU.Build.0 = Release|Any CPU
{B6C305C4-ACFA-467E-9408-9617886B7D4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6C305C4-ACFA-467E-9408-9617886B7D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6C305C4-ACFA-467E-9408-9617886B7D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6C305C4-ACFA-467E-9408-9617886B7D4D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -7,15 +7,31 @@ namespace ClientTest
{
public static Task Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Please provide both CharacterId and TeamId as arguments.");
return Task.CompletedTask;
}
if (!int.TryParse(args[0], out int characterId))
{
Console.WriteLine("Invalid CharacterId. Please provide a valid integer.");
return Task.CompletedTask;
}
if (!int.TryParse(args[1], out int teamId))
{
Console.WriteLine("Invalid TeamId. Please provide a valid integer.");
return Task.CompletedTask;
}
Thread.Sleep(3000);
Channel channel = new("127.0.0.1:8888", ChannelCredentials.Insecure);
var client = new AvailableService.AvailableServiceClient(channel);
CharacterMsg playerInfo = new()
{
CharacterId = 0,
TeamId = 0,
SideFlag = 0,
CharacterType = CharacterType.TangSeng
CharacterId = characterId,
TeamId = teamId,
CharacterType = teamId == 0 ? CharacterType.JiuLing : CharacterType.TangSeng,
SideFlag = 1 - teamId
};
var call = client.AddCharacter(playerInfo);
MoveMsg moveMsg = new()

5
run_all.cmd Normal file
View File

@ -0,0 +1,5 @@
start logic\Server\bin\Debug\net8.0\Server.exe --port 8888 --CharacterNum 1
start logic\ClientTest\bin\Debug\net8.0\ClientTest.exe 0 0
start logic\ClientTest\bin\Debug\net8.0\ClientTest.exe 0 1
start logic\ClientTest\bin\Debug\net8.0\ClientTest.exe 1 0
start logic\ClientTest\bin\Debug\net8.0\ClientTest.exe 1 1