Interactions, Policies & Edicts
This page covers all the "action" content types -- things the player or AI can do. There are four types of interaction, plus policies and edicts.
UDiplomacyInteractionUSpyInteractionUPersonInteractionUPowerBlocInteractionUPolicyUEdictShared Concepts#
Before diving into each type, here are concepts shared across all interactions.
Cost, Duration, and Difficulty
All faction-level and person interactions share these base properties:
Costint32Durationint320 = instant.DifficultySuccess Chance
Success chance is built from an array of named factors. Each factor contributes a value that is summed and clamped to determine the final probability.
Override BlueprintSuccessChance() to provide your factors:
UFUNCTION(BlueprintOverride)
TArray<FSuccessChanceFactor> BlueprintSuccessChance() const
{
TArray<FSuccessChanceFactor> Factors;
FSuccessChanceFactor BaseFactor;
BaseFactor.FactorName = Localization::GetText("MyMod", "Factor_Base");
BaseFactor.ChanceValue = 0.5f; // 50% base
Factors.Add(BaseFactor);
return Factors;
}For person interactions, the equivalent uses FModifierBreakdown:
UFUNCTION(BlueprintOverride)
FModifierBreakdown BlueprintSuccessChance()
{
FModifierBreakdown Breakdown;
Breakdown.Modifiers.Add(
Localization::GetText("MyMod", "Factor_Base").ToString(), 0.5f
);
return Breakdown;
}Availability Checks
Override BlueprintRequirements() to control when the interaction is available, greyed out, or hidden:
UFUNCTION(BlueprintOverride)
TArray<FInteractionAvailabilityReason> BlueprintRequirements() const
{
TArray<FInteractionAvailabilityReason> Reasons;
if (SomeCondition)
{
FInteractionAvailabilityReason Reason;
Reason.Reason = Localization::GetText("MyMod", "Reason_SomeCondition");
Reason.AvailabilityStatus = EInteractionAvailability::GreyedOut;
Reasons.Add(Reason);
}
return Reasons;
}Return an empty array if the interaction is available. Each reason with a non-Available status will either grey out or hide the interaction.
Availability Status Values
Faction interactions (UDiplomacyInteraction, USpyInteraction, UEdict):
enum EInteractionAvailability { Available, GreyedOut, Hidden }Person interactions (UPersonInteraction):
enum EPersonInteractionAvailability { Available, GreyedOut, Hidden }Notifications
All interaction types can provide custom completion notifications:
UFUNCTION(BlueprintOverride)
FText GetSuccessNotificationTitle()
{
return Localization::GetText("MyMod", "SuccessTitle");
}
UFUNCTION(BlueprintOverride)
FText GetSuccessNotificationBody()
{
return Localization::GetText("MyMod", "SuccessBody");
}
UFUNCTION(BlueprintOverride)
FText GetFailureNotificationTitle()
{
return Localization::GetText("MyMod", "FailureTitle");
}
UFUNCTION(BlueprintOverride)
FText GetFailureNotificationBody()
{
return Localization::GetText("MyMod", "FailureBody");
}Input Requirements
Some interactions require additional input from the player (selecting settlements, entering gold amounts, etc.). These are configured through InputRequirements.
Faction interaction input types:
enum EInteractionInputType { SettlementSelection, GoldAmount, FactionSelection }Person interaction input types:
enum EPersonInteractionInputType {
PersonSelection, GoldAmount, FactionSelection,
SettlementSelection, MilitarySelection, TextMessage
}Input requirements are added in the constructor or in the Initialize() override:
UFUNCTION(BlueprintOverride)
void Initialize()
{
FInteractionInputRequirement Req;
Req.InputID = n"target_settlements";
Req.InputType = EInteractionInputType::SettlementSelection;
Req.InputPrompt = Localization::GetText("MyMod", "SelectSettlements");
Req.MinSettlements = 1;
Req.MaxSettlements = 3;
Req.SettlementSelectionSource = ESettlementSelectionSource::TargetFaction;
InputRequirements.Add(Req);
}Retrieve provided inputs in your completion handler:
UFUNCTION(BlueprintOverride)
void OnInteractionComplete()
{
TArray<APopulationCentre> Settlements;
if (GetProvidedSettlements(n"target_settlements", Settlements))
{
// Use the selected settlements
}
}Diplomacy Interactions (UDiplomacyInteraction)#
Faction-to-faction diplomatic actions. Inherits from UFactionInteraction.
Difficulty Enum
enum EInteractionDifficulty { VeryEasy = 0, Easy = 1, Medium = 2, Hard = 3, VeryHard = 4 }Properties
All inherited from UFactionInteraction:
InteractionNameFTextDescriptionFTextIconKeyFNameCostint32Durationint320 = instant.DifficultyEInteractionDifficultybRequiresAgentboolbAllowCharacterSelectionboolSuccessOutcomesTArray<FText>FailureOutcomesTArray<FText>RequiredStatsTMap<EPersonStatistic, int32>Overridable Methods
void Initialize()TArray<FInteractionAvailabilityReason> BlueprintRequirements() constTArray<FSuccessChanceFactor> BlueprintSuccessChance() constvoid OnInteractionComplete()void OnInteractionFailure()bool CanPersonBeSelected(UPerson Person) constFText GetSuccessNotificationTitle()FText GetSuccessNotificationBody()FText GetFailureNotificationTitle()FText GetFailureNotificationBody()Runtime Properties
These are available at runtime within your overrides:
FactionAFactionSelectedCharacterUPersonbAllowCharacterSelection is true).InProgressboolExample
class UProposeTrade : UDiplomacyInteraction
{
default InteractionName = Localization::GetText("MyMod", "ProposeTrade_Name");
default Description = Localization::GetText("MyMod", "ProposeTrade_Description");
default Cost = 200;
default Duration = 30;
default Difficulty = EInteractionDifficulty::Easy;
default bRequiresAgent = true;
default IconKey = n"ProposeTrade";
UFUNCTION(BlueprintOverride)
TArray<FInteractionAvailabilityReason> BlueprintRequirements() const
{
TArray<FInteractionAvailabilityReason> Reasons;
AFaction Source = StrategyGameplay::PlayerFaction;
// Cannot trade with enemies
if (Source.GetStatusWith(Faction) == EFactionRelationStatus::War)
{
FInteractionAvailabilityReason R;
R.Reason = Localization::GetText("MyMod", "ProposeTrade_AtWar");
R.AvailabilityStatus = EInteractionAvailability::Hidden;
Reasons.Add(R);
}
return Reasons;
}
UFUNCTION(BlueprintOverride)
TArray<FSuccessChanceFactor> BlueprintSuccessChance() const
{
TArray<FSuccessChanceFactor> Factors;
FSuccessChanceFactor Base;
Base.FactorName = Localization::GetText("MyMod", "Factor_Base");
Base.ChanceValue = 0.6f;
Factors.Add(Base);
return Factors;
}
UFUNCTION(BlueprintOverride)
void OnInteractionComplete()
{
AFaction Source = StrategyGameplay::PlayerFaction;
Faction.DiplomacyComponent.AddOpinionModifier(Source, "Trade Agreement", 15.0f, 365);
}
UFUNCTION(BlueprintOverride)
void OnInteractionFailure()
{
AFaction Source = StrategyGameplay::PlayerFaction;
Faction.DiplomacyComponent.AddOpinionModifier(Source, "Failed Trade Proposal", -5.0f, 180);
}
}Spy Interactions (USpyInteraction)#
Espionage actions against a target faction. Extends UFactionInteraction with spy-specific properties.
Additional Properties
RequiredNetworkStrengthfloatCooldownDaysint32LastCompletionDateint32Additional Methods
UPerson GetAssignedSpy() constSpy interactions inherit all the same overridable methods as diplomacy interactions. The system automatically checks RequiredNetworkStrength and CooldownDays in addition to your custom requirements.
Example
class UStealSecrets : USpyInteraction
{
default InteractionName = Localization::GetText("MyMod", "StealSecrets_Name");
default Description = Localization::GetText("MyMod", "StealSecrets_Description");
default Cost = 500;
default Duration = 60;
default Difficulty = EInteractionDifficulty::Hard;
default bRequiresAgent = true;
default IconKey = n"StealSecrets";
default RequiredNetworkStrength = 0.4f;
default CooldownDays = 180;
UFUNCTION(BlueprintOverride)
TArray<FSuccessChanceFactor> BlueprintSuccessChance() const
{
TArray<FSuccessChanceFactor> Factors;
FSuccessChanceFactor Base;
Base.FactorName = Localization::GetText("MyMod", "Factor_Base");
Base.ChanceValue = 0.3f;
Factors.Add(Base);
return Factors;
}
UFUNCTION(BlueprintOverride)
void OnInteractionComplete()
{
AFaction Source = StrategyGameplay::PlayerFaction;
Source.Gold += 800;
}
UFUNCTION(BlueprintOverride)
void OnInteractionFailure()
{
AFaction Source = StrategyGameplay::PlayerFaction;
Faction.DiplomacyComponent.AddOpinionModifier(Source, "Caught Spy", -30.0f, 365);
}
}Person Interactions (UPersonInteraction)#
Actions between two characters -- the initiator and the target.
Category Enum
enum EPersonInteractionCategory {
Diplomacy, Intrigue, Personal, Military, Economic, Marriage, Family
}Difficulty Enum
enum EPersonInteractionDifficulty { VeryEasy = 0, Easy = 1, Medium = 2, Hard = 3, VeryHard = 4 }Properties
InteractionNameFTextDescriptionFTextIconKeyFNameCategoryEPersonInteractionCategoryPersonalCostint32Durationint320 = instant.CooldownDaysint32DifficultyEPersonInteractionDifficultyMediumbRequiresPresenceboolbCanTargetDeadPersonsboolbAllowInitiatorSelectionboolRequiredRelationshipfloatRequiredStatsTMap<EPersonStatistic, int32>SuccessOutcomesTArray<FText>FailureOutcomesTArray<FText>Overridable Methods
TArray<FPersonInteractionAvailabilityReason> BlueprintRequirements() constFModifierBreakdown BlueprintSuccessChance()bool CanPersonInitiate(UPerson Person) constbAllowInitiatorSelection is truevoid OnInteractionComplete()void OnInteractionFailure()FText GetSuccessNotificationTitle()FText GetSuccessNotificationBody()FText GetFailureNotificationTitle()FText GetFailureNotificationBody()Runtime Properties
InitiatingPersonUPersonTargetPersonUPersonExample
UCLASS(Blueprintable)
class UChallengeToDuel : UPersonInteraction
{
default InteractionName = Localization::GetText("MyMod", "Duel_Name");
default Description = Localization::GetText("MyMod", "Duel_Description");
default Category = EPersonInteractionCategory::Personal;
default Cost = 0;
default Duration = 7;
default CooldownDays = 365;
default Difficulty = EPersonInteractionDifficulty::Medium;
default bRequiresPresence = true;
default IconKey = n"ChallengeToDuel";
UFUNCTION(BlueprintOverride)
TArray<FPersonInteractionAvailabilityReason> BlueprintRequirements() const
{
TArray<FPersonInteractionAvailabilityReason> Reasons;
if (InitiatingPerson == TargetPerson)
{
FPersonInteractionAvailabilityReason R;
R.Reason = Localization::GetText("MyMod", "Duel_CannotSelf");
R.AvailabilityStatus = EPersonInteractionAvailability::Hidden;
Reasons.Add(R);
}
if (TargetPerson != nullptr && TargetPerson.Stats.GetConstitution() < 20.0f)
{
FPersonInteractionAvailabilityReason R;
R.Reason = Localization::GetText("MyMod", "Duel_TooWeak");
R.AvailabilityStatus = EPersonInteractionAvailability::GreyedOut;
Reasons.Add(R);
}
return Reasons;
}
UFUNCTION(BlueprintOverride)
FModifierBreakdown BlueprintSuccessChance()
{
FModifierBreakdown Breakdown;
Breakdown.Modifiers.Add(
Localization::GetText("MyMod", "Factor_Base").ToString(), 0.4f
);
if (InitiatingPerson != nullptr)
{
float TacticsBonus = InitiatingPerson.Stats.GetTactics() * 0.005f;
Breakdown.Modifiers.Add(
Localization::GetText("MyMod", "Factor_Tactics").ToString(), TacticsBonus
);
}
return Breakdown;
}
UFUNCTION(BlueprintOverride)
void OnInteractionComplete()
{
if (TargetPerson != nullptr)
{
TargetPerson.ModifyRelationship(InitiatingPerson, -20.0f);
}
}
UFUNCTION(BlueprintOverride)
FText GetSuccessNotificationBody()
{
if (TargetPerson != nullptr)
{
return FText::FromString(
Localization::GetText("MyMod", "Duel_SuccessBody").ToString()
.Replace("{Target}", TargetPerson.GetFormattedName())
);
}
return Localization::GetText("MyMod", "Duel_SuccessBodyGeneric");
}
}Power Bloc Interactions (UPowerBlocInteraction)#
Actions targeting internal political factions (power blocs). These use a simpler interface than other interaction types.
Properties
InteractionNameFTextDescriptionFTextIconKeyFNameGoldCostint32CooldownDaysint32Overridable Methods
bool IsApplicableToBloc(UPowerBloc Bloc) constTArray<FBlocInteractionUnavailableReason> GetUnavailableReasons() constFBlocInteractionSuccessBreakdown GetSuccessChance() constvoid OnSuccess()void OnFailure()Runtime Properties
TargetBlocUPowerBlocSuccess Chance Breakdown
Power bloc interactions use FBlocInteractionSuccessBreakdown:
UFUNCTION(BlueprintOverride)
FBlocInteractionSuccessBreakdown GetSuccessChance() const
{
FBlocInteractionSuccessBreakdown Breakdown;
Breakdown.BaseChance = 0.5f; // 50% base
Breakdown.Modifiers.Add("Relationship Bonus", 0.15f);
return Breakdown;
}The final chance is BaseChance + sum of all modifiers, clamped to 0.0--1.0.
Example
UCLASS()
class UHostBanquet : UPowerBlocInteraction
{
default InteractionName = Localization::GetText("MyMod", "HostBanquet_Name");
default Description = Localization::GetText("MyMod", "HostBanquet_Description");
default IconKey = n"HostBanquet";
default GoldCost = 300;
default CooldownDays = 90;
UFUNCTION(BlueprintOverride)
TArray<FBlocInteractionUnavailableReason> GetUnavailableReasons() const
{
TArray<FBlocInteractionUnavailableReason> Reasons;
AFaction PlayerFaction = StrategyGameplay::PlayerFaction;
if (PlayerFaction.Gold < GoldCost)
{
FBlocInteractionUnavailableReason R;
R.Reason = Localization::GetText("MyMod", "HostBanquet_NotEnoughGold");
Reasons.Add(R);
}
return Reasons;
}
UFUNCTION(BlueprintOverride)
FBlocInteractionSuccessBreakdown GetSuccessChance() const
{
FBlocInteractionSuccessBreakdown Breakdown;
Breakdown.BaseChance = 0.7f;
return Breakdown;
}
UFUNCTION(BlueprintOverride)
void OnSuccess()
{
AFaction PlayerFaction = StrategyGameplay::PlayerFaction;
PlayerFaction.Gold -= GoldCost;
// Increase bloc satisfaction
}
UFUNCTION(BlueprintOverride)
void OnFailure()
{
AFaction PlayerFaction = StrategyGameplay::PlayerFaction;
PlayerFaction.Gold -= GoldCost;
// Gold spent but no benefit
}
}Policies (UPolicy)#
Policies are adjustable sliders that affect faction-wide modifiers. Each policy has a value range that the player can adjust.
Properties
PolicyNameFTextDescriptionFTextIconKeyFNameValuefloatMinValuefloatMaxValuefloatbIncreaseCausesUnrestboolbDecreaseCausesUnrestboolOverridable Methods
void OnApply(AFaction Faction)FText GetEffectDescription() constExample
UCLASS()
class UTaxRate : UPolicy
{
default PolicyName = Localization::GetText("MyMod", "TaxRate_Name");
default Description = Localization::GetText("MyMod", "TaxRate_Description");
default IconKey = n"TaxRate";
default MinValue = 0.0f;
default MaxValue = 1.0f;
default Value = 0.5f;
default bIncreaseCausesUnrest = true;
default bDecreaseCausesUnrest = false;
UFUNCTION(BlueprintOverride)
void OnApply(AFaction Faction)
{
Faction.EconomyComponent.TaxRateModifier = Value;
}
UFUNCTION(BlueprintOverride)
FText GetEffectDescription() const
{
int32 Percentage = int32(Value * 100.0f);
return FText::FromString(
Localization::GetText("MyMod", "TaxRate_Effect").ToString()
.Replace("{Percentage}", "" + Percentage)
);
}
}Edicts (UEdict)#
Edicts are one-time faction actions that always succeed. They extend UFactionInteraction with a simplified interface.
Edicts inherit all the interaction properties (InteractionName, Description, Cost, Duration, etc.) but override the success chance to always return 100%.
Overridable Methods
void OnApply(AFaction OwningFaction)TArray<FInteractionAvailabilityReason> BlueprintRequirements() constExample
UCLASS()
class UCallLevies : UEdict
{
default InteractionName = Localization::GetText("MyMod", "CallLevies_Name");
default Description = Localization::GetText("MyMod", "CallLevies_Description");
default Cost = 500;
default Duration = 0;
default IconKey = n"CallLevies";
UFUNCTION(BlueprintOverride)
void OnApply(AFaction OwningFaction)
{
OwningFaction.Gold -= Cost;
// Spawn levy units across settlements
}
UFUNCTION(BlueprintOverride)
TArray<FInteractionAvailabilityReason> BlueprintRequirements() const
{
TArray<FInteractionAvailabilityReason> Reasons;
AFaction PlayerFaction = StrategyGameplay::PlayerFaction;
if (PlayerFaction.Gold < Cost)
{
FInteractionAvailabilityReason R;
R.Reason = Localization::GetText("MyMod", "CallLevies_NotEnoughGold");
R.AvailabilityStatus = EInteractionAvailability::GreyedOut;
Reasons.Add(R);
}
return Reasons;
}
}Character Stat Reference#
Several interaction types reference character statistics. Here is the full enum:
enum EPersonStatistic {
Tactics, // Military command ability
Authority, // Leadership and influence
Cunning, // Subterfuge and intelligence
Governance, // Administrative ability
Loyalty, // Faithfulness and reliability
Constitution, // Physical health and endurance
None // No specific stat
}Access stats on a character:
float Tactics = Character.Stats.GetTactics();
float Authority = Character.Stats.GetAuthority();
float Cunning = Character.Stats.GetCunning();
float Governance = Character.Stats.GetGovernance();
float Loyalty = Character.Stats.GetLoyalty();
float Constitution = Character.Stats.GetConstitution();Next Steps#
- Content Types -- Traits, buildings, units, and other content
- Events -- Scripted events with branching narrative
- Assets & Packaging -- Icons, localisation, and distribution