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
- Overview
- NPCPrefabBuilder Methods
- Spawn Position Configuration
- WithAppearanceDefaults
- Customer Configuration
- Relationship Configuration
- Schedule Configuration
- Configuration Workflow
- 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 spawnrotation: 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
Customercomponent to the NPC - Enables customer behavior
- Required for
WithCustomerDefaultsto 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 rangeWithOrdersPerWeek(min, max): Number of orders per weekWithPreferredOrderDay(day): Preferred day for ordersWithOrderTime(hhmm): Preferred time for orders (24h format)WithStandards(standard): Customer quality standardsAllowDirectApproach(allow): Can be approached directlyGuaranteeFirstSample(guarantee): First sample always succeedsWithMutualRelationRequirement(minAt50, maxAt100): Relationship requirementsWithCallPoliceChance(chance): Chance to call police (0-1)WithDependence(baseAddiction, multiplier): Addiction mechanicsWithAffinities(affinities): Product type preferencesWithPreferredProperties(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
EquippablePathconstants, 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 valueSetUnlocked(unlocked): Whether NPC is initially unlockedSetUnlockType(type): How the NPC can be unlockedWithConnectionsById(ids): Connect to other NPCs by IDWithConnections(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 locationEnsureDealSignal(): Enable customer deal waitingAdd(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 type0.5f: Likes this product type0.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 themRecommendation: 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
- Set identity (id, firstName, lastName)
- Set icon (optional)
- Set spawn position
- Add customer component (if needed)
- Configure customer defaults (if customer)
- Set relationship defaults
- 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()beforeWithCustomerDefaults()
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, andLocationDialogueSpecusage
Next Steps
Now that you understand prefab configuration, explore:
- Appearance Customization - Visual customization
- Scheduling System - Detailed schedule management
- Location-Based Actions - SmokeBreak, Graffiti, Drinking, and HoldItem actions
- Customer Behavior - Customer system details
- Relationship Management - Relationship system