Skip to content

Commit c465f3d

Browse files
Release Opponent Memory 1.1
1 parent d5d6588 commit c465f3d

5 files changed

Lines changed: 156 additions & 27 deletions

File tree

OpponentMemory.Tests/Program.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ private static int Main()
2323
SettingsAreClamped();
2424
StandardLeaderboardWaitsForEightPlayers();
2525
PartialLeaderboardRequiresStability();
26+
MatchIdentityUsesNewStatsHandleAfterMenu();
27+
MatchIdentityPreservesMatchingHandleAfterMenu();
28+
MatchIdentityWaitsForAmbiguousConflict();
29+
MatchIdentityPreservesStoredHandleDuringActiveConflict();
30+
MatchEndPolicyRequiresAConfirmedResult();
2631
Console.WriteLine("All Opponent Memory tests passed.");
2732
return 0;
2833
}
@@ -142,6 +147,41 @@ private static IReadOnlyList<LeaderboardPlayer> Players(int count)
142147
return players;
143148
}
144149

150+
private static void MatchIdentityUsesNewStatsHandleAfterMenu()
151+
{
152+
var snapshot = new GameHandleSnapshot(100, 200);
153+
var decision = MatchIdentityResolver.Evaluate(100, true, snapshot);
154+
Equal(MatchIdentityAction.StartNew, decision.Action, "new stats handle starts a new match");
155+
Equal<uint?>(200, decision.GameHandle, "new stats handle is selected");
156+
}
157+
158+
private static void MatchIdentityPreservesMatchingHandleAfterMenu()
159+
{
160+
var decision = MatchIdentityResolver.Evaluate(100, true, new GameHandleSnapshot(100, 100));
161+
Equal(MatchIdentityAction.Preserve, decision.Action, "matching handles preserve state");
162+
Equal<uint?>(100, decision.GameHandle, "matching handle remains active");
163+
}
164+
165+
private static void MatchIdentityWaitsForAmbiguousConflict()
166+
{
167+
var decision = MatchIdentityResolver.Evaluate(100, true, new GameHandleSnapshot(200, 300));
168+
Equal(MatchIdentityAction.Wait, decision.Action, "unrelated conflicting handles wait for stable data");
169+
}
170+
171+
private static void MatchIdentityPreservesStoredHandleDuringActiveConflict()
172+
{
173+
var decision = MatchIdentityResolver.Evaluate(200, false, new GameHandleSnapshot(100, 200));
174+
Equal(MatchIdentityAction.Preserve, decision.Action, "active match keeps its known handle during a transient conflict");
175+
Equal<uint?>(200, decision.GameHandle, "known active handle is retained");
176+
}
177+
178+
private static void MatchEndPolicyRequiresAConfirmedResult()
179+
{
180+
False(MatchEndPolicy.ShouldClearState(false, false), "missing game stats do not clear match state");
181+
False(MatchEndPolicy.ShouldClearState(true, false), "pending result does not clear match state");
182+
True(MatchEndPolicy.ShouldClearState(true, true), "confirmed result clears match state");
183+
}
184+
145185
private static void True(bool value, string name) { if(!value) throw new InvalidOperationException(name); }
146186
private static void False(bool value, string name) { if(value) throw new InvalidOperationException(name); }
147187
private static void Equal<T>(T expected, T actual, string name) { if(!Equals(expected, actual)) throw new InvalidOperationException(name + ": expected " + expected + ", actual " + actual); }

OpponentMemory/BattlegroundsPlayerResolver.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using HearthDb.Enums;
55
using Hearthstone_Deck_Tracker;
6+
using Hearthstone_Deck_Tracker.Enums;
67
using Hearthstone_Deck_Tracker.Hearthstone.Entities;
78

89
namespace OpponentMemory
@@ -17,13 +18,19 @@ public bool IsSupportedSoloMatch()
1718

