NPC Pregnancy

Estimated reading time: 9 minutes

NPC pregnancy support is a bridge into the vanilla pregnancy system. It does not turn one species into another.

The system is bidirectional:

  • An NPC can impregnate the PC.
  • The PC can impregnate an NPC.

The same pregnancy type and generator are used for both directions.

Type And NPC Layers

Pregnancy registration has two layers:

  • maplebirch.npc.addPregnancy(type, config) registers a pregnancy type. A type owns the generator, ETA, default birth locations, child config, baby wording, and transformation data.
  • maplebirch.npc.Pregnancy.addNpc(npcName, type, config) registers a single NPC. An NPC entry decides whether that character joins the pregnancy system, which pregnancy type it uses, whether it can be pregnant or impregnate the PC, and whether it overrides cycle or birth behavior.

The two layers are separate. Multiple NPCs can use the same pregnancy type, and an NPC can use a pregnancy type different from its normal npc.type.

NPC Data

Set pregnancy.enabled on NPCs that should join the pregnancy system. Set pregnancy.type to the pregnancy species used by sperm records and pregnancy generation.

maplebirch.npc.add({
  nam: 'Plant Girl',
  type: 'plant',
  gender: 'f',
  vagina: 'clothed',
  penis: 'none',
  pregnancy: {
    enabled: true,
    type: 'plant'
  }
});

Add Type

Custom pregnancy types are registered in script. Adding a type only lets the framework keep that type in pregnancy filters. It does not create pregnancy data by itself.

maplebirch.npc.addPregnancy('plant');

Full Registration

A custom type needs at least a generator. Other callbacks are optional, but they make remaining-day display, child activity, and baby wording work cleanly.

maplebirch.npc.addPregnancy('plant', {
  generator(mother, father, fatherKnown, genital) {
    const pcIsMother = mother === 'pc';
    const pcIsFather = father === 'pc';

    return {
      type: 'plant',
      timer: 0,
      timerEnd: pcIsMother ? random(120, 180) : random(160, 220),
      fetus: [
        {
          type: 'plant',
          mother,
          father,
          fatherKnown: fatherKnown || pcIsFather,
          genital,
          birthId: 0,
          childId: `plant-${mother}-${father}-${Time.days}`,
          gender: 'f',
          features: {},
          localVariables: {}
        }
      ]
    };
  },

  eta(pregnancy) {
    return pregnancy.timerEnd ? Math.floor(pregnancy.timerEnd - pregnancy.timer) : null;
  },

  birth: {
    birthLocation: 'forest',
    location: 'home'
  },

  child: {
    defaults: {
      nursery: 'planter'
    },
    transform: 'plant',
    activity(childId, child) {
      return random(0, 1) ? 'sleeping' : 'sprouting';
    },
    text: {
      single: 'seedling',
      multiple: 'seedlings'
    }
  }
});

Per-NPC Config

Use maplebirch.npc.Pregnancy.addNpc() when a vanilla or custom NPC should join the pregnancy system.

maplebirch.npc.Pregnancy.addNpc('Some NPC', 'human', {
  canBePregnant: true,
  canImpregnatePlayer: false,
  multiplier: 1.5,
  birth: {
    birthLocation: 'home',
    location: 'home'
  },
  cycleMode: 'after',
  onMissedBirth(npcName, pregnancy) {
    pregnancy.missedBirth = true;
    pregnancy.missedBirthCount = (pregnancy.missedBirthCount || 0) + 1;
  }
});

If the NPC already has pregnancy.type, you can pass only config:

maplebirch.npc.Pregnancy.addNpc('Some NPC', {
  type: 'plant',
  canBePregnant: true
});

You can also place per-NPC data inside a type registration:

maplebirch.npc.addPregnancy('plant', {
  generator,
  npc: {
    'Some NPC': {
      multiplier: 1.5
    }
  }
});

Config Fields

