Table of Contents

Prefab Configuration

The ConfigurePrefab method is where you set up your NPC's components and default behavior before the NPC is spawned. This is crucial for proper save/load behavior and network compatibility.

Table of Contents

  1. Overview
  2. NPCPrefabBuilder Methods
  3. Spawn Position Configuration
  4. WithAppearanceDefaults
  5. Customer Configuration
  6. Relationship Configuration
  7. Schedule Configuration
  8. Configuration Workflow
  9. Best Practices

Overview

The ConfigurePrefab method is called during NPC prefab creation and allows you to:

  • Set spawn position and rotation
  • Configure customer behavior defaults
  • Set relationship parameters
  • Define schedule actions
  • Add required components

Important: Customer, relationship, and schedule configuration must be done in ConfigurePrefab to ensure proper save/load behavior and network compatibility.

NPCPrefabBuilder Methods

WithIdentity

Sets the NPC's unique identifier and display name. This must be called in ConfigurePrefab for proper network spawn support.

builder.WithIdentity(
    id: "my_custom_npc",           // Unique identifier
    firstName: "John",             // Display name
    lastName: "Doe");              // Optional last name

Parameters:

  • id: Unique identifier used for save/load and game systems (required)
  • firstName: Display name shown in UI elements (required)
  • lastName: Optional last name (can be null)

Notes:

  • Must be unique across all NPCs
  • Used for save/load persistence
  • Should be descriptive and consistent
  • Examples: "shopkeeper_alex", "informant_mike"

WithIcon

Sets the icon sprite for UI elements such as messages, contacts, and relationships.

builder.WithIcon(iconSprite); // Optional, can be null

Parameters:

  • icon: Optional sprite for UI elements (can be null to use default)

Notes:

  • Used in messages, contacts, relationships
  • Should be a 64x64 or 128x128 sprite
  • Uses default icon if not set

WithSpawnPosition

Sets the spawn position and rotation for the NPC.

// Set position only (default rotation)
builder.WithSpawnPosition(new Vector3(0, 0, 0));

// Set position and rotation
builder.WithSpawnPosition(new Vector3(0, 0, 0), Quaternion.Euler(0, 90, 0));

Parameters:

  • position: World position where the NPC will spawn
  • rotation: Optional rotation (defaults to Quaternion.identity)

Notes:

  • Applied every time the NPC is spawned (new games and loaded games)
  • Use world coordinates
  • Consider building entrances, roads, and safe spawn areas

WithAppearanceDefaults

Configures the prefab's default avatar settings before the NPC is spawned. Use this for appearance values that need to exist on the networked prefab, including far-distance impostor textures.

using S1API.Entities.Impostors;
using S1API.Rendering;

builder.WithAppearanceDefaults(avatar => {
    avatar.Gender = 0.0f;
    avatar.Height = 1.0f;
    avatar.Weight = 0.5f;
    avatar.SkinColor = new Color32(150, 120, 95, 255);
    avatar.HairPath = "Avatar/Hair/Spiky/Spiky";
    avatar.HairColor = Color.black;

    // Use a base-game impostor discovered from Resources/charactersettings.
    avatar.WithImpostor("Kyle");
});

Existing Game Impostors

Use NPCImpostorCatalog when you want to inspect or choose from the impostors available in the current game build. The catalog is populated from loaded base-game NPC avatar settings, so query it after the game world has loaded.

var kyle = NPCImpostorCatalog.Find("Kyle");
if (kyle != null)
{
    builder.WithAppearanceDefaults(avatar => avatar.WithImpostor(kyle));
}

You can also pass a resource path:

builder.WithAppearanceDefaults(avatar => avatar.WithImpostor("charactersettings/Mick"));

Custom Embedded Impostors

For mod-owned impostors, embed a PNG in your mod assembly and load it with TextureUtils.

Texture2D impostorTexture = TextureUtils.LoadTextureFromResource(
    typeof(MyMod).Assembly,
    "MyMod.Resources.MyNpcImpostor.png");

builder.WithAppearanceDefaults(avatar => {
    avatar.WithImpostorTexture(impostorTexture);
});

S1API stores the texture reference on the prefab's AvatarSettings. It does not network raw texture bytes, so every visual client must have the same mod assembly/resource available locally.

Random Impostors

Random selection is deterministic. Without an explicit seed, S1API uses the NPC type name so the same NPC type keeps the same impostor across runs.

// Pick from all discoverable impostors.
builder.WithAppearanceDefaults(avatar => avatar.WithRandomImpostor());

// Pick from a constrained pool.
builder.WithAppearanceDefaults(avatar => avatar.WithRandomImpostor("Kyle", "Mac", "Mick"));

// Pick from a constrained pool with an explicit seed.
builder.WithAppearanceDefaults(avatar => avatar.WithRandomImpostor(71, "Kyle", "Mac", "Mick"));

If no impostor is configured, S1API preserves the existing custom NPC behavior.

EnsureCustomer

Adds customer behavior component to the NPC.

builder.EnsureCustomer();

What it does:

  • Adds the Customer component to the NPC
  • Enables customer behavior
  • Required for WithCustomerDefaults to work

Use when:

  • NPC should act as a business customer
  • NPC should buy products from the player
  • NPC should participate in the economy

WithCustomerDefaults

Configures customer behavior using the CustomerDataBuilder.

builder.WithCustomerDefaults(cd => {
    // Spending behavior
    cd.WithSpending(minWeekly: 150f, maxWeekly: 600f)
      .WithOrdersPerWeek(1, 4)
      .WithPreferredOrderDay(Day.Friday)
      .WithOrderTime(1100); // 11:00 AM
    
    // Customer standards and behavior
    cd.WithStandards(CustomerStandard.VeryLow)
      .AllowDirectApproach(true)
      .GuaranteeFirstSample(true)
      .WithCallPoliceChance(0.15f);
    
    // Relationship requirements
    cd.WithMutualRelationRequirement(minAt50: 2.5f, maxAt100: 4.0f);
    
    // Addiction and dependence
    cd.WithDependence(baseAddiction: 0.1f, dependenceMultiplier: 1.1f);
    
    // Product preferences
    cd.WithAffinities(new[] {
        (DrugType.Marijuana, 0.45f),
        (DrugType.Cocaine, -0.2f)
    });
    
    // Property preferences
    cd.WithPreferredProperties(Property.Munchies, Property.Energizing);
});

Available Methods:

  • WithSpending(minWeekly, maxWeekly): Weekly spending range
  • WithOrdersPerWeek(min, max): Number of orders per week
  • WithPreferredOrderDay(day): Preferred day for orders
  • WithOrderTime(hhmm): Preferred time for orders (24h format)
  • WithStandards(standard): Customer quality standards
  • AllowDirectApproach(allow): Can be approached directly
  • GuaranteeFirstSample(guarantee): First sample always succeeds
  • WithMutualRelationRequirement(minAt50, maxAt100): Relationship requirements
  • WithCallPoliceChance(chance): Chance to call police (0-1)
  • WithDependence(baseAddiction, multiplier): Addiction mechanics
  • WithAffinities(affinities): Product type preferences
  • WithPreferredProperties(properties): Property preferences

EnsureSmokeBreak

Adds the SmokeBreakBehaviour component to the NPC. Required before using .OnArriveSmokeBreak() in a schedule.

// Default base-game cigarette equippable
builder.EnsureSmokeBreak();

// With verbose debug logging (useful during development)
builder.EnsureSmokeBreak(debugMode: true);

// Custom cigarette EquippableData or TPEquippedItem Resources path
builder.EnsureSmokeBreak(cigarettePrefabPath: "equippables/cigarette/Cigarette");

EnsureGraffiti

Adds the GraffitiBehaviour component to the NPC. Required before using .OnArriveGraffiti() in a schedule.

