### Complete Dialogue System Setup Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/dialogue-system.md Sets up the dialogue database, defines a dialogue container with multiple nodes and choices, and configures a choice callback for purchasing logic. It also sets the container to be used on player interaction. ```csharp protected override void OnCreated() { base.OnCreated(); // Build dialogue database Dialogue.BuildAndSetDatabase(db => { db.WithModuleEntry("Reactions", "GREETING", "Hello there!"); db.WithModuleEntry("Reactions", "ANGRY", "I'm not happy about this!"); }); // Create dialogue container Dialogue.BuildAndRegisterContainer("ShopDialogue", c => { c.AddNode("ENTRY", "Welcome to my shop! What can I help you with?", ch => { ch.Add("BUY_ITEM", "I'd like to buy something", "ITEM_SELECTION") .Add("SELL_ITEM", "I want to sell something", "SELL_DIALOGUE") .Add("LEAVE", "Never mind", "EXIT"); }); c.AddNode("ITEM_SELECTION", "Here's what I have available...", ch => { ch.Add("PURCHASE", "I'll take it", "PURCHASE_CONFIRM") .Add("BACK", "Let me think", "ENTRY"); }); c.AddNode("PURCHASE_CONFIRM", "That'll be $100. Deal?", ch => { ch.Add("YES", "Yes, deal!", "PURCHASE_COMPLETE") .Add("NO", "Too expensive", "ENTRY"); }); c.AddNode("PURCHASE_COMPLETE", "Pleasure doing business!"); c.AddNode("EXIT", "Come back anytime!"); }); // Set up choice callbacks Dialogue.OnChoiceSelected("PURCHASE", () => { // Handle purchase logic var playerCash = Money.GetCashBalance(); if (playerCash >= 100f) { Money.ChangeCashBalance(-100f, visualizeChange: true); Dialogue.JumpTo("ShopDialogue", "PURCHASE_COMPLETE"); } else { Dialogue.JumpTo("ShopDialogue", "NOT_ENOUGH_CASH"); } }); // Use container when player interacts Dialogue.UseContainerOnInteract("ShopDialogue"); } ``` -------------------------------- ### Complete Example: Creating and Registering an Avatar Equippable Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/avatar-equippable-prefabs.md This C# example demonstrates how to create a custom equippable item that can be worn by an avatar. It covers item creation, viewmodel configuration, avatar equippable setup, and registration using AvatarEquippableRegistry. Ensure the asset path matches between item creation and registration. ```csharp using MelonLoader; using S1API.AssetBundles; using S1API.Items; using System.Reflection; using UnityEngine; public class MyMod : MelonMod { private bool _initialized = false; public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Main" && !_initialized) { InitializeMod(); _initialized = true; } } private void InitializeMod() { RegisterAvatarEquippable(); var equippable = ItemCreator.CreateEquippableBuilder() .CreateViewmodelEquippable("MyCustomItem") .WithInteraction(canInteract: true, canPickup: true) .WithViewmodelTransform( position: new Vector3(0.2f, -0.15f, 0.3f), rotation: Vector3.zero, scale: Vector3.one ) .WithAvatarEquippable( assetPath: "Equippables/MyCustomItem", hand: AvatarHand.Right, animationTrigger: "RightArm_Hold_ClosedHand" ) .WithUseCallback((itemInstance) => { MelonLogger.Msg($"Used: {itemInstance.Definition.Name}"); }) .Build(); var item = ItemCreator.CreateBuilder() .WithBasicInfo( id: "my_custom_item", name: "My Custom Item", description: "A custom item with viewmodel and third-person animation", category: ItemCategory.Tools ) .WithEquippable(equippable) .Build(); MelonLogger.Msg("Created custom item with AvatarEquippable!"); } private void RegisterAvatarEquippable() { try { bool success = AvatarEquippableRegistry.LoadAndRegisterFromEmbeddedBundle( bundleName: "myitem_equippables", prefabName: "MyCustomItem_AvatarEquippable", assetPath: "Equippables/MyCustomItem", assemblyOverride: Assembly.GetExecutingAssembly() ); if (success) { MelonLogger.Msg("Successfully registered AvatarEquippable prefab"); } else { MelonLogger.Error("Failed to register AvatarEquippable prefab"); } } catch (System.Exception ex) { MelonLogger.Error($"Exception registering AvatarEquippable: {ex.Message}"); MelonLogger.Error(ex.StackTrace); } } } ``` -------------------------------- ### Complete Example: Simple Usable Item Initialization Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/equippable-items.md This example demonstrates initializing a custom equippable item, including loading an icon and setting basic properties. It's designed to be part of a `MelonMod`'s `OnSceneWasLoaded` event. ```csharp using MelonLoader; using S1API.Internal.Utils; using S1API.Items; using System.Reflection; using UnityEngine; public class MyMod : MelonMod { private bool _itemsInitialized = false; public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Main" && !_itemsInitialized) { InitializeItems(); _itemsInitialized = true; } } private void InitializeItems() { Sprite icon = LoadIconFromResources(); var equippable = CreateCustomEquippable(); var scratcherTicket = ItemCreator.CreateBuilder() .WithBasicInfo( id: "scratcher_ticket", name: "Scratcher Ticket", description: "A lottery ticket that can be scratched to reveal potential prizes.", category: ItemCategory.Consumable ) .WithStackLimit(10) .WithPricing(5f, 0.1f) .WithLegalStatus(LegalStatus.Legal) .WithIcon(icon) .WithEquippable(equippable) .Build(); MelonLogger.Msg($"Created custom item: {scratcherTicket.Name}"); } private Sprite LoadIconFromResources() { var assembly = Assembly.GetExecutingAssembly(); using (var stream = assembly.GetManifestResourceStream("MyMod.Resources.icon.png")) { if (stream != null) { var data = new byte[stream.Length]; stream.Read(data, 0, data.Length); return ImageUtils.LoadImageRaw(data); } } return null; } private Equippable CreateCustomEquippable() { return ItemCreator.CreateEquippableBuilder() .CreateBasicEquippable("ScratcherEquippable") .WithInteraction(canInteract: true, canPickup: true) .Build(); } } ``` -------------------------------- ### Basic Dialogue Setup Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/dialogue-system.md Configures a basic dialogue database with a single entry and registers a simple dialogue container. This is a minimal setup for a new dialogue flow. ```csharp protected override void OnCreated() { base.OnCreated(); // Basic dialogue setup Dialogue.BuildAndSetDatabase(db => { db.WithModuleEntry("Reactions", "GREETING", "Hello!"); }); Dialogue.BuildAndRegisterContainer("BasicDialogue", c => { c.AddNode("ENTRY", "Hello there!", ch => { ch.Add("GREET", "Hello!", "RESPONSE"); }); c.AddNode("RESPONSE", "Nice to meet you!"); }); Dialogue.UseContainerOnInteract("BasicDialogue"); } ``` -------------------------------- ### Complete Custom Clothing Item Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/clothing-items.md This example demonstrates the full lifecycle of creating and registering a custom clothing item, from initializing definitions to adding it to compatible shops. Ensure correct resource paths and asset loading. ```csharp using MelonLoader; using S1API.Items; using S1API.Lifecycle; using S1API.Rendering; using S1API.Internal.Utils; using S1API.Shops; using System.Collections.Generic; using System.Reflection; using UnityEngine; public class MyMod : MelonMod { private const string CustomItemId = "custom_cap"; private const string CustomAccessoryPath = "MyMod/Accessories/CustomCap"; private const string CustomTextureResourceName = "MyMod.Resources.CustomCap.custom_cap_texture.png"; private bool _itemsInitialized = false; private ClothingItemDefinition customCap; public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Main" && !_itemsInitialized) { GameLifecycle.OnPreLoad += InitializeCustomClothing; GameLifecycle.OnLoadComplete += AddCustomClothingToShops; _itemsInitialized = true; } } private void InitializeCustomClothing() { if (ItemManager.IsItemRegistered(CustomItemId)) { customCap = ItemManager.GetItemDefinition(CustomItemId) as ClothingItemDefinition; if (customCap == null) { MelonLogger.Error($"Registered item '{CustomItemId}' is not a ClothingItemDefinition; custom clothing setup cannot continue."); return; } return; } // Step 1: Create and register custom accessory var assembly = Assembly.GetExecutingAssembly(); if (!RuntimeResourceRegistry.IsRegistered(CustomAccessoryPath)) { var customTexture = TextureUtils.LoadTextureFromResource( assembly, CustomTextureResourceName); if (customTexture == null) { MelonLogger.Error($"Failed to load custom clothing texture resource: {CustomTextureResourceName}"); return; } var textureReplacements = new Dictionary { { "_MainTex", customTexture }, { "_BaseMap", customTexture }, { "_Albedo", customTexture } }; bool accessoryRegistered = AccessoryFactory.CreateAndRegisterAccessory( sourceResourcePath: "avatar/accessories/head/cap/Cap", targetResourcePath: CustomAccessoryPath, newName: "CustomCap", textureReplacements: textureReplacements, colorTint: null); if (!accessoryRegistered) { MelonLogger.Error("Failed to register custom accessory"); return; } } // Step 2: Create clothing item definition var icon = ImageUtils.LoadImageFromResource( assembly, "MyMod.Resources.CustomCap.icon.png"); customCap = ClothingItemCreator.CloneFrom("cap") .WithBasicInfo( id: CustomItemId, name: "Custom Cap", description: "A custom cap with unique style.") .WithClothingAsset(CustomAccessoryPath) .WithColorable(false) .WithDefaultColor(ClothingColor.White) .WithPricing(75f, 0.5f) .Build(); if (icon != null) { customCap.Icon = icon; } MelonLogger.Msg($"Created custom clothing item: {customCap.Name}"); } private void AddCustomClothingToShops() { if (customCap == null) { MelonLogger.Warning($"Skipping shop registration because '{CustomItemId}' was not created as a ClothingItemDefinition."); return; } int shopsAdded = ShopManager.AddToCompatibleShops(customCap); MelonLogger.Msg($"Added to {shopsAdded} shop(s)"); } } ``` -------------------------------- ### Complete NPC Schedule Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/location-based-actions.md This C# example demonstrates a full-day schedule for an NPC, exercising four different arrive behaviors with varied parameters. It includes setting up NPC identity, spawn position, and defining a detailed schedule with specific locations, times, durations, and actions. ```csharp using S1API.Entities; using S1API.Entities.Equippables; using S1API.Entities.Schedule; using S1API.Map; using S1API.Map.Buildings; using UnityEngine; public sealed class MyScheduledNPC : NPC { public override bool IsPhysical => true; protected override void ConfigurePrefab(NPCPrefabBuilder builder) { var northApartments = Building.Get(); Vector3 spawnPos = new(-53.57f, 1.065f, 67.8f); Vector3 smokeSpot = new(-28.06f, 1.065f, 62.07f); Vector3 phoneSpot = new(-35f, 1.065f, 58f); Vector3 coffeeSpot = new(-42f, 1.065f, 65f); Vector3 graffitiSpot = new(-50f, 1.065f, 55f); Vector3 nightSpot = new(-55f, 1.065f, 70f); Vector3 barSpot = new(-60f, 1.065f, 62f); builder .WithIdentity("my_scheduled_npc", "Sam", "Actions") .WithSpawnPosition(spawnPos) // Declare which behaviours this NPC can perform .EnsureSmokeBreak() .EnsureGraffiti() .EnsureDrinking() .EnsureItemHolding() .WithSchedule(plan => { plan.EnsureDealSignal() // 08:00 — smoke break for 15 min (no extra params, nearest surface auto-selected) .LocationBased(smokeSpot, 8 * 60, 15) .Within(1.5f) .Named("MorningSmoke") .OnArriveSmokeBreak() // 09:30 — hold phone for 20 min .LocationBased(phoneSpot, 9 * 60 + 30, 20) .WithItem(EquippablePath.Phone_Lowered) .OnArriveHoldItem() // 11:00 — drink coffee for 15 min .LocationBased(coffeeSpot, 11 * 60, 15) .WithDrink(EquippablePath.Coffee) .OnArriveDrinking() // 12:00 — lunch inside a building .StayInBuilding(northApartments, 12 * 60, 60) // 14:00 — graffiti: pick an available surface in Northtown .LocationBased(graffitiSpot, 14 * 60, 45) .WithSpraySurfaceInRegion(Region.Northtown) .Named("AfternoonTag") .OnArriveGraffiti() // 16:00 — hold flashlight for 25 min .LocationBased(nightSpot, 16 * 60, 25) .WithItem(EquippablePath.Flashlight) .OnArriveHoldItem() // 18:00 — drink beer for 30 min .LocationBased(barSpot, 18 * 60, 30) .WithDrink(EquippablePath.Beer) .OnArriveDrinking(); }); } protected override void OnCreated() { base.OnCreated(); Appearance.Build(); Region = Region.Northtown; Schedule.Enable(); } } ``` -------------------------------- ### Minimal TV App Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/tv-app.md A basic TV application demonstrating app metadata definition, UI creation in OnCreatedUI, and handling of OnOpened and OnUpdate events. This example creates a simple 'Hello World' UI. ```csharp using UnityEngine; using UnityEngine.UI; using S1API.TVApp; using S1API.UI; public class HelloWorldTVApp : TVApp { // Define app metadata. These properties are used by S1API to register and display your app. protected override string AppName => "HelloWorld"; protected override string AppTitle => "Hello World"; protected override Sprite Icon => _cachedIcon ??= CreateIcon(); private Text? _messageText; private static Sprite? _cachedIcon; // OnCreatedUI is called when the app's UI is created. // Build your UI inside the provided container. protected override void OnCreatedUI(GameObject container) { // Create background with explicit sizeDelta (required for WorldSpace canvas) var background = new GameObject("Background"); background.transform.SetParent(container.transform, false); var bgRT = background.AddComponent(); bgRT.anchorMin = new Vector2(0.5f, 0.5f); bgRT.anchorMax = new Vector2(0.5f, 0.5f); bgRT.pivot = new Vector2(0.5f, 0.5f); bgRT.sizeDelta = new Vector2(500, 350); bgRT.anchoredPosition = Vector2.zero; var bgImg = background.AddComponent(); bgImg.texture = CreateSolidTexture(new Color(0.05f, 0.05f, 0.15f, 1f)); bgImg.raycastTarget = false; // Create "Hello World" text centered on screen _messageText = UIFactory.Text( "HelloText", "Hello World!", container.transform, 48, TextAnchor.MiddleCenter, FontStyle.Bold ); var textRT = _messageText.GetComponent(); textRT.anchorMin = new Vector2(0.5f, 0.5f); textRT.anchorMax = new Vector2(0.5f, 0.5f); textRT.pivot = new Vector2(0.5f, 0.5f); textRT.sizeDelta = new Vector2(400, 100); textRT.anchoredPosition = Vector2.zero; } // Called when the app is opened protected override void OnOpened() { if (_messageText != null) _messageText.color = Color.white; } // Called every frame while the app is open protected override void OnUpdate() { // Your frame update logic here } // Create a simple icon programmatically private static Sprite CreateIcon() { int size = 256; var tex = new Texture2D(size, size); Color bgColor = new Color(0.1f, 0.1f, 0.2f); Color fgColor = Color.cyan; // Fill background for (int x = 0; x < size; x++) for (int y = 0; y < size; y++) tex.SetPixel(x, y, bgColor); // Draw a simple shape (H letter) int margin = 40; int barWidth = 30; // Left vertical bar for (int x = margin; x < margin + barWidth; x++) for (int y = margin; y < size - margin; y++) tex.SetPixel(x, y, fgColor); // Right vertical bar for (int x = size - margin - barWidth; x < size - margin; x++) for (int y = margin; y < size - margin; y++) tex.SetPixel(x, y, fgColor); // Horizontal bar int midY = size / 2; for (int x = margin; x < size - margin; x++) for (int y = midY - barWidth / 2; y < midY + barWidth / 2; y++) tex.SetPixel(x, y, fgColor); tex.Apply(); return Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f)); } private static Texture2D CreateSolidTexture(Color color) { var tex = new Texture2D(4, 4); for (int x = 0; x < 4; x++) for (int y = 0; y < 4; y++) tex.SetPixel(x, y, color); tex.Apply(); return tex; } } ``` -------------------------------- ### Minimal Custom NPC Implementation Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/custom-npcs.md A basic C# example demonstrating how to create a custom NPC with identity, spawn position, customer defaults, and appearance settings. This snippet is useful for getting started with custom NPC development. ```csharp public sealed class MyFirstNPC : NPC { protected override bool IsPhysical => true; protected override void ConfigurePrefab(NPCPrefabBuilder builder) { builder.WithIdentity( id: "my_first_npc", firstName: "John", lastName: "Doe") .WithSpawnPosition(new Vector3(0, 0, 0)) .EnsureCustomer() .WithCustomerDefaults(cd => { cd.WithSpending(100f, 500f) .WithOrdersPerWeek(1, 3); }); } public MyFirstNPC() : base() { } protected override void OnCreated() { base.OnCreated(); // Set up appearance Appearance .Set(0.5f) .Set(1.0f) .Build(); // Enable systems Schedule.Enable(); Schedule.InitializeActions(); } } ``` -------------------------------- ### Complete Selective Customer Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/products-system.md A comprehensive example of an NPC customer configuration, including identity, appearance, spending habits, drug preferences, and relationship settings. ```csharp using S1API.Entities; using S1API.Economy; using S1API.GameTime; using S1API.Growing; using S1API.Properties; using UnityEngine; public sealed class SelectiveCustomer : NPC { public override bool IsPhysical => true; protected override void ConfigurePrefab(NPCPrefabBuilder builder) { builder.WithIdentity("selective_customer", "Sarah", "Johnson") .WithSpawnPosition(new Vector3(0, 0, 0)) .WithAppearanceDefaults(av => { av.Gender = 1.0f; av.Height = 0.95f; }) .EnsureCustomer() .WithCustomerDefaults(cd => { // High spending, selective customer cd.WithSpending(minWeekly: 800f, maxWeekly: 3000f) .WithOrdersPerWeek(2, 4) .WithPreferredOrderDay(Day.Friday) .WithOrderTime(1800) // 6 PM // Quality conscious .WithStandards(CustomerStandard.High) .AllowDirectApproach(false) // Must be introduced .GuaranteeFirstSample(true) // Relationship requirements .WithMutualRelationRequirement(minAt50: 3.0f, maxAt100: 4.5f) .WithCallPoliceChance(0.05f) // Low risk // Addiction profile .WithDependence(baseAddiction: 0.2f, dependenceMultiplier: 1.2f) // Drug preferences - loves weed, dislikes hard drugs .WithAffinities(new[] { (DrugType.Marijuana, 0.9f), // Strongly prefers (DrugType.Cocaine, -0.6f), // Strongly dislikes (DrugType.Methamphetamine, -0.8f) // Very much dislikes }) // Property preferences .WithPreferredProperties( Property.Calming, Property.Euphoric, Property.Munchies ); }) .WithRelationshipDefaults(r => { r.WithDelta(2.0f) .SetUnlocked(false) .SetUnlockType(NPCRelationship.UnlockType.Introduction); }); } protected override void OnCreated() { base.OnCreated(); Appearance.Build(); Dialogue.BuildAndSetDatabase(db => { db.WithModuleEntry("Reactions", "GREETING", "I only deal with quality products. No junk."); }); Aggressiveness = 1f; Region = Region.Downtown; Schedule.Enable(); } } ``` -------------------------------- ### Complete Cartel Status Watcher Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/cartel-system.md A comprehensive example demonstrating how to monitor cartel status changes and react accordingly. It includes subscribing to events and a fallback detection mechanism. ```csharp using S1API.Cartel; using MelonLoader; public class CartelStatusWatcher { private CartelStatus? _lastKnownStatus; private bool _subscribed = false; public void Update() { var cartel = Cartel.Instance; // Cartel might not be available during scene transitions if (cartel == null) { _subscribed = false; _lastKnownStatus = null; return; } // Subscribe to events if we haven't yet if (!_subscribed) { cartel.OnStatusChange += OnStatusChanged; _subscribed = true; } // Check if status changed (backup detection) if (_lastKnownStatus != cartel.Status) { if (_lastKnownStatus.HasValue) { OnStatusChanged(_lastKnownStatus.Value, cartel.Status); } _lastKnownStatus = cartel.Status; } } private void OnStatusChanged(CartelStatus oldStatus, CartelStatus newStatus) { MelonLogger.Msg($"Cartel: {oldStatus} → {newStatus}"); switch (newStatus) { case CartelStatus.Hostile: OnCartelBecameHostile(); break; case CartelStatus.Truced: OnCartelBecameTruced(); break; case CartelStatus.Defeated: OnCartelDefeated(); break; } } private void OnCartelBecameHostile() { MelonLogger.Msg("Cartel is now hostile! Prepare for attacks."); // Spawn additional NPCs, send warning messages, etc. } private void OnCartelBecameTruced() { MelonLogger.Msg("Peace with the cartel has been restored."); // Despawn hostile NPCs, send peace messages, etc. } private void OnCartelDefeated() { MelonLogger.Msg("The cartel has been defeated!"); // Trigger victory events, rewards, etc. } } // Usage in your MelonMod public class MyMod : MelonMod { private CartelStatusWatcher _watcher = new CartelStatusWatcher(); public override void OnUpdate() { _watcher.Update(); } } ``` -------------------------------- ### Wanted Level Monitor Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/police-dispatch.md A MelonMod example that logs changes in the player's wanted level and provides warnings for critical levels like 'Lethal'. ```csharp using MelonLoader; using S1API.Entities; using S1API.Law; public class WantedLevelMonitor : MelonMod { private PursuitLevel _lastLevel = PursuitLevel.None; public override void OnUpdate() { Player player = Player.Local; if (player == null) return; PursuitLevel currentLevel = LawManager.GetWantedLevel(player); if (currentLevel != _lastLevel) { LoggerInstance.Msg($"Wanted level changed: {_lastLevel} → {currentLevel}"); _lastLevel = currentLevel; // Handle level changes if (currentLevel == PursuitLevel.Lethal) { LoggerInstance.Warning("Lethal force authorized!"); } else if (currentLevel == PursuitLevel.None) { LoggerInstance.Msg("Wanted level cleared"); } } } } ``` -------------------------------- ### ConfigurePrefab with Schedule Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/scheduling-system.md Example of how to set up an NPC's schedule within the ConfigurePrefab method using PrefabScheduleBuilder. ```APIDOC ## ConfigurePrefab with Schedule ### Description This method demonstrates how to define an NPC's schedule by providing a `PrefabScheduleBuilder` to the `WithSchedule` method. ### Method `NPCPrefabBuilder.WithSchedule(Action scheduleBuilder)` ### Parameters - `builder`: An instance of `NPCPrefabBuilder`. - `scheduleBuilder`: An Action that takes a `PrefabScheduleBuilder` to define schedule actions. ### Code Example ```csharp protected override void ConfigurePrefab(NPCPrefabBuilder builder) { builder.WithSchedule(plan => { // Schedule actions here plan.WalkTo(new Vector3(0, 0, 0), 900); plan.EnsureDealSignal(); }); } ``` ``` -------------------------------- ### UseATM Action Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/scheduling-system.md Schedule an NPC to use an ATM at a specific time. An optional `atmGUID` can be provided to target a particular ATM. ```csharp plan.UseATM(startTime, atmGUID, name); ``` -------------------------------- ### Minimal Physical NPC Example Source: https://github.com/ifbars/s1api/blob/stable/skills/schedule-one-custom-npcs/references/s1api-custom-npc-reference.md Demonstrates the creation of a basic physical NPC with identity, spawn position, appearance defaults, and a simple schedule. ```APIDOC ## MyFirstNpc ### Description This class defines a minimal physical NPC with a specific identity, spawn location, appearance, and a simple walking schedule. ### Method Signature ```csharp public sealed class MyFirstNpc : NPC ``` ### Configuration (`ConfigurePrefab`) - **Identity**: Sets the NPC's unique ID, display name, and description. - **Spawn Position**: Defines the initial location where the NPC will appear. - **Appearance Defaults**: Configures basic avatar properties like gender, height, weight, and hair. - **Schedule**: Assigns a simple walking route to the NPC. ### Runtime (`OnCreated`) - **Build Appearance**: Triggers the avatar build process. - **Enable Schedule**: Activates the NPC's defined schedule. ### Code Example ```csharp public sealed class MyFirstNpc : NPC { public override bool IsPhysical => true; protected override void ConfigurePrefab(NPCPrefabBuilder builder) { var spawnPos = new Vector3(-50f, 1.06f, 70f); var hangoutPos = new Vector3(-28f, 1.06f, 62f); builder.WithIdentity("my_first_npc", "Alex", "Example") .WithSpawnPosition(spawnPos) .WithAppearanceDefaults(av => { av.Gender = 0.5f; av.Height = 1.0f; av.Weight = 0.5f; av.HairPath = "Avatar/Hair/Spiky/Spiky"; }) .WithSchedule(plan => { plan.WalkTo(hangoutPos, 900, faceDestinationDir: true); }); } protected override void OnCreated() { base.OnCreated(); Appearance.Build(); Schedule.Enable(); } } ``` ``` -------------------------------- ### Error Handling for Dialogue Configuration Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/dialogue-system.md Wraps dialogue configuration and event setup in a try-catch block to gracefully handle exceptions during initialization. Logs errors using MelonLogger if setup fails. ```csharp protected override void OnCreated() { base.OnCreated(); try { Dialogue.BuildAndRegisterContainer("MyDialogue", c => { // Dialogue configuration }); Dialogue.OnChoiceSelected("MY_CHOICE", () => { // Choice handling }); } catch (Exception ex) { MelonLogger.Error($"Failed to set up dialogue for {FullName}: {ex.Message}"); } } ``` -------------------------------- ### Common Accessory Paths Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/appearance-customization.md Examples of common paths for different accessory types, such as shoes and hats. ```csharp // Shoes "Avatar/Accessories/Feet/Sneakers/Sneakers" "Avatar/Accessories/Feet/Boots" "Avatar/Accessories/Feet/Sandals" // Hats "Avatar/Accessories/Head/BaseballCap" "Avatar/Accessories/Head/Beanie" "Avatar/Accessories/Head/Hat" ``` -------------------------------- ### Guard Spawning and Management Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/cartel-system.md A C# class demonstrating how to spawn guards at specific positions, equip them with fists, alert them to attack the player, and manage their cleanup. ```csharp using S1API.Cartel; using UnityEngine; using System.Collections.Generic; public class GuardSpawner { private List _guards = new List(); public void SpawnGuards(Vector3[] positions) { var cartel = Cartel.Instance; if (cartel?.GoonPool == null) return; _guards = cartel.GoonPool.SpawnGoonsAtPositions(positions); foreach (var guard in _guards) { guard.SetDefaultWeapon(null); // Fists only } } public void AlertGuards() { foreach (var guard in _guards) { if (guard != null && guard.IsConscious) { guard.AttackPlayer(); } } } public int RemainingGuards => _guards.Count(g => g != null && g.IsConscious); public void Cleanup() { foreach (var guard in _guards) { guard?.Despawn(); } _guards.Clear(); } } ``` -------------------------------- ### DriveToCarPark Action Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/scheduling-system.md Utilize the `DriveToCarPark` action for NPCs to drive a vehicle to a parking lot and park it. Ensure you have valid `parkingLot` and `vehicle` wrapper objects. ```csharp var parkingLot = ParkingLots.GetByGUID("parking-lot-guid"); var vehicle = VehicleRegistry.GetByGUID("vehicle-guid"); plan.DriveToCarPark(parkingLot, vehicle, 1700, ParkingAlignment.FrontToKerb); ``` -------------------------------- ### UseVendingMachine Action Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/scheduling-system.md Schedule an NPC to use a vending machine at a specific time. An optional `machineGUID` can be provided to target a particular machine. ```csharp plan.UseVendingMachine(1400, "vending-machine-guid", "BuySnack"); ``` -------------------------------- ### FullRank Comparison Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/leveling.md Demonstrates how to compare FullRank values using standard comparison operators. This is useful for checking if a player has reached a certain rank. ```csharp if (LevelManager.CurrentRank >= new FullRank(Rank.Hustler, 3)) { UnlockFeature(); } ``` -------------------------------- ### Logging Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/modules-overview.md Instantiate a logger for a specific mod and log messages or errors. Ensure the Log class is imported from S1API.Logging. ```csharp private static readonly Log Logger = new Log("MyMod"); Logger.Msg("Hello from my mod!"); Logger.Error("Something went wrong!"); ``` -------------------------------- ### Configure Schedule with Error Handling Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/scheduling-system.md Demonstrates how to wrap schedule configuration in a try-catch block to handle potential exceptions during setup, logging errors with MelonLogger. ```csharp protected override void ConfigurePrefab(NPCPrefabBuilder builder) { try { builder.WithSchedule(plan => { // Schedule configuration using wrapper methods plan.WalkTo(new Vector3(0, 0, 0), 900); plan.StayInBuilding(Building.Get(), 1000, 60); plan.EnsureDealSignal(); }); } catch (Exception ex) { MelonLogger.Error($"Failed to configure schedule for {GetType().Name}: {ex.Message}"); } } ``` -------------------------------- ### Accessing the Cartel Instance Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/cartel-system.md Demonstrates how to get the singleton instance of the Cartel class to interact with the game's cartel system. ```APIDOC ## Accessing the Cartel The `Cartel` class is a singleton that wraps the game's internal cartel system: ```csharp using S1API.Cartel; // Access the current cartel instance var cartel = Cartel.Instance; if (cartel != null) { // Check the current status CartelStatus status = cartel.Status; // Check how long the status has been active int hours = cartel.HoursSinceStatusChange; } ``` ``` -------------------------------- ### Quest Activation: Manual Begin Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/quests-complete-example.md Set `AutoBegin => false` to prevent quests from starting automatically. Call `Begin()` manually, for example, when the player accepts a quest via dialogue. ```csharp protected override bool AutoBegin => false; public void StartQuest() { Begin(); // Manually activate the quest } ``` -------------------------------- ### Custom Police Dispatch Hotkeys Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/police-dispatch.md A MelonMod example demonstrating custom dispatch actions triggered by hotkeys. Includes calling police, clearing wanted levels, and starting foot patrols. ```csharp using MelonLoader; using S1API.Entities; using S1API.Law; using UnityEngine; public class CustomDispatch : MelonMod { public override void OnUpdate() { // Hotkey: Press F10 to call police on local player if (Input.GetKeyDown(KeyCode.F10)) { Player player = Player.Local; if (player != null) { LawManager.CallPolice(player); LoggerInstance.Msg("Police called!"); } } // Hotkey: Press F11 to clear wanted level if (Input.GetKeyDown(KeyCode.F11)) { Player player = Player.Local; if (player != null) { LawManager.ClearWantedLevel(player); LoggerInstance.Msg("Wanted level cleared"); } } // Hotkey: Press F12 to start a random foot patrol if (Input.GetKeyDown(KeyCode.F12)) { var routes = LawManager.GetAllFootPatrolRoutes(); if (routes.Length > 0) { var route = routes[Random.Range(0, routes.Length)]; var patrol = LawManager.StartFootPatrol(route, 2); if (patrol != null) { LoggerInstance.Msg($"Started patrol on route: {route.RouteName}"); } } } } } ``` -------------------------------- ### Example: Full Viewmodel Equippable with Avatar and Callback Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/equippable-items.md Demonstrates creating a complete viewmodel equippable item, including first-person transform, third-person avatar using a base game asset, and a custom use callback. ```csharp using S1API.Items; using UnityEngine; var equippable = ItemCreator.CreateEquippableBuilder() .CreateViewmodelEquippable("MyCustomKnife") .WithInteraction(canInteract: true, canPickup: true) .WithViewmodelTransform( position: new Vector3(0.2f, -0.15f, 0.3f), rotation: Vector3.zero, scale: Vector3.one ) .WithAvatarEquippable( assetPath: AvatarEquippablePaths.Knife, hand: AvatarHand.Right, animationTrigger: "RightArm_Hold_ClosedHand" ) .WithUseCallback((itemInstance) => { MelonLogger.Msg("Custom knife used!"); }) .Build(); var item = ItemCreator.CreateBuilder() .WithBasicInfo( id: "my_custom_knife", name: "Custom Knife", description: "A custom knife with base game animations", category: ItemCategory.Tools ) .WithEquippable(equippable) .Build(); ``` -------------------------------- ### WalkTo Action Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/scheduling-system.md Use the `WalkTo` action to make an NPC move to a specific world position at a designated start time. The `faceDestinationDir` parameter controls whether the NPC faces their destination. ```csharp plan.WalkTo(new Vector3(-28.060f, 1.065f, 62.070f), 900, faceDestinationDir: true); ``` -------------------------------- ### Manage Action Durations and Overlaps Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/location-based-actions.md Ensure that the duration of one action does not overlap with the start time of the next scheduled action. This example shows a safe gap between a smoke break and a subsequent item holding action. ```csharp // Smoke at 08:00 for 15 min ends at 08:15 // Phone at 09:30 starts at 09:30 — safe gap of 1h15m .LocationBased(smokeSpot, 8 * 60, 15).OnArriveSmokeBreak() .LocationBased(phoneSpot, 9 * 60 + 30, 20).OnArriveHoldItem() ``` -------------------------------- ### Configure Customer Preferences with Product Properties Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/products-system.md Shows how to retrieve a product definition and configure customer preferences using its associated properties. ```csharp using S1API.Properties; using S1API.Products; // Get a product instance var weedProduct = ProductDefinition.GetByType(DrugType.Marijuana); if (weedProduct != null) { // Properties are accessed through the product's internal system // You'll typically use properties when configuring customers // Example: Customer preferences for properties .WithPreferredProperties(Property.Munchies, Property.Energizing, Property.Cyclopean) } ``` -------------------------------- ### Start Vehicle Patrol Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/police-dispatch.md Initiates a vehicle patrol using a predefined route. Ensure the patrol route exists before attempting to start. ```csharp using S1API.Law; // Find a vehicle patrol route VehiclePatrolRoute route = LawManager.FindVehiclePatrolRoute("Highway"); if (route != null) { // Start a vehicle patrol (1 officer + vehicle) bool started = LawManager.StartVehiclePatrol(route); if (started) { LoggerInstance.Msg($"Started vehicle patrol on route: {route.RouteName}"); } } ``` -------------------------------- ### Restore and Build Project with .NET CLI Source: https://github.com/ifbars/s1api/blob/stable/CONTRIBUTING.md Use these commands to restore packages and build the solution if you are using a light IDE or editor. Ensure you are in the base repository directory. ```bash dotnet restore ``` ```bash dotnet build ./S1API.sln ``` -------------------------------- ### Create a Packaged Product Instance - C# Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/products-populator.md Creates a product instance with specified packaging and quantity. Requires a valid packaging definition and a product definition. ```csharp using S1API.Products; var packaging = ProductPopulator.GetPackaging("jar"); if (packaging != null) { var productDef = ProductPopulator.GetAllProductDefinitions()[0]; var inst = ProductPopulator.CreatePackagedProduct(productDef, packaging, quantity: 20); } ``` -------------------------------- ### Accessing and Using S1 API Components Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/runtime-management.md Demonstrates how to access and utilize various systems available through the S1 API. Ensure components are initialized before use. ```csharp var appearance = Appearance; appearance.Set(1.1f); appearance.Build(); ``` ```csharp var dialogue = Dialogue; dialogue.BuildAndRegisterContainer("MyDialogue", c => { // Dialogue configuration }); ``` ```csharp var schedule = Schedule; schedule.Enable(); schedule.InitializeActions(); ``` ```csharp var customer = Customer; customer.ForceDealOffer(); ``` ```csharp var relationship = Relationship; relationship.Add(1.0f); ``` ```csharp var inventory = Inventory; bool hasItem = inventory.HasItem("SpecialItem"); ``` ```csharp var movement = Movement; movement.Goto(new Vector3(10, 0, 10)); ``` -------------------------------- ### Example Registration of Dialogue Injection - C# Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/dialogue-injection.md An example demonstrating how to register a dialogue injection within a static class. The onConfirmed callback is defined separately to handle the feature logic. ```csharp using S1API.Dialogues; public static class PayoutDialogueFeature { public static void Register() { DialogueInjector.Register(new DialogueInjection( npc: "Philip", container: "CasinoDialogue", from: "INTRO_NODE_GUID", to: "PAYOUT_INFO_NODE_GUID", label: "ASK_PAYOUT_QUESTION", text: "Quick question about payouts.", onConfirmed: OnPayoutQuestionSelected)); } private static void OnPayoutQuestionSelected() { // Put your feature logic here } } ``` -------------------------------- ### Basic Runtime Management Example Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/runtime-management.md Sets basic properties, sends text messages, enables systems, and subscribes to the OnDealCompleted event for the Customer component. Ensure base OnCreated is called. ```csharp protected override void OnCreated() { base.OnCreated(); // Set basic properties Region = Region.Northtown; Aggressiveness = 3f; // Set up messaging SendTextMessage("Hello! I'm here to help."); // Enable systems Schedule.Enable(); Schedule.InitializeActions(); // Set up events Customer.OnDealCompleted(() => { SendTextMessage("Thanks for the business!"); Relationship.Add(0.5f); }); } ``` -------------------------------- ### Populate Storage from StorageInstance - C# Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/products-populator.md Adds packaged products to a storage instance. Specify the packaging ID and quantity per item. ```csharp using S1API.Products; using S1API.Storages; int added = ProductPopulator.PopulateWithPackagedProducts(storage, packagingId: "jar", quantityPerItem: 20); ``` -------------------------------- ### Handle Product Sold Event Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/products-system.md Demonstrates how to access a ProductInstance and its Definition when a product is sold. Product instances are typically managed by the game systems. ```csharp using S1API.Products; // Product instances are typically created/managed by the game // Access them through game systems or events public void HandleProductSold(ProductInstance instance) { if (instance != null) { var definition = instance.Definition; // Get the product definition // Work with the specific product instance } } ``` -------------------------------- ### Namespace Structure Example Source: https://github.com/ifbars/s1api/blob/stable/CODING_STANDARDS.md Internal API code should be placed in S1API.Internal sub-namespaces. ```C# namespace S1API.Internal.Utils { ... } ``` -------------------------------- ### Runtime Behavior Initialization with Error Handling Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/runtime-management.md Demonstrates how to initialize runtime behavior and subscribe to events within the `OnCreated` method, including robust error handling using try-catch blocks. Ensure base methods are called and exceptions are logged. ```csharp protected override void OnCreated() { base.OnCreated(); try { // Runtime setup Region = Region.Northtown; Aggressiveness = 3f; // Set up events Customer.OnDealCompleted(() => { // Event handling }); } catch (Exception ex) { MelonLogger.Error($"Failed to set up runtime behavior for {FullName}: {ex.Message}"); } } ``` -------------------------------- ### Get Product Properties Source: https://github.com/ifbars/s1api/blob/stable/S1API/docs/products-api.md Accesses the runtime-agnostic properties associated with a product definition. ```APIDOC ## Get Product Properties ### Description Retrieves a collection of property wrappers for a given product definition. ### Method `ProductDefinition.Properties` (property access) ### Example ```csharp using S1API.Products; foreach (var prop in def.Properties) { MelonLoader.MelonLogger.Msg(prop.ID); } ``` ```