FieldTypePurpose
generator(mother, father, fatherKnown, genital) => pregnancyCreates pregnancy data and installs it into window.pregnancyGenerator[type].
birthObject or (type, pregnancy, npcName) => objectRegisters default birth and child locations for this custom type or NPC.
typeStringNPC-only field. Selects which pregnancy type this NPC uses.
enabledBooleanNPC-only field. Set to false to keep override data without adding the NPC to pregnancy lists.
canBePregnantBooleanNPC-only field. Adds this NPC to setup.pregnancy.canBePregnant.
canImpregnatePlayerBooleanNPC-only field. Adds this NPC to setup.pregnancy.canImpregnatePlayer.
multiplierNumber or (npcName, pregnancy) => numberDaily pregnancy timer growth.
autoEndBoolean or (npcName, pregnancy) => booleanWhether missed births should be resolved automatically after the grace period.
cycleMode'range' or 'after'Fertility cycle check mode.
forcePregnancyBoolean or (npcName, pregnancy) => booleanForces the pregnancy attempt to use the first available sperm when the random pick misses.
nonCycleFlagStringField written to the pregnancy object when non-cycle RNG succeeds.
onMissedBirth(npcName, pregnancy) => voidRuns before an automatically resolved missed birth.
npcRecord<npcName, config>Per-NPC overrides using the same fields above.
eta(pregnancy) => number | nullOverrides window.pregnancyDaysEta() for this custom type.
childObjectRegisters child defaults, transformation data, activity, and baby wording for children born from this type.
childActivity(childId, child) => string | null | false | voidOverrides <<updateChildActivity>> for children of this custom type.
textObject or (pregnancy, count, target) => stringOverrides <<pregnancyBabyText>> wording for this custom type.

Callback Parameters

generator(mother, father, fatherKnown, genital)

ParameterMeaning
motherPregnant side. It can be 'pc' or a named NPC name.
fatherImpregnating side. It can be 'pc' or a named NPC name.
fatherKnownWhether the father is known to the pregnancy record.
genitalPregnancy location. Usually 'vagina'; PC pregnancy can also pass another vanilla genital key.

mother === 'pc' means an NPC made the PC pregnant. father === 'pc' means the PC made an NPC pregnant.

The returned object must contain a non-empty fetus array. If it should progress into vanilla birth and child systems, keep the structure close to vanilla pregnancy objects.

eta(pregnancy)

ParameterMeaning
pregnancyCurrent pregnancy object. For PC pregnancy this is usually V.sexStats[genital].pregnancy; for NPC pregnancy this is C.npc[name].pregnancy.

Return remaining days, or null when no clean display is available.

birth

birth can be a plain object:

birth: {
  birthLocation: 'forest',
  location: 'home'
}

It can also be a resolver:

birth(type, pregnancy) {
  return {
    birthLocation: type === 'plant' ? 'forest' : 'unknown',
    location: pregnancy?.npcAwareOf ? 'home' : 'forest'
  };
}
ParameterMeaning
typeRegistered pregnancy type, such as 'plant'.
pregnancyOptional pregnancy object passed by framework callers.
npcNameOptional named NPC when the location is being resolved for a named NPC.
Returned FieldMeaning
birthLocationWhere birth happened. Vanilla stores this on each child as V.children[childId].birthLocation.
locationWhere the child currently belongs after birth. Vanilla stores this on each child as V.children[childId].location.

The registered data is read with maplebirch.npc.Pregnancy.birthLocation(type, pregnancy, npcName). The framework resolves NPC-level data first, then type-level data, then falls back to unknown.

childActivity(childId, child)

child.activity is the preferred location for this callback. The old top-level childActivity field still works when only activity needs to be registered.

ParameterMeaning
childIdKey used in V.children.
childThe child object at V.children[childId]. It normally contains fields such as type, born, localVariables, and vanilla child data.

Return a string to write child.localVariables.activity. Return null, false, or undefined to mark the callback handled without changing activity.

child.defaults

child.defaults runs after vanilla <<endNpcPregnancy>> has created V.children[childId]. It can be an object merged into the child, or a resolver:

