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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions S1API.Tests/Internal/Patches/ContactsAppWaitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using S1API.Internal.Patches;

namespace S1API.Tests.Internal.Patches;

public sealed class ContactsAppWaitTests
{
private static Func<string> Id(string id) => () => id;
private static Func<string> Throws() => () => throw new NullReferenceException("NPC data is gone");

[Fact]
public void AnNpcWhoseIdThrowsDoesNotHideTheOthers()
{
Assert.True(ContactsAppPatches.AllIdsPresent(
new[] { Id("big_willy"), Throws(), Id("disco_davey"), Throws() },
new[] { "big_willy", "disco_davey" }));
}

[Fact]
public void AMissingCustomNpcIsStillWaitedFor()
{
Assert.False(ContactsAppPatches.AllIdsPresent(
new[] { Id("big_willy"), Throws() },
new[] { "big_willy", "disco_davey" }));
}

[Fact]
public void NothingReadableMeansNothingPresent()
{
Assert.False(ContactsAppPatches.AllIdsPresent(new[] { Throws() }, new[] { "big_willy" }));
}
}
32 changes: 27 additions & 5 deletions S1API/Internal/Patches/ContactsAppPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,9 @@ private static IEnumerator WaitForNPCs(S1ContactsApp.ContactsApp contactsApp)
if (customNPCs.Count == 0)
yield break;

yield return new WaitUntil((Func<bool>)(() =>
{
var allSceneNPCs = Object.FindObjectsOfType<S1NPCs.NPC>(true);
return customNPCs.All(npc => allSceneNPCs.Any(sn => sn.ID == npc.ID));
}));
// A few checks a second, not every frame: this searches the whole scene, inactive objects included.
while (!AllPresentInScene(customNPCs))
yield return new WaitForSeconds(0.25f);

yield return new WaitUntil((Func<bool>)(() =>
{
Expand Down Expand Up @@ -201,6 +199,30 @@ private static IEnumerator WaitForNPCs(S1ContactsApp.ContactsApp contactsApp)
AddRelationCircles(contactsApp);
}

private static bool AllPresentInScene(System.Collections.Generic.List<NPC> customNPCs) =>
AllIdsPresent(
Object.FindObjectsOfType<S1NPCs.NPC>(true).Select(sceneNpc => (System.Func<string>)(() => sceneNpc.ID)),
customNPCs.Select(npc => npc.ID));

/// <summary>
/// Are all <paramref name="wanted"/> ids among the scene's NPC ids? Some of the game's own NPCs throw from
/// <c>ID</c> (the pooled special customers on IL2CPP); they are never waited for, so they are skipped rather
/// than failing the whole check.
/// </summary>
internal static bool AllIdsPresent(
System.Collections.Generic.IEnumerable<System.Func<string>> sceneIds,
System.Collections.Generic.IEnumerable<string> wanted)
{
var present = new System.Collections.Generic.HashSet<string>();
foreach (System.Func<string> readId in sceneIds)
{
try { present.Add(readId()); }
catch (System.Exception) { }
}

return wanted.All(present.Contains);
}

/// <summary>
/// Creates and positions relation circles for all custom NPCs.
/// </summary>
Expand Down
Loading