Tutorial: Setting Up a Faction War¶
This tutorial walks through creating a complete faction war scenario where two opposing factions fight for territory. NPCs attack hostile-faction players on sight, reputation changes propagate to allies, and players can shift allegiances through gameplay.
Overview¶
By the end of this tutorial you will have:
- Three faction definitions with configured relationships
- AI NPCs that attack hostile-faction players via AI Perception
- Reputation propagation rules so allied factions share standing
- A reputation reward system tied to killing enemy NPCs
- Custom thresholds controlling when factions flip between war and peace
Step 1: Define Your Factions¶
Create three UFWFactionDefinition DataAssets in /Game/Data/Factions/:
DA_Faction_Ironclad¶
| Property | Value |
|---|---|
| FactionId | Ironclad |
| DisplayName | Ironclad Vanguard |
| TeamId | 1 |
| FactionColor | (R=0.2, G=0.4, B=0.9, A=1.0) |
| bPlayerJoinable | true |
| FactionTags | Faction.Type.Military |
DA_Faction_Shadow¶
| Property | Value |
|---|---|
| FactionId | Shadow |
| DisplayName | Shadow Covenant |
| TeamId | 2 |
| FactionColor | (R=0.7, G=0.1, B=0.1, A=1.0) |
| bPlayerJoinable | true |
| FactionTags | Faction.Type.Military |
DA_Faction_Merchants¶
| Property | Value |
|---|---|
| FactionId | Merchants |
| DisplayName | Merchant Confederacy |
| TeamId | 3 |
| FactionColor | (R=0.9, G=0.8, B=0.2, A=1.0) |
| bPlayerJoinable | false |
| bHidden | false |
| FactionTags | Faction.Type.Civilian |
Step 2: Configure Relationships¶
Ironclad Relationships¶
| Other Faction | Base Reputation | Starting Attitude |
|---|---|---|
| Shadow | -100.0 |
Hostile |
| Merchants | 50.0 |
Friendly |
Shadow Relationships¶
| Other Faction | Base Reputation | Starting Attitude |
|---|---|---|
| Ironclad | -100.0 |
Hostile |
| Merchants | -10.0 |
Neutral |
Merchants Relationships¶
| Other Faction | Base Reputation | Starting Attitude |
|---|---|---|
| Ironclad | 50.0 |
Friendly |
| Shadow | -10.0 |
Neutral |
This creates the war dynamic: Ironclad and Shadow are mutually hostile, Merchants are friendly to Ironclad and neutral to Shadow.
Step 3: Set Up Reputation Propagation¶
On the Ironclad DataAsset, add Propagation Rules:
| Target Faction | Falloff Multiplier |
|---|---|
| Merchants | 0.5 |
On the Shadow DataAsset, add Propagation Rules:
| Target Faction | Falloff Multiplier |
|---|---|
| (none) | -- |
Now when a player gains reputation with Ironclad, 50% of that gain also applies to Merchants. Shadow has no allies, so its reputation changes do not propagate.
Step 4: Customize Reputation Thresholds¶
To make the faction war feel dynamic, customize thresholds so that peace is possible but hard-won.
Ironclad Thresholds¶
| Threshold | Value | Notes |
|---|---|---|
| Allied | 90.0 |
Very hard to achieve full alliance |
| Friendly | 25.0 |
Standard |
| Unfriendly | -25.0 |
Standard |
| Hostile | -50.0 |
Easier to become hostile (military faction) |
Shadow Thresholds¶
| Threshold | Value | Notes |
|---|---|---|
| Allied | 90.0 |
Nearly impossible -- they trust no one |
| Friendly | 40.0 |
Harder to befriend |
| Unfriendly | -15.0 |
Quick to distrust |
| Hostile | -40.0 |
Very quick to become hostile |
Step 5: Create the AI Controller¶
Create a C++ AI Controller that uses the faction system for attitude solving.
// FactionAIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "FactionAIController.generated.h"
UCLASS()
class AFactionAIController : public AAIController
{
GENERATED_BODY()
public:
AFactionAIController();
virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override;
};
// FactionAIController.cpp
#include "FactionAIController.h"
#include "Components/FWFactionComponent.h"
AFactionAIController::AFactionAIController()
{
// AI Perception uses this to determine team affiliation
SetGenericTeamId(FGenericTeamId(255)); // Overridden by component
}
ETeamAttitude::Type AFactionAIController::GetTeamAttitudeTowards(const AActor& Other) const
{
return UFWFactionComponent::SolveTeamAttitude(GetPawn(), &Other);
}
How SolveTeamAttitude Works
- Finds
UFWFactionComponenton both actors. - If either is missing, returns
ETeamAttitude::Neutral. - Queries the subsystem for the attitude between the two actors (personal rep takes priority over global matrix).
- Converts the five-level
EFWFactionAttitudeto UE's three-stateETeamAttitudeusingFWFactionUtils::ConvertToTeamAttitude.
Step 6: Configure AI Perception¶
On your NPC Blueprint (or AI Controller):
- Add an AI Perception component.
- Add an AI Sight sense configuration.
-
Set Detection by Affiliation:
- Detect Enemies: Checked
- Detect Neutrals: Checked (optional)
- Detect Friendlies: Unchecked
-
In the AI Controller's
OnPerceptionUpdatedevent, filter for hostile targets:
void AFactionAIController::OnPerceptionUpdated(const TArray<AActor*>& UpdatedActors)
{
for (AActor* Actor : UpdatedActors)
{
if (GetTeamAttitudeTowards(*Actor) == ETeamAttitude::Hostile)
{
// Add to threat list, begin combat behavior
SetFocus(Actor);
MoveToActor(Actor);
}
}
}
Alternatively, in a Behavior Tree, use the AI Perception decorator with the Sense set to AISense_Sight and filter by detected actor's team attitude.
Step 7: Award Reputation on Kill¶
When a player kills an enemy NPC, award positive reputation with the player's faction and negative reputation with the NPC's faction.
void AMyGameMode::OnNPCKilled(ACharacter* Killer, ACharacter* Victim)
{
UFWFactionComponent* KillerFaction = Killer->FindComponentByClass<UFWFactionComponent>();
UFWFactionComponent* VictimFaction = Victim->FindComponentByClass<UFWFactionComponent>();
if (!KillerFaction || !VictimFaction)
{
return;
}
FName KillerFactionId = KillerFaction->GetCurrentFactionId();
FName VictimFactionId = VictimFaction->GetCurrentFactionId();
// Award positive rep with killer's own faction
KillerFaction->ModifyPersonalReputation(KillerFactionId, 5.0f);
// Award negative rep with victim's faction
KillerFaction->ModifyPersonalReputation(VictimFactionId, -10.0f);
// Optionally modify the global faction matrix
UFWFactionSubsystem* Subsystem = GetWorld()->GetSubsystem<UFWFactionSubsystem>();
if (Subsystem)
{
// Small global shift: the war intensifies
Subsystem->ModifyFactionReputation(
KillerFactionId,
VictimFactionId,
-1.0f, // Delta
true // bPropagate -- ripples to allied factions
);
}
}
Step 8: Listen for War and Peace Events¶
The subsystem fires events when factions cross critical attitude thresholds.
void AMyGameState::BeginPlay()
{
Super::BeginPlay();
if (UFWFactionSubsystem* Subsystem = GetWorld()->GetSubsystem<UFWFactionSubsystem>())
{
Subsystem->OnFactionWarDeclared.AddDynamic(this, &AMyGameState::HandleWarDeclared);
Subsystem->OnFactionAllianceFormed.AddDynamic(this, &AMyGameState::HandleAllianceFormed);
}
}
void AMyGameState::HandleWarDeclared(FName FactionA, FName FactionB)
{
// Global announcement: "War has broken out between X and Y!"
BroadcastWorldMessage(FString::Printf(
TEXT("War declared between %s and %s!"), *FactionA.ToString(), *FactionB.ToString()
));
}
void AMyGameState::HandleAllianceFormed(FName FactionA, FName FactionB)
{
// Global announcement: "X and Y have formed an alliance!"
BroadcastWorldMessage(FString::Printf(
TEXT("Alliance formed between %s and %s!"), *FactionA.ToString(), *FactionB.ToString()
));
}
Step 9: Test with Debug Tools¶
Use console commands to verify your setup at runtime:
Expected output:
[FWFactionSystem] Loaded factions:
Ironclad (TeamId=1) - Ironclad Vanguard
Shadow (TeamId=2) - Shadow Covenant
Merchants (TeamId=3) - Merchant Confederacy
Displays the full reputation matrix with color-coded attitude levels.
Toggle the debug HUD for a visual overlay:
Step 10: Putting It All Together¶
Your faction war is now fully operational:
- Ironclad and Shadow start hostile -- NPCs from both factions attack players of the opposing faction on sight.
- Killing enemy NPCs awards reputation -- positive with the player's faction, negative with the victim's faction.
- Reputation propagates -- gaining Ironclad rep also gives 50% to Merchants.
- Dynamic thresholds -- Shadow faction is quick to become hostile but hard to befriend. Peace requires sustained effort.
- Global events -- When factions cross critical thresholds, the entire server is notified.
- Personal overrides -- Individual players can have unique standing with any faction, independent of the global matrix.
Advanced: Faction Switching
Players can defect to another faction by calling SetFaction(). Their personal reputations persist, so a former Shadow player who joins Ironclad retains their hard-earned standing with Merchants.