Compare commits
8 Commits
935e1c202c
...
cffa1c879f
Author | SHA1 | Date |
---|---|---|
![]() |
cffa1c879f | |
![]() |
331594278a | |
![]() |
8cc0401124 | |
![]() |
45751d88d4 | |
![]() |
0f8ae51306 | |
![]() |
d54b51538d | |
![]() |
2b61d9f9c1 | |
![]() |
ccc933d4f5 |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -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 用于绘制边框 (例如给白色方块)
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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}";
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -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), "加成 (未知)"));
|
||||
|
||||
// 根据需要添加更多条目
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
//}
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -4,15 +4,8 @@ using Avalonia.Controls;
|
|||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace debug_interface.Views
|
||||
{
|
||||
|
||||
//public partial class MainWindow : Window
|
||||
//{
|
||||
// public MainWindow()
|
||||
// {
|
||||
// InitializeComComponent();
|
||||
// }
|
||||
//}
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 主窗口的后置代码,负责初始化组件。
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<Grid>
|
||||
<!-- 使用空的Grid,将在代码中填充 -->
|
||||
<Grid Name="MapGrid" />
|
||||
<Grid Name="MapGrid" Background="Transparent" />
|
||||
|
||||
<!-- Character Positions Canvas -->
|
||||
<Canvas Name="CharacterCanvas" Background="Transparent" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue