diff --git a/S1API/Entities/NPC.cs b/S1API/Entities/NPC.cs
index f83fd874..3f482155 100644
--- a/S1API/Entities/NPC.cs
+++ b/S1API/Entities/NPC.cs
@@ -3652,6 +3652,25 @@ internal void CreateFromClientNetworkSpawn()
_clientNetworkSpawnHydrationDepth--;
}
+ // Clients never run the instance constructor, so the behaviour owners the host sets there are missing on
+ // this clone of the template. Without them the host's behaviour RPCs throw here and are dropped.
+ if (gameObject is null)
+ {
+ Logger.Debug("[NPC] Client wrapper has no GameObject; behaviour owners not bound.");
+ }
+ else
+ {
+ try
+ {
+ RepairBehaviourOwnership(gameObject, GetPreferredNpcComponent(gameObject));
+ NPCCustomer.BindDealAttendance(gameObject, GetSafeNpcId());
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"[NPC] Binding behaviour owners on client for '{GetSafeNpcId()}' failed: {ex.Message}");
+ }
+ }
+
MarkCustomNpcFinalized();
}
diff --git a/S1API/Entities/NPCCustomer.cs b/S1API/Entities/NPCCustomer.cs
index 803cb1d4..48a73f2b 100644
--- a/S1API/Entities/NPCCustomer.cs
+++ b/S1API/Entities/NPCCustomer.cs
@@ -633,6 +633,88 @@ internal static bool EnsureDealAttendanceSupport(GameObject? prefabRoot, Type? o
return true;
}
+ ///
+ /// INTERNAL: Points a live customer's "Customer attend deal" behaviour at the NPC's own NPCBehaviour
+ /// and Customer. Co-op clients get custom NPCs as clones of the S1API template, where the behaviour's
+ /// owner (Behaviour.beh) and its cached _customer stay null. The host's Enable/Activate RPCs then
+ /// throw inside the behaviour, FishNet drops the exception, and the customer never waits at the drop.
+ ///
+ internal static void BindDealAttendance(GameObject? npcRoot, string? npcId)
+ {
+ string id = npcId ?? "";
+ if (npcRoot == null)
+ {
+ Logger.Warning($"Cannot bind deal attendance for '{id}': NPC object is missing.");
+ return;
+ }
+
+ var customer = npcRoot.GetComponent();
+ if (customer == null)
+ return;
+
+ var behaviourManager = npcRoot.GetComponentInChildren(true);
+ if (behaviourManager == null)
+ {
+ Logger.Warning($"Cannot bind deal attendance for '{id}': NPCBehaviour is missing.");
+ return;
+ }
+
+ var attendance = npcRoot.GetComponentInChildren(true);
+ if (attendance == null)
+ {
+ Logger.Warning($"Cannot bind deal attendance for '{id}': CustomerAttendDealBehaviour is missing.");
+ return;
+ }
+
+ var repaired = new List();
+#if IL2CPPMELON
+ if (attendance.beh == null || attendance.beh.Pointer != behaviourManager.Pointer)
+ {
+ attendance.beh = behaviourManager;
+ repaired.Add("owner");
+ }
+
+ if (attendance._customer == null)
+ {
+ attendance._customer = customer;
+ repaired.Add("customer");
+ }
+
+ if (customer._attendDealBehaviour == null)
+ {
+ customer._attendDealBehaviour = attendance;
+ repaired.Add("customer link");
+ }
+#else
+ if (!ReferenceEquals(attendance.beh, behaviourManager))
+ {
+ if (ReflectionUtils.TrySetFieldOrProperty(attendance, "beh", behaviourManager))
+ repaired.Add("owner");
+ else
+ Logger.Error($"Cannot set the deal attendance owner for '{id}'.");
+ }
+
+ if (ReflectionUtils.TryGetFieldOrProperty(attendance, "_customer") == null)
+ {
+ if (ReflectionUtils.TrySetFieldOrProperty(attendance, "_customer", customer))
+ repaired.Add("customer");
+ else
+ Logger.Error($"Cannot set the deal attendance customer for '{id}'.");
+ }
+
+ if (ReflectionUtils.TryGetFieldOrProperty(customer, "_attendDealBehaviour") == null)
+ {
+ if (ReflectionUtils.TrySetFieldOrProperty(customer, "_attendDealBehaviour", attendance))
+ repaired.Add("customer link");
+ else
+ Logger.Error($"Cannot set the customer's deal attendance for '{id}'.");
+ }
+#endif
+
+ if (repaired.Count > 0)
+ Logger.Msg($"Bound deal attendance for '{id}': set {string.Join(", ", repaired)}.");
+ }
+
///
/// INTERNAL: Ensure UnityEvents are allocated so runtime wiring does not NRE.
///