From 2a6b7ab3cdca75e44e72da83a9276683441d9e0d Mon Sep 17 00:00:00 2001 From: HazDS Date: Sat, 14 Mar 2026 13:57:35 +0000 Subject: [PATCH 1/2] v0.4.4f6 compatibility Schedule 1 v0.4.4f6 introduced a new `ScheduleOne.Core` assembly, splitting base item/entity types out of `Assembly-CSharp`. --- S1API/Entities/NPC.cs | 2 +- S1API/Growing/SeedCreator.cs | 4 ++- .../Internal/Patches/LoadingScreenPatches.cs | 2 +- S1API/Internal/Patches/NPCPatches.cs | 25 +++++++++++-- S1API/Items/AdditiveDefinitionBuilder.cs | 31 ++++------------ S1API/Items/BuildableItemDefinition.cs | 8 ----- S1API/Items/BuildableItemDefinitionBuilder.cs | 35 ++++--------------- S1API/Items/ClothingItemDefinitionBuilder.cs | 33 +++-------------- S1API/Items/ItemDefinition.cs | 24 +++---------- S1API/Items/StorableItemDefinitionBuilder.cs | 33 ++++------------- S1API/S1API.csproj | 4 +++ S1API/Shops/Shop.cs | 4 ++- 12 files changed, 62 insertions(+), 143 deletions(-) diff --git a/S1API/Entities/NPC.cs b/S1API/Entities/NPC.cs index c00a3640..8bfba895 100644 --- a/S1API/Entities/NPC.cs +++ b/S1API/Entities/NPC.cs @@ -1720,7 +1720,7 @@ public void LerpScale(float scale, float lerpTime) => /// Causes the NPC to become panicked. /// public void Panic() => - S1NPC.SetPanicked(); + S1NPC.SetPanicked_Server(); /// /// Causes the NPC to stop panicking, if they are currently. diff --git a/S1API/Growing/SeedCreator.cs b/S1API/Growing/SeedCreator.cs index 2dbe7044..115af405 100644 --- a/S1API/Growing/SeedCreator.cs +++ b/S1API/Growing/SeedCreator.cs @@ -1,10 +1,12 @@ #if (IL2CPPMELON) using S1Growing = Il2CppScheduleOne.Growing; using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; using S1Registry = Il2CppScheduleOne.Registry; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1Growing = ScheduleOne.Growing; using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; using S1Registry = ScheduleOne.Registry; #endif @@ -32,7 +34,7 @@ public static SeedDefinition CreateSeed( seed.Name = name; seed.Description = description; seed.StackLimit = stackLimit; - seed.Category = S1ItemFramework.EItemCategory.Agriculture; + seed.Category = S1CoreItemFramework.EItemCategory.Agriculture; // if (icon != null) // { diff --git a/S1API/Internal/Patches/LoadingScreenPatches.cs b/S1API/Internal/Patches/LoadingScreenPatches.cs index c3e04ca3..1ac7d156 100644 --- a/S1API/Internal/Patches/LoadingScreenPatches.cs +++ b/S1API/Internal/Patches/LoadingScreenPatches.cs @@ -211,7 +211,7 @@ private static void CloseLoadingScreenDirectly(S1UI.LoadingScreen loadingScreen) { ReflectionUtils.TrySetFieldOrProperty(loadingScreen, "IsOpen", false); - var musicPlayer = S1DevUtilities.Singleton.Instance; + var musicPlayer = S1DevUtilities.Singleton.Instance; if (musicPlayer != null) { musicPlayer.SetTrackEnabled("Loading Screen", enabled: false); diff --git a/S1API/Internal/Patches/NPCPatches.cs b/S1API/Internal/Patches/NPCPatches.cs index 61964d84..933f9b7e 100644 --- a/S1API/Internal/Patches/NPCPatches.cs +++ b/S1API/Internal/Patches/NPCPatches.cs @@ -1579,8 +1579,29 @@ private static bool NPCHealth_Revive_Prefix(S1NPCs.NPCHealth __instance) if (apiNpc == null || !apiNpc.IsCustomNPC) return true; // use original for base NPCs - // Skip S1API NPCs for now - return false; + try + { + // Local-only revive: set state flags via reflection (read-only properties) + Utils.ReflectionUtils.TrySetFieldOrProperty(__instance, "IsDead", false); + Utils.ReflectionUtils.TrySetFieldOrProperty(__instance, "IsKnockedOut", false); + + // Set health backing field directly to bypass SyncVar setter + Utils.ReflectionUtils.TrySetFieldOrProperty( + __instance, "k__BackingField", __instance.MaxHealth); + + // Disable behaviours locally (non-networked equivalent of Disable_Server) + baseNpc.Behaviour.DeadBehaviour?.Disable(); + baseNpc.Behaviour.UnconsciousBehaviour?.Disable(); + + // Fire revive event so downstream listeners still react + __instance.onRevive?.Invoke(); + } + catch (Exception ex) + { + MelonLogger.Warning($"[S1API] Revive guard failed for custom NPC: {ex.Message}"); + } + + return false; // skip original to avoid SyncVar/networking calls } /// diff --git a/S1API/Items/AdditiveDefinitionBuilder.cs b/S1API/Items/AdditiveDefinitionBuilder.cs index 0f52ec47..d78cb394 100644 --- a/S1API/Items/AdditiveDefinitionBuilder.cs +++ b/S1API/Items/AdditiveDefinitionBuilder.cs @@ -1,9 +1,11 @@ #if (IL2CPPMELON) using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; using S1Registry = Il2CppScheduleOne.Registry; using S1Storage = Il2CppScheduleOne.Storage; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; using S1Registry = ScheduleOne.Registry; using S1Storage = ScheduleOne.Storage; #endif @@ -39,11 +41,10 @@ internal AdditiveDefinitionBuilder() _definition.StackLimit = 10; _definition.BasePurchasePrice = 10f; _definition.ResellMultiplier = 0.5f; - _definition.Category = S1ItemFramework.EItemCategory.Agriculture; - _definition.legalStatus = S1ItemFramework.ELegalStatus.Legal; + _definition.Category = S1CoreItemFramework.EItemCategory.Agriculture; + _definition.legalStatus = S1CoreItemFramework.ELegalStatus.Legal; _definition.AvailableInDemo = true; _definition.UsableInFilters = true; - _definition.LabelDisplayColor = Color.white; // Provide a minimal StoredItem placeholder so the field is never null in tooling/inspectors. _storedItemPlaceholder = new GameObject("S1API_DefaultStoredItem"); @@ -82,10 +83,8 @@ private void CopyPropertiesFrom(S1ItemFramework.AdditiveDefinition source) _definition.Description = source.Description; _definition.Category = source.Category; _definition.StackLimit = source.StackLimit; - _definition.Keywords = source.Keywords; _definition.AvailableInDemo = source.AvailableInDemo; _definition.UsableInFilters = source.UsableInFilters; - _definition.LabelDisplayColor = source.LabelDisplayColor; _definition.Icon = source.Icon; _definition.legalStatus = source.legalStatus; _definition.PickpocketDifficultyMultiplier = source.PickpocketDifficultyMultiplier; @@ -116,7 +115,7 @@ public AdditiveDefinitionBuilder WithBasicInfo(string id, string name, string de _definition.ID = id; _definition.Name = name; _definition.Description = description; - _definition.Category = (S1ItemFramework.EItemCategory)category; + _definition.Category = (S1CoreItemFramework.EItemCategory)category; var displayName = string.IsNullOrEmpty(name) ? id : name; if (!string.IsNullOrEmpty(displayName)) @@ -164,25 +163,7 @@ public AdditiveDefinitionBuilder WithPricing(float basePurchasePrice, float rese /// public AdditiveDefinitionBuilder WithLegalStatus(LegalStatus status) { - _definition.legalStatus = (S1ItemFramework.ELegalStatus)status; - return this; - } - - /// - /// Sets the color of the label displayed in UI. - /// - public AdditiveDefinitionBuilder WithLabelColor(Color color) - { - _definition.LabelDisplayColor = color; - return this; - } - - /// - /// Sets keywords used for filtering and searching this additive. - /// - public AdditiveDefinitionBuilder WithKeywords(params string[] keywords) - { - _definition.Keywords = keywords; + _definition.legalStatus = (S1CoreItemFramework.ELegalStatus)status; return this; } diff --git a/S1API/Items/BuildableItemDefinition.cs b/S1API/Items/BuildableItemDefinition.cs index 4ef3a291..65b0ca64 100644 --- a/S1API/Items/BuildableItemDefinition.cs +++ b/S1API/Items/BuildableItemDefinition.cs @@ -41,14 +41,6 @@ public BuildSoundType BuildSoundType set => S1BuildableItemDefinition.BuildSoundType = (S1ItemFramework.BuildableItemDefinition.EBuildSoundType)value; } - /// - /// The color displayed on the item's label in the UI. - /// - public new Color LabelDisplayColor - { - get => S1BuildableItemDefinition.LabelDisplayColor; - set => S1BuildableItemDefinition.LabelDisplayColor = value; - } } /// diff --git a/S1API/Items/BuildableItemDefinitionBuilder.cs b/S1API/Items/BuildableItemDefinitionBuilder.cs index ec2e9aec..3c4d1a88 100644 --- a/S1API/Items/BuildableItemDefinitionBuilder.cs +++ b/S1API/Items/BuildableItemDefinitionBuilder.cs @@ -1,8 +1,10 @@ #if (IL2CPPMELON) using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; using S1Registry = Il2CppScheduleOne.Registry; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; using S1Registry = ScheduleOne.Registry; #endif @@ -30,11 +32,10 @@ internal BuildableItemDefinitionBuilder() _definition.StackLimit = 10; _definition.BasePurchasePrice = 10f; _definition.ResellMultiplier = 0.5f; - _definition.Category = S1ItemFramework.EItemCategory.Furniture; - _definition.legalStatus = S1ItemFramework.ELegalStatus.Legal; + _definition.Category = S1CoreItemFramework.EItemCategory.Furniture; + _definition.legalStatus = S1CoreItemFramework.ELegalStatus.Legal; _definition.AvailableInDemo = true; _definition.UsableInFilters = true; - _definition.LabelDisplayColor = Color.white; _definition.BuildSoundType = S1ItemFramework.BuildableItemDefinition.EBuildSoundType.Wood; } @@ -57,10 +58,8 @@ private void CopyPropertiesFrom(S1ItemFramework.BuildableItemDefinition source) _definition.Description = source.Description; _definition.Category = source.Category; _definition.StackLimit = source.StackLimit; - _definition.Keywords = source.Keywords; _definition.AvailableInDemo = source.AvailableInDemo; _definition.UsableInFilters = source.UsableInFilters; - _definition.LabelDisplayColor = source.LabelDisplayColor; _definition.Icon = source.Icon; _definition.legalStatus = source.legalStatus; _definition.PickpocketDifficultyMultiplier = source.PickpocketDifficultyMultiplier; @@ -144,7 +143,7 @@ public BuildableItemDefinitionBuilder WithPricing(float basePurchasePrice, float /// The builder instance for fluent chaining. public BuildableItemDefinitionBuilder WithCategory(ItemCategory category) { - _definition.Category = (S1ItemFramework.EItemCategory)category; + _definition.Category = (S1CoreItemFramework.EItemCategory)category; return this; } @@ -159,28 +158,6 @@ public BuildableItemDefinitionBuilder WithStackLimit(int limit) return this; } - /// - /// Sets keywords used for filtering and searching this item. - /// - /// Array of keywords. - /// The builder instance for fluent chaining. - public BuildableItemDefinitionBuilder WithKeywords(params string[] keywords) - { - _definition.Keywords = keywords; - return this; - } - - /// - /// Sets the color of the label displayed in UI. - /// - /// The color to use for the item label. - /// The builder instance for fluent chaining. - public BuildableItemDefinitionBuilder WithLabelColor(Color color) - { - _definition.LabelDisplayColor = color; - return this; - } - /// /// Sets the legal status of the item. /// @@ -188,7 +165,7 @@ public BuildableItemDefinitionBuilder WithLabelColor(Color color) /// The builder instance for fluent chaining. public BuildableItemDefinitionBuilder WithLegalStatus(LegalStatus status) { - _definition.legalStatus = (S1ItemFramework.ELegalStatus)status; + _definition.legalStatus = (S1CoreItemFramework.ELegalStatus)status; return this; } diff --git a/S1API/Items/ClothingItemDefinitionBuilder.cs b/S1API/Items/ClothingItemDefinitionBuilder.cs index db479050..e6bb6f02 100644 --- a/S1API/Items/ClothingItemDefinitionBuilder.cs +++ b/S1API/Items/ClothingItemDefinitionBuilder.cs @@ -1,11 +1,13 @@ #if (IL2CPPMELON) using S1Clothing = Il2CppScheduleOne.Clothing; using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; using S1Registry = Il2CppScheduleOne.Registry; using Il2CppCollections = Il2CppSystem.Collections.Generic; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1Clothing = ScheduleOne.Clothing; using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; using S1Registry = ScheduleOne.Registry; using Il2CppCollections = System.Collections.Generic; #endif @@ -34,12 +36,11 @@ internal ClothingItemDefinitionBuilder() _definition.StackLimit = 10; _definition.BasePurchasePrice = 10f; _definition.ResellMultiplier = 0.5f; - _definition.Category = S1ItemFramework.EItemCategory.Clothing; - _definition.legalStatus = S1ItemFramework.ELegalStatus.Legal; + _definition.Category = S1CoreItemFramework.EItemCategory.Clothing; + _definition.legalStatus = S1CoreItemFramework.ELegalStatus.Legal; _definition.AvailableInDemo = true; _definition.UsableInFilters = true; - _definition.LabelDisplayColor = Color.white; - + // Clothing-specific defaults _definition.Slot = S1Clothing.EClothingSlot.Head; _definition.ApplicationType = S1Clothing.EClothingApplicationType.Accessory; @@ -72,8 +73,6 @@ internal ClothingItemDefinitionBuilder(S1Clothing.ClothingDefinition source) _definition.legalStatus = source.legalStatus; _definition.AvailableInDemo = source.AvailableInDemo; _definition.UsableInFilters = source.UsableInFilters; - _definition.LabelDisplayColor = source.LabelDisplayColor; - _definition.Keywords = source.Keywords; _definition.StoredItem = source.StoredItem; _definition.Equippable = source.Equippable; @@ -215,28 +214,6 @@ public ClothingItemDefinitionBuilder WithPricing(float basePurchasePrice, float return this; } - /// - /// Sets keywords used for filtering and searching this item. - /// - /// Array of keywords. - /// The builder instance for fluent chaining. - public ClothingItemDefinitionBuilder WithKeywords(params string[] keywords) - { - _definition.Keywords = keywords; - return this; - } - - /// - /// Sets the color of the label displayed in UI. - /// - /// The color to use for the item label. - /// The builder instance for fluent chaining. - public ClothingItemDefinitionBuilder WithLabelColor(Color color) - { - _definition.LabelDisplayColor = color; - return this; - } - /// /// Builds the clothing item definition, registers it with the game's registry, and returns a wrapper. /// diff --git a/S1API/Items/ItemDefinition.cs b/S1API/Items/ItemDefinition.cs index ab8a9122..e5454afd 100644 --- a/S1API/Items/ItemDefinition.cs +++ b/S1API/Items/ItemDefinition.cs @@ -1,7 +1,9 @@ #if (IL2CPPMELON) using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; #endif using UnityEngine; @@ -74,7 +76,7 @@ public int StackLimit public ItemCategory Category { get => (ItemCategory)S1ItemDefinition.Category; - set => S1ItemDefinition.Category = (S1ItemFramework.EItemCategory)value; + set => S1ItemDefinition.Category = (S1CoreItemFramework.EItemCategory)value; } /// @@ -101,28 +103,10 @@ public bool AvailableInDemo public LegalStatus LegalStatus { get => (LegalStatus)S1ItemDefinition.legalStatus; - set => S1ItemDefinition.legalStatus = (S1ItemFramework.ELegalStatus)value; + set => S1ItemDefinition.legalStatus = (S1CoreItemFramework.ELegalStatus)value; } - /// - /// The color of the label shown in UI. - /// - public Color LabelDisplayColor - { - get => S1ItemDefinition.LabelDisplayColor; - set => S1ItemDefinition.LabelDisplayColor = value; - } - - /// - /// Any keywords used to filter/search this item. - /// - public string[] Keywords - { - get => S1ItemDefinition.Keywords; - set => S1ItemDefinition.Keywords = value; - } - /// /// Creates a new item instance with the specified quantity. /// diff --git a/S1API/Items/StorableItemDefinitionBuilder.cs b/S1API/Items/StorableItemDefinitionBuilder.cs index 21e81318..4f36e79f 100644 --- a/S1API/Items/StorableItemDefinitionBuilder.cs +++ b/S1API/Items/StorableItemDefinitionBuilder.cs @@ -1,10 +1,12 @@ #if (IL2CPPMELON) using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; using S1Registry = Il2CppScheduleOne.Registry; using S1StationFramework = Il2CppScheduleOne.StationFramework; using S1Storage = Il2CppScheduleOne.Storage; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; using S1Registry = ScheduleOne.Registry; using S1StationFramework = ScheduleOne.StationFramework; using S1Storage = ScheduleOne.Storage; @@ -50,11 +52,10 @@ internal StorableItemDefinitionBuilder() _definition.StackLimit = 10; _definition.BasePurchasePrice = 10f; _definition.ResellMultiplier = 0.5f; - _definition.Category = S1ItemFramework.EItemCategory.Tools; - _definition.legalStatus = S1ItemFramework.ELegalStatus.Legal; + _definition.Category = S1CoreItemFramework.EItemCategory.Tools; + _definition.legalStatus = S1CoreItemFramework.ELegalStatus.Legal; _definition.AvailableInDemo = true; _definition.UsableInFilters = true; - _definition.LabelDisplayColor = Color.white; // Provide a minimal StoredItem placeholder so the field is never null in tooling/inspectors. _storedItemPlaceholder = new GameObject("S1API_DefaultStoredItem"); @@ -78,7 +79,7 @@ public StorableItemDefinitionBuilder WithBasicInfo(string id, string name, strin _definition.ID = id; _definition.Name = name; _definition.Description = description; - _definition.Category = (S1ItemFramework.EItemCategory)category; + _definition.Category = (S1CoreItemFramework.EItemCategory)category; // Update the underlying ScriptableObject name for clarity in inspectors/debuggers. var displayName = string.IsNullOrEmpty(name) ? id : name; @@ -135,29 +136,7 @@ public StorableItemDefinitionBuilder WithPricing(float basePurchasePrice, float /// The builder instance for fluent chaining. public StorableItemDefinitionBuilder WithLegalStatus(LegalStatus status) { - _definition.legalStatus = (S1ItemFramework.ELegalStatus)status; - return this; - } - - /// - /// Sets the color of the label displayed in UI. - /// - /// The color to use for the item label. - /// The builder instance for fluent chaining. - public StorableItemDefinitionBuilder WithLabelColor(Color color) - { - _definition.LabelDisplayColor = color; - return this; - } - - /// - /// Sets keywords used for filtering and searching this item. - /// - /// Array of keywords. - /// The builder instance for fluent chaining. - public StorableItemDefinitionBuilder WithKeywords(params string[] keywords) - { - _definition.Keywords = keywords; + _definition.legalStatus = (S1CoreItemFramework.ELegalStatus)status; return this; } diff --git a/S1API/S1API.csproj b/S1API/S1API.csproj index f10716f0..e481b8a5 100644 --- a/S1API/S1API.csproj +++ b/S1API/S1API.csproj @@ -55,6 +55,7 @@ + @@ -80,6 +81,9 @@ $(MonoAssembliesPath)/Assembly-CSharp.dll + + $(MonoAssembliesPath)/ScheduleOne.Core.dll + $(MonoAssembliesPath)/UnityEngine.dll diff --git a/S1API/Shops/Shop.cs b/S1API/Shops/Shop.cs index 4a52bf6c..ac71f757 100644 --- a/S1API/Shops/Shop.cs +++ b/S1API/Shops/Shop.cs @@ -1,9 +1,11 @@ #if (IL2CPPMELON) using S1UIShop = Il2CppScheduleOne.UI.Shop; using S1ItemFramework = Il2CppScheduleOne.ItemFramework; +using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework; #elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX) using S1UIShop = ScheduleOne.UI.Shop; using S1ItemFramework = ScheduleOne.ItemFramework; +using S1CoreItemFramework = ScheduleOne.Core.Items.Framework; #endif using System.Collections.Generic; @@ -63,7 +65,7 @@ public bool HasItem(string itemId) /// True if the shop sells at least one item in this category. public bool SellsCategory(ItemCategory category) { - var s1Category = (S1ItemFramework.EItemCategory)category; + var s1Category = (S1CoreItemFramework.EItemCategory)category; foreach (var listing in S1ShopInterface.Listings) { From e7ef1ba517b3f5d9f1f0a0b65575868c7cf9bf97 Mon Sep 17 00:00:00 2001 From: ifBars Date: Sun, 15 Mar 2026 02:14:03 -0700 Subject: [PATCH 2/2] chore: Address CodeRabbit Comments --- S1API/Entities/NPC.cs | 7 ++++++- S1API/Internal/Patches/NPCPatches.cs | 25 ++++++++++++++++--------- S1API/S1API.csproj | 4 +++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/S1API/Entities/NPC.cs b/S1API/Entities/NPC.cs index 8bfba895..3f293997 100644 --- a/S1API/Entities/NPC.cs +++ b/S1API/Entities/NPC.cs @@ -1719,8 +1719,13 @@ public void LerpScale(float scale, float lerpTime) => /// /// Causes the NPC to become panicked. /// - public void Panic() => + public void Panic() + { + if (!SafeIsServer()) + return; + S1NPC.SetPanicked_Server(); + } /// /// Causes the NPC to stop panicking, if they are currently. diff --git a/S1API/Internal/Patches/NPCPatches.cs b/S1API/Internal/Patches/NPCPatches.cs index 933f9b7e..4f65e34d 100644 --- a/S1API/Internal/Patches/NPCPatches.cs +++ b/S1API/Internal/Patches/NPCPatches.cs @@ -1581,13 +1581,18 @@ private static bool NPCHealth_Revive_Prefix(S1NPCs.NPCHealth __instance) try { - // Local-only revive: set state flags via reflection (read-only properties) - Utils.ReflectionUtils.TrySetFieldOrProperty(__instance, "IsDead", false); - Utils.ReflectionUtils.TrySetFieldOrProperty(__instance, "IsKnockedOut", false); - - // Set health backing field directly to bypass SyncVar setter - Utils.ReflectionUtils.TrySetFieldOrProperty( + bool healthSet = Utils.ReflectionUtils.TrySetFieldOrProperty( __instance, "k__BackingField", __instance.MaxHealth); + bool isDeadSet = Utils.ReflectionUtils.TrySetFieldOrProperty(__instance, "IsDead", false); + bool isKnockedOutSet = Utils.ReflectionUtils.TrySetFieldOrProperty(__instance, "IsKnockedOut", false); + + if (!healthSet || !isDeadSet || !isKnockedOutSet) + { + MelonLogger.Warning( + $"[S1API] Revive guard reflection failed for custom NPC '{baseNpc?.ID ?? ""}' " + + $"(Health={healthSet}, IsDead={isDeadSet}, IsKnockedOut={isKnockedOutSet}); falling back to original revive."); + return true; + } // Disable behaviours locally (non-networked equivalent of Disable_Server) baseNpc.Behaviour.DeadBehaviour?.Disable(); @@ -1595,13 +1600,15 @@ private static bool NPCHealth_Revive_Prefix(S1NPCs.NPCHealth __instance) // Fire revive event so downstream listeners still react __instance.onRevive?.Invoke(); + + return false; // skip original to avoid SyncVar/networking calls } catch (Exception ex) { - MelonLogger.Warning($"[S1API] Revive guard failed for custom NPC: {ex.Message}"); + MelonLogger.Warning( + $"[S1API] Revive guard failed for custom NPC '{baseNpc?.ID ?? ""}': {ex.Message}. Falling back to original revive."); + return true; } - - return false; // skip original to avoid SyncVar/networking calls } /// diff --git a/S1API/S1API.csproj b/S1API/S1API.csproj index e481b8a5..b8759979 100644 --- a/S1API/S1API.csproj +++ b/S1API/S1API.csproj @@ -55,7 +55,9 @@ - + + $(Il2CppAssembliesPath)\Il2CppScheduleOne.Core.dll +