Relationship System

v1.1.0

Advanced Features

Modifiers, History, Caps, Directional Clamping, Diplomacy, Decay Curves, and Runtime Factions.

Temporary Modifiers

Modifiers are temporary effects that alter how relationship changes are processed. They can buff, debuff, or completely override changes for a duration.

Modifier Types

Flat+10

Adds/subtracts fixed amount from every change

Percentage+25%

Multiplies the change by a percentage

Override=50

Replaces all changes with a fixed value

Stacking Order

Modifiers are processed in order: Override → Percentage → Flat. An Override modifier stops further processing.

// Add a 2-hour reputation boost
manager.AddModifier(new RelationshipModifier {
    ModifierId = "guild_bonus",
    Source = "Guild Membership",
    Type = ModifierType.Percentage,
    Value = 0.25f, // +25% to all gains
    Duration = 7200f, // 2 hours
    AffectsGainOnly = true
});

// Add a reputation penalty (only affects losses)
manager.AddModifier(new RelationshipModifier {
    ModifierId = "curse_penalty",
    Source = "Witch's Curse",
    Type = ModifierType.Flat,
    Value = -5f, // Extra -5 to all losses
    Duration = 3600f,
    AffectsLossOnly = true
});
Use AffectsGainOnly and AffectsLossOnly to create asymmetric modifiers that only affect positive or negative changes.

Relationship History

The HistoryTracker maintains a circular buffer audit log of all relationship changes. Useful for debugging, analytics, and "recently changed" UI.

// Get history for a relationship
var entries = manager.GetHistory("player", "merchant");
foreach (var entry in entries) {
    Debug.Log($"{entry.Timestamp}: {entry.OldValue} → {entry.NewValue} ({entry.Source})");
}

// Configure history size (default: 50 entries per relationship)
manager.SetHistoryCapacity(100);

Reputation Caps

Caps constrain relationship values within boundaries. They are processed by the CapProcessor after every modification. When a modification is constrained, the OnCapReached event fires.

// Cap merchant reputation at 50 until "Guild Membership" quest is complete:
manager.AddCap("player", "merchant", new RelationshipCap {
    CapId = "guild_membership_gate",
    Source = "Guild Quest",
    MaxValue = 50f,
    MinValue = null // No floor
});

// Player completes quest → remove cap:
manager.RemoveCap("player", "merchant", "guild_membership_gate");
// Reputation can now grow beyond 50
Caps are ideal for gating progression. Subscribe to OnCapReached to show UI hints like "Complete Quest X to improve this relationship further."

Cap Registrar Component

The CapRegistrar component provides a visual way to register caps without code. Place it on a GameObject and configure:

  • Entity A / Entity B — The entities to cap (GC2 PropertyGet)
  • Relationship Definition — Optional definition scope
  • Cap ID — Unique identifier for removal
  • Min/Max Values — The cap boundaries
  • Auto-Register — Register on Start

Directional Caps (v1.1)

New in v1.1: Caps now use directional clamping instead of absolute clamping. This means caps only prevent crossing a boundary, not being outside it.

Why Directional?

Scenario: You want players to lose reputation on death, but not below 40. With absolute capping, a new player at 0 reputation would be lifted to 40 — not intended!

❌ Absolute (Old)

Clamp(value, min, max) forces value into range.

Cap: Min=40, Max=100
Player at 10 → becomes 40 ❌

✓ Directional (New)

Only prevents crossing the boundary.

Cap: Min=40, Max=100
Player at 10 → stays at 10 ✓
Player at 50, loses 20 → stops at 40 ✓

Behavior Table

Old ValueDeltaNew ValueResultReason
0+101010Below min, but rising — allowed
50-203040Would cross min — capped
50+60110100Would cross max — capped
110-209090Above max, but falling — allowed
// Death penalty floor - prevents dropping below 40, but allows starting lower
manager.AddCap("player", "guild", new RelationshipCap {
    CapId = "death_penalty_floor",
    Source = "Death Mechanic",
    MinValue = 40f,  // Can't DROP below 40
    MaxValue = null  // No ceiling
});

// New players at 0 can still gain reputation normally
// But established players can't fall below 40 on death
Important: If you need absolute clamping (force values into range), use the RelationshipDefinition.minValue and maxValue properties instead. Caps are for conditional boundaries.

Diplomacy States

The DiplomacyManager adds discrete state-machine behavior to faction-to-faction relationships. States apply modifiers to relationship changes and can auto-transition based on value thresholds.

Available States

StateRep GainRep LossTradeTravel
War×0.5×2.0×0No
Hostile×0.75×1.5×0.25No
Ceasefire×1.0×1.0×0.5Limited
Neutral×1.0×1.0×1.0Yes
Friendly×1.25×0.75×1.25Yes
Alliance×1.5×0.5×1.5Yes
// Set war state between two factions
manager.SetDiplomacyState(factionA.EntityId, factionB.EntityId, warState);

// Check current state
var currentState = manager.GetDiplomacyState(factionA.EntityId, factionB.EntityId);

// Auto-transition: When relationship > 60, transition to Alliance
warState.autoTransitionAbove = 60f;
warState.transitionAboveTo = allianceState;

Advanced Decay

Relationships can decay toward a target value over time. Configure decay in the RelationshipDefinition.

Decay Modes

Linear

Constant rate per second

0.5f/sec → value decreases steadily
Curve

AnimationCurve for custom decay

Fast at extremes, slow at neutral
Asymmetric

Different rates for positive/negative

Positive decays fast, negative slow
// In RelationshipDefinition asset:
enableDecay = true;
decayMode = DecayMode.Asymmetric;
decayTarget = 0f; // Neutral
positiveDecayRate = 0.5f; // Fast decay from positive
negativeDecayRate = 0.1f; // Slow decay from negative

Runtime Factions

Create factions dynamically at runtime for player-created guilds, procedural content, or multiplayer.

// Create a player guild
var playerGuild = manager.CreateRuntimeFaction(
    name: "Dragon Slayers",
    parentFaction: adventurersGuild, // Optional parent
    inheritanceRate: 0.5f
);

// Add members
manager.AddToFaction(playerId, playerGuild);

// Dissolve when empty
manager.DissolveRuntimeFaction(playerGuild);
Runtime factions are fully serialized by the Remember system. They persist across save/load cycles with all their relationships intact.