Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion interface/AvaloniaUI/ViewModels/CharacterViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public partial class CharacterViewModel : ViewModelBase
[ObservableProperty]
private CharacterType characterType;

public string Coordinates => $"坐标 ({PosX / 1000},{PosY / 1000})";
public string Coordinates => $"坐标 ({PosX / 1000.0:0.0},{PosY / 1000.0:0.0})";

public string HealthText => $"生命值 {Hp}";

Expand Down
101 changes: 88 additions & 13 deletions interface/AvaloniaUI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace THUAI9_Avalonia.ViewModels
public partial class MainWindowViewModel : ViewModelBase
{
private const string DefaultServerAddress = "127.0.0.1:8888";
private const long SpectatorTeamId = 0;
private const int SpectatorSideFlag = 0;
private static readonly TimeSpan AutoReconnectInterval = TimeSpan.FromSeconds(2);

[ObservableProperty]
Expand Down Expand Up @@ -60,6 +62,7 @@ public partial class MainWindowViewModel : ViewModelBase
private readonly Dictionary<long, CharacterViewModel> _team3CharacterIndex = new();
private readonly Dictionary<long, CharacterViewModel> _team4CharacterIndex = new();
private readonly MapDynamicStateManager _dynamicStateManager;
private readonly long _spectatorPlayerId = 2023 + Environment.ProcessId;

private Channel? _channel;
private AvailableService.AvailableServiceClient? _client;
Expand Down Expand Up @@ -237,8 +240,17 @@ private async Task<bool> TryConnectOnceAsync(string serverAddress, CancellationT
}

_isConnected = true;
ConnectionStatus = "已连接";
LogConsoleVM.AddLog("实时注册已禁用,当前客户端不会自动占用队伍", "INFO");
bool streamStarted = await StartSpectatorStreamAsync(cancellationToken);
if (!streamStarted)
{
ReleaseConnectionResources();
_isConnected = false;
ConnectionStatus = "等待服务器";
return false;
}

ConnectionStatus = "等待首帧";
LogConsoleVM.AddLog("当前以 spectator 身份接入实时流,不占用任何队伍。", "INFO");
return true;
}
catch
Expand Down Expand Up @@ -266,6 +278,48 @@ private void ReleaseConnectionResources()
}
}

private async Task<bool> StartSpectatorStreamAsync(CancellationToken cancellationToken)
{
if (_client == null)
{
return false;
}

try
{
_stream?.Dispose();
_stream = null;
_hasReceivedFirstFrame = false;

var request = new RegisterFactoryMsg
{
TeamId = SpectatorTeamId,
PlayerId = _spectatorPlayerId,
SideFlag = SpectatorSideFlag
};

_stream = _client.RegisterFactory(request, cancellationToken: cancellationToken);
LogConsoleVM.AddLog($"已发起实时观战流注册:SpectatorId={_spectatorPlayerId}", "INFO");
_ = ReceiveMessagesAsync();
await Task.Yield();
return true;
}
catch (RpcException ex)
{
LogConsoleVM.AddLog($"实时观战流注册失败:{ex.Status.StatusCode} - {ex.Status.Detail}", "ERROR");
_stream?.Dispose();
_stream = null;
return false;
}
catch (Exception ex)
{
LogConsoleVM.AddLog($"实时观战流注册失败:{ex.Message}", "ERROR");
_stream?.Dispose();
_stream = null;
return false;
}
}

private async Task ReceiveMessagesAsync()
{
try
Expand Down Expand Up @@ -313,8 +367,8 @@ private void ProcessMessage(MessageToClient message)
if (!_hasReceivedFirstFrame)
{
_hasReceivedFirstFrame = true;
ConnectionStatus = "已收到首帧";
LogConsoleVM.AddLog("已收到首帧游戏消息", "SUCCESS");
ConnectionStatus = "实时观战中";
LogConsoleVM.AddLog("已收到首帧实时游戏消息", "SUCCESS");
}

UpdateCharacters(message);
Expand Down Expand Up @@ -363,18 +417,22 @@ private void UpdateCharacters(MessageToClient message)
Hp = data.Hp,
PosX = data.X,
PosY = data.Y,
ActiveState = GetCharacterStateName(data.CharacterActiveState)
ActiveState = GetCharacterStateName(data.CharacterActiveState, keepMoving: false)
};

targetList.Add(newCharacter);
InsertCharacterSorted(targetList, newCharacter);
targetIndex[data.Guid] = newCharacter;
UpdateCharacterOnMap(data, newCharacter.MaxHp);
continue;
}

