Skip to content

NPC

An NPC is a non-player character — a person in the universe who can pilot a ship, run a station post, deliver a mission, or stand around a bar. Modders interact with NPCs to populate ships and stations, to create mission actors, and to give the player conversation partners.

Datatypes in the inheritance chain:

component → entity → nonplayer → npc

Modders almost always work at the entity level (which has the bulk of properties: race, role, skills, inventory, conversation state, control post) — npc adds only a few clothing/body/conversation-specific accessors.

Where NPCs live: Inside a Ship or Station (.environment = the container). Position is described by .roomslot (which componentslot they’re standing in).

How NPCs get assigned to work: Via control posts (controlpost.aipilot, .engineer, .shiptrader, .shadyguy, .defencenpc, .tradenpc, …) and roles (entityrole.passenger, .marine, .pilot, .captain, .engineer, …).

Most of an NPC’s accessors are inherited from entity (vanilla scriptproperties.xml:1358). NPC-specific additions at :1429.

PropertyTypeDescription
.type / .typenameentitytype / stringPersonality vector
.roleentityroleCurrent role (passenger, marine, engineer, …)
.roleobjectcontrollableObject this role is assigned to (their ship or station)
.raceraceargon / paranid / teladi / split / terran / boron / xenon / khaak
.isfemaleboolGender
.icon / .iconoverridestringUI portrait
.titleoverridestringDisplay title override
.occupationnamestringJob-title display
PropertyTypeDescription
.controlpostcontrolpostCurrent post (controlpost.aipilot, …)
.controlledcontrollableObject this entity is currently controlling
.assignedcontrolledcontrollableObject assigned to control (may differ from .controlled if absent)
.iscontrolentityboolActs as a control entity for a controllable
.iscommable / .isremotecommableboolPlayer can talk via comms / remote
.ismissionactorboolIs a mission actor
.isintransitboolCurrently moving to assignment
.isbusyboolEngaged in something
.isindependentboolOwnership independent of host object
PropertyTypeDescription
.skill.{skilltype}int 0..15Per-skill value (skilltype.boarding, .engineering, …)
.combinedskillint 0..100Weighted skill for current post
.potentialskill.{entityrole}int 0..100What combined skill would be in that role
.experienceprogressintBoarding XP toward next level
PropertyTypeDescription
.roomslotcomponentslotAssigned / current slot
.iswalkingboolIn motion
.walkspeed / .runspeed / .slowwalkspeedfloatDefined speeds
.dockarea / .walkablemodule / .buildmodulevariousWhere they currently are
.spacesuitspacesuitIf in EVA
PropertyTypeDescription
.inventorywareamountlistWares carried
.stockwarelistStocked wares (for traders)
.cancraft.{ware}boolCan craft a particular ware
.command.value / .command.paramcommandCurrent command being executed
.commandaction.value / .commandaction.paramcommandactionCurrent step within command
.$<variable>variousPer-entity blackboard variable
PropertyTypeDescription
.isspeakingboolCurrently in voice line
.isinspeakrange / .isinspeakrange.{entity}boolWithin direct-speak distance
.lastspeaktimetimeWhen they last spoke
.facecutscenestringFace cutscene key
.pageintVoice text page id
PropertyTypeDescription
.npctemplatenpctemplateTemplate ref (for available people queries)
.currentchaircomponentslotChair currently occupied
.targetslotcomponentslotNext destination slot
.hasclothingmod / .hasclothingmod.{ware}boolClothing mods
.hastoolboolCurrently carrying a tool
.hasbodyboolHas a body (for animations — false for some script-only NPCs)
.isinconversationboolIn a player conversation
<create_npc_from_template
name="$actor"
object="$ship"
template="$actortemplate"
slot="$spawnslot"
owner="faction.player"/>

The standard mission-actor spawn pattern. Without slot= the NPC may end up unplaced. From memory: omitting both slot= and slottags= triggers an assertion failure — the NPC is alive but invisible. Use slottags=[tag.npc_generic] if you don’t have a specific slot.

To place into a different object than the template’s host:

<create_npc_from_template
name="$actor"
object="$oldroleobject"
template="$actortemplate"
owner="faction.player"
placementobject="$selectedobject.controlroom"
required="true"/>

Vanilla pattern from conversations.xml:1352.

<create_npc_template
name="$newtemplate"
object="$selectedobject"
entity="$actor"
role="$selectedposition"/>

Used to “remember” an NPC so they can be re-instantiated elsewhere later. See conversations.xml:1327, 1401.

<assign_control_entity
actor="$pilot"
object="$ship"
post="controlpost.aipilot"
init="true"
transfer="true"/>

This wires the NPC to a post on a controllable. Common posts: controlpost.aipilot, .engineer, .shiptrader, .shadyguy, .defencenpc, .tradenpc, .captain (player ship).

transfer="true" removes the actor from their previous post; without it you get two assignments. init="true" runs the post’s init aiscript immediately.

Vanilla canonical pattern: cpu_ship_manager.xml:264 (assign aipilot to a spawned ship), gmc_supervised_mining.xml:1106 (assign to a mining ship).

Set assignment / role (without changing post)

Section titled “Set assignment / role (without changing post)”
<set_npc_role entity="$actor" role="entityrole.passenger"/>

For roles that are not control-post-tied (passenger, missionactor, …).

<destroy_npc entity="$actor"/>

For mission cleanup. Permanent — they’re gone.

NPC creation has a dedicated framework rather than LIB_Generic helpers:

LibraryFilePurpose
md.NPC_Placement_Manager.Place_NPCmd/npc_instantiation.xmlHandles all NPC placement: slot assignment, fallback positions, validation
md.NPC_Instantiation.X cuesmd/npc_instantiation.xmlThe 4000-line state machine for NPC lifecycle
md.NPC_State_Machines.X cuesmd/npc_state_machines.xmlPer-state behaviour (walking, sitting, working)
md.LIB_Generic.Find_NPC_Slots_By_Groupmd/lib_generic.xml:5057Find slots matching a tag group
md.LIB_Generic.GenerateSkillStarsmd/lib_generic.xml:515Render skill bars in UI text
md.LIB_Generic.GenerateRatingStarsTextmd/lib_generic.xml:552Render rating in UI text

For ad-hoc work, the most reliable path is the same one vanilla uses: create_npc_from_template + assign_control_entity. Don’t try to call into the placement manager directly.

EventWhenNotes
event_npc_createdNPC spawnedAlso fires on save load (vanilla has a <!--HACK--> note about this)
event_npc_walk_finishedNPC arrived at target slotobject=$NPC filter
event_npc_slots_validatedNPC slot system recomputed for an objectobject= or group=. Used heavily by mission system to detect when stations are “populated enough” to host missions
event_entity_enteredEntity entered a space= (cluster/sector/zone)Used for boundary detection
event_entity_leftEntity left a space=Same
event_entity_transport_finishedEntity transport pod arrivedCrew transfer between ships
  • create_npc_from_template without $position= OR $slottags= triggers an assertion failure. The NPC is alive but unplaced (invisible). Use $slottags=[tag.npc_generic] for ship-class-agnostic placement. From vanilla npc_instantiation.xml.
  • event_npc_created fires on save load too. Vanilla has an open <!--HACK @Owen--> comment about this. If your handler does setup work, gate it on event.param != 'load'-style checks (vanilla pattern in npc_state_machines.xml:54-56).
  • assign_control_entity without transfer="true" leaves the actor double-assigned. They will appear in .assigneddock and .assignedcontrolled of two objects.
  • .controlled vs .assignedcontrolled — the first is the object they’re currently on, the second is what they’re assigned to. They differ during transit (.isintransit=true).
  • Ship → NPC accessors use <role> shortcut not {$role} lookup. Vanilla syntax: $ship.people.engineer.count (string-as-role shortcut), and $ship.people.{$myrolevar}.count (variable lookup). Mixing breaks.
  • Entity icon has TWO layers. set_entity_overrides icon= controls only the character-head floater. The ship-marker icon is per-role in parameters.xml (e.g. missionactor → npc_missionactor) and is global per role, not per individual.
  • isremotecommable is auto-set/unset on instantiate. Don’t set it manually on a template — it’s only meaningful on actors.
  • .npctemplate returns null for some script-created NPCs. Templates exist only for NPCs spawned from a template via create_npc_from_template. Hand-rolled <create_npc> actors have no template back-reference.

Example 1: Spawn a pilot for a created ship

Section titled “Example 1: Spawn a pilot for a created ship”
<create_ship name="$ship" macro="$Macro" sector="$Sector">
<owner exact="faction.argon"/>
<pilot/>
</create_ship>

Then either:

<!-- Default pilot from <pilot/> is usually enough -->

Or with a specific pilot:

<create_npc_from_template
name="$pilot"
object="$ship"
template="$myPilotTemplate"
slottags="[tag.aipilot]"
owner="faction.argon"/>
<assign_control_entity
actor="$pilot"
object="$ship"
post="controlpost.aipilot"
init="true"
transfer="true"/>

Pattern from cpu_ship_manager.xml:264.

Example 2: Find the most skilled marine on a ship

Section titled “Example 2: Find the most skilled marine on a ship”
<do_for_each name="$slot" in="$ship.crew">
<do_if value="$slot.role == entityrole.marine
and $slot.skill.boarding gt $bestSkill">
<set_value name="$best" exact="$slot"/>
<set_value name="$bestSkill" exact="$slot.skill.boarding"/>
</do_if>
</do_for_each>
<do_if value="@$best">
<write_to_logbook
text="'Best marine: ' + $best.knownname + ' (' + $bestSkill + '/15)'"/>
</do_if>

Example 3: Listen for the player meeting a specific NPC

Section titled “Example 3: Listen for the player meeting a specific NPC”
<cue name="WatchPlayerConvo" instantiate="true">
<conditions>
<event_conversation_started actor="$myActor"/>
<check_value value="event.param.{1} == player.entity"/>
</conditions>
<actions>
<write_to_logbook
text="'Player started talking to ' + $myActor.knownname"/>
</actions>
</cue>
  • How NPCs are placed at game start: Architectural overview Galaxy seedinggod.xml declares NPC roles per station, factionlogic.xml runtime spawner fills posts.
  • How NPCs move around on a station: Architectural overview NPC state machinesnpc_state_machines.xml 14000-line state machine: idle → walk → sit → work → leave.
  • How crew skills affect ship behaviour: Architectural overview Crew skillscombinedskill per post drives Travel Drive availability, scan range, boarding strength, trader margins.
  • Conversation lifecycle: Architectural overview Conversationsconversations.xml pipeline from <start_conversation> to <event_conversation_finished>.
  • Ship — where pilot / crew NPCs live.
  • Station — where staff / mission-giver NPCs live.
  • Faction — owner of NPCs.
  • Ware — what NPCs carry in inventory.
  • Order — what ship-piloting NPCs execute.
  • Spacesuit — special ship for EVA NPCs.