child: {
  defaults(child, pregnancy, npcName) {
    return {
      nursery: child.mother === 'pc' ? 'home' : 'forest',
      plantStage: 0
    };
  }
}
ParameterMeaning
childThe newly created V.children[childId] entry.
pregnancyThe pregnancy object that produced this child.
npcNameNamed NPC mother when the birth came from an NPC pregnancy.

Use this for custom child fields that should exist only after birth. Fields required during pregnancy should still be created by the generator inside each fetus object.

child.transform

child.transform supports vanilla child transformation fields and framework-added transformation data. Vanilla stores these values under child.features, so the framework writes them after birth.

The common form is a string:

child: {
  transform: 'plant'
}

This writes child.features.maplebirchTransform = 'plant'.

child: {
  transform: {
    animal: 'wolf',
    divine: 'angel',
    maplebirch: 'plant',
    features: {
      plantStage: 1
    }
  }
}
FieldWritten ToPurpose
animalchild.features.beastTransformAnimal transformation marker. The value may be a vanilla animal transform or a framework-added physical/animal transform name.
divinechild.features.divineTransformDivine transformation marker. The value may be a vanilla divine/demon transform or a framework-added divine transform name.
maplebirchchild.features.maplebirchTransformFramework or mod-added transformation marker.
featureschild.featuresDirectly appends any vanilla-compatible feature fields.

transform can also be a function with the same parameters as child.defaults when the result depends on parents, location, or NPC.

text(pregnancy, count, target)

ParameterMeaning
pregnancyPregnancy object being displayed.
countDisplay count. The framework uses 1 unless vanilla awareness flags reveal multiples.
targetOptional display target passed by <<pregnancyBabyText>>; usually undefined, 'pc', or a named NPC name.

Child-Only Registration

Use maplebirch.npc.Pregnancy.addChild() when the generator is registered elsewhere and you only need child fields, transformation data, activity, or wording:

maplebirch.npc.Pregnancy.addChild('plant', {
  defaults: {
    nursery: 'planter'
  },
  transform: 'plant',
  activity(childId, child) {
    return child.location === 'forest' ? 'sprouting' : 'sleeping';
  },
  text: {
    single: 'seedling',
    multiple: 'seedlings'
  }
});

Runtime Flow

ELK not loaded. Ensure /elk.bundled.js is loaded before the app.

Runtime Touch Points

EntryVanilla RoleFramework Role
setup.pregnancy.typesEnabledFilters valid sperm types in recordSperm.Adds custom pregnancy types.
window.pregnancyGeneratorStores vanilla pregnancy generators.Adds custom generators.
window.recordSpermRecords sperm from Twine events and combat.Fills spermType for custom named NPCs before calling vanilla logic.
time.js daily npcPregnancyCycle() callVanilla daily NPC pregnancy cycle.Replaced with maplebirch.npc.Pregnancy.cycle() so the framework has one daily entry point.
<<playerPregnancyAttempt>>Attempts PC pregnancy.Handles custom sperm, otherwise delegates to vanilla macro.
<<namedNpcPregnancy>>Makes a named NPC pregnant.Handles custom mother/father type combinations, otherwise delegates to vanilla macro.
<<endNpcPregnancy>>Ends named NPC pregnancy and calls vanilla child birth logic.Resolves registered birth locations before delegating to vanilla macro.
window.pregnancyDaysEta()Displays remaining pregnancy days.Uses registered eta for custom types.
<<updateChildActivity>>Updates child daily activity.Uses registered childActivity for custom child types.
<<pregnancyBabyText>>Outputs baby/pup/chick text.Uses registered text for custom types.

npcPregnancyCycle() is a closure function inside vanilla time.js / pregnancy.js, so the framework does not call the original function. Instead, the time.js call site is patched to call the framework cycle. giveBirthToChildren() stays inside vanilla and is reached through the original <<endNpcPregnancy>> macro.

Vanilla Route

Vanilla species and special NPC behavior are represented as default data inside the framework. Custom mods use the same type-level and NPC-level configuration path instead of adding new hardcoded branches.