string activeState = GetCharacterStateName(data.CharacterActiveState);
bool visualChanged = existingCharacter.PosX != data.X
bool movedThisFrame = existingCharacter.PosX != data.X
|| existingCharacter.PosY != data.Y
;
string activeState = GetCharacterStateName(
data.CharacterActiveState,
keepMoving: existingCharacter.ActiveState == "移动中" && movedThisFrame);
bool visualChanged = movedThisFrame
|| existingCharacter.Hp != data.Hp
|| existingCharacter.ActiveState != activeState
|| existingCharacter.TeamId != data.TeamId;
Expand All @@ -399,9 +457,26 @@ private void UpdateCharacters(MessageToClient message)

private void UpdateCharacterOnMap(MessageOfCharacter data, int maxHp)
{
int gridX = data.X / 1000;
int gridY = data.Y / 1000;
_mapView?.UpdateCharacterOnMap(data.Guid, gridX, gridY, (int)data.TeamId, data.Hp, maxHp);
_mapView?.UpdateCharacterOnMap(
data.Guid,
data.X,
data.Y,
(int)data.TeamId,
data.Hp,
maxHp,
data.PlayerId,
data.CharacterType);
}

private static void InsertCharacterSorted(ObservableCollection<CharacterViewModel> targetList, CharacterViewModel character)
{
int insertIndex = 0;
while (insertIndex < targetList.Count && targetList[insertIndex].CharacterId <= character.CharacterId)
{
insertIndex++;
}

targetList.Insert(insertIndex, character);
}

private ObservableCollection<CharacterViewModel>? GetTeamList(long teamId)
Expand Down Expand Up @@ -596,11 +671,11 @@ private string GetCharacterName(CharacterType type)
};
}

private static string GetCharacterStateName(CharacterState state)
private static string GetCharacterStateName(CharacterState state, bool keepMoving)
{
return state switch
{
CharacterState.None => "未知",
CharacterState.None => keepMoving ? "移动中" : "空闲",
CharacterState.Idle => "空闲",
CharacterState.Harvesting => "采集中",
CharacterState.Attacking => "攻击中",
Expand Down
6 changes: 3 additions & 3 deletions interface/AvaloniaUI/ViewModels/MapDynamicStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ private void UpsertComputeCenter(MessageOfComputeCenter center, HashSet<string>
CellX = center.X / 1000,
CellY = center.Y / 1000,
Label = center.OwnerTeamId > 0
? $"队{center.OwnerTeamId}"
: center.OccupyProgress > 0 ? $"{center.OccupyProgress}%" : "算力",
? center.OwnerTeamId.ToString(CultureInfo.InvariantCulture)
: center.OccupyProgress > 0 ? center.OccupyProgress.ToString(CultureInfo.InvariantCulture) : string.Empty,
Tooltip = $"算力中心 #{center.CenterId}\n归属:{(center.OwnerTeamId > 0 ? GetTeamName(center.OwnerTeamId) : "中立")}\n占领进度:{center.OccupyProgress}",
Background = center.OwnerTeamId > 0 ? GetTeamBrush(center.OwnerTeamId) : Brushes.LightBlue,
BorderBrush = Brushes.White,
Expand All @@ -234,7 +234,7 @@ private void UpsertMarket(MessageOfMarket market, HashSet<string> seenMarkets)
Kind = MapOverlayKind.Market,
CellX = market.X / 1000,
CellY = market.Y / 1000,
Label = market.PriceList.Count > 0 ? $"市场{market.PriceList.Count}" : "市场",
Label = string.Empty,
Tooltip = BuildMarketTooltip(market),
Background = Brushes.MediumPurple,
BorderBrush = Brushes.White,
Expand Down
4 changes: 0 additions & 4 deletions interface/AvaloniaUI/ViewModels/MapViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ private void UpdateCellType(int x, int y, PlaceType placeType)
case PlaceType.Factory:
cell.CellType = MapCellType.Factory;
cell.DisplayColor = new SolidColorBrush(Colors.Cyan);
cell.DisplayText = "厂";
break;
case PlaceType.Space:
cell.CellType = MapCellType.Space;
Expand All @@ -118,17 +117,14 @@ private void UpdateCellType(int x, int y, PlaceType placeType)
case PlaceType.Resource:
cell.CellType = MapCellType.Resource;
cell.DisplayColor = new SolidColorBrush(Colors.Gold);
cell.DisplayText = "资";
break;
case PlaceType.ComputeCenter:
cell.CellType = MapCellType.ComputeCenter;
cell.DisplayColor = new SolidColorBrush(Colors.LightBlue);
cell.DisplayText = "算";
break;
case PlaceType.Market:
cell.CellType = MapCellType.Market;
cell.DisplayColor = new SolidColorBrush(Colors.LightYellow);
cell.DisplayText = "市";
break;
default:
cell.CellType = MapCellType.Space;
Expand Down
Loading
Loading