// Default spray paint equippable
builder.EnsureGraffiti();

// Explicit spray paint equippable
builder.EnsureGraffiti(EquippablePath.SprayPaint);

// Custom equippable
builder.EnsureGraffiti(EquippablePath.Custom("MyMod/SprayPaint_AvatarEquippable"));

EnsureDrinking

Adds the DrinkItem component to the NPC. Required before using .OnArriveDrinking() in a schedule. The equippable passed here is the prefab-level default; individual schedule slots can override it with .WithDrink(...).

// Default (Beer)
builder.EnsureDrinking();

// Prefab-level default drink
builder.EnsureDrinking(EquippablePath.Coffee);

EnsureItemHolding

Adds the HoldItem component to the NPC. Required before using .OnArriveHoldItem() in a schedule. The equippable passed here is the prefab-level default; individual schedule slots can override it with .WithItem(...).

// Default (Phone_Lowered)
builder.EnsureItemHolding();

// Prefab-level default item
builder.EnsureItemHolding(EquippablePath.Flashlight);

See Location-Based Actions for a complete guide on all four behaviours, the EquippablePath constants, and example schedules.

WithRelationshipDefaults

Configures relationship parameters using the NPCRelationshipDataBuilder.

builder.WithRelationshipDefaults(r => {
    // Starting relationship level (0-5)
    r.WithDelta(1.5f);
    
    // Unlock settings
    r.SetUnlocked(false)
     .SetUnlockType(NPCRelationship.UnlockType.DirectApproach);
    
    // Connection to other NPCs
    r.WithConnectionsById("kyle_cooley", "ludwig_meyer", "austin_steiner");
});

Available Methods:

  • WithDelta(delta): Starting relationship level (0-5)
  • WithNormalized(normalized): Relationship level as 0-1 value
  • SetUnlocked(unlocked): Whether NPC is initially unlocked
  • SetUnlockType(type): How the NPC can be unlocked
  • WithConnectionsById(ids): Connect to other NPCs by ID
  • WithConnections(npcs): Connect to other NPCs by reference

WithSchedule

Defines the NPC's schedule using the PrefabScheduleBuilder.

builder.WithSchedule(plan => {
    // Basic actions
    plan.EnsureDealSignal();
    plan.WalkTo(new Vector3(10, 0, 10), 900);
    
    // Custom action specs
    plan.Add(new StayInBuildingSpec { 
        BuildingName = "North apartments", 
        StartTime = 1000, 
        DurationMinutes = 60 
    });
    
    plan.Add(new UseVendingMachineSpec { 
        StartTime = 1400,
        MachineGUID = "vending-machine-guid"
    });
    
    plan.Add(new LocationDialogueSpec {
        Destination = new Vector3(20, 0, 20),
        StartTime = 1900,
        FaceDestinationDirection = true,
        GreetingOverrideToEnable = 1
    });
});

Available Methods:

  • WalkTo(destination, startTime, ...): Move to location
  • EnsureDealSignal(): Enable customer deal waiting
  • Add(spec): Add custom action spec

Spawn Position Configuration

Choosing Spawn Positions

Consider these factors when choosing spawn positions:

// Good: Near building entrance
Vector3 goodSpawn = new Vector3(-28.060f, 1.065f, 62.070f);

// Bad: Inside building or underground
Vector3 badSpawn = new Vector3(-28.060f, -5.0f, 62.070f);

// Good: On road or walkable surface
Vector3 roadSpawn = new Vector3(-53.5701f, 1.065f, 67.7955f);

Best Practices:

  • Use world coordinates from the game
  • Ensure the position is on a walkable surface
  • Avoid spawning inside buildings or underground
  • Consider proximity to buildings, roads, and other NPCs
  • Test spawn positions in-game

Dynamic Spawn Positions

You can calculate spawn positions dynamically:

