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
+10Adds/subtracts fixed amount from every change
+25%Multiplies the change by a percentage
=50Replaces all changes with a fixed value
Stacking Order
Modifiers are processed in order: Flat → Percentage → Override. An Override modifier replaces the result of everything before it.
Each modifier also has a Target — GainOnly, LossOnly or Both — and a Duration in seconds (0 for permanent until removed). Stacking controls whether adding the same modifier again allows multiple simultaneous instances or refreshes the duration of the existing one. Modifiers are removable by ID, by definition, by source, by faction, by entity, or all at once.
// 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
});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})");
}
// Net change since a point in time
float net = manager.GetNetChange("player", "merchant", sinceTime);
// Configure history size (default: 50 entries per relationship)
manager.SetHistoryCapacity(100);History is opt-in per definition via Enable History, bounded by Max Entries. Check for it from Visual Scripting with the Has History Condition, or from code with GetHistory / GetNetChange.
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.
Caps are stackable: several caps from different sources can constrain the same relationship at once, each with its own Source label, so systems never fight over who set the limit. Caps can be removed individually (by ID), by definition, or all at once.
// 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 50OnCapReached 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.
Player at 10 → becomes 40 ❌
✓ Directional (New)
Only prevents crossing the boundary.
Player at 10 → stays at 10 ✓
Player at 50, loses 20 → stops at 40 ✓
Behavior Table
| Old Value | Delta | New Value | Result | Reason |
|---|---|---|---|---|
| 0 | +10 | 10 | 10 | Below min, but rising — allowed |
| 50 | -20 | 30 | 40 | Would cross min — capped |
| 50 | +60 | 110 | 100 | Would cross max — capped |
| 110 | -20 | 90 | 90 | Above 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 deathRelationshipDefinition.minValue and maxValue properties instead. Caps are for conditional boundaries.Diplomacy States
The DiplomacyManager adds a validated state machine to faction-to-faction relationships, built from Diplomacy State Definition assets. The default set is War / Neutral / Friends, but the states themselves — and which transitions are allowed between them — are entirely yours to define.
- Allowed transitions — Transition Diplomacy State validates against them; Set Diplomacy State force-sets and bypasses them.
- Auto-transition thresholds — rise above / drop below a value and the state changes itself.
- Is Hostile — feeds Auto Hostility.
- Trade modifier, reputation gain/loss multipliers, and travel permission (Allows Faction Travel) — read from your own logic via the manager API.
Example States
| State | Rep Gain | Rep Loss | Trade | Travel |
|---|---|---|---|---|
| War | ×0.5 | ×2.0 | ×0 | No |
| Hostile | ×0.75 | ×1.5 | ×0.25 | No |
| Ceasefire | ×1.0 | ×1.0 | ×0.5 | Limited |
| Neutral | ×1.0 | ×1.0 | ×1.0 | Yes |
| Friendly | ×1.25 | ×0.75 | ×1.25 | Yes |
| Alliance | ×1.5 | ×0.5 | ×1.5 | Yes |
// 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
Constant rate per second
0.5f/sec → value decreases steadilyAnimationCurve for custom decay
Fast at extremes, slow at neutralDifferent 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 negativeRuntime Factions
Create factions dynamically at runtime for player-created guilds, procedural content, or multiplayer. They support hierarchy — including re-parenting at runtime — membership and dissolution, and are saved and restored like everything else.
// Create a player guild
var playerGuild = manager.CreateRuntimeFaction(
name: "Dragon Slayers",
parentFaction: adventurersGuild, // Optional parent
inheritanceRate: 0.5f
);
// Add members
manager.AddToFaction(playerId, playerGuild);
// Re-parent in the hierarchy (Set Faction Parent instruction does the same)
manager.SetFactionParent(playerGuild, newParentFaction);
// Dissolve when empty
manager.DissolveRuntimeFaction(playerGuild);