-
Notifications
You must be signed in to change notification settings - Fork 28
fix(velvet): treat an unknown race as unavailable instead of allowed #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| using Aetherphone.Core.Game; | ||
| using Xunit; | ||
|
|
||
| namespace Aetherphone.Tests; | ||
|
|
||
| public sealed class RaceWatchTests | ||
| { | ||
| private const byte HyurRace = 1; | ||
| private const byte Unpopulated = 0; | ||
| private const int CustomizeLength = 26; | ||
|
|
||
| [Fact] | ||
| public void StartsUnknown_SoNothingIsReportedBeforeACharacterLoads() | ||
| { | ||
| var watch = new RaceWatch(); | ||
|
|
||
| Assert.Null(watch.Race); | ||
| Assert.Null(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Observe_RecordsTheRaceItRead() | ||
| { | ||
| var watch = new RaceWatch(); | ||
|
|
||
| watch.Observe(Customize(HyurRace)); | ||
|
|
||
| Assert.Equal(HyurRace, watch.Race); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Observe_RecordsALalafell() | ||
| { | ||
| var watch = new RaceWatch(); | ||
|
|
||
| watch.Observe(Customize(RaceWatch.LalafellRaceId)); | ||
|
|
||
| Assert.Equal(RaceWatch.LalafellRaceId, watch.Race); | ||
| Assert.True(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Observe_RecordsANonLalafell() | ||
| { | ||
| var watch = new RaceWatch(); | ||
|
|
||
| watch.Observe(Customize(HyurRace)); | ||
|
|
||
| Assert.False(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Observe_LeavesTheAnswerUnknownWhenThereIsNoCustomizeData() | ||
| { | ||
| var watch = new RaceWatch(); | ||
|
|
||
| watch.Observe([]); | ||
|
|
||
| Assert.Null(watch.Race); | ||
| Assert.Null(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Observe_TreatsAnUnpopulatedRaceByteAsNoAnswer() | ||
| { | ||
| var watch = new RaceWatch(); | ||
|
|
||
| watch.Observe(Customize(Unpopulated)); | ||
|
|
||
| Assert.Null(watch.Race); | ||
| Assert.Null(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AnUnpopulatedReadDoesNotOverwriteAKnownAnswer() | ||
| { | ||
| var watch = new RaceWatch(); | ||
| watch.Observe(Customize(RaceWatch.LalafellRaceId)); | ||
|
|
||
| watch.Observe(Customize(Unpopulated)); | ||
|
|
||
| Assert.True(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void AFailedReadDoesNotEraseAnAnswerWeAlreadyHave() | ||
| { | ||
| var watch = new RaceWatch(); | ||
| watch.Observe(Customize(RaceWatch.LalafellRaceId)); | ||
|
|
||
| watch.Observe([]); | ||
|
|
||
| Assert.True(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Observe_TracksARaceChangeRatherThanLatchingTheFirstRead() | ||
| { | ||
| var watch = new RaceWatch(); | ||
| watch.Observe(Customize(HyurRace)); | ||
|
|
||
| watch.Observe(Customize(RaceWatch.LalafellRaceId)); | ||
|
|
||
| Assert.True(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Forget_ReturnsToUnknownWhenTheCharacterGoesAway() | ||
| { | ||
| var watch = new RaceWatch(); | ||
| watch.Observe(Customize(RaceWatch.LalafellRaceId)); | ||
|
|
||
| watch.Forget(); | ||
|
|
||
| Assert.Null(watch.Race); | ||
| Assert.Null(watch.IsLalafell); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ObserveAfterForget_DoesNotKeepThePreviousCharactersAnswer() | ||
| { | ||
| var watch = new RaceWatch(); | ||
| watch.Observe(Customize(RaceWatch.LalafellRaceId)); | ||
| watch.Forget(); | ||
|
|
||
| watch.Observe(Customize(HyurRace)); | ||
|
|
||
| Assert.False(watch.IsLalafell); | ||
| } | ||
|
|
||
| private static byte[] Customize(byte race) | ||
| { | ||
| var customize = new byte[CustomizeLength]; | ||
| customize[RaceWatch.RaceIndex] = race; | ||
| return customize; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,6 @@ | |
| using Aetherphone.Core.Wallpapers; | ||
| using Aetherphone.Windows.Components; | ||
| using Dalamud.Bindings.ImGui; | ||
| using Dalamud.Game.ClientState.Objects.Enums; | ||
| using Dalamud.Interface; | ||
| using Dalamud.Interface.Utility; | ||
|
|
||
|
|
@@ -31,6 +30,7 @@ namespace Aetherphone.Apps.Velvet; | |
| internal sealed partial class VelvetShell : IPhoneApp | ||
| { | ||
| private const float HeartbeatSeconds = 45f; | ||
| private const long RaceProbeIntervalMilliseconds = 1000; | ||
|
|
||
| private readonly VelvetStore store; | ||
| private readonly StoryPresenter stories; | ||
|
|
@@ -66,11 +66,13 @@ internal sealed partial class VelvetShell : IPhoneApp | |
| private readonly RouterDraw<VelvetView> drawView; | ||
| private readonly Action back; | ||
|
|
||
| private readonly RaceWatch race = new(); | ||
| private readonly FrameworkTicker raceTicker; | ||
|
|
||
| private PhoneTheme theme = PhoneTheme.Default; | ||
| private INavigator navigation = null!; | ||
| private VelvetPage activeTab = VelvetPage.Discover; | ||
| private float sinceHeartbeat = HeartbeatSeconds; | ||
| private bool cachedLalafell; | ||
|
|
||
| public VelvetShell(AethernetSession session, AethernetApi net, LodestoneService lodestone, | ||
| Configuration configuration, PhotoLibrary library, HttpService http, RemoteImageCache images, | ||
|
|
@@ -109,6 +111,8 @@ public VelvetShell(AethernetSession session, AethernetApi net, LodestoneService | |
| drawView = DrawView; | ||
| back = () => router.Pop(); | ||
| threadView = new ThreadView(this); | ||
| raceTicker = new FrameworkTicker(Plugin.Framework, RaceProbeIntervalMilliseconds, ProbeRace, | ||
| installer.Gate(Id)); | ||
| } | ||
|
|
||
| public string Id => "velvet"; | ||
|
|
@@ -206,7 +210,7 @@ public void Draw(in PhoneContext context) | |
| return; | ||
| } | ||
|
|
||
| if (IsLalafellCharacter() || store.AccessBlocked) | ||
| if (Unavailable) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| { | ||
| TourHolds.Hold(Id); | ||
| store.EnsureMe(); | ||
|
|
@@ -263,6 +267,7 @@ public void Draw(in PhoneContext context) | |
|
|
||
| public void Dispose() | ||
| { | ||
| raceTicker.Dispose(); | ||
| threadView.Dispose(); | ||
| stories.Dispose(); | ||
| store.Dispose(); | ||
|
|
@@ -272,34 +277,38 @@ public void Dispose() | |
| configuration.VelvetAcknowledgedGate && | ||
| configuration.VelvetAcknowledgedGateVersion >= Configuration.VelvetGateVersion; | ||
|
|
||
| private bool Unavailable => race.IsLalafell is not false || store.AccessBlocked; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
|
|
||
| private void TickHeartbeat() | ||
| { | ||
| sinceHeartbeat += ImGui.GetIO().DeltaTime; | ||
| if (sinceHeartbeat >= HeartbeatSeconds) | ||
| if (sinceHeartbeat < HeartbeatSeconds) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (race.IsLalafell is not { } isLalafell) | ||
| { | ||
| sinceHeartbeat = 0f; | ||
| store.Heartbeat(SocialRegion.EffectiveCode(configuration, gameData), IsLalafellCharacter()); | ||
| return; | ||
| } | ||
|
|
||
| sinceHeartbeat = 0f; | ||
| store.Heartbeat(SocialRegion.EffectiveCode(configuration, gameData), isLalafell); | ||
| } | ||
|
|
||
| private bool IsLalafellCharacter() | ||
| private void ProbeRace() | ||
| { | ||
| const byte lalafellRaceId = 3; | ||
| if (!Plugin.Framework.IsInFrameworkUpdateThread) | ||
| if (!Plugin.ClientState.IsLoggedIn) | ||
| { | ||
| return cachedLalafell; | ||
| race.Forget(); | ||
| return; | ||
| } | ||
|
|
||
| var local = gameData.LocalPlayer; | ||
| if (local is null) | ||
| if (local is not null) | ||
| { | ||
| return cachedLalafell; | ||
| race.Observe(local.Customize); | ||
| } | ||
|
|
||
| var customize = local.Customize; | ||
| var raceIndex = (int)CustomizeIndex.Race; | ||
| cachedLalafell = customize.Length > raceIndex && customize[raceIndex] == lalafellRaceId; | ||
| return cachedLalafell; | ||
| } | ||
|
|
||
| private void DrawView(VelvetView view, Rect area, int depth) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,35 @@ | ||||||||||||||||||||
| using Dalamud.Game.ClientState.Objects.Enums; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| namespace Aetherphone.Core.Game; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| internal sealed class RaceWatch | ||||||||||||||||||||
| { | ||||||||||||||||||||
| internal const int RaceIndex = (int)CustomizeIndex.Race; | ||||||||||||||||||||
| internal const byte LalafellRaceId = 3; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private const byte UnknownRaceId = 0; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private volatile byte race = UnknownRaceId; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public byte? Race => race == UnknownRaceId ? null : race; | ||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two volatile loads. If
Suggested change
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| public bool? IsLalafell => Race is { } known ? known == LalafellRaceId : null; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public void Forget() => race = UnknownRaceId; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public void Observe(ReadOnlySpan<byte> customize) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| if (customize.Length <= RaceIndex) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| var observed = customize[RaceIndex]; | ||||||||||||||||||||
| if (observed == UnknownRaceId) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| return; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| race = observed; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one second floor means every login with Velvet open shows that screen briefly, not just the title screen. Skip the rate limit while the race is unknown.