Skip to content
Open
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
5 changes: 2 additions & 3 deletions S1API/Entities/NPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -854,9 +854,8 @@ internal static bool TryApplyDealerDefaults(S1Economy.Dealer dealerComponent, De
#else
dealerComponent.DealerType = (S1Economy.EDealerType)(int)data.DealerType;
#endif
dealerComponent.SellInsufficientQualityItems = data.SellInsufficientQualityItems;
dealerComponent.SellExcessQualityItems = data.SellExcessQualityItems;

// Note: SellInsufficientQualityItems and SellExcessQualityItems were removed in v0.4.3

// Store Home building reference in NPCPrefabIdentity for resolution in Main scene
// This runs in Menu scene where buildings aren't available yet
string buildingNameToStore = null;
Expand Down
2 changes: 1 addition & 1 deletion S1API/Entities/NPCDealer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ public void AssignCustomer(NPC customer)

try
{
Component.SendAddCustomer(customer.ID);
Component.AddCustomer_Server(customer.ID);

// Best-effort local wiring so both sides have AssignedDealer set immediately
try
Expand Down
24 changes: 5 additions & 19 deletions S1API/GameTime/TimeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,37 +166,23 @@ private static void RemoveFromActionList(object actionList, Action handler)
/// <summary>
/// Whether the player is currently sleeping.
/// </summary>
public static bool SleepInProgress => S1GameTime.TimeManager.Instance.SleepInProgress;

/// <summary>
/// Whether the time is currently overridden (frozen or custom).
/// </summary>
public static bool TimeOverridden => S1GameTime.TimeManager.Instance.TimeOverridden;
public static bool SleepInProgress => S1GameTime.TimeManager.Instance.IsSleepInProgress;

/// <summary>
/// The current normalized time of day (0.0 = start, 1.0 = end).
/// </summary>
public static float NormalizedTime => S1GameTime.TimeManager.Instance.NormalizedTime;
public static float NormalizedTime => S1GameTime.TimeManager.Instance.NormalizedTimeOfDay;

/// <summary>
/// Total playtime (in seconds).
/// </summary>
public static float Playtime => S1GameTime.TimeManager.Instance.Playtime;

/// <summary>
/// Fast-forwards time to morning wake time (7:00 AM).
/// </summary>
public static void FastForwardToWakeTime() => S1GameTime.TimeManager.Instance.FastForwardToWakeTime();

/// <summary>
/// Sets the current time manually.
/// </summary>
public static void SetTime(int time24h, bool local = false) => S1GameTime.TimeManager.Instance.SetTime(time24h, local);

/// <summary>
/// Sets the number of elapsed in-game days.
/// Sets the current time manually and synchronizes across the network.
/// This can only be called by the host.
/// </summary>
public static void SetElapsedDays(int days) => S1GameTime.TimeManager.Instance.SetElapsedDays(days);
public static void SetTime(int time24h) => S1GameTime.TimeManager.Instance.SetTimeAndSync(time24h);

/// <summary>
/// Gets the current time formatted in 12-hour AM/PM format.
Expand Down
18 changes: 9 additions & 9 deletions S1API/Graffiti/GraffitiManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ private static S1Graffiti.GraffitiManager Instance
}

/// <summary>
/// Gets all spray surfaces in the game.
/// Gets all world spray surfaces in the game.
/// </summary>
/// <returns>A list of all spray surfaces, wrapped in S1API SpraySurface objects.</returns>
/// <returns>A list of all world spray surfaces, wrapped in S1API SpraySurface objects.</returns>
public static List<SpraySurface> GetAllSpraySurfaces()
{
var instance = Instance;
Expand All @@ -46,24 +46,24 @@ public static List<SpraySurface> GetAllSpraySurfaces()
var result = new List<SpraySurface>();

#if (IL2CPPMELON)
// IL2CPP: Access SpraySurfaces directly as Il2CppSystem.Collections.Generic.List
if (instance.SpraySurfaces == null)
// IL2CPP: Access WorldSpraySurfaces directly as Il2CppSystem.Collections.Generic.List
if (instance.WorldSpraySurfaces == null)
return result;

for (int i = 0; i < instance.SpraySurfaces.Count; i++)
for (int i = 0; i < instance.WorldSpraySurfaces.Count; i++)
{
var surface = instance.SpraySurfaces[i];
var surface = instance.WorldSpraySurfaces[i];
if (surface != null)
{
result.Add(new SpraySurface(surface));
}
}
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
// Mono: Access SpraySurfaces and iterate directly
if (instance.SpraySurfaces == null)
// Mono: Access WorldSpraySurfaces and iterate directly
if (instance.WorldSpraySurfaces == null)
return result;

foreach (var surface in instance.SpraySurfaces)
foreach (var surface in instance.WorldSpraySurfaces)
{
if (surface != null)
{
Expand Down
14 changes: 7 additions & 7 deletions S1API/Graffiti/SpraySurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@
namespace S1API.Graffiti
{
/// <summary>
/// Represents a surface that can be spray painted with graffiti.
/// Represents a world spray surface that can be spray painted with graffiti.
/// </summary>
public sealed class SpraySurface
{
/// <summary>
/// INTERNAL: The in-game spray surface instance.
/// INTERNAL: The in-game world spray surface instance.
/// </summary>
internal readonly S1Graffiti.SpraySurface S1SpraySurface;
internal readonly S1Graffiti.WorldSpraySurface S1SpraySurface;

/// <summary>
/// INTERNAL: Creates a SpraySurface wrapper.
/// </summary>
/// <param name="spraySurface">The in-game spray surface instance.</param>
internal SpraySurface(S1Graffiti.SpraySurface spraySurface) =>
/// <param name="spraySurface">The in-game world spray surface instance.</param>
internal SpraySurface(S1Graffiti.WorldSpraySurface spraySurface) =>
S1SpraySurface = spraySurface;

/// <summary>
Expand Down Expand Up @@ -72,10 +72,10 @@ public Vector3 Position
S1SpraySurface.DrawingPaintedPixelCount;

/// <summary>
/// Whether the drawing has been finalized (player closed the graffiti UI).
/// Whether the drawing has ever been marked by the player.
/// </summary>
public bool HasDrawingBeenFinalized =>
S1SpraySurface.HasDrawingBeenFinalized;
S1SpraySurface.HasEverBeenMarkedByPlayer;

/// <summary>
/// The output texture for the drawing on this surface.
Expand Down
20 changes: 10 additions & 10 deletions S1API/Internal/Abstraction/Saveable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,16 @@ public abstract class Saveable : Registerable, ISaveable
/// Requests the game to perform a save operation. If a game is not currently loaded,
/// the request is ignored and the method returns false.
/// </summary>
/// <param name="immediate">When true, saves immediately; otherwise schedules a short delayed save.</param>
/// <param name="immediate">This parameter is ignored in v0.4.3+ (kept for backwards compatibility).</param>
/// <returns>True if a save was requested; false if the game is not in a savable state.</returns>
public static bool RequestGameSave(bool immediate = false)
public static bool RequestGameSave(bool immediate) => RequestGameSave();

/// <summary>
/// Requests the game to perform a save operation. If a game is not currently loaded,
/// the request is ignored and the method returns false.
/// </summary>
/// <returns>True if a save was requested; false if the game is not in a savable state.</returns>
public static bool RequestGameSave()
{
try
{
Expand All @@ -87,14 +94,7 @@ public static bool RequestGameSave(bool immediate = false)
if (saveManager == null)
return false;

if (immediate)
{
saveManager.Save();
}
else
{
saveManager.DelayedSave();
}
saveManager.Save();

return true;
}
Expand Down
40 changes: 25 additions & 15 deletions S1API/Internal/Patches/GraffitiPatches.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#if (IL2CPPMELON)
using S1Graffiti = Il2CppScheduleOne.Graffiti;
using Il2CppGuid = Il2CppSystem.Guid;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Graffiti = ScheduleOne.Graffiti;
#endif

using System;
using System.Collections.Generic;
using HarmonyLib;
using S1API.Graffiti;
using S1API.Logging;
Expand All @@ -18,36 +20,44 @@ namespace S1API.Internal.Patches
internal class GraffitiPatches
{
private static readonly Log Logger = new Log("GraffitiPatches");
private static readonly HashSet<string> _processedSurfaces = new HashSet<string>();

/// <summary>
/// Fires the GraffitiCompleted event when a player finishes a graffiti piece and receives rewards.
/// Patch SetFinalized (ObserversRpc) which is called after the drawing is marked as complete.
/// This is more reliable than Reward() which doesn't run on IL2CPP for some reason.
/// </summary>
/// <param name="__instance">The SpraySurfaceInteraction instance that called Reward.</param>
[HarmonyPatch(typeof(S1Graffiti.SpraySurfaceInteraction), "Reward")]
[HarmonyPatch(typeof(S1Graffiti.WorldSpraySurface), "SetFinalized")]
[HarmonyPostfix]
private static void SpraySurfaceInteraction_Reward_Postfix(S1Graffiti.SpraySurfaceInteraction __instance)
private static void WorldSpraySurface_SetFinalized_Postfix(S1Graffiti.WorldSpraySurface __instance)
{
try
{
if (__instance == null)
{
Logger.Warning("__instance is null in Reward patch");
return;
}

if (__instance.SpraySurface == null)
{
Logger.Warning("SpraySurface is null in Reward patch");
// Convert GUID to string for tracking
#if (IL2CPPMELON)
string guidString = __instance.GUID.ToString();
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
string guidString = __instance.GUID.ToString();
#endif

// Check if we've already processed this surface
if (_processedSurfaces.Contains(guidString))
return;
}

// Fire the event through GraffitiEvents
var wrappedSurface = new SpraySurface(__instance.SpraySurface);
GraffitiEvents.OnGraffitiRewarded(wrappedSurface);
// Only fire the event if the surface has actually been marked by the player
if (__instance.HasEverBeenMarkedByPlayer && __instance.DrawingStrokeCount > 0)
{
_processedSurfaces.Add(guidString);

var wrappedSurface = new SpraySurface(__instance);
GraffitiEvents.OnGraffitiRewarded(wrappedSurface);
}
}
catch (Exception ex)
{
Logger.Error($"Error in Reward patch: {ex.Message}");
Logger.Error($"Error in SetFinalized patch: {ex.Message}");
Logger.Error($"StackTrace: {ex.StackTrace}");
}
}
Expand Down
2 changes: 1 addition & 1 deletion S1API/Internal/Patches/NPCPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

using System;
using System.Collections;
using System.Collections.Generic;

Check warning on line 37 in S1API/Internal/Patches/NPCPatches.cs

View workflow job for this annotation

GitHub Actions / coverage

The using directive for 'System.Collections.Generic' appeared previously in this namespace
using System.IO;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -2465,7 +2465,7 @@

try
{
dealer.SendAddCustomer(customer.NPC.ID);
dealer.AddCustomer_Server(customer.NPC.ID);
}
catch (Exception ex)
{
Expand Down
2 changes: 1 addition & 1 deletion S1API/S1API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using S1API.Lifecycle;
using S1API.Map;

[assembly: MelonInfo(typeof(S1API.S1API), "S1API (Forked by Bars)", "2.9.3", "KaBooMa")]
[assembly: MelonInfo(typeof(S1API.S1API), "S1API (Forked by Bars)", "2.9.4", "KaBooMa")]
[assembly: MelonPriority(Int32.MinValue)]
// Marked as incompatible as it breaks base game apps (causes them to show the mod manager instead of the base game app)
// See https://www.nexusmods.com/schedule1/mods/1484?tab=bugs
Expand Down
Loading