Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.
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
29 changes: 29 additions & 0 deletions src/BazaarPlusPlus/Game/CombatReplay/CapturedReplayRoute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#nullable enable
using BazaarPlusPlus.Game.PvpBattles;

namespace BazaarPlusPlus.Game.CombatReplay;

internal enum CapturedReplayRoute
{
CurrentNative,
PersistedPvp,
}

internal static class CapturedReplayRouter
{
internal static CapturedReplayRoute Resolve(PvpBattleManifest manifest)
{
if (manifest == null)
throw new ArgumentNullException(nameof(manifest));

return manifest.CombatKind switch
{
"Combat" => CapturedReplayRoute.CurrentNative,
"PVPCombat" => CapturedReplayRoute.PersistedPvp,
_ => throw new ArgumentException(
$"Unsupported replay combat kind: {manifest.CombatKind ?? "<null>"}.",
nameof(manifest)
),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal sealed class CombatReplayCaptureService
CaptureOpponentSkillsFromOpening(message)
GameSimEventCardSpawned
GameSimEventPlayerSkillEquipped
return state == ERunState.PVPCombat;
return state == ERunState.Combat || state == ERunState.PVPCombat;
OpponentName = candidate.OpponentName
OpponentAccountId = candidate.OpponentAccountId
CapturedEmpty
Expand Down Expand Up @@ -59,7 +59,7 @@ public CombatReplayCaptureService(Func<DateTimeOffset>? clock = null)
{
if (_candidate.SpawnMessage == null)
{
if (_matcher.IsPvpCombatOpeningMessage(message))
if (_matcher.IsCombatOpeningMessage(message))
{
_candidate = CreateOpeningCandidate(message, runId);
}
Expand All @@ -69,7 +69,7 @@ public CombatReplayCaptureService(Func<DateTimeOffset>? clock = null)

if (_candidate.CombatMessage == null)
{
if (_matcher.IsPvpCombatOpeningMessage(message))
if (_matcher.IsCombatOpeningMessage(message))
{
_candidate = CreateOpeningCandidate(message, runId);
}
Expand All @@ -81,11 +81,9 @@ public CombatReplayCaptureService(Func<DateTimeOffset>? clock = null)
return null;
}

if (_matcher.IsAnyCombatOpeningMessage(message))
if (_matcher.IsCombatOpeningMessage(message))
{
_candidate = _matcher.IsPvpCombatOpeningMessage(message)
? CreateOpeningCandidate(message, runId)
: _matcher.ResetCandidate();
_candidate = CreateOpeningCandidate(message, runId);
return null;
}

Expand Down
13 changes: 12 additions & 1 deletion src/BazaarPlusPlus/Game/CombatReplay/CombatReplayRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,21 @@ public void ObserveMessage(BazaarGameShared.Infra.Messages.INetMessage message)
if (artifact == null)
return;

var route = CapturedReplayRouter.Resolve(artifact.Manifest);
_currentRecordingManifest = artifact.Manifest;
_currentRecording.LatchBattle(artifact.Manifest.BattleId);
PrepareCurrentReplayRecordingAvailability();
_persistence.Enqueue(artifact.Payload, artifact.Manifest);
switch (route)
{
case CapturedReplayRoute.CurrentNative:
_currentRecording.MarkCurrentNativeReady(artifact.Manifest.BattleId);
break;
case CapturedReplayRoute.PersistedPvp:
_persistence.Enqueue(artifact.Payload, artifact.Manifest);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal sealed class CurrentReplayRecordingButtonController : MonoBehaviour
private readonly Vector3[] _tooltipWorldCorners = new Vector3[4];
private readonly BppDockButtonScreenLayout _screenLayout = new();
private readonly CurrentReplayRecordingUiLogState _uiLogState = new();
private readonly CurrentReplayRecordingTooltipStyle _tooltipStyle = new();
private bool _layoutAvailable;
private CurrentReplayRecordingUiLayoutReasonCode _layoutReasonCode;

Expand Down Expand Up @@ -273,6 +274,7 @@ internal void HideTooltip()
StopCoroutine(_tooltipPositionCoroutine);
_tooltipPositionCoroutine = null;
}
_tooltipStyle.Restore();
Data.TooltipParentComponent?.HideAuxiliaryTooltipController();
}

Expand All @@ -290,10 +292,13 @@ private IEnumerator PositionTooltipBesideButton()
tooltip == null
|| tooltipParent == null
|| !tooltipParent.IsAuxiliaryTooltipDisplayed
|| tooltip._coroutine != null
)
continue;

_tooltipStyle.Apply(tooltip);
if (tooltip._coroutine != null)
continue;

tooltip.PositionOverUI(_cloneRect);
var tooltipRect = tooltip.PositioningRectTransform;
LayoutRebuilder.ForceRebuildLayoutImmediate(tooltipRect);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal sealed class CurrentReplayRecordingState
private string? _recordingId;
private string? _finalFilePath;
private string? _reason;
private bool _battlePersisted;
private bool _replaySourceReady;
private bool _replayStateActive;
private bool _availabilityReady;
private bool _nativeReplayStarted;
Expand All @@ -60,7 +60,7 @@ internal void LatchBattle(string battleId)
_recordingId = null;
_finalFilePath = null;
_reason = null;
_battlePersisted = false;
_replaySourceReady = false;
_availabilityReady = false;
_nativeReplayStarted = false;
Phase = CurrentReplayRecordingPhase.AwaitingBattlePersistence;
Expand All @@ -77,14 +77,25 @@ internal void MarkBattlePersistence(string battleId, bool succeeded, string? rea
if (!MatchesBattle(battleId) || HasActiveSession)
return;

_battlePersisted = succeeded;
_replaySourceReady = succeeded;
_reason = succeeded ? null : reason;
Phase = succeeded
? CurrentReplayRecordingPhase.Preparing
: CurrentReplayRecordingPhase.Failed;
RefreshReadyPhase();
}

internal void MarkCurrentNativeReady(string battleId)
{
if (!MatchesBattle(battleId) || HasActiveSession)
return;

_replaySourceReady = true;
_reason = null;
Phase = CurrentReplayRecordingPhase.Preparing;
RefreshReadyPhase();
}

internal void SetAvailability(bool ready, string? reason)
{
if (
Expand All @@ -100,7 +111,7 @@ or CurrentReplayRecordingPhase.Degraded
if (!ready)
{
_reason = reason;
if (_battlePersisted && Phase != CurrentReplayRecordingPhase.Failed)
if (_replaySourceReady && Phase != CurrentReplayRecordingPhase.Failed)
Phase = CurrentReplayRecordingPhase.Preparing;
return;
}
Expand All @@ -117,7 +128,7 @@ internal bool TryArm(string recordingId)
if (
string.IsNullOrWhiteSpace(recordingId)
|| !_replayStateActive
|| !_battlePersisted
|| !_replaySourceReady
|| !_availabilityReady
|| Phase
is not (CurrentReplayRecordingPhase.Ready or CurrentReplayRecordingPhase.Failed)
Expand Down Expand Up @@ -161,7 +172,7 @@ internal void RollbackArm(string recordingId, string reason)
_nativeReplayStarted = false;
_reason = reason;
Phase =
_battlePersisted && _availabilityReady
_replaySourceReady && _availabilityReady
? CurrentReplayRecordingPhase.Ready
: CurrentReplayRecordingPhase.Failed;
}
Expand Down Expand Up @@ -229,7 +240,7 @@ is CurrentReplayRecordingPhase.Succeeded
or CurrentReplayRecordingPhase.Degraded;
var canStart =
visible
&& _battlePersisted
&& _replaySourceReady
&& _availabilityReady
&& Phase is CurrentReplayRecordingPhase.Ready or CurrentReplayRecordingPhase.Failed;
return new CurrentReplayRecordingSnapshot(
Expand All @@ -253,7 +264,7 @@ private bool MatchesSession(string recordingId, string battleId) =>

private void RefreshReadyPhase()
{
if (_battleId == null || HasActiveSession || !_battlePersisted)
if (_battleId == null || HasActiveSession || !_replaySourceReady)
return;

Phase =
Expand All @@ -268,7 +279,7 @@ private void Reset()
_recordingId = null;
_finalFilePath = null;
_reason = null;
_battlePersisted = false;
_replaySourceReady = false;
_replayStateActive = false;
_availabilityReady = false;
_nativeReplayStarted = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#nullable enable

namespace BazaarPlusPlus.Game.CombatReplay;

internal static class CurrentReplayRecordingTooltipPadding
{
internal static (int Top, int Bottom) Balance(int top, int bottom)
{
var total = Math.Max(0, top) + Math.Max(0, bottom);
var balancedTop = total / 2;
return (balancedTop, total - balancedTop);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#nullable enable
using TheBazaar.UI.Tooltips;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace BazaarPlusPlus.Game.CombatReplay;

internal sealed class CurrentReplayRecordingTooltipStyle
{
private const float TooltipScale = 0.94f;
private const float TooltipFontScale = 0.94f;

private AuxiliaryTooltipController? _styledTooltip;
private Vector3 _styledTooltipOriginalLocalScale;
private float _styledTooltipOriginalScaleRatio;
private float _styledTooltipOriginalHeaderFontSize;
private float _styledTooltipOriginalBodyFontSize;
private bool _styledTooltipOriginalHeaderAutoSizing;
private bool _styledTooltipOriginalBodyAutoSizing;
private TextAlignmentOptions _styledTooltipOriginalHeaderAlignment;
private TextAlignmentOptions _styledTooltipOriginalBodyAlignment;
private VerticalLayoutGroup? _styledTooltipLayout;
private TextAnchor _styledTooltipOriginalChildAlignment;
private int _styledTooltipOriginalPaddingTop;
private int _styledTooltipOriginalPaddingBottom;

internal void Apply(AuxiliaryTooltipController tooltip)
{
if (!ReferenceEquals(_styledTooltip, tooltip))
{
Restore();
_styledTooltip = tooltip;
_styledTooltipOriginalLocalScale = tooltip.transform.localScale;
_styledTooltipOriginalScaleRatio = tooltip.scaleRatio;

if (tooltip.headerText != null)
{
_styledTooltipOriginalHeaderFontSize = tooltip.headerText.fontSize;
_styledTooltipOriginalHeaderAutoSizing = tooltip.headerText.enableAutoSizing;
_styledTooltipOriginalHeaderAlignment = tooltip.headerText.alignment;
}

if (tooltip.bodyText != null)
{
_styledTooltipOriginalBodyFontSize = tooltip.bodyText.fontSize;
_styledTooltipOriginalBodyAutoSizing = tooltip.bodyText.enableAutoSizing;
_styledTooltipOriginalBodyAlignment = tooltip.bodyText.alignment;
}

_styledTooltipLayout = tooltip.auxParent?.GetComponent<VerticalLayoutGroup>();
if (_styledTooltipLayout != null)
{
_styledTooltipOriginalChildAlignment = _styledTooltipLayout.childAlignment;
_styledTooltipOriginalPaddingTop = _styledTooltipLayout.padding.top;
_styledTooltipOriginalPaddingBottom = _styledTooltipLayout.padding.bottom;
}
}

ApplyTextStyle(tooltip.headerText, _styledTooltipOriginalHeaderFontSize);
ApplyTextStyle(tooltip.bodyText, _styledTooltipOriginalBodyFontSize);

tooltip.scaleRatio = _styledTooltipOriginalScaleRatio * TooltipScale;
if (tooltip._coroutine == null)
tooltip.transform.localScale = _styledTooltipOriginalLocalScale * TooltipScale;

if (_styledTooltipLayout != null)
{
_styledTooltipLayout.childAlignment = TextAnchor.MiddleLeft;
var balancedPadding = CurrentReplayRecordingTooltipPadding.Balance(
_styledTooltipOriginalPaddingTop,
_styledTooltipOriginalPaddingBottom
);
_styledTooltipLayout.padding.top = balancedPadding.Top;
_styledTooltipLayout.padding.bottom = balancedPadding.Bottom;
LayoutRebuilder.ForceRebuildLayoutImmediate(
(RectTransform)_styledTooltipLayout.transform
);
}

LayoutRebuilder.ForceRebuildLayoutImmediate(tooltip.PositioningRectTransform);
}

private static void ApplyTextStyle(TMP_Text? text, float originalFontSize)
{
if (text == null)
return;

text.enableAutoSizing = false;
text.fontSize = originalFontSize * TooltipFontScale;
text.alignment = TextAlignmentOptions.MidlineLeft;
}

internal void Restore()
{
var tooltip = _styledTooltip;
if (tooltip == null)
{
_styledTooltip = null;
return;
}

tooltip.transform.localScale = _styledTooltipOriginalLocalScale;
tooltip.scaleRatio = _styledTooltipOriginalScaleRatio;
if (tooltip.headerText != null)
{
tooltip.headerText.fontSize = _styledTooltipOriginalHeaderFontSize;
tooltip.headerText.enableAutoSizing = _styledTooltipOriginalHeaderAutoSizing;
tooltip.headerText.alignment = _styledTooltipOriginalHeaderAlignment;
}
if (tooltip.bodyText != null)
{
tooltip.bodyText.fontSize = _styledTooltipOriginalBodyFontSize;
tooltip.bodyText.enableAutoSizing = _styledTooltipOriginalBodyAutoSizing;
tooltip.bodyText.alignment = _styledTooltipOriginalBodyAlignment;
}
if (_styledTooltipLayout != null)
{
_styledTooltipLayout.childAlignment = _styledTooltipOriginalChildAlignment;
_styledTooltipLayout.padding.top = _styledTooltipOriginalPaddingTop;
_styledTooltipLayout.padding.bottom = _styledTooltipOriginalPaddingBottom;
}

_styledTooltip = null;
_styledTooltipLayout = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace BazaarPlusPlus.Game.CombatReplay.Video;
internal static class ReplayVideoCaptureDefaults
{
internal const int FallbackFps = 30;
internal const int MaxFps = 60;
internal const int MaxFps = 30;
internal const int Crf = 23;
internal const string Preset = "veryfast";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ public void Save(PvpBattleManifest manifest)
if (string.IsNullOrWhiteSpace(manifest.BattleId))
throw new ArgumentException("Battle id is required.", nameof(manifest));
if (!string.Equals(manifest.CombatKind, "PVPCombat", StringComparison.Ordinal))
return;
throw new ArgumentException(
"Only PVPCombat manifests can be saved to the PvP battle catalog.",
nameof(manifest)
);

using var connection = OpenConnection();
using var transaction = connection.BeginTransaction();
Expand Down
Loading