Relationship System

The relationship system allows companions to develop deep bonds with the player through approval mechanics, relationship tiers, party formation, and intimate interactions including dual cultivation.

Relationship Categories

Four main relationship categories define the bond level:

type CharacterRelationship = 'Hostile' | 'Neutral' | 'Friendly' | 'Intimate';

Each category has distinct visual indicators:

  • Hostile: Red border, attack/threaten options available
  • Neutral: Gray border, basic interactions only
  • Friendly: Green border, party and advanced interactions
  • Intimate: Pink border, dual cultivation and maximum benefits

Gaining Approval

// Gift giving
{
  kind: 'approval',
  character: 'Companion Name',
  amount: '3'  // Can be negative for disapproval
}

// Sparring victory
{
  kind: 'approval',
  character: 'Companion Name',
  amount: '1'
}

// Quest completion
{
  kind: 'approval',
  character: 'Companion Name',
  amount: '5'
}

Relationship Definition Structure

interface CharacterRelationshipDefinition {
  requiredApproval: number; // Minimum approval needed to unlock progression event to next tier
  relationshipCategory: CharacterRelationship;
  name: string; // Tier name (e.g., "Best Friend")
  tooltip: Translatable; // Description shown in UI

  followCharacter?: FollowCharacterDefinition; // Party mechanics
  dualCultivation?: DualCultivationDefinition; // Intimate interactions

  progressionEvent: {
    // Event to reach next tier
    name: string; // Event title
    tooltip: Translatable; // Event description
    event: EventStep[]; // Event content
    locationOverride?: string; // Specific location required
    requirement?: {
      // Additional requirements
      condition: string;
      tooltip: Translatable;
    };
  };

  /**
   * Alternative progression event used when the player's sexuality setting does not match
   * the character's gender. Should lead naturally to a platonic or sworn path without any
   * romantic overtures. Uses the same name, tooltip, and requirement as progressionEvent.
   */
  platonicProgressionEvent?: {
    event: EventStep[];
    locationOverride?: string;
  };
}

Relationship Tiers Example

const relationshipProgression: CharacterRelationshipDefinition[] = [
  // Tier 1: Acquaintance (progression event unlocks at 5 approval)
  {
    requiredApproval: 5,
    relationshipCategory: 'Neutral',
    name: 'Acquaintance',
    tooltip: "You've just met and know little about each other.",

    progressionEvent: {
      name: 'First Conversation',
      tooltip: 'Get to know your new acquaintance',
      event: [
        {
          kind: 'speech',
          character: 'Companion',
          text: "It's nice to finally talk properly...",
        },
        // Dialogue about background
        {
          kind: 'progressRelationship',
          character: 'Companion',
        },
      ],
    },
  },

  // Tier 2: Friend (progression event unlocks at 6 approval)
  {
    requiredApproval: 6,
    relationshipCategory: 'Friendly',
    name: 'Friend',
    tooltip: 'A trusted friend who will aid you in battle.',

    followCharacter: {
      formParty: [
        {
          kind: 'speech',
          character: 'Companion',
          text: "Let's travel together!",
        },
      ],
      duration: 3,
      buff: {
        canStack: false,
        stats: {
          defense: { value: 1.5, stat: 'power' },
        },
        onTechniqueEffects: [],
        onRoundEffects: [],
        stacks: 1,
      },
      cooldown: 5,
      dissolveParty: [
        {
          kind: 'speech',
          character: 'Companion',
          text: 'I need to handle my own affairs for now.',
        },
      ],
    },

    progressionEvent: {
      name: 'Deeper Bonds',
      tooltip: 'Your friendship deepens',
      requirement: {
        condition: 'realm >= meridianOpening',
        tooltip: 'Reach Meridian Opening realm',
      },
      event: [
        // Emotional conversation
      ],
    },
  },

  // Tier 3: Close Friend (progression event unlocks at 8 approval)
  {
    requiredApproval: 8,
    relationshipCategory: 'Friendly',
    name: 'Close Friend',
    tooltip: 'Your bond is unbreakable.',

    followCharacter: {
      duration: 5, // Longer party duration
      buff: {
        // Stronger buffs
        stats: {
          defense: { value: 2.5, stat: 'power' },
          barrierMitigation: { value: 5, stat: undefined },
        },
      },
      cooldown: 3, // Shorter cooldown
    },

    progressionEvent: {
      name: 'A Moment of Truth',
      tooltip: 'Something important is about to happen',
      locationOverride: 'Nine Mountain Sect', // Will override the character location whilst this event is available
      event: [
        // Confession or major revelation
      ],
    },
  },

  // Tier 4: Sworn Sibling (progression event unlocks at 10 approval)
  {
    requiredApproval: 10,
    relationshipCategory: 'Friendly',
    name: 'Sworn Brother/Sister',
    tooltip: 'Bound by oath and honor.',

    followCharacter: {
      duration: 7,
      buff: {
        stats: {
          defense: { value: 3, stat: 'power' },
          barrierMitigation: { value: 8, stat: undefined },
        },
        onTechniqueEffects: [
          {
            kind: 'damage',
            amount: { value: 0.1, stat: 'power' },
          },
        ],
      },
      cooldown: 2,
    },

    progressionEvent: {
      name: 'Oath Ceremony',
      tooltip: 'Become sworn siblings',
      requirement: {
        condition: 'realm >= qiCondensation && defeated_boss == 1',
        tooltip: 'Reach Qi Condensation and defeat a major enemy together',
      },
      event: [
        {
          kind: 'text',
          text: 'You perform the ancient ceremony...',
        },
        {
          kind: 'speech',
          character: 'Companion',
          text: 'From this day forward, we are family.',
        },
        {
          kind: 'progressRelationship',
          character: 'Companion',
        },
      ],
    },
  },

  // Tier 5: Partner (progression event unlocks at 10 approval)
  {
    requiredApproval: 10,
    relationshipCategory: 'Intimate',
    name: 'Partner',
    tooltip: 'Your hearts beat as one.',

    followCharacter: {
      duration: 10,
      buff: {
        // Maximum combat buffs
        stats: {
          defense: { value: 4, stat: 'power' },
          barrierMitigation: { value: 10, stat: undefined },
          power: { value: 0.5, stat: 'power' },
        },
        onTechniqueEffects: [
          {
            kind: 'damage',
            amount: { value: 0.15, stat: 'power' },
          },
        ],
      },
      cooldown: 1,
    },

    dualCultivation: {
      condition: '1',
      traits: [lovesTender, lovesPassionate], // Required trait objects
      intro: [
        {
          kind: 'speech',
          character: 'Companion',
          text: 'Shall we cultivate together, my love?',
        },
      ],
      success: [
        {
          kind: 'qi',
          amount: 'maxqi * 0.3',
        },
        {
          kind: 'approval',
          character: 'Companion',
          amount: '1',
        },
        {
          kind: 'text',
          text: 'Your qi harmonizes perfectly, strengthening both of you.',
        },
      ],
      failure: [
        {
          kind: 'text',
          text: 'Your techniques clash, but the attempt brings you closer.',
        },
      ],
    },

    progressionEvent: {
      name: 'Eternal Bond',
      tooltip: 'Become Dao Partners',
      requirement: {
        condition: 'realm >= coreFormation',
        tooltip: 'Reach Core Formation realm',
      },
      event: [
        // Dao partner ceremony
      ],
    },
  },

  // Tier 6: Dao Partner (progression event unlocks at 10 approval)
  {
    requiredApproval: 10,
    relationshipCategory: 'Intimate',
    name: 'Dao Partner',
    tooltip: 'Your souls are forever intertwined.',

    followCharacter: {
      duration: -1, // Unlimited
      buff: {
        // Transcendent buffs
        stats: {
          defense: { value: 5, stat: 'power' },
          barrierMitigation: { value: 15, stat: undefined },
          power: { value: 1, stat: 'power' },
          healthMax: { value: 0.5, stat: 'healthMax' },
        },
        onTechniqueEffects: [
          {
            kind: 'damage',
            amount: { value: 0.2, stat: 'power' },
          },
        ],
        onRoundEffects: [
          {
            kind: 'heal',
            amount: { value: 0.05, stat: 'healthMax' },
          },
        ],
      },
      cooldown: 0, // No cooldown
    },

    dualCultivation: {
      condition: '1',
      traits: [lovesTender, lovesPassionate, energetic],
      intro: [
        {
          kind: 'speech',
          character: 'Companion',
          text: 'Our souls resonate as one.',
        },
      ],
      success: [
        {
          kind: 'qi',
          amount: 'maxqi * 0.5', // Maximum qi gain
        },
        {
          kind: 'approval',
          character: 'Companion',
          amount: '2',
        },
        {
          kind: 'buff',
          buff: 'Harmonized Souls', // Temporary combat buff
          duration: 30,
        },
      ],
      failure: [], // Cannot fail at this level
    },

    progressionEvent: {
      name: '', // No further progression
      tooltip: '',
      event: [],
    },
  },
];