1819
public int GetRound() => Core.Game?.GetTurnNumber() ?? 0;
1920
public bool RequiresEightPlayerLeaderboard() => Core.Game?.CurrentGameType == GameType.GT_BATTLEGROUNDS;
20-
public uint? GetGameHandle()
21+
public GameHandleSnapshot GetGameHandles()
2122
{
2223
var metadataHandle = Core.Game?.MetaData.ServerInfo?.GameHandle ?? 0;
23-
if(metadataHandle > 0)
24-
return metadataHandle;
2524
var statsHandle = Core.Game?.CurrentGameStats?.ServerInfo?.GameHandle ?? 0;
26-
return statsHandle > 0 ? statsHandle : (uint?)null;
25+
return new GameHandleSnapshot(
26+
metadataHandle > 0 ? metadataHandle : (uint?)null,
27+
statsHandle > 0 ? statsHandle : (uint?)null);
28+
}
29+
30+
public bool HasDefinitiveMatchResult()
31+
{
32+
var stats = Core.Game?.CurrentGameStats;
33+
return MatchEndPolicy.ShouldClearState(stats != null, stats != null && stats.Result != GameResult.None);
2734
}
2835
public int? GetScheduledOpponentPlayerId()
2936
{
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
namespace OpponentMemory
2+
{
3+
public readonly struct GameHandleSnapshot
4+
{
5+
public GameHandleSnapshot(uint? metadataHandle, uint? statsHandle)
6+
{
7+
MetadataHandle = metadataHandle;
8+
StatsHandle = statsHandle;
9+
}
10+
11+
public uint? MetadataHandle { get; }
12+
public uint? StatsHandle { get; }
13+
public bool HasConflict => MetadataHandle.HasValue && StatsHandle.HasValue && MetadataHandle.Value != StatsHandle.Value;
14+
public uint? AvailableHandle => MetadataHandle ?? StatsHandle;
15+
public bool Contains(uint handle) => MetadataHandle == handle || StatsHandle == handle;
16+
}
17+
18+
public enum MatchIdentityAction
19+
{
20+
Preserve,
21+
StartNew,
22+
Wait
23+
}
24+
25+
public readonly struct MatchIdentityDecision
26+
{
27+
public MatchIdentityDecision(MatchIdentityAction action, uint? gameHandle)
28+
{
29+
Action = action;
30+
GameHandle = gameHandle;
31+
}
32+
33+
public MatchIdentityAction Action { get; }
34+
public uint? GameHandle { get; }
35+
}
36+
37+
public static class MatchIdentityResolver
38+
{
39+
public static MatchIdentityDecision Evaluate(uint? storedHandle, bool afterMenu, GameHandleSnapshot snapshot)
40+
{
41+
if(snapshot.HasConflict)
42+
{
43+
// HDT can retain the previous metadata handle while the current game
44+
// statistics already identify the new match.
45+
if(afterMenu
46+
&& storedHandle.HasValue
47+
&& snapshot.MetadataHandle == storedHandle
48+
&& snapshot.StatsHandle.HasValue
49+
&& snapshot.StatsHandle != storedHandle)
50+
return new MatchIdentityDecision(MatchIdentityAction.StartNew, snapshot.StatsHandle);
51+
52+
if(!afterMenu && storedHandle.HasValue && snapshot.Contains(storedHandle.Value))
53+
return new MatchIdentityDecision(MatchIdentityAction.Preserve, storedHandle);
54+
55+
return new MatchIdentityDecision(MatchIdentityAction.Wait, null);
56+
}
57+
58+
var currentHandle = snapshot.AvailableHandle;
59+
if(afterMenu)
60+
{
61+
if(!currentHandle.HasValue)
62+
return new MatchIdentityDecision(MatchIdentityAction.Wait, null);
63+
if(storedHandle.HasValue && currentHandle.Value == storedHandle.Value)
64+
return new MatchIdentityDecision(MatchIdentityAction.Preserve, currentHandle);
65+
return new MatchIdentityDecision(MatchIdentityAction.StartNew, currentHandle);
66+
}
67+
68+
if(currentHandle.HasValue && storedHandle.HasValue && currentHandle.Value != storedHandle.Value)
69+
return new MatchIdentityDecision(MatchIdentityAction.StartNew, currentHandle);
70+
71+
return new MatchIdentityDecision(MatchIdentityAction.Preserve, currentHandle ?? storedHandle);
72+
}
73+
}
74+
75+
public static class MatchEndPolicy
76+
{
77+
public static bool ShouldClearState(bool hasGameStats, bool hasDefinitiveResult)
78+
=> hasGameStats && hasDefinitiveResult;
79+
}
80+
}

OpponentMemory/OpponentMemory.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
<UseWPF>true</UseWPF>
55
<AssemblyName>OpponentMemory</AssemblyName>
66
<RootNamespace>OpponentMemory</RootNamespace>
7-
<Version>1.0.0</Version>
8-
<AssemblyVersion>1.0.0.0</AssemblyVersion>
9-
<FileVersion>1.0.0.0</FileVersion>
7+
<Version>1.1.0</Version>
8+
<AssemblyVersion>1.1.0.0</AssemblyVersion>
9+
<FileVersion>1.1.0.0</FileVersion>
1010
<PlatformTarget>x64</PlatformTarget>
1111
<Platforms>x64</Platforms>
1212
<LangVersion>10</LangVersion>
1313
<Nullable>enable</Nullable>
1414
<Deterministic>true</Deterministic>
1515
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
16+
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
1617
<DebugType>portable</DebugType>
1718
<DebugSymbols>true</DebugSymbols>
1819
<PathMap>$(MSBuildProjectDirectory)=/_/OpponentMemory</PathMap>

OpponentMemory/OpponentMemoryPlugin.cs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace OpponentMemory
99
{
1010
public sealed class OpponentMemoryPlugin : IPlugin
1111
{
12-
public const string DisplayVersion = "1.0";
12+
public const string DisplayVersion = "1.1";
1313
private readonly EncounterTracker _tracker = new EncounterTracker();
1414
private readonly BattlegroundsPlayerResolver _resolver = new BattlegroundsPlayerResolver();
1515
private readonly OpponentMemoryOverlay _overlay = new OpponentMemoryOverlay();
@@ -33,7 +33,7 @@ public sealed class OpponentMemoryPlugin : IPlugin
3333
public string Description => "Tracks how many times you have faced each opponent in Hearthstone Battlegrounds.";
3434
public string ButtonText => "Settings";
3535
public string Author => "";
36-
public Version Version => new Version(1, 0, 0, 0);
36+
public Version Version => new Version(1, 1, 0, 0);
3737
public MenuItem MenuItem => _menuItem ??= BuildMenu();
3838

3939
public void OnLoad()
@@ -70,15 +70,18 @@ public void OnUpdate()
7070
CompleteActiveCombat();
7171
_wasCombat = false;
7272
}
73-
_sawMenu = true;
73+
if(_resolver.HasDefinitiveMatchResult())
74+
ResetMatchState();
75+
else
76+
_sawMenu = true;
7477
}
7578
_wasSupported = false;
7679
InvokeUi(_overlay.Hide);
7780
return;
7881
}
7982
var round = _resolver.GetRound();
80-
var currentGameHandle = _resolver.GetGameHandle();
81-
if(!PrepareMatchState(currentGameHandle))
83+
var gameHandles = _resolver.GetGameHandles();
84+
if(!PrepareMatchState(gameHandles))
8285
{
8386
InvokeUi(_overlay.Hide);
8487
return;
@@ -136,30 +139,28 @@ private void CompleteActiveCombat()
136139
_forceOverlayRefresh = true;
137140
}
138141