protected override void ConfigurePrefab(NPCPrefabBuilder builder)
{
    // Get a random building
    var buildings = Buildings.GetAll();
    var randomBuilding = buildings[UnityEngine.Random.Range(0, buildings.Count)];
    var buildingPos = randomBuilding.Position;
    
    // Spawn near the building
    var spawnPos = buildingPos + new Vector3(5, 0, 5);
    
    builder.WithSpawnPosition(spawnPos);
}

Customer Configuration

Spending Behavior

Configure how much the customer spends:

cd.WithSpending(minWeekly: 100f, maxWeekly: 500f)
  .WithOrdersPerWeek(1, 3)
  .WithPreferredOrderDay(Day.Friday)
  .WithOrderTime(1400); // 2:00 PM

Spending Ranges:

  • Low: 50-200 per week
  • Medium: 200-500 per week
  • High: 500-1000+ per week

Customer Standards

Set quality expectations:

cd.WithStandards(CustomerStandard.VeryLow)  // Accepts very low quality
  .WithStandards(CustomerStandard.Low)      // Accepts low quality
  .WithStandards(CustomerStandard.Moderate) // Expects decent quality
  .WithStandards(CustomerStandard.High)     // Demands high quality
  .WithStandards(CustomerStandard.VeryHigh); // Demands very high quality

Product Preferences

Define what products the customer likes:

cd.WithAffinities(new[] {
    (DrugType.Marijuana, 0.6f),    // Likes marijuana
    (DrugType.Cocaine, 0.3f),      // Somewhat likes cocaine
    (DrugType.Heroin, -0.5f)       // Dislikes heroin
});

Affinity Values:

  • 1.0f: Loves this product type
  • 0.5f: Likes this product type
  • 0.0f: Neutral
  • -0.5f: Dislikes this product type
  • -1.0f: Hates this product type

Property Preferences

Set preferred product properties:

cd.WithPreferredProperties(Property.Munchies, Property.Energizing, Property.Cyclopean);

Relationship Configuration

Starting Relationship

Set the initial relationship level:

r.WithDelta(0.0f);    // Stranger
r.WithDelta(1.0f);    // Acquaintance
r.WithDelta(2.5f);    // Friend
r.WithDelta(4.0f);    // Good friend
r.WithDelta(5.0f);    // Best friend

Unlock Settings

Configure how the NPC can be unlocked:

r.SetUnlocked(false)  // Must be unlocked
  .SetUnlockType(NPCRelationship.UnlockType.DirectApproach);

r.SetUnlocked(true);  // Already unlocked

Unlock Types:

  • DirectApproach: Can be unlocked by talking to them
  • Recommendation: Must be recommended by another NPC

Connections

Link NPCs together:

// By ID
r.WithConnectionsById("kyle_cooley", "ludwig_meyer");

// By reference (if NPCs are available)
r.WithConnections(Get<KyleCooley>(), Get<LudwigMeyer>());

Schedule Configuration

Basic Schedule Actions

plan.WalkTo(new Vector3(10, 0, 10), 900, faceDestinationDir: true);
plan.EnsureDealSignal();

Custom Action Specs

Use action specs for complex behaviors:

// Stay in building
plan.Add(new StayInBuildingSpec { 
    BuildingName = "North apartments", 
    StartTime = 1000, 
    DurationMinutes = 60,
    DoorIndex = 0
});

// Use vending machine
plan.Add(new UseVendingMachineSpec { 
    StartTime = 1400,
    MachineGUID = "vending-machine-guid"
});

// Location-based dialogue
plan.Add(new LocationDialogueSpec {
    Destination = new Vector3(20, 0, 20),
    StartTime = 1900,
    FaceDestinationDirection = true,
    GreetingOverrideToEnable = 1,
    ChoiceToEnable = 2
});

// Drive to car park
plan.Add(new DriveToCarParkSpec {
    StartTime = 1700,
    ParkingLotGUID = "parking-lot-guid",
    VehicleGUID = "vehicle-guid",
    Alignment = ParkingAlignment.FrontToKerb
});

Configuration Workflow

Step-by-Step Process

  1. Set identity (id, firstName, lastName)
  2. Set icon (optional)
  3. Set spawn position
  4. Add customer component (if needed)
  5. Configure customer defaults (if customer)
  6. Set relationship defaults
  7. Define schedule (if physical NPC)

Complete Example

protected override void ConfigurePrefab(NPCPrefabBuilder builder)
{
    Vector3 shopPosition = new Vector3(-28.060f, 1.065f, 62.070f);
    Vector3 spawnPosition = new Vector3(-53.5701f, 1.065f, 67.7955f);
    
    builder.WithIdentity(
            id: "basic_shopkeeper",
            firstName: "Alex",
            lastName: "Shopkeeper")
            .WithIcon(null)
            .WithSpawnPosition(spawnPosition)
            .EnsureCustomer()
            .WithCustomerDefaults(cd => {
                cd.WithSpending(200f, 800f)
                  .WithOrdersPerWeek(2, 5)
                  .WithPreferredOrderDay(Day.Friday)
                  .WithOrderTime(1400)
                  .WithStandards(CustomerStandard.Low)
                  .AllowDirectApproach(true)
                  .WithAffinities(new[] {
                      (DrugType.Marijuana, 0.6f),
                      (DrugType.Cocaine, 0.3f)
                  });
            })
            .WithRelationshipDefaults(r => {
                r.WithDelta(2.0f)
                 .SetUnlocked(true)
                 .SetUnlockType(NPCRelationship.UnlockType.DirectApproach);
            })
            .WithSchedule(plan => {
                plan.EnsureDealSignal();
                plan.WalkTo(shopPosition, 800);
                plan.Add(new StayInBuildingSpec { 
                    BuildingName = "North apartments", 
                    StartTime = 900, 
                    DurationMinutes = 480 
                });
                plan.WalkTo(spawnPosition, 1800);
            });
}

Best Practices

Do's

  • Always configure customer, relationship, and schedule data in ConfigurePrefab
  • Use meaningful spawn positions that make sense for the NPC's role
  • Test spawn positions in-game to ensure they work properly
  • Use the builder pattern for fluent configuration
  • Handle exceptions gracefully in configuration code

Don'ts

  • Don't modify customer, relationship, or schedule data at runtime (except through proper APIs)
  • Don't spawn NPCs in inaccessible locations
  • Don't use invalid GUIDs for buildings, vehicles, or machines
  • Don't forget to call EnsureCustomer() before WithCustomerDefaults()

Error Handling

Wrap configuration code in try-catch blocks:

protected override void ConfigurePrefab(NPCPrefabBuilder builder)
{
    try
    {
        // Configuration code here
        builder.WithSpawnPosition(spawnPos)
               .EnsureCustomer()
               .WithCustomerDefaults(cd => {
                   // Customer configuration
               });
    }
    catch (Exception ex)
    {
        MelonLogger.Error($"Failed to configure prefab for {GetType().Name}: {ex.Message}");
        // Fallback configuration or re-throw
    }
}

Complete Configuration Examples

The S1API NPC Example Repository contains multiple complete prefab configuration examples:

Customer Configuration

See ExamplePhysicalNPC1 for:

  • Detailed appearance customization with avatar layers
  • Customer behavior with spending patterns and product affinities
  • Complex schedule with vending machines, buildings, and vehicles
  • Inventory setup with startup items and random cash

Dealer Configuration

See ExamplePhysicalDealerNPC for:

  • Dealer-specific configuration with signing fees and commission rates
  • Home building assignment
  • Quality control settings
  • Schedule with EnsureDealSignal() for contract handling

Advanced Schedule Specs

See ExamplePhysicalNPC2 for:

  • Schedule configuration using the Add() method with spec objects
  • UseVendingMachineSpec, StayInBuildingSpec, and LocationDialogueSpec usage

Next Steps

Now that you understand prefab configuration, explore: