From 5ce15adea9fa3828930056e0c96bcd5e9068129e Mon Sep 17 00:00:00 2001 From: line27 <41178658+Linear27@users.noreply.github.com> Date: Sun, 31 May 2026 21:33:12 +0800 Subject: [PATCH 1/2] fix: restore mod source build - remove duplicate legacy implementations left by the v3.3.0 migration - reconnect runtime services, config paths, and preview model boundaries --- bazaarplusplus-mod/BppComposition.cs | 3 + bazaarplusplus-mod/Core/Config/BppConfig.cs | 8 + bazaarplusplus-mod/Core/Config/IBppConfig.cs | 2 + .../Core/Paths/BppPathService.cs | 24 + bazaarplusplus-mod/Core/Paths/IPathService.cs | 8 + .../Core/Runtime/BppRuntimeHost.cs | 117 +-- .../Core/Runtime/BppRuntimeServices.cs | 3 + .../Core/Runtime/IBppServices.cs | 1 + .../Game/CombatReplay/CombatReplayRuntime.cs | 3 +- .../Game/EncounterPreviewSpecConverter.cs | 70 -- bazaarplusplus-mod/Game/GameDataReader.cs | 132 --- .../HistoryPanel/HistoryBattlePreviewData.cs | 3 +- .../HistoryPanel.Canvas.Layout.cs | 395 --------- .../HistoryPanel/HistoryPanel.Canvas.Lists.cs | 714 --------------- .../HistoryPanel.Canvas.Styles.cs | 823 ------------------ .../Game/HistoryPanel/HistoryPanel.Canvas.cs | 456 ---------- .../HistoryPanelPreviewRenderer.cs | 108 ++- .../HistoryPanelRepository.Preview.cs | 108 ++- .../Architecture/PreviewCardSpecFilter.cs | 5 + .../EncounterPreviewSpecConverter.cs | 1 - .../MonsterPreviewBoardRenderTarget.cs | 2 + .../MonsterPreviewItemCardFactory.cs | 1 + .../MonsterPreviewSkillCardFactory.cs | 1 + .../Game/Online/V3UploadDefaults.cs | 1 + .../Board/PreviewBoardRenderTarget.cs | 79 +- .../Game/PvpBattles/PvpBattleRecorded.cs | 7 - bazaarplusplus-mod/Game/ShowcaseCardMarker.cs | 6 - bazaarplusplus-mod/Models/RunInfo.cs | 90 -- bazaarplusplus-mod/Plugin.cs | 2 + 29 files changed, 358 insertions(+), 2815 deletions(-) delete mode 100644 bazaarplusplus-mod/Game/EncounterPreviewSpecConverter.cs delete mode 100644 bazaarplusplus-mod/Game/GameDataReader.cs delete mode 100644 bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Layout.cs delete mode 100644 bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Lists.cs delete mode 100644 bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Styles.cs delete mode 100644 bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.cs delete mode 100644 bazaarplusplus-mod/Game/PvpBattles/PvpBattleRecorded.cs delete mode 100644 bazaarplusplus-mod/Game/ShowcaseCardMarker.cs delete mode 100755 bazaarplusplus-mod/Models/RunInfo.cs diff --git a/bazaarplusplus-mod/BppComposition.cs b/bazaarplusplus-mod/BppComposition.cs index e87b26b..b952ba2 100644 --- a/bazaarplusplus-mod/BppComposition.cs +++ b/bazaarplusplus-mod/BppComposition.cs @@ -20,6 +20,7 @@ internal sealed class BppComposition : IDisposable private readonly InMemoryBppEventBus _eventBus = new(); private readonly BppConfig _config = new(); private readonly BppPathService _paths = new(); + private readonly MonsterDatabase _monsterCatalog = new(); private readonly RunContextStore _runContext = new(); private readonly GameStateProbe _gameStateProbe = new(); private readonly BppRuntimeServices _services; @@ -40,6 +41,7 @@ public BppComposition(ManualLogSource logger, ConfigFile configFile) _config.Initialize(configFile); _paths.Initialize(); + _monsterCatalog.Initialize(); _runContext.Reset(); _services = new BppRuntimeServices( @@ -48,6 +50,7 @@ public BppComposition(ManualLogSource logger, ConfigFile configFile) _paths, _runContext, _gameStateProbe, + _monsterCatalog, logger ); diff --git a/bazaarplusplus-mod/Core/Config/BppConfig.cs b/bazaarplusplus-mod/Core/Config/BppConfig.cs index fb581b0..23d1fba 100644 --- a/bazaarplusplus-mod/Core/Config/BppConfig.cs +++ b/bazaarplusplus-mod/Core/Config/BppConfig.cs @@ -19,6 +19,8 @@ internal sealed class BppConfig : IBppConfig public ConfigEntry? UpgradePreviewHotkeyPathConfig { get; private set; } + public ConfigEntry? EnableCommunityContributionConfig { get; private set; } + public ConfigEntry? ChineseLocaleModeConfig { get; private set; } public ConfigEntry? LegendaryPositionDisplayModeConfig @@ -77,6 +79,12 @@ public void Initialize(ConfigFile config) "/shift", "Binding path for upgrade preview tooltip mode." ); + EnableCommunityContributionConfig = config.Bind( + "CommunityContribution", + "Enabled", + false, + "Whether to participate in BazaarPlusPlus community data contribution features, including background uploads and History Review access while out of a live run." + ); ChineseLocaleModeConfig = config.Bind( "Localization", "ChineseLocaleMode", diff --git a/bazaarplusplus-mod/Core/Config/IBppConfig.cs b/bazaarplusplus-mod/Core/Config/IBppConfig.cs index f83308f..1b6e1e4 100644 --- a/bazaarplusplus-mod/Core/Config/IBppConfig.cs +++ b/bazaarplusplus-mod/Core/Config/IBppConfig.cs @@ -19,6 +19,8 @@ internal interface IBppConfig ConfigEntry? UpgradePreviewHotkeyPathConfig { get; } + ConfigEntry? EnableCommunityContributionConfig { get; } + ConfigEntry? ChineseLocaleModeConfig { get; } ConfigEntry? LegendaryPositionDisplayModeConfig { get; } diff --git a/bazaarplusplus-mod/Core/Paths/BppPathService.cs b/bazaarplusplus-mod/Core/Paths/BppPathService.cs index fb708bf..4f51527 100644 --- a/bazaarplusplus-mod/Core/Paths/BppPathService.cs +++ b/bazaarplusplus-mod/Core/Paths/BppPathService.cs @@ -4,6 +4,8 @@ namespace BazaarPlusPlus.Core.Paths; internal sealed class BppPathService : IPathService { + public string? CardsJsonPath { get; private set; } + public string? RunLogDatabasePath { get; private set; } public string? CombatReplayDirectoryPath { get; private set; } @@ -12,8 +14,15 @@ internal sealed class BppPathService : IPathService public string? IdentityDirectoryPath { get; private set; } + public string? RunUploadInstallIdentityPath { get; private set; } + + public string? RunUploadClientStatePath { get; private set; } + + public string? RunUploadPrivateKeyPath { get; private set; } + public void Initialize() { + CardsJsonPath = CardJsonPathResolver.GetCardsJsonPath(); RunLogDatabasePath = System.IO.Path.Combine( BepInEx.Paths.GameRootPath, "BazaarPlusPlus", @@ -34,5 +43,20 @@ public void Initialize() "BazaarPlusPlus", "Identity" ); + RunUploadInstallIdentityPath = System.IO.Path.Combine( + BepInEx.Paths.GameRootPath, + "BazaarPlusPlus", + "install-id.txt" + ); + RunUploadClientStatePath = System.IO.Path.Combine( + BepInEx.Paths.GameRootPath, + "BazaarPlusPlus", + "run-upload-client.json" + ); + RunUploadPrivateKeyPath = System.IO.Path.Combine( + BepInEx.Paths.GameRootPath, + "BazaarPlusPlus", + "run-upload-rsa.json" + ); } } diff --git a/bazaarplusplus-mod/Core/Paths/IPathService.cs b/bazaarplusplus-mod/Core/Paths/IPathService.cs index f23c614..e9a99dd 100644 --- a/bazaarplusplus-mod/Core/Paths/IPathService.cs +++ b/bazaarplusplus-mod/Core/Paths/IPathService.cs @@ -3,6 +3,8 @@ namespace BazaarPlusPlus.Core.Paths; internal interface IPathService { + string? CardsJsonPath { get; } + string? RunLogDatabasePath { get; } string? CombatReplayDirectoryPath { get; } @@ -10,4 +12,10 @@ internal interface IPathService string? ScreenshotsDirectoryPath { get; } string? IdentityDirectoryPath { get; } + + string? RunUploadInstallIdentityPath { get; } + + string? RunUploadClientStatePath { get; } + + string? RunUploadPrivateKeyPath { get; } } diff --git a/bazaarplusplus-mod/Core/Runtime/BppRuntimeHost.cs b/bazaarplusplus-mod/Core/Runtime/BppRuntimeHost.cs index d9f4513..ba355c7 100644 --- a/bazaarplusplus-mod/Core/Runtime/BppRuntimeHost.cs +++ b/bazaarplusplus-mod/Core/Runtime/BppRuntimeHost.cs @@ -2,136 +2,57 @@ using System; using System.Collections.Generic; using BazaarGameShared.Domain.Core.Types; -using BazaarPlusPlus; using BazaarPlusPlus.Core.Config; using BazaarPlusPlus.Core.Events; using BazaarPlusPlus.Core.GameState; using BazaarPlusPlus.Core.Paths; using BazaarPlusPlus.Core.RunContext; -using BazaarPlusPlus.Game.CombatReplay; -using BazaarPlusPlus.Game.CombatStatusBar; -using BazaarPlusPlus.Game.RunLifecycle; -using BepInEx.Configuration; using BepInEx.Logging; -using UnityEngine; namespace BazaarPlusPlus.Core.Runtime; -internal sealed class BppRuntimeHost +internal static class BppRuntimeHost { - private static readonly BppRuntimeServices DetachedServices = new( - new InMemoryBppEventBus(), - new BppConfig(), - new BppPathService(), - new EmptyMonsterCatalog(), - new RunContextStore(), - new GameStateProbe() - ); - private readonly ManualLogSource _logger; - private readonly InMemoryBppEventBus _eventBus = new(); - private readonly BppConfig _config = new(); - private readonly BppPathService _paths = new(); - private readonly MonsterDatabase _monsterCatalog = new(); - private readonly RunContextStore _runContext = new(); - private readonly GameStateProbe _gameStateProbe = new(); - private readonly RunLifecycleModule _runLifecycle; - private readonly CombatReplayModule _combatReplayModule; - private readonly CombatStatusBarModule _combatStatusBarModule; - private readonly BppFeatureRegistry _featureRegistry; - - public BppRuntimeHost( - GameObject hostObject, - ManualLogSource logger, - ConfigFile configFile, - Func combatReplayRuntimeAccessor - ) - { - if (hostObject == null) - throw new ArgumentNullException(nameof(hostObject)); - if (logger == null) - throw new ArgumentNullException(nameof(logger)); - if (configFile == null) - throw new ArgumentNullException(nameof(configFile)); - if (combatReplayRuntimeAccessor == null) - throw new ArgumentNullException(nameof(combatReplayRuntimeAccessor)); - - _logger = logger; - _config.Initialize(configFile); - _paths.Initialize(); - _monsterCatalog.Initialize(); - _runContext.Reset(); - Services = new BppRuntimeServices( - _eventBus, - _config, - _paths, - _monsterCatalog, - _runContext, - _gameStateProbe - ); - _runLifecycle = new RunLifecycleModule( - Services.EventBus, - Services.GameStateProbe, - Services.RunContext - ); - _combatReplayModule = new CombatReplayModule( - Services.EventBus, - combatReplayRuntimeAccessor - ); - _combatStatusBarModule = new CombatStatusBarModule(Services.EventBus, Services.RunContext); - _featureRegistry = new BppFeatureRegistry(); - _featureRegistry.Register(_runLifecycle); - _featureRegistry.Register(_combatReplayModule); - _featureRegistry.Register(_combatStatusBarModule); - } - - public static BppRuntimeHost? Current { get; private set; } - - public BppRuntimeServices Services { get; } + private static IBppServices? _services; - public RunLifecycleModule LifecycleModule => _runLifecycle; + public static IBppEventBus EventBus => + _services?.EventBus ?? throw CreateNotInstalledException(); - public static IBppEventBus EventBus => Current?.Services.EventBus ?? DetachedServices.EventBus; + public static ManualLogSource? Logger => _services?.Logger; - public static ManualLogSource? Logger => Current?._logger; + public static IBppConfig Config => _services?.Config ?? throw CreateNotInstalledException(); - public static IBppConfig Config => Current?.Services.Config ?? DetachedServices.Config; - - public static IPathService Paths => Current?.Services.Paths ?? DetachedServices.Paths; + public static IPathService Paths => _services?.Paths ?? throw CreateNotInstalledException(); public static IMonsterCatalog MonsterCatalog => - Current?.Services.MonsterCatalog ?? DetachedServices.MonsterCatalog; + _services?.MonsterCatalog ?? EmptyMonsterCatalog.Instance; public static IRunContext RunContext => - Current?.Services.RunContext ?? DetachedServices.RunContext; + _services?.RunContext ?? throw CreateNotInstalledException(); public static IGameStateProbe GameStateProbe => - Current?.Services.GameStateProbe ?? DetachedServices.GameStateProbe; - - public static RunLifecycleModule RunLifecycle => - Current?._runLifecycle - ?? throw new InvalidOperationException("Runtime host is not installed."); + _services?.GameStateProbe ?? throw CreateNotInstalledException(); - public void Install() + public static void Install(IBppServices services) { - Current = this; - BppLog.Info("RuntimeHost", "Installed runtime host"); + _services = services ?? throw new ArgumentNullException(nameof(services)); + BppLog.Info("RuntimeHost", "Installed runtime service bridge"); } - public void Start() + public static void Reset() { - _featureRegistry.Start(); - BppLog.Info("RuntimeHost", "Started runtime host"); + _services = null; } - public void Stop() + private static InvalidOperationException CreateNotInstalledException() { - _featureRegistry.Stop(); - if (ReferenceEquals(Current, this)) - Current = null; + return new InvalidOperationException("Runtime services are not installed."); } private sealed class EmptyMonsterCatalog : IMonsterCatalog { + public static readonly EmptyMonsterCatalog Instance = new(); + public bool TryGetByEncounterId(Guid encounterId, out MonsterInfo? monster) { monster = null; diff --git a/bazaarplusplus-mod/Core/Runtime/BppRuntimeServices.cs b/bazaarplusplus-mod/Core/Runtime/BppRuntimeServices.cs index 9f3c50a..c4b7d27 100644 --- a/bazaarplusplus-mod/Core/Runtime/BppRuntimeServices.cs +++ b/bazaarplusplus-mod/Core/Runtime/BppRuntimeServices.cs @@ -17,6 +17,7 @@ public BppRuntimeServices( IPathService paths, IRunContext runContext, IGameStateProbe gameStateProbe, + IMonsterCatalog monsterCatalog, ManualLogSource logger ) { @@ -25,6 +26,7 @@ ManualLogSource logger Paths = paths ?? throw new ArgumentNullException(nameof(paths)); RunContext = runContext ?? throw new ArgumentNullException(nameof(runContext)); GameStateProbe = gameStateProbe ?? throw new ArgumentNullException(nameof(gameStateProbe)); + MonsterCatalog = monsterCatalog ?? throw new ArgumentNullException(nameof(monsterCatalog)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -33,5 +35,6 @@ ManualLogSource logger public IPathService Paths { get; } public IRunContext RunContext { get; } public IGameStateProbe GameStateProbe { get; } + public IMonsterCatalog MonsterCatalog { get; } public ManualLogSource Logger { get; } } diff --git a/bazaarplusplus-mod/Core/Runtime/IBppServices.cs b/bazaarplusplus-mod/Core/Runtime/IBppServices.cs index 50a1f09..7264075 100644 --- a/bazaarplusplus-mod/Core/Runtime/IBppServices.cs +++ b/bazaarplusplus-mod/Core/Runtime/IBppServices.cs @@ -15,5 +15,6 @@ internal interface IBppServices IPathService Paths { get; } IRunContext RunContext { get; } IGameStateProbe GameStateProbe { get; } + IMonsterCatalog MonsterCatalog { get; } ManualLogSource Logger { get; } } diff --git a/bazaarplusplus-mod/Game/CombatReplay/CombatReplayRuntime.cs b/bazaarplusplus-mod/Game/CombatReplay/CombatReplayRuntime.cs index b5c8492..eca8108 100644 --- a/bazaarplusplus-mod/Game/CombatReplay/CombatReplayRuntime.cs +++ b/bazaarplusplus-mod/Game/CombatReplay/CombatReplayRuntime.cs @@ -570,7 +570,7 @@ private static Func CreateHandleSpawnMessageAsync( GameSimHandler gameSimHandler ) { - return async spawnMessage => + return spawnMessage => { if (!processor.Handle(spawnMessage)) throw new InvalidOperationException( @@ -579,6 +579,7 @@ GameSimHandler gameSimHandler Data.UpdateFromGameSimAsync(spawnMessage); MarkGameSimMessageHandled(gameSimHandler, spawnMessage.MessageId); + return Task.CompletedTask; }; } diff --git a/bazaarplusplus-mod/Game/EncounterPreviewSpecConverter.cs b/bazaarplusplus-mod/Game/EncounterPreviewSpecConverter.cs deleted file mode 100644 index b4c2d40..0000000 --- a/bazaarplusplus-mod/Game/EncounterPreviewSpecConverter.cs +++ /dev/null @@ -1,70 +0,0 @@ -#pragma warning disable CS0436 -using System.Collections.Generic; -using BazaarPlusPlus.Game.MonsterPreview; - -namespace BazaarPlusPlus; - -internal static class EncounterPreviewSpecConverter -{ - internal static List BuildCachedSpecs(List cards) - { - var specs = new List(); - if (cards == null) - return specs; - - foreach (var card in cards) - { - if (card == null || string.IsNullOrWhiteSpace(card.TemplateId)) - continue; - - specs.Add( - new PreviewCardSpec - { - TemplateId = card.TemplateId, - SourceName = card.SourceName ?? string.Empty, - Tier = card.Tier, - Size = card.Size <= 0 ? 1 : card.Size, - Enchant = string.IsNullOrWhiteSpace(card.Enchant) ? "None" : card.Enchant, - Attributes = - card.Attributes != null - ? new Dictionary(card.Attributes) - : new Dictionary(), - } - ); - } - - return specs; - } - - internal static List ToCachedCards( - IEnumerable specs - ) - { - var cards = new List(); - if (specs == null) - return cards; - - foreach (var spec in specs) - { - if (spec == null || string.IsNullOrWhiteSpace(spec.TemplateId)) - continue; - - cards.Add( - new RunInfo.MonsterPreviewCard - { - TemplateId = spec.TemplateId, - SourceName = spec.SourceName ?? string.Empty, - Tier = spec.Tier, - Size = spec.Size <= 0 ? 1 : spec.Size, - Enchant = string.IsNullOrWhiteSpace(spec.Enchant) ? "None" : spec.Enchant, - Attributes = - spec.Attributes != null - ? new Dictionary(spec.Attributes) - : new Dictionary(), - } - ); - } - - return cards; - } -} diff --git a/bazaarplusplus-mod/Game/GameDataReader.cs b/bazaarplusplus-mod/Game/GameDataReader.cs deleted file mode 100644 index 7ea1f8b..0000000 --- a/bazaarplusplus-mod/Game/GameDataReader.cs +++ /dev/null @@ -1,132 +0,0 @@ -#pragma warning disable CS0436 -using System; -using System.Collections.Generic; -using System.Linq; -using BazaarGameClient.Domain.Models.Cards; -using BazaarGameShared.Domain.Core.Types; -using BazaarGameShared.Domain.Players; -using BazaarPlusPlus.Core.Runtime; -using TheBazaar; - -namespace BazaarPlusPlus; - -internal static class GameDataReader -{ - public static RunInfo GetRunInfo() - { - if (Data.Run == null) - { - BppLog.Warn("GameDataReader", "GetRunInfo requested while Data.Run is null"); - return new RunInfo - { - Name = BppClientCacheBridge.TryGetProfileUsername(), - AvailableEncounters = new List(), - CurrentEncounterChoices = new List(), - }; - } - - var opponent = Data.Run.Opponent; - BppLog.Debug( - "GameDataReader", - $"Building run snapshot: hero={Data.Run.Player?.Hero}, day={Data.Run.Day}, opponent={(opponent == null ? "none" : opponent.Hero.ToString())}" - ); - - return new RunInfo - { - Wins = Data.Run.Victories, - Losses = Data.Run.Losses, - Hero = Data.Run.Player.Hero.ToString(), - Day = (int)Data.Run.Day, - Gold = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.Gold), - Income = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.Income), - Cards = GetCardInfo(GetItemsAsCards(Data.Run.Player.Hand)), - Stash = GetCardInfo(GetItemsAsCards(Data.Run.Player.Stash)), - Skills = GetSkillInfo(Data.Run.Player.Skills), - OppCards = GetCardInfo(GetItemsAsCards(Data.Run.Opponent?.Hand)), - OppStash = GetCardInfo(GetItemsAsCards(Data.Run.Opponent?.Stash)), - OppSkills = GetSkillInfo(Data.Run.Opponent?.Skills), - Health = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.HealthMax), - Shield = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.Shield), - Regen = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.HealthRegen), - Level = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.Level), - Prestige = Data.Run.Player.GetAttributeValue(EPlayerAttributeType.Prestige), - Name = BppClientCacheBridge.TryGetProfileUsername(), - OppHealth = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.HealthMax), - OppRegen = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.HealthRegen), - OppName = Data.Run.Opponent?.Hero == EHero.Common ? "PvE" : Data.SimPvpOpponent?.Name, - OppHero = Data.Run.Opponent?.Hero.ToString(), - OppShield = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.Shield), - OppGold = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.Gold), - OppIncome = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.Income), - OppLevel = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.Level), - OppPrestige = Data.Run.Opponent?.GetAttributeValue(EPlayerAttributeType.Prestige), - PlayMode = Data.SelectedPlayMode == EPlayMode.Ranked, - AvailableEncounters = new List(), - CurrentEncounterChoices = new List(), - }; - } - - public static List GetItemsAsCards(IPlayerInventory container) - { - if (container?.Container == null) - { - BppLog.Debug( - "GameDataReader", - "Inventory container missing, returning empty card list" - ); - return new List(); - } - - return container.Container.GetSocketables().Cast().ToList(); - } - - public static List GetSkillInfo(IEnumerable skills) - { - var skillInfos = new List(); - if (skills == null) - { - BppLog.Debug("GameDataReader", "Skill collection missing, returning empty skill list"); - return skillInfos; - } - - foreach (var skill in skills) - { - if (skill.Template != null) - skillInfos.Add( - new RunInfo.SkillInfo - { - TemplateId = skill.TemplateId, - Tier = skill.Tier, - Name = skill.Template.Localization.Title.Text, - Attributes = skill.Attributes, - } - ); - } - return skillInfos; - } - - public static List GetCardInfo(List cards) - { - var cardInfos = new List(); - if (cards == null || cards.Count == 0) - return cardInfos; - - foreach (var card in cards) - { - cardInfos.Add( - new RunInfo.CardInfo - { - TemplateId = card.TemplateId, - Tier = card.Tier, - Left = card.LeftSocketId, - Instance = card.GetInstanceId(), - Attributes = card.Attributes, - Tags = card.Tags, - Name = card.Template?.InternalName, - Enchant = card.GetEnchantment().ToString(), - } - ); - } - return cardInfos; - } -} diff --git a/bazaarplusplus-mod/Game/HistoryPanel/HistoryBattlePreviewData.cs b/bazaarplusplus-mod/Game/HistoryPanel/HistoryBattlePreviewData.cs index 73f93bb..dcfeddd 100644 --- a/bazaarplusplus-mod/Game/HistoryPanel/HistoryBattlePreviewData.cs +++ b/bazaarplusplus-mod/Game/HistoryPanel/HistoryBattlePreviewData.cs @@ -1,7 +1,8 @@ #nullable enable using System.Collections.Generic; -using BazaarPlusPlus.Game.MonsterPreview; using BazaarPlusPlus.Game.PreviewSurface; +using PreviewBoardModel = BazaarPlusPlus.Game.PreviewSurface.PreviewBoardModel; +using PreviewCardSpec = BazaarPlusPlus.Game.PreviewSurface.PreviewCardSpec; namespace BazaarPlusPlus.Game.HistoryPanel; diff --git a/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Layout.cs b/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Layout.cs deleted file mode 100644 index 1bdd8ef..0000000 --- a/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Layout.cs +++ /dev/null @@ -1,395 +0,0 @@ -#nullable enable -using TMPro; -using UnityEngine; -using UnityEngine.UI; - -namespace BazaarPlusPlus.Game.HistoryPanel; - -internal sealed partial class HistoryPanel -{ - private void BuildHeader() - { - var header = CreateRect("Header", _panelRoot!); - header.anchorMin = new Vector2(0f, 1f); - header.anchorMax = new Vector2(1f, 1f); - header.pivot = new Vector2(0.5f, 1f); - header.anchoredPosition = new Vector2(0f, -20f); - header.sizeDelta = new Vector2(-48f, 112f); - - var headerLayout = CreateVerticalGroup( - "HeaderLayout", - header, - 6f, - CreatePadding(0f, 0f, 0f, 0f), - TextAnchor.UpperLeft, - true, - true, - true, - false - ); - StretchToParent(headerLayout, 0f, 0f, 0f, 0f); - - var title = CreateText("Title", headerLayout, 28, FontStyle.Bold, TextAnchor.UpperLeft); - title.text = "Game History"; - title.color = new Color(0.97f, 0.85f, 0.57f, 1f); - ConfigureLayoutElement(title.gameObject, preferredHeight: 32f, minHeight: 32f); - - var subtitle = CreateText( - "Subtitle", - headerLayout, - 14, - FontStyle.Normal, - TextAnchor.UpperLeft - ); - subtitle.text = - "Review your game history and replay any battle you want."; - subtitle.color = new Color(0.82f, 0.86f, 0.91f, 0.94f); - subtitle.textWrappingMode = TextWrappingModes.Normal; - subtitle.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement(subtitle.gameObject, preferredHeight: 28f, minHeight: 28f); - - var chipsRow = CreateHorizontalGroup( - "ChipsRow", - headerLayout, - 8f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(chipsRow.gameObject, preferredHeight: 34f, minHeight: 34f); - - _countChipText = CreateChip(chipsRow, 86f); - _battleChipText = CreateChip(chipsRow, 96f); - _databaseChipText = CreateChip(chipsRow, 110f); - CreateFlexibleSpacer("Spacer", chipsRow); - (_runsTabButton, _runsTabButtonBackground, _runsTabButtonLabel) = CreateStyledButton( - "RunsTabButton", - chipsRow, - "Runs", - 92f, - 32f - ); - _runsTabButton.onClick.AddListener(() => SetSectionMode(HistorySectionMode.Runs)); - (_ghostTabButton, _ghostTabButtonBackground, _ghostTabButtonLabel) = CreateStyledButton( - "GhostTabButton", - chipsRow, - "Ghost", - 92f, - 32f - ); - _ghostTabButton.onClick.AddListener(() => SetSectionMode(HistorySectionMode.Ghost)); - (_syncGhostButton, _syncGhostButtonBackground, _syncGhostButtonLabel) = CreateStyledButton( - "SyncGhostButton", - chipsRow, - "Sync Ghost", - 114f, - 32f - ); - _syncGhostButton.onClick.AddListener(TrySyncGhostBattles); - (_dynamicPreviewButton, _dynamicPreviewButtonBackground, _dynamicPreviewButtonLabel) = - CreateStyledButton( - "DynamicPreviewButton", - chipsRow, - GetDynamicPreviewButtonLabel(false), - 120f, - 32f - ); - _dynamicPreviewButton.onClick.AddListener(ToggleDynamicPreviewFromUi); - CreateActionButton("CloseButton", chipsRow, "Close", 86f, () => SetHistoryVisible(false)); - - _statusText = CreateText( - "Status", - headerLayout, - 12, - FontStyle.Normal, - TextAnchor.UpperLeft - ); - _statusText.color = new Color(0.93f, 0.79f, 0.51f, 0.98f); - _statusText.textWrappingMode = TextWrappingModes.NoWrap; - _statusText.overflowMode = TextOverflowModes.Ellipsis; - _statusText.gameObject.SetActive(false); - ConfigureLayoutElement(_statusText.gameObject, preferredHeight: 18f, minHeight: 18f); - } - - private void BuildContent() - { - var content = CreateRect("Content", _panelRoot!); - content.anchorMin = new Vector2(0f, 0f); - content.anchorMax = new Vector2(1f, 1f); - content.offsetMin = new Vector2(24f, 96f); - content.offsetMax = new Vector2(-24f, -164f); - - var outerLayout = CreateVerticalGroup( - "OuterLayout", - content, - 10f, - null, - TextAnchor.UpperLeft, - true, - true, - true, - false - ); - StretchToParent(outerLayout, 0f, 0f, 0f, 0f); - - var columnsRow = CreateHorizontalGroup( - "ColumnsRow", - outerLayout, - 18f, - null, - TextAnchor.UpperLeft, - true, - true, - false, - true - ); - ConfigureLayoutElement(columnsRow.gameObject, flexibleWidth: 1f, flexibleHeight: 1f); - - _runSectionPanel = CreateSectionPanel(columnsRow, "RunsPanel"); - ConfigureLayoutElement( - _runSectionPanel.gameObject, - preferredWidth: ListColumnWidth, - minWidth: ListColumnWidth, - preferredHeight: 0f, - flexibleHeight: 1f - ); - var leftLayout = CreateVerticalGroup( - "RunsLayout", - _runSectionPanel, - 10f, - CreatePadding(14f, 14f, 14f, 14f), - TextAnchor.UpperLeft, - true, - true, - true, - false - ); - StretchToParent(leftLayout, 0f, 0f, 0f, 0f); - BuildSectionHeader( - leftLayout, - "Runs", - "Choose one run to see its recorded battles.", - out _runSectionTitle, - out _ - ); - _runListContent = CreateScrollSection(leftLayout, "RunScroll"); - - var right = CreateSectionPanel(columnsRow, "BattlesPanel"); - ConfigureLayoutElement( - right.gameObject, - flexibleWidth: 1f, - preferredHeight: 0f, - flexibleHeight: 1f - ); - BuildGhostBattleSection(right); - BuildRunsBattleSection(right); - - BuildPreviewSection(outerLayout); - } - - private void BuildGhostBattleSection(RectTransform parent) - { - var layout = CreateVerticalGroup( - "GhostBattleLayout", - parent, - 2f, - CreatePadding(14f, 14f, 4f, 4f), - TextAnchor.UpperLeft, - true, - true, - true, - false - ); - StretchToParent(layout, 0f, 0f, 0f, 0f); - _ghostModeRoot = layout; - - var filterRow = CreateHorizontalGroup( - "GhostFilterRow", - layout, - 8f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement( - filterRow.gameObject, - preferredHeight: GhostFilterButtonHeight, - minHeight: GhostFilterButtonHeight - ); - (_ghostFilterAllButton, _ghostFilterAllButtonBackground, _ghostFilterAllButtonLabel) = - CreateStyledButton( - "GhostFilterAllButton", - filterRow, - "All", - 70f, - GhostFilterButtonHeight - ); - ConfigureCompactGhostFilterLabel(_ghostFilterAllButtonLabel); - _ghostFilterAllButton.onClick.AddListener(() => - SetGhostBattleFilter(GhostBattleFilter.All) - ); - (_ghostFilterIWonButton, _ghostFilterIWonButtonBackground, _ghostFilterIWonButtonLabel) = - CreateStyledButton( - "GhostFilterIWonButton", - filterRow, - "I Won", - 78f, - GhostFilterButtonHeight - ); - ConfigureCompactGhostFilterLabel(_ghostFilterIWonButtonLabel); - _ghostFilterIWonButton.onClick.AddListener(() => - SetGhostBattleFilter(GhostBattleFilter.IWon) - ); - (_ghostFilterILostButton, _ghostFilterILostButtonBackground, _ghostFilterILostButtonLabel) = - CreateStyledButton( - "GhostFilterILostButton", - filterRow, - "I Lost", - 78f, - GhostFilterButtonHeight - ); - ConfigureCompactGhostFilterLabel(_ghostFilterILostButtonLabel); - _ghostFilterILostButton.onClick.AddListener(() => - SetGhostBattleFilter(GhostBattleFilter.ILost) - ); - CreateFlexibleSpacer("GhostFilterSpacer", filterRow); - - _ghostBattleListContent = CreateScrollSection(layout, "GhostBattleScroll"); - } - - private void BuildRunsBattleSection(RectTransform parent) - { - var layout = CreateVerticalGroup( - "RunsBattleLayout", - parent, - 10f, - CreatePadding(14f, 14f, 14f, 14f), - TextAnchor.UpperLeft, - true, - true, - true, - false - ); - StretchToParent(layout, 0f, 0f, 0f, 0f); - _runsModeRoot = layout; - - BuildSectionHeader(layout, "Battles", string.Empty, out _, out _runsBattleSectionSubtitle); - _runsBattleListContent = CreateScrollSection(layout, "RunsBattleScroll"); - } - - private void BuildFooter() - { - var footer = CreateRect("Footer", _panelRoot!); - footer.anchorMin = new Vector2(0f, 0f); - footer.anchorMax = new Vector2(1f, 0f); - footer.pivot = new Vector2(0.5f, 0f); - footer.anchoredPosition = new Vector2(0f, 20f); - footer.sizeDelta = new Vector2(-48f, 68f); - AddImage(footer.gameObject, new Color(0.10f, 0.12f, 0.16f, 0.98f)); - - var divider = CreateRect("Divider", footer); - divider.anchorMin = new Vector2(0f, 1f); - divider.anchorMax = new Vector2(1f, 1f); - divider.pivot = new Vector2(0.5f, 1f); - divider.sizeDelta = new Vector2(0f, 1f); - divider.gameObject.AddComponent().color = new Color(0.76f, 0.65f, 0.36f, 0.28f); - - var footerLayout = CreateHorizontalGroup( - "FooterLayout", - footer, - 12f, - CreatePadding(16f, 16f, 8f, 8f), - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - StretchToParent(footerLayout, 0f, 0f, 0f, 0f); - - var textArea = CreateVerticalGroup( - "TextArea", - footerLayout, - 2f, - null, - TextAnchor.UpperLeft, - true, - false, - true, - false - ); - ConfigureLayoutElement(textArea.gameObject, flexibleWidth: 1f); - - _footerPrimaryText = CreateText( - "Primary", - textArea, - 15, - FontStyle.Bold, - TextAnchor.UpperLeft - ); - _footerPrimaryText.color = Color.white; - _footerPrimaryText.textWrappingMode = TextWrappingModes.NoWrap; - _footerPrimaryText.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement(_footerPrimaryText.gameObject, preferredHeight: 22f, minHeight: 22f); - - _footerSecondaryText = CreateText( - "Secondary", - textArea, - 12, - FontStyle.Normal, - TextAnchor.UpperLeft - ); - _footerSecondaryText.color = new Color(0.72f, 0.77f, 0.84f, 0.94f); - _footerSecondaryText.textWrappingMode = TextWrappingModes.NoWrap; - _footerSecondaryText.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement( - _footerSecondaryText.gameObject, - preferredHeight: 22f, - minHeight: 22f - ); - - var actions = CreateHorizontalGroup( - "Actions", - footerLayout, - 10f, - null, - TextAnchor.MiddleRight, - false, - true, - false, - false - ); - ConfigureLayoutElement(actions.gameObject, preferredWidth: 390f, minWidth: 390f); - - (_deleteRunButton, _deleteRunButtonBackground, _deleteRunButtonLabel) = CreateStyledButton( - "DeleteRunButton", - actions, - GetDeleteRunButtonLabel(false), - 130f, - 36f - ); - _deleteRunButton.onClick.AddListener(TryDeleteSelectedRun); - - (_replayButton, _replayButtonBackground, _replayButtonLabel) = CreateStyledButton( - "ReplayButton", - actions, - "Replay", - 120f, - 36f - ); - _replayButton.onClick.AddListener(TryReplaySelectedBattle); - CreateActionButton( - "FooterCloseButton", - actions, - "Close", - 120f, - () => SetHistoryVisible(false) - ); - } -} diff --git a/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Lists.cs b/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Lists.cs deleted file mode 100644 index d93102f..0000000 --- a/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Lists.cs +++ /dev/null @@ -1,714 +0,0 @@ -#nullable enable -using System; -using System.Collections.Generic; -using TMPro; -using UnityEngine; -using UnityEngine.UI; - -namespace BazaarPlusPlus.Game.HistoryPanel; - -internal sealed partial class HistoryPanel -{ - private void RefreshListsIfNeeded() - { - RefreshRunListIfNeeded(); - RefreshBattleListIfNeeded(); - } - - private void RefreshRunListIfNeeded() - { - var signature = BuildRunListSignature(); - if (!string.Equals(_lastRenderedRunListSignature, signature, StringComparison.Ordinal)) - { - RebuildRunList(); - _lastRenderedRunListSignature = signature; - _lastRenderedRunSelectionIndex = _selectedRunIndex; - return; - } - - if (_lastRenderedRunSelectionIndex == _selectedRunIndex) - return; - - UpdateRunItemSelectionStates(); - _lastRenderedRunSelectionIndex = _selectedRunIndex; - } - - private void RefreshBattleListIfNeeded() - { - var signature = BuildBattleListSignature(); - if ( - _lastRenderedBattleSectionMode != _sectionMode - || !string.Equals(_lastRenderedBattleListSignature, signature, StringComparison.Ordinal) - ) - { - RebuildBattleList(); - _lastRenderedBattleSectionMode = _sectionMode; - _lastRenderedBattleListSignature = signature; - _lastRenderedBattleSelectionIndex = GetCurrentBattleSelectionIndex(); - return; - } - - var selectedIndex = GetCurrentBattleSelectionIndex(); - if (_lastRenderedBattleSelectionIndex == selectedIndex) - return; - - UpdateBattleItemSelectionStates(); - _lastRenderedBattleSelectionIndex = selectedIndex; - } - - private string BuildRunListSignature() - { - if (_runs.Count == 0) - return "runs:empty"; - - return "runs:" - + string.Join( - "|", - _runs.ConvertAll(run => - string.Join( - "~", - run.RunId, - run.Hero ?? string.Empty, - run.RawStatus ?? string.Empty, - run.GameMode ?? string.Empty, - run.LastSeenAtUtc.ToUnixTimeSeconds(), - run.FinalDay?.ToString() ?? string.Empty, - run.BattleCount, - run.Victories?.ToString() ?? string.Empty, - run.Losses?.ToString() ?? string.Empty, - run.PlayerRank ?? string.Empty, - run.PlayerRating?.ToString() ?? string.Empty, - run.MaxHealth?.ToString() ?? string.Empty, - run.Prestige?.ToString() ?? string.Empty, - run.Level?.ToString() ?? string.Empty, - run.Income?.ToString() ?? string.Empty, - run.Gold?.ToString() ?? string.Empty, - run.StartedAtUtc.ToUnixTimeSeconds(), - run.EndedAtUtc?.ToUnixTimeSeconds().ToString() ?? string.Empty - ) - ) - ); - } - - private string BuildBattleListSignature() - { - var visibleBattles = - _sectionMode == HistorySectionMode.Ghost - ? FilteredGhostBattles - : (IReadOnlyList)_battles; - if (visibleBattles.Count == 0) - return $"{_sectionMode}:empty:{SelectedRun?.RunId ?? string.Empty}"; - - var parts = new List(visibleBattles.Count); - foreach (var battle in visibleBattles) - { - parts.Add( - string.Join( - "~", - battle.BattleId, - battle.Result ?? string.Empty, - battle.Day?.ToString() ?? string.Empty, - battle.OpponentName ?? string.Empty, - battle.PlayerHero ?? string.Empty, - battle.OpponentHero ?? string.Empty, - battle.OpponentRank ?? string.Empty, - battle.OpponentRating?.ToString() ?? string.Empty, - battle.PlayerLevel?.ToString() ?? string.Empty, - battle.OpponentLevel?.ToString() ?? string.Empty, - battle.RecordedAtUtc.ToUnixTimeSeconds(), - battle.SnapshotSummary ?? string.Empty, - battle.ReplayDownloaded, - battle.ReplayAvailable - ) - ); - } - - return $"{_sectionMode}:{SelectedRun?.RunId ?? string.Empty}:{string.Join("|", parts)}"; - } - - private int GetCurrentBattleSelectionIndex() - { - return _sectionMode == HistorySectionMode.Ghost - ? _selectedGhostBattleIndex - : _selectedBattleIndex; - } - - private void UpdateRunItemSelectionStates() - { - foreach (var itemView in _runItemViews) - { - if (itemView.Background == null) - continue; - - ApplyItemState( - itemView.Background, - itemView.Index == _selectedRunIndex, - new Color(0.11f, 0.14f, 0.18f, 0.98f), - new Color(0.17f, 0.24f, 0.32f, 0.99f) - ); - } - } - - private void UpdateBattleItemSelectionStates() - { - var visibleBattles = - _sectionMode == HistorySectionMode.Ghost - ? FilteredGhostBattles - : (IReadOnlyList)_battles; - var selectedIndex = GetCurrentBattleSelectionIndex(); - foreach (var itemView in _battleItemViews) - { - if (itemView.Background == null) - continue; - if (itemView.Index < 0 || itemView.Index >= visibleBattles.Count) - continue; - - var palette = GetBattlePalette(visibleBattles[itemView.Index]); - ApplyItemState( - itemView.Background, - itemView.Index == selectedIndex, - palette.Normal, - palette.Selected - ); - } - } - - private void RebuildRunList() - { - if (_runListContent == null) - return; - - ClearContainer(_runListContent, _runItemViews); - - if (_runs.Count == 0) - { - CreatePlaceholder(_runListContent, "No runs found yet."); - _lastRenderedRunSelectionIndex = -1; - return; - } - - for (var i = 0; i < _runs.Count; i++) - _runItemViews.Add(CreateRunItem(_runListContent, i, _runs[i], i == _selectedRunIndex)); - } - - private void RebuildBattleList() - { - if (_sectionMode == HistorySectionMode.Ghost) - RebuildGhostBattleList(); - else - RebuildRunsBattleList(); - } - - private void RebuildGhostBattleList() - { - if (_ghostBattleListContent == null) - return; - - ClearContainer(_ghostBattleListContent, _battleItemViews); - - if (FilteredGhostBattles.Count == 0) - { - CreatePlaceholder(_ghostBattleListContent, "No ghost battles synced yet."); - _lastRenderedBattleSelectionIndex = -1; - return; - } - - for (var i = 0; i < FilteredGhostBattles.Count; i++) - _battleItemViews.Add( - CreateBattleItem( - _ghostBattleListContent, - i, - FilteredGhostBattles[i], - i == _selectedGhostBattleIndex - ) - ); - } - - private void RebuildRunsBattleList() - { - if (_runsBattleListContent == null) - return; - - ClearContainer(_runsBattleListContent, _battleItemViews); - - if (SelectedRun == null) - { - CreatePlaceholder(_runsBattleListContent, "Select a run first."); - _lastRenderedBattleSelectionIndex = -1; - return; - } - - if (_battles.Count == 0) - { - CreatePlaceholder(_runsBattleListContent, "No recorded battles for this run."); - _lastRenderedBattleSelectionIndex = -1; - return; - } - - for (var i = 0; i < _battles.Count; i++) - _battleItemViews.Add( - CreateBattleItem(_runsBattleListContent, i, _battles[i], i == _selectedBattleIndex) - ); - } - - private ListItemView CreateRunItem( - Transform parent, - int index, - HistoryRunRecord run, - bool selected - ) - { - var (button, background) = CreateCardButtonShell($"RunItem_{index}", parent, 130f); - button.onClick.AddListener(() => SelectRun(index)); - - var (_, body) = BuildCardShell( - button.transform, - selected - ? new Color(0.46f, 0.70f, 0.92f, 0.94f) - : new Color(0.24f, 0.31f, 0.39f, 0.96f), - 4f, - CreatePadding(12f, 12f, 10f, 10f) - ); - - var topRow = CreateHorizontalGroup( - "TopRow", - body, - 8f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(topRow.gameObject, preferredHeight: 22f, minHeight: 22f); - - var pillRow = CreateHorizontalGroup( - "PillRow", - topRow, - 5f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(pillRow.gameObject, flexibleWidth: 1f); - - var runHeroStyle = GetHeroBadgeStyle(run.Hero); - AddPill( - pillRow, - "Hero", - runHeroStyle.ShortCode, - runHeroStyle.Background, - runHeroStyle.Text, - 60f - ); - BuildRunRankBadge(pillRow, run); - var achievement = HistoryPanelFormatter.FormatRunAchievement(run); - if (!string.IsNullOrWhiteSpace(achievement)) - { - AddPill( - pillRow, - "Achievement", - achievement, - GetRunAchievementBackground(achievement), - GetRunAchievementText(achievement), - 72f - ); - } - - var time = CreateText("Time", topRow, 11, FontStyle.Normal, TextAnchor.UpperRight); - time.text = HistoryPanelFormatter.FormatTimestamp(run.LastSeenAtUtc); - time.color = new Color(0.72f, 0.78f, 0.85f, 0.9f); - time.textWrappingMode = TextWrappingModes.NoWrap; - time.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement( - time.gameObject, - preferredWidth: 96f, - minWidth: 84f, - preferredHeight: 16f - ); - - var metaParts = new List - { - HistoryPanelFormatter.FormatDayOnly(run.FinalDay), - $"{run.BattleCount} battles", - }; - if (run.Victories.HasValue) - metaParts.Add($"{run.Victories.Value} wins"); - var duration = HistoryPanelFormatter.FormatRunDuration(run); - if (!string.IsNullOrWhiteSpace(duration)) - metaParts.Add(duration); - - AddDetailLine( - body, - string.Join(" | ", metaParts), - 13, - FontStyle.Normal, - new Color(0.82f, 0.86f, 0.92f, 0.96f) - ); - BuildRunStatStrip(body, run); - - ApplyItemState( - background, - selected, - new Color(0.11f, 0.14f, 0.18f, 0.98f), - new Color(0.17f, 0.24f, 0.32f, 0.99f) - ); - return new ListItemView { Index = index, Background = background }; - } - - private ListItemView CreateBattleItem( - Transform parent, - int index, - HistoryBattleRecord battle, - bool selected - ) - { - var isGhostBattle = battle.Source == HistoryBattleSource.Ghost; - var palette = GetBattlePalette(battle); - var (button, background) = CreateCardButtonShell($"BattleItem_{index}", parent, 88f); - button.onClick.AddListener(() => SelectBattle(index)); - - var (_, body) = BuildCardShell( - button.transform, - palette.Accent, - 3f, - CreatePadding(12f, 12f, 8f, 8f) - ); - - var topRow = CreateHorizontalGroup( - "TopRow", - body, - 8f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(topRow.gameObject, preferredHeight: 22f, minHeight: 22f); - - var pillRow = CreateHorizontalGroup( - "PillRow", - topRow, - 6f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(pillRow.gameObject, flexibleWidth: 1f); - - AddPill( - pillRow, - "Day", - HistoryPanelFormatter.FormatDayOnly(battle.Day), - new Color(0.18f, 0.21f, 0.27f, 0.94f), - new Color(0.92f, 0.95f, 1f, 1f), - 72f - ); - var playerHero = HistoryPanelFormatter.FormatOpponentHero(battle.PlayerHero); - if (isGhostBattle && !string.IsNullOrWhiteSpace(playerHero)) - { - var playerHeroStyle = GetHeroBadgeStyle(playerHero); - AddPill( - pillRow, - "PlayerHero", - $"YOU {playerHeroStyle.ShortCode}", - playerHeroStyle.Background, - playerHeroStyle.Text, - 88f - ); - } - var opponentHero = HistoryPanelFormatter.FormatOpponentHero(battle.OpponentHero); - if (!string.IsNullOrWhiteSpace(opponentHero)) - { - var opponentHeroStyle = GetHeroBadgeStyle(opponentHero); - AddPill( - pillRow, - "OpponentHero", - opponentHeroStyle.ShortCode, - opponentHeroStyle.Background, - opponentHeroStyle.Text, - 60f - ); - } - - var time = CreateText("Time", topRow, 11, FontStyle.Normal, TextAnchor.UpperRight); - time.text = HistoryPanelFormatter.FormatTimestamp(battle.RecordedAtUtc); - time.color = new Color(0.72f, 0.78f, 0.85f, 0.9f); - time.textWrappingMode = TextWrappingModes.NoWrap; - time.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement( - time.gameObject, - preferredWidth: 120f, - minWidth: 80f, - preferredHeight: 16f - ); - - BuildBattleNameRow(body, battle); - var participantSummary = isGhostBattle ? BuildBattleParticipantSummary(battle) : null; - if (!string.IsNullOrWhiteSpace(participantSummary)) - { - AddDetailLine( - body, - participantSummary, - 12, - FontStyle.Normal, - new Color(0.82f, 0.87f, 0.93f, 0.95f) - ); - } - AddDetailLine( - body, - $"ID: {ShortenBattleId(battle.BattleId)}", - 11, - FontStyle.Normal, - new Color(0.70f, 0.75f, 0.83f, 0.90f) - ); - if (!string.IsNullOrWhiteSpace(battle.SnapshotSummary)) - { - AddDetailLine( - body, - battle.SnapshotSummary, - 12, - FontStyle.Normal, - new Color(0.74f, 0.80f, 0.87f, 0.95f) - ); - } - - ApplyItemState(background, selected, palette.Normal, palette.Selected); - return new ListItemView { Index = index, Background = background }; - } - - private (RectTransform accent, RectTransform body) BuildCardShell( - Transform buttonTransform, - Color accentColor, - float bodySpacing, - RectOffset bodyPadding - ) - { - var rootLayout = CreateHorizontalGroup( - "RootLayout", - buttonTransform, - 0f, - null, - TextAnchor.UpperLeft, - true, - true, - false, - false - ); - StretchToParent(rootLayout, 0f, 0f, 0f, 0f); - - var accent = CreateRect("Accent", rootLayout); - ConfigureLayoutElement( - accent.gameObject, - preferredWidth: 6f, - minWidth: 6f, - flexibleHeight: 1f - ); - AddImage(accent.gameObject, accentColor); - - var body = CreateVerticalGroup( - "Body", - rootLayout, - bodySpacing, - bodyPadding, - TextAnchor.UpperLeft, - true, - true, - true, - false - ); - ConfigureLayoutElement(body.gameObject, flexibleWidth: 1f, flexibleHeight: 1f); - - return (accent, body); - } - - private void AddPill( - RectTransform parent, - string name, - string label, - Color bg, - Color textColor, - float minWidth - ) - { - var displayLabel = FormatPillLabel(label); - var width = Mathf.Max(minWidth, MeasurePillWidth(displayLabel)); - var pill = CreatePill(parent, name, displayLabel, bg, textColor); - ConfigureLayoutElement( - pill.gameObject, - preferredWidth: width, - minWidth: width, - preferredHeight: 22f, - minHeight: 22f - ); - } - - private void AddDetailLine( - RectTransform parent, - string text, - int fontSize, - FontStyle style, - Color color - ) - { - var line = CreateText("Detail", parent, fontSize, style, TextAnchor.UpperLeft); - line.text = text; - line.color = color; - line.textWrappingMode = TextWrappingModes.NoWrap; - line.overflowMode = TextOverflowModes.Ellipsis; - var height = fontSize <= 12 ? 16f : 20f; - ConfigureLayoutElement(line.gameObject, preferredHeight: height, minHeight: height); - } - - private static void ApplyItemState( - Image background, - bool selected, - Color normal, - Color selectedColor - ) - { - background.color = selected ? selectedColor : normal; - } - - private static void ClearContainer(RectTransform container, List views) - { - foreach (Transform child in container) - { - child.gameObject.SetActive(false); - Destroy(child.gameObject); - } - - views.Clear(); - } - - private void CreatePlaceholder(Transform parent, string message) - { - var placeholder = CreateRect("Placeholder", parent); - ConfigureLayoutElement(placeholder.gameObject, preferredHeight: 96f, minHeight: 96f); - AddImage(placeholder.gameObject, new Color(0.12f, 0.14f, 0.18f, 0.98f)); - - var text = CreateText("Text", placeholder, 14, FontStyle.Normal, TextAnchor.MiddleCenter); - text.text = message; - text.color = new Color(0.72f, 0.77f, 0.84f, 0.95f); - StretchToParent(text.rectTransform, 14f, 14f, 0f, 0f); - } - - private void BuildBattleNameRow(RectTransform parent, HistoryBattleRecord battle) - { - var row = CreateHorizontalGroup( - "BattleNameRow", - parent, - 6f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(row.gameObject, preferredHeight: 20f, minHeight: 20f); - - BuildRankBadge(row, battle.OpponentRank, battle.OpponentRating, "OpponentRank"); - - var nameText = CreateText("OpponentName", row, 15, FontStyle.Bold, TextAnchor.MiddleLeft); - nameText.text = battle.OpponentName ?? "Unknown Opponent"; - nameText.color = Color.white; - nameText.textWrappingMode = TextWrappingModes.NoWrap; - nameText.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement( - nameText.gameObject, - flexibleWidth: 1f, - preferredHeight: 20f, - minHeight: 20f - ); - } - - private static string? BuildBattleParticipantSummary(HistoryBattleRecord battle) - { - var playerHero = HistoryPanelFormatter.FormatOpponentHero(battle.PlayerHero) ?? "?"; - var opponentHero = HistoryPanelFormatter.FormatOpponentHero(battle.OpponentHero) ?? "?"; - var playerLevel = battle.PlayerLevel?.ToString() ?? "?"; - var opponentLevel = battle.OpponentLevel?.ToString() ?? "?"; - if (playerHero == "?" && opponentHero == "?" && playerLevel == "?" && opponentLevel == "?") - return null; - - return $"YOU {playerHero} Lv{playerLevel} | OPP {opponentHero} Lv{opponentLevel}"; - } - - private void BuildRankBadge( - RectTransform parent, - string? rawRank, - int? rating, - string badgeName - ) - { - var rank = FormatRank(rawRank); - if (string.IsNullOrWhiteSpace(rank)) - return; - - if (string.Equals(rank, "Legendary", StringComparison.OrdinalIgnoreCase)) - { - AddPill( - parent, - badgeName, - rating.HasValue ? rating.Value.ToString() : "LEG", - ColorFromRgb(241, 54, 41), - Color.white, - 68f - ); - return; - } - - var palette = GetRankBadgePalette(rank); - AddPill(parent, badgeName, rank.ToUpperInvariant(), palette.Background, palette.Text, 68f); - } - - private void BuildRunRankBadge(RectTransform parent, HistoryRunRecord run) - { - if (string.Equals(run.GameMode?.Trim(), "Ranked", StringComparison.OrdinalIgnoreCase)) - { - BuildRankBadge(parent, run.PlayerRank, run.PlayerRating, "PlayerRank"); - return; - } - - AddPill( - parent, - "PlayerRank", - "Unrank", - new Color(0.22f, 0.24f, 0.29f, 0.98f), - new Color(0.90f, 0.94f, 1f, 1f), - 84f - ); - } - - private void BuildRunStatStrip(Transform parent, HistoryRunRecord run) - { - var row = CreateHorizontalGroup( - "RunStatsRow", - parent, - 6f, - null, - TextAnchor.MiddleLeft, - true, - true, - false, - false - ); - ConfigureLayoutElement(row.gameObject, preferredHeight: 40f, minHeight: 40f); - - CreateRunStatChip(row, "HP", run.MaxHealth, new Color(0.63f, 0.98f, 0.35f, 1f)); - CreateRunStatChip(row, "PRE", run.Prestige, new Color(1f, 0.65f, 0.13f, 1f)); - CreateRunStatChip(row, "LVL", run.Level, new Color(0.36f, 0.79f, 1f, 1f)); - CreateRunStatChip(row, "INC", run.Income, new Color(1f, 0.86f, 0.10f, 1f)); - CreateRunStatChip(row, "GLD", run.Gold, new Color(1f, 0.86f, 0.10f, 1f)); - } -} diff --git a/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Styles.cs b/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Styles.cs deleted file mode 100644 index 4e9865e..0000000 --- a/bazaarplusplus-mod/Game/HistoryPanel/HistoryPanel.Canvas.Styles.cs +++ /dev/null @@ -1,823 +0,0 @@ -#nullable enable -using System; -using System.Collections.Generic; -using TMPro; -using UnityEngine; -using UnityEngine.UI; - -namespace BazaarPlusPlus.Game.HistoryPanel; - -internal sealed partial class HistoryPanel -{ - private RectTransform CreateSectionPanel(Transform parent, string name) - { - var panel = CreateRect(name, parent); - AddImage(panel.gameObject, new Color(0.11f, 0.13f, 0.18f, 0.98f)); - - var border = CreateRect("Border", panel); - StretchToParent(border, 0f, 0f, 0f, 0f); - AddImage(border.gameObject, new Color(0.77f, 0.83f, 0.91f, 0.08f)); - return panel; - } - - private void BuildSectionHeader( - Transform parent, - string titleText, - string subtitleText, - out TextMeshProUGUI title, - out TextMeshProUGUI subtitle - ) - { - title = CreateText("SectionTitle", parent, 20, FontStyle.Bold, TextAnchor.UpperLeft); - title.text = titleText.ToUpperInvariant(); - title.color = new Color(0.76f, 0.91f, 1f, 1f); - ConfigureLayoutElement(title.gameObject, preferredHeight: 24f, minHeight: 24f); - - subtitle = CreateText( - "SectionSubtitle", - parent, - 12, - FontStyle.Normal, - TextAnchor.UpperLeft - ); - subtitle.text = subtitleText; - subtitle.color = new Color(0.72f, 0.77f, 0.84f, 0.92f); - subtitle.textWrappingMode = TextWrappingModes.Normal; - subtitle.overflowMode = TextOverflowModes.Ellipsis; - ConfigureLayoutElement(subtitle.gameObject, preferredHeight: 30f, minHeight: 18f); - } - - private RectTransform CreateScrollSection(Transform parent, string name) - { - var root = CreateRect(name, parent); - ConfigureLayoutElement(root.gameObject, flexibleHeight: 1f, flexibleWidth: 1f); - - var viewport = CreateRect("Viewport", root); - StretchToParent(viewport, 0f, 14f, 0f, 0f); - viewport.gameObject.AddComponent(); - - var content = CreateRect("Content", viewport); - content.anchorMin = new Vector2(0f, 1f); - content.anchorMax = new Vector2(1f, 1f); - content.pivot = new Vector2(0.5f, 1f); - content.anchoredPosition = Vector2.zero; - content.offsetMin = Vector2.zero; - content.offsetMax = Vector2.zero; - content.sizeDelta = Vector2.zero; - var group = content.gameObject.AddComponent(); - group.spacing = 10f; - group.childControlWidth = true; - group.childControlHeight = false; - group.childForceExpandWidth = true; - group.childForceExpandHeight = false; - var fitter = content.gameObject.AddComponent(); - fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained; - fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; - - var scroll = root.gameObject.AddComponent(); - scroll.viewport = viewport; - scroll.content = content; - scroll.horizontal = false; - scroll.vertical = true; - scroll.scrollSensitivity = 20f; - scroll.movementType = ScrollRect.MovementType.Clamped; - scroll.verticalScrollbar = CreateScrollbar(root); - scroll.verticalScrollbarVisibility = ScrollRect - .ScrollbarVisibility - .AutoHideAndExpandViewport; - return content; - } - - private void BuildPreviewSection(Transform parent) - { - var preview = CreateRect("PreviewPanel", parent); - ConfigureLayoutElement( - preview.gameObject, - preferredHeight: PreviewSectionHeight, - minHeight: PreviewSectionHeight - ); - AddImage(preview.gameObject, new Color(0.07f, 0.09f, 0.12f, 0.99f)); - - var surfaceFrame = CreateRect("PreviewSurfaceFrame", preview); - StretchToParent(surfaceFrame, 10f, 10f, 10f, 10f); - AddImage(surfaceFrame.gameObject, new Color(0.03f, 0.04f, 0.06f, 0.98f)); - - var rawImageRect = CreateRect("PreviewRawImage", surfaceFrame); - StretchToParent(rawImageRect, 1f, 1f, 1f, 1f); - _previewSurface = rawImageRect.gameObject.AddComponent(); - _previewSurface.color = new Color(1f, 1f, 1f, 0.10f); - _previewSurface.raycastTarget = false; - - _previewStatusText = CreateText( - "PreviewStatus", - surfaceFrame, - 13, - FontStyle.Normal, - TextAnchor.MiddleCenter - ); - _previewStatusText.text = "Select a battle to preview its recorded cards."; - _previewStatusText.color = new Color(0.82f, 0.87f, 0.93f, 0.96f); - _previewStatusText.textWrappingMode = TextWrappingModes.Normal; - _previewStatusText.overflowMode = TextOverflowModes.Ellipsis; - StretchToParent(_previewStatusText.rectTransform, 28f, 28f, 18f, 18f); - - _previewDebugText = CreateText( - "PreviewDebug", - surfaceFrame, - 11, - FontStyle.Bold, - TextAnchor.UpperRight - ); - _previewDebugText.color = new Color(0.97f, 0.85f, 0.57f, 0.96f); - _previewDebugText.gameObject.SetActive(false); - _previewDebugText.textWrappingMode = TextWrappingModes.NoWrap; - _previewDebugText.overflowMode = TextOverflowModes.Overflow; - _previewDebugText.rectTransform.anchorMin = new Vector2(1f, 1f); - _previewDebugText.rectTransform.anchorMax = new Vector2(1f, 1f); - _previewDebugText.rectTransform.pivot = new Vector2(1f, 1f); - _previewDebugText.rectTransform.anchoredPosition = new Vector2(-14f, -12f); - _previewDebugText.rectTransform.sizeDelta = new Vector2(560f, 36f); - } - - private Scrollbar CreateScrollbar(Transform parent) - { - var root = CreateRect("Scrollbar", parent); - root.anchorMin = new Vector2(1f, 0f); - root.anchorMax = new Vector2(1f, 1f); - root.pivot = new Vector2(1f, 0.5f); - root.sizeDelta = new Vector2(10f, 0f); - - var track = AddImage(root.gameObject, new Color(0.16f, 0.18f, 0.22f, 0.88f)); - track.raycastTarget = true; - var area = CreateRect("Area", root); - StretchToParent(area, 0f, 0f, 0f, 0f); - var handle = CreateRect("Handle", area); - handle.anchorMin = new Vector2(0f, 1f); - handle.anchorMax = new Vector2(1f, 1f); - handle.pivot = new Vector2(0.5f, 1f); - handle.sizeDelta = new Vector2(0f, 56f); - var handleImage = AddImage(handle.gameObject, new Color(0.74f, 0.62f, 0.31f, 0.96f)); - handleImage.raycastTarget = true; - var scrollbar = root.gameObject.AddComponent(); - scrollbar.direction = Scrollbar.Direction.BottomToTop; - scrollbar.handleRect = handle; - scrollbar.targetGraphic = handleImage; - scrollbar.colors = BuildColorBlock( - new Color(0.74f, 0.62f, 0.31f, 0.96f), - new Color(0.86f, 0.73f, 0.38f, 1f), - new Color(0.24f, 0.26f, 0.30f, 0.45f) - ); - return scrollbar; - } - - private TextMeshProUGUI CreateChip(Transform parent, float width) - { - var chip = CreateRect("Chip", parent); - ConfigureLayoutElement( - chip.gameObject, - preferredWidth: width, - minWidth: width, - preferredHeight: 32f, - minHeight: 32f - ); - AddImage(chip.gameObject, new Color(0.14f, 0.18f, 0.23f, 0.96f)); - var text = CreateText("Label", chip, 12, FontStyle.Bold, TextAnchor.MiddleCenter); - text.color = new Color(0.95f, 0.96f, 0.98f, 1f); - StretchToParent(text.rectTransform, 8f, 8f, 0f, 0f); - return text; - } - - private RectTransform CreatePill( - Transform parent, - string name, - string labelText, - Color backgroundColor, - Color textColor - ) - { - var pill = CreateRect(name, parent); - AddImage(pill.gameObject, backgroundColor); - var text = CreateText("Label", pill, 12, FontStyle.Bold, TextAnchor.MiddleCenter); - text.text = labelText; - text.color = textColor; - text.enableAutoSizing = true; - text.fontSizeMin = 10f; - text.fontSizeMax = 12f; - text.maxVisibleCharacters = PillMaxVisibleCharacters; - text.overflowMode = TextOverflowModes.Ellipsis; - StretchToParent(text.rectTransform, 10f, 10f, 0f, 0f); - return pill; - } - - private void CreateActionButton( - string name, - Transform parent, - string label, - float width, - UnityEngine.Events.UnityAction onClick - ) - { - var (button, background, text) = CreateStyledButton(name, parent, label, width, 38f); - button.onClick.AddListener(onClick); - RefreshActionButton( - button, - background, - text, - true, - new Color(0.23f, 0.27f, 0.32f, 0.98f), - new Color(0.35f, 0.39f, 0.44f, 1f), - new Color(0.24f, 0.26f, 0.30f, 0.50f), - Color.white - ); - } - - private (Button button, Image background, TextMeshProUGUI label) CreateStyledButton( - string name, - Transform parent, - string labelText, - float width, - float preferredHeight = 36f - ) - { - var rect = CreateRect(name, parent); - if (width > 0f) - { - ConfigureLayoutElement( - rect.gameObject, - preferredWidth: width, - minWidth: width, - preferredHeight: preferredHeight, - minHeight: preferredHeight - ); - } - else - { - ConfigureLayoutElement( - rect.gameObject, - flexibleWidth: 1f, - preferredHeight: preferredHeight, - minHeight: preferredHeight - ); - } - - var background = AddImage(rect.gameObject, Color.white); - background.raycastTarget = true; - var button = rect.gameObject.AddComponent