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
6 changes: 6 additions & 0 deletions S1API.Tests/Entities/DealerLifecyclePolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ public void DealerDealBehaviourMatchesNativePriority()
Assert.Equal(5, NPCPrefabBuilder.DealerAttendDealPriority);
}

[Fact]
public void BehaviourStackObjectsRemainActiveWhileTheirInternalStateIsManaged()
{
Assert.True(NPCPrefabBuilder.BehaviourObjectsRemainActive);
}

[Theory]
[InlineData("DealerHomeEvent", true)]
[InlineData("HomeEvent", true)]
Expand Down
22 changes: 22 additions & 0 deletions S1API.Tests/Internal/Utils/ManagedEventRegistrationTrackerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using S1API.Internal.Utils;

namespace S1API.Tests.Internal.Utils;

public sealed class ManagedEventRegistrationTrackerTests
{
[Fact]
public void DuplicateAddsAreRemovedOneAtATimeInReverseRegistrationOrder()
{
var tracker = new ManagedEventRegistrationTracker<string>();
Action handler = () => { };

tracker.Add(handler, "first");
tracker.Add(handler, "second");

Assert.True(tracker.TryTakeLast(handler, out string? second));
Assert.Equal("second", second);
Assert.True(tracker.TryTakeLast(handler, out string? first));
Assert.Equal("first", first);
Assert.False(tracker.TryTakeLast(handler, out _));
}
}
73 changes: 73 additions & 0 deletions S1API.Tests/Internal/Utils/ReflectionUtilsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using S1API.Internal.Utils;

namespace S1API.Tests.Internal.Utils;

public sealed class ReflectionUtilsTests
{
[Fact]
public void InstanceAccessSupportsMonoFieldAndIl2CppPropertyShapes()
{
var mono = new MonoShape();
var il2Cpp = new Il2CppShape();

Assert.True(ReflectionUtils.TrySetFieldOrProperty(mono, "runtimeMember", 14));
Assert.True(ReflectionUtils.TrySetFieldOrProperty(il2Cpp, "runtimeMember", 14));
Assert.Equal(14, ReflectionUtils.TryGetFieldOrProperty(mono, "runtimeMember"));
Assert.Equal(14, ReflectionUtils.TryGetFieldOrProperty(il2Cpp, "runtimeMember"));
}

[Fact]
public void StaticAccessSupportsMonoFieldAndIl2CppPropertyShapes()
{
ReflectionUtils.TrySetStaticFieldOrProperty(typeof(MonoStaticShape), "RuntimeMember", 31);
ReflectionUtils.TrySetStaticFieldOrProperty(typeof(Il2CppStaticShape), "RuntimeMember", 31);

Assert.Equal(31, ReflectionUtils.TryGetStaticFieldOrProperty(typeof(MonoStaticShape), "RuntimeMember"));
Assert.Equal(31, ReflectionUtils.TryGetStaticFieldOrProperty(typeof(Il2CppStaticShape), "RuntimeMember"));
}

[Fact]
public void StaticAccessWalksBaseTypesForNonPublicMembers()
{
ReflectionUtils.TrySetStaticFieldOrProperty(typeof(DerivedStaticShape), "RuntimeMember", 47);

Assert.Equal(
47,
ReflectionUtils.TryGetStaticFieldOrProperty(typeof(DerivedStaticShape), "RuntimeMember"));
}

private sealed class MonoShape
{
#pragma warning disable CS0169
private int runtimeMember;
#pragma warning restore CS0169
}

private sealed class Il2CppShape
{
public int runtimeMember { get; set; }
}

private static class MonoStaticShape
{
#pragma warning disable CS0649
public static int RuntimeMember;
#pragma warning restore CS0649
}

private static class Il2CppStaticShape
{
public static int RuntimeMember { get; set; }
}

private class BaseStaticShape
{
#pragma warning disable CS0169, CS0649
private static int RuntimeMember;
#pragma warning restore CS0169, CS0649
}

private sealed class DerivedStaticShape : BaseStaticShape
{
}
}
12 changes: 2 additions & 10 deletions S1API/Entities/NPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4784,17 +4784,9 @@ private static void EnsureScheduleActionsOnPrefab(GameObject prefabRoot)
// Best-effort wire internal references so actions have context even while inactive
try
{
#if MONOMELON
var npcField = t.GetField("npc", BindingFlags.NonPublic | BindingFlags.Instance);
var schedField = t.GetField("schedule", BindingFlags.NonPublic | BindingFlags.Instance);
#else
var npcField = t.GetField("npc", Il2CppSystem.Reflection.BindingFlags.NonPublic | Il2CppSystem.Reflection.BindingFlags.Public | Il2CppSystem.Reflection.BindingFlags.Instance);
var schedField = t.GetField("schedule", Il2CppSystem.Reflection.BindingFlags.NonPublic | Il2CppSystem.Reflection.BindingFlags.Public | Il2CppSystem.Reflection.BindingFlags.Instance);
#endif
var baseNpc = prefabRoot.GetComponent<S1NPCs.NPC>();

npcField?.SetValue(comp, baseNpc);
schedField?.SetValue(comp, existingMgr);
Internal.Utils.ReflectionUtils.TrySetFieldOrProperty(comp, "npc", baseNpc);
Internal.Utils.ReflectionUtils.TrySetFieldOrProperty(comp, "schedule", existingMgr);
}
catch { }
go.SetActive(false);
Expand Down
52 changes: 20 additions & 32 deletions S1API/Entities/NPCCustomer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ internal static bool EnsureDealAttendanceSupport(GameObject? prefabRoot, Type? o
component = behaviourObject.AddComponent<S1NPCs.Behaviour.CustomerAttendDealBehaviour>();
}

component.gameObject.SetActive(NPCPrefabBuilder.BehaviourObjectsRemainActive);
component.EnabledOnAwake = false;
component.Name = "Customer attend deal";
component.Priority = 4;
Expand Down Expand Up @@ -770,8 +771,7 @@ private bool EnsureContractAssignedHook()

try
{
var onContractAssignedField = typeof(S1Economy.Customer).GetField("onContractAssigned", BindingFlags.Public | BindingFlags.Instance);
var evt = onContractAssignedField?.GetValue(Component);
var evt = Utils.ReflectionUtils.TryGetFieldOrProperty(Component, "onContractAssigned");
if (evt == null)
return false;

Expand Down Expand Up @@ -834,36 +834,35 @@ private void HandleContractAssigned(object contract)
int winStart = 0;
int winEnd = 0;

var contractType = contract.GetType();
var paymentProp = contractType.GetProperty("Payment", BindingFlags.Public | BindingFlags.Instance);
if (paymentProp != null)
payment = Convert.ToSingle(paymentProp.GetValue(contract));
var paymentValue = Utils.ReflectionUtils.TryGetFieldOrProperty(contract, "Payment");
if (paymentValue != null)
payment = Convert.ToSingle(paymentValue);

var productListProp = contractType.GetProperty("ProductList", BindingFlags.Public | BindingFlags.Instance);
var productList = productListProp?.GetValue(contract);
var productList = Utils.ReflectionUtils.TryGetFieldOrProperty(contract, "ProductList");
if (productList != null)
{
var entriesField = productList.GetType().GetField("entries", BindingFlags.Public | BindingFlags.Instance);
var entries = entriesField?.GetValue(productList) as System.Collections.IEnumerable;
var entries = Utils.ReflectionUtils.TryGetFieldOrProperty(productList, "entries") as System.Collections.IEnumerable;
if (entries != null)
{
foreach (var e in entries)
{
var qtyField = e.GetType().GetField("Quantity", BindingFlags.Public | BindingFlags.Instance);
if (qtyField != null)
totalQty += Convert.ToInt32(qtyField.GetValue(e));
if (e != null)
{
var quantity = Utils.ReflectionUtils.TryGetFieldOrProperty(e, "Quantity");
if (quantity != null)
totalQty += Convert.ToInt32(quantity);
}
}
}
}

var windowProp = contractType.GetProperty("DeliveryWindow", BindingFlags.Public | BindingFlags.Instance);
var window = windowProp?.GetValue(contract);
var window = Utils.ReflectionUtils.TryGetFieldOrProperty(contract, "DeliveryWindow");
if (window != null)
{
var startField = window.GetType().GetField("WindowStartTime", BindingFlags.Public | BindingFlags.Instance);
var endField = window.GetType().GetField("WindowEndTime", BindingFlags.Public | BindingFlags.Instance);
if (startField != null) winStart = Convert.ToInt32(startField.GetValue(window));
if (endField != null) winEnd = Convert.ToInt32(endField.GetValue(window));
var start = Utils.ReflectionUtils.TryGetFieldOrProperty(window, "WindowStartTime");
var end = Utils.ReflectionUtils.TryGetFieldOrProperty(window, "WindowEndTime");
if (start != null) winStart = Convert.ToInt32(start);
if (end != null) winEnd = Convert.ToInt32(end);
}

foreach (Action<float, int, int, int> handler in handlers.GetInvocationList())
Expand Down Expand Up @@ -1113,19 +1112,8 @@ private System.Collections.IEnumerator WaitAndShowDialogue(S1Dialogue.DialogueCo

private static void SetNonPublicInstanceField(object target, string fieldName, object? value)
{
try
{
if (target == null || string.IsNullOrEmpty(fieldName)) return;
var type = target.GetType();
FieldInfo? field = null;
while (type != null && field == null)
{
field = type.GetField(fieldName, BindingFlags.Instance | System.Reflection.BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
}
field?.SetValue(target, value);
}
catch (Exception) { }
if (target == null || string.IsNullOrEmpty(fieldName)) return;
Utils.ReflectionUtils.TrySetFieldOrProperty(target, fieldName, value);
}
}
}
Loading
Loading