139-
private bool PrepareMatchState(uint? currentGameHandle)
142+
private bool PrepareMatchState(GameHandleSnapshot gameHandles)
140143
{
141144
if(!_hasMatchState)
142145
{
143-
InitializeNewMatch(currentGameHandle);
144-
return true;
145-
}
146-
if(_sawMenu)
147-
{
148-
if(!currentGameHandle.HasValue)
146+
if(gameHandles.HasConflict)
149147
return false;
150-
if(_gameHandle.HasValue && currentGameHandle.Value == _gameHandle.Value)
151-
_sawMenu = false;
152-
else
153-
InitializeNewMatch(currentGameHandle);
148+
InitializeNewMatch(gameHandles.AvailableHandle);
154149
return true;
155150
}
156-
if(currentGameHandle.HasValue && _gameHandle.HasValue && currentGameHandle.Value != _gameHandle.Value)
151+
152+
var decision = MatchIdentityResolver.Evaluate(_gameHandle, _sawMenu, gameHandles);
153+
if(decision.Action == MatchIdentityAction.Wait)
154+
return false;
155+
if(decision.Action == MatchIdentityAction.StartNew)
157156
{
158-
InitializeNewMatch(currentGameHandle);
157+
InitializeNewMatch(decision.GameHandle);
159158
return true;
160159
}
161-
if(currentGameHandle.HasValue)
162-
_gameHandle = currentGameHandle;
160+
161+
_sawMenu = false;
162+
if(decision.GameHandle.HasValue)
163+
_gameHandle = decision.GameHandle;
163164
return true;
164165
}
165166

0 commit comments

Comments
 (0)