Party System

Companions can join the player’s party (normally unlocked Friendly relationship or higher).

interface FollowCharacterDefinition {
  formParty: EventStep[];     // Party formation dialogue
  duration: number;           // Days in party (-1 for unlimited)
  buff: Omit<Buff, 'name' | 'icon'>;          // Combat bonuses while in party
  craftingBuff?: Omit<CraftingBuff, 'name' | 'icon'>; // Optional crafting bonuses while in party
  cooldown: number;           // Days before can party again
  dissolveParty: EventStep[]; // Party dissolution dialogue
  supportsJoiningParty?: boolean; // If true, the companion can join the player's party via the party UI (max 3)
}

Party Buffs Scale with Relationship

// Friend tier buffs
buff: {
  stats: {
    defense: { value: 1.5, stat: 'power' }
  }
}

// Close Friend tier buffs
buff: {
  stats: {
    defense: { value: 2.5, stat: 'power' },
    barrierMitigation: { value: 5, stat: undefined }
  }
}

// Dao Partner tier buffs
buff: {
  stats: {
    defense: { value: 5, stat: 'power' },
    power: { value: 1, stat: 'power' },
    healthMax: { value: 0.5, stat: 'healthMax' }
  },
  onRoundEffects: [{
    kind: 'heal',
    amount: { value: 0.05, stat: 'healthMax' }
  }]
}

Crafting Buffs in Party

craftingBuff applies a crafting buff to the player while the companion is in the party. It uses the same structure as a regular CraftingBuff minus name and icon:

craftingBuff: {
  canStack: false,
  stats: {
    successChanceBonus: { value: 0.06, stat: undefined },
    itemEffectiveness: { value: 6, stat: undefined },
  },
  effects: [],
  stacks: 1,
  displayLocation: 'none',
},

Joining Party

supportsJoiningParty: true allows the companion to be added to the player’s party through the party management UI, up to a maximum of three companions. Without this flag the companion can still form a party via their interaction dialogue, but they will not appear in the party management panel.

Dual Cultivation

Available at Intimate relationship levels for deep qi sharing.

interface DualCultivationDefinition {
  condition: string; // Additional requirements
  traits: IntimateTrait[]; // Required traits for success
  intro: EventStep[]; // Initiation dialogue
  success: EventStep[]; // Successful cultivation
  failure: EventStep[]; // Failed attempt
}

Intimate Traits

Success in dual cultivation depends on matching traits:

// Import trait objects from the game's intimateTraits module
import {
  lovesRough,      // Prefers rough intimacy
  lovesTender,     // Prefers tender intimacy
  lovesPassionate, // Passionate lover
  aggressiveLover, // Dislikes tender approaches (tender/passionate penalty)
  hardToPlease,    // High satisfaction threshold
  hairTrigger,     // Lower satisfaction threshold — quick to finish
  energetic,       // Extra starting energy
  lowStamina,      // Reduced starting energy
  painTolerant,    // Higher pain threshold
  sensitive,       // Lower pain threshold
} from 'afnm-types/data/dualCultivation/intimateTraits';

Traits are IntimateTrait objects, not string literals. Import them from the game package and pass them directly in the traits array:

dualCultivation: {
  condition: '1',
  traits: [lovesTender, lovesPassionate],
  // ...
}

Dual Cultivation Rewards

// Partner level (basic intimacy)
success: [
  {
    kind: 'qi',
    amount: 'maxqi * 0.3', // 30% max qi
  },
  {
    kind: 'approval',
    character: 'Companion',
    amount: '1',
  },
];

// Dao Partner level (perfect unity)
success: [
  {
    kind: 'qi',
    amount: 'maxqi * 0.5', // 50% max qi
  },
  {
    kind: 'approval',
    character: 'Companion',
    amount: '2',
  },
  {
    kind: 'buff',
    buff: 'Harmonized Cultivation',
    duration: 7, // Week-long buff
  },
];

Progression Events

Special events that advance the relationship to the next tier.

Event Requirements

requirement: {
  condition: 'realm >= qiCondensation && quest_complete == 1', // Block advancement until the player reaches Qi Condensation and completes a specific character sidequest
  tooltip: 'Reach Qi Condensation and complete the quest'
}

Location Override

Force events to occur at specific locations:

locationOverride: 'Nine Mountain Sect'; // Must be at sect

Progression Event Example

progressionEvent: {
  name: 'Heart to Heart',
  tooltip: 'Have an important conversation',
  requirement: {
    condition: 'realm >= meridianOpening',
    tooltip: 'Reach Meridian Opening realm'
  },
  locationOverride: 'Starlit Peak',
  event: [
    {
      kind: 'text',
      text: 'You meet at the peak under the stars...'
    },
    {
      kind: 'speech',
      character: 'Companion',
      text: 'There\'s something I need to tell you...'
    },
    {
      kind: 'choice',
      choices: [
        {
          text: 'I feel the same way',
          children: [
            {
              kind: 'speech',
              character: 'Companion',
              text: 'Really? You do?'
            },
            {
              kind: 'progressRelationship',
              character: 'Companion'
            }
          ]
        },
        {
          text: 'We should remain friends',
          children: [
            {
              kind: 'speech',
              character: 'Companion',
              text: 'I... I understand.'
            },
            {
              kind: 'approval',
              character: 'Companion',
              amount: '-2'
            }
          ]
        }
      ]
    }
  ]
}

Implementation Best Practices

Approval Pacing

Balance approval gains to create meaningful progression:

  • Small gains (1-2): Regular interactions, sparring
  • Medium gains (3-5): Gifts, quest completion
  • Large gains (6-10): Major story events, breakthrough aid

Relationship Gates

Use requirements to pace relationship progression:

// Early relationship - no requirements
requirement: undefined

// Mid relationship - realm gate
requirement: {
  condition: 'realm >= meridianOpening',
  tooltip: 'Reach Meridian Opening'
}

// Late relationship - story gate
requirement: {
  condition: 'realm >= qiCondensation && main_quest_complete == 1',
  tooltip: 'Complete the main questline'
}

Narrative Consistency

Maintain character voice across relationship tiers:

// Acquaintance - Formal
text: 'It is good to meet a fellow disciple.';

// Friend - Casual
text: 'Hey! Good to see you again!';

// Close Friend - Warm
text: 'I was just thinking about you!';

// Partner - Intimate
text: "My love, you've returned to me.";

// Dao Partner - Transcendent
text: 'Our souls sing in harmony once more.';

Visual Feedback

The game automatically handles visual indicators:

  • Character portrait border color changes
  • Relationship status in character menu
  • Heart icons at intimate levels
  • Approval bar showing progress to next tier

Branching Relationship Paths

Companions can have multiple named relationship progressions — for example, a friendship path and a rivalry path. The player is directed onto a branch by a selectRelationshipPath event step. Once a path is selected, all future progressRelationship steps follow that branch’s tier list.

Defining Paths

Add a relationshipPaths field to the character definition alongside (or instead of) the default relationship array:

const myCompanion: Character = {
  name: 'Mei Xing',
  relationship: defaultRelationshipTiers, // Used if no path is selected
  relationshipPaths: {
    friend: friendRelationshipTiers,
    rival: rivalRelationshipTiers,
  },
  // ...other fields
};

Each value in relationshipPaths is a CharacterRelationshipDefinition[] identical in structure to the default relationship array.

Selecting a Path

Use the selectRelationshipPath event step to lock the character onto a named branch:

{
  kind: 'selectRelationshipPath',
  character: 'Mei Xing',
  path: 'rival',
}

Selecting a path resets the character’s relationship index and approval to 0. The player then earns their way up through the new path from the first tier. Once a path is selected it persists — there is no way to revert to the default relationship array.

Example: Two-Path Companion

const friendPath: CharacterRelationshipDefinition[] = [
  {
    requiredApproval: 5,
    relationshipCategory: 'Friendly',
    name: 'Ally',
    tooltip: 'A reliable friend and ally.',
    progressionEvent: {
      name: 'Trust Formed',
      tooltip: 'Your friendship deepens',
      event: [
        { kind: 'speech', character: 'Mei Xing', text: 'I am glad we chose this path.' },
        { kind: 'progressRelationship', character: 'Mei Xing' },
      ],
    },
  },
  // ...further tiers
];

const rivalPath: CharacterRelationshipDefinition[] = [
  {
    requiredApproval: 5,
    relationshipCategory: 'Friendly',
    name: 'Acknowledged Rival',
    tooltip: 'She respects your strength.',
    progressionEvent: {
      name: 'First Test',
      tooltip: 'Prove yourself in sparring',
      event: [
        { kind: 'speech', character: 'Mei Xing', text: 'You are stronger than I expected.' },
        { kind: 'progressRelationship', character: 'Mei Xing' },
      ],
    },
  },
  // ...further tiers
];

const meiXing: Character = {
  name: 'Mei Xing',
  relationship: defaultPath, // fallback if the branching event never fires
  relationshipPaths: {
    friend: friendPath,
    rival: rivalPath,
  },
  // ...other fields
};

See also: Select Relationship Path Step for the event step reference.

Complete Relationship Example

const companionRelationships: CharacterRelationshipDefinition[] = [
  {
    requiredApproval: 0,
    relationshipCategory: 'Neutral',
    name: 'Stranger',
    tooltip: "A fellow cultivator you've recently encountered.",
    progressionEvent: {
      name: 'Breaking the Ice',
      tooltip: 'Have your first real conversation',
      event: [
        /* introduction dialogue */
      ],
    },
  },
  {
    requiredApproval: 5,
    relationshipCategory: 'Friendly',
    name: 'Friend',
    tooltip: 'A dependable ally in your cultivation journey.',
    followCharacter: {
      formParty: [
        /* party formation */
      ],
      duration: 3,
      buff: {
        /* combat bonuses */
      },
      cooldown: 5,
      dissolveParty: [
        /* party end */
      ],
    },
    progressionEvent: {
      name: 'Shared Secrets',
      tooltip: 'Learn about their past',
      requirement: {
        condition: 'realm >= meridianOpening',
        tooltip: 'Reach Meridian Opening',
      },
      event: [
        /* backstory reveal */
      ],
    },
  },
  {
    requiredApproval: 15,
    relationshipCategory: 'Intimate',
    name: 'Lover',
    tooltip: 'Your hearts are intertwined.',
    followCharacter: {
      duration: 7,
      buff: {
        /* enhanced bonuses */
      },
      cooldown: 1,
    },
    dualCultivation: {
      condition: '1',
      traits: [lovesPassionate, sensitive],
      intro: [
        /* initiation */
      ],
      success: [
        /* qi gains and bonuses */
      ],
      failure: [
        /* minor setback */
      ],
    },
    progressionEvent: {
      name: 'Eternal Vow',
      tooltip: 'Make your bond permanent',
      requirement: {
        condition: 'realm >= coreFormation',
        tooltip: 'Both reach Core Formation',
      },
      locationOverride: 'Sacred Grove',
      event: [
        /* dao partner ceremony */
      ],
    },
  },
];

// Attach to character
const myCompanion: Character = {
  name: 'Companion Name',
  relationship: companionRelationships,
  // ...other properties
};