Questlight Studio
Docs / Battle fundamentals

9. Battle fundamentals

In short: Every fight in the plugin is an encounter: a data asset that names the battle mode, the enemies, the rules, the objectives and the rewards. The modes (grid Tactics, Free Tactics, ATB, turn-based, Action) share one core: units, abilities, damage, statuses, victory and defeat, rewards. This chapter covers that shared core, how encounters start, the battle mode override, battle sites, and the headless battle simulator that tests it all without graphics.

9.1 Basics

9.1.1 The words

WordMeaning
Battle modeHow a fight is played. The content decides it, never the player: Battle.Mode.Tactics, FreeTactics, ATB, Turn or Action.
EncounterOne battle as a data asset (URSEEncounterData).
Battle directorThe actor that runs one battle on the map: ARSETacticsBattleDirector for a grid, ARSEATBBattleDirector for a classic stage.
Battle siteAn actor (ARSEEncounterSite) that holds one encounter's places for every mode, so the same encounter can run in any mode that makes sense.
UnitOne combatant in a battle. Each unit has an id used by the battle's functions.
TeamA side. Player and enemy teams are gameplay tags (Team.Player, Team.Enemy).
Rules profileA data asset that overrides some settings (damage, grid, AI...) for one encounter, a boss, or the whole game.

9.1.2 Make your first encounter

  1. Open RPG Saga Engine → Studio → World → New battle (see the Studio). Pick a battle mode, add enemies from your library, set levels and rewards, and let the wizard place the battle in the open level.
  2. Or create the asset by hand: Miscellaneous → Data Asset → RSEEncounterData. Set Battle Mode, add entries to Enemies, and (for grid battles) lay them out with the Encounter Layout mode (below).
  3. Put a trigger in the level: an RSE Encounter Trigger volume that points at the director (or at an RSE Encounter Site). Walk into it in Play.
  4. Check the encounter with the headless simulator (below) before you tune it by playing.

The starter set in /RPGSagaEngine/Data/Encounters/ has a whole campaign of examples (DA_Encounter_Starter_Z1_Pack, _Elite, _Key, _Boss and so on for each zone).

9.1.3 What an encounter holds

GroupFields
ModeBattle Mode, Co-op Conversion and Co-op Alternative (how a live battle behaves in co-op), Allow Mode Override, Override Modes, Mode Rules Profiles.
SidesPlayer Team, Enemy Team, Enemies (character, level, tile and facing for grids, row for classic, AI profile, key unit, Pact, starting statuses), Enemy Items (the enemy team's item pouch), Required Characters.
LevelsLevel Mode: Scaled (enemy levels follow the party within the progression zone's range) or Fixed (story and boss battles). Progression Zone, Scaled Level Offset.
ObjectivesVictory Conditions and Defeat Conditions. Empty victory means "defeat every other team". Losing every player unit is always a defeat.
RewardsReward Items, Loot Rule, Encounter Loot (money and drops), Victory Effects (story effects run on a win). Experience comes from the defeated enemies.
FlowCan Flee (classic battles), Defeat Continues Story (the party may lose without a game over).
AIIntelligence, Min Intelligence, Max Intelligence (the limits adaptive difficulty may use).
GridDeployment Tiles, Field Objects (walls, barrels).
ScriptedReinforcements and Battle Events.
PresentationRules Profile, Battle Music, Transition Style, Intro Text.
BalanceBalance Role for the balance tools (Auto, Normal, Elite, Key, Boss, Ultra, Excluded).

Encounters hold no tile coordinates for the grid itself. The grid belongs to the map (see Grid Tactics).

9.1.4 Objectives

Victory and defeat conditions are small objects. The built-in ones:

  • Team Defeated: every unit of a team is down.
  • Character Defeated: one named character falls (a boss to kill, an escort to protect).
  • Turns Elapsed: a team has taken a number of turns (survive, or a time limit).
  • Team Reaches Tiles: any unit of a team stands on given tiles (reach the exit). Grid battles only.

Each has a Description the HUD shows as the objective text. Any of the victory conditions met means a win. A new condition is a Blueprint or C++ subclass of URSEBattleCondition that overrides IsMet.

9.1.5 Encounter Layout (grid and classic stages)

Instead of typing coordinates, use the Encounter Layout mode:

  1. Select a Tactics Zone (bake its grid first) or an ATB Stage.
  2. Press the Encounter Layout toolbar button, or choose RPG Saga Engine → Advanced tools → Encounter Layout Mode.
  3. Pick an encounter, then Add Enemy and click a tile, Paint Deployment for the player's start tiles, or Add Field Object for walls and barrels.
  4. Read the Issues list: an enemy on a blocked tile, two enemies on one tile, no deployment tile, a zone without a baked grid. Fix every error.

Keys: E/Q rotate, Delete removes, R switches an ATB enemy's row, Ctrl+Z / Ctrl+Y undo and redo (a whole brush stroke is one step). The mode edits only the encounter asset, so battles read exactly what you saw.

9.1.6 Battle zones and sites

The quickest way to place a battle in a level is RPG Saga Engine → Advanced tools → Make Battle Zone Here... (or right-click in the viewport → RPG Saga → Make Battle Zone Here). It builds a ready battle site from an area: a Tactics zone with its director, the encounter laid out, a trigger, and optionally a classic stage next to it. Run it again with the same name and it updates the site instead of making a second one. Several sites on one map work. See World building.

9.1.7 Rewards and results

On victory the party receives experience (split among members that took part), loot, money and any progression rewards (battle points, mastery, new abilities, ranks). Then:

  • Screen (default for Tactics and classic modes): a results screen after a short delay, with a game-over screen on defeat.
  • Feed (default for live battles): no blocking screen. Floating "+340 XP" texts appear over each character with an animated "level up" line, and a stack of notifications slides in at the side for items, money, Pact points, recruits, bestiary entries and quest changes. Exploration continues at once.

The style is set in Project Settings → RPG Saga Engine → Battle Flow UI → Results Presentation (Screen or Feed) and, for Free Tactics, in the Free Tactics settings. The look of the feed is a style asset (URSERewardFeedStyle) selected in Reward Feed. Providers (URSERewardToastProvider) decide what becomes a notification, and you can add your own. A battle that is lost with Defeat Continues Story ticked shows no game over: the fallen members get back on their feet (see the party settings' defeat recovery) and the game goes on.

9.2 How it works

9.2.1 Layers

URSEEncounterData  ──►  Director (RSE)  ──►  URSEBattle subclass (RSECore)  ──►  battle events  ──►  presentation (RSE)
(data)                  builds units,        Tactics / ATB / Turn / FreeTactics /     (damage, status,        animations, camera,
                        deployment, AI       Action: the rules, headless              turn, defeat...)        UI, sounds

URSEBattle is the common base. It owns the units, resources, damage, healing, statuses, reactions, objectives and the event stream. Each mode adds one subclass: URSETacticsBattle, URSEATBBattle (and URSETurnBattle derived from it), the Free Tactics and Action battles. Because the rules never touch actors or rendering, a battle can run headless for tests and simulation. The director turns battle events (ERSEBattleEventType: Turn Started, Ability Used, Damage Dealt, Status Applied, Unit Defeated, Reaction Triggered, Battle Ended and many more) into animations and UI.

9.2.2 The action pipeline

When a unit uses an ability or item:

  1. The mode checks the command is legal (whose turn, resources, range or scope, use conditions, item availability).
  2. Costs are paid (resources, items, Action Points in grids, charge time in ATB).
  3. The ability's effects resolve, per target and once per use (see Abilities). Positional modifiers of the mode apply (flank and height in grids, rows in classic battles).
  4. Damage, healing and statuses become events. Shields absorb, affinities scale, the unit may fall.
  5. Reactions queue and resolve (counterattacks, guards, overwatch), up to Max Reaction Depth.
  6. Objectives are checked after every state change. The battle ends with an outcome: Victory, Defeat or Fled.

9.2.3 Damage and hits

The formulas are data (Project Settings → RPG Saga Engine → Combat Formulas) and a rules profile can replace them. The defaults:

  • Physical and magical damage = Power * Offense * DefenseFactor * Position * Variance. Offense is Attack (physical) or Magic (magical), DefenseFactor is DefenseConstant / (DefenseConstant + max(0, Defense)), so armor has diminishing returns and never makes a unit immune.
  • Fixed damage (bombs) ignores stats, defense, position and variance.
  • Then a critical hit multiplies the damage, a minimum damage applies, and the target's element affinity scales it.
  • Hit chance = Attacker.Accuracy - Target.Evasion + HitBonus, clamped to the minimum and maximum hit chance.
  • Healing from magic = Power * Attacker.Magic * Variance. Fixed and percent-of-max restores stay as authored.

Element affinities are Normal, Weak, Resist, Immune and Absorb. When several sources apply, Absorb wins, then Immune, then Resist. Weak and Resist together cancel. The multipliers are in Battle → Elements → Affinity Multipliers (defaults 1, 2, 0.5, 0 and -1, so absorb heals).

The offense and defense stats, the formulas and the attribute registry are part of the Game Foundation (see Game foundation).

9.2.4 Determinism

All randomness comes from one seeded stream per battle (loot dice come from a stream derived from the same seed). The same seed and the same commands give the same battle. Custom code must draw numbers with Roll Int and Roll Percent on the battle, never with the engine's global random functions.

9.2.5 Starting a battle

An encounter trigger is the usual start: a volume the player walks into (Repeat: One Shot or Repeatable), touching a roaming enemy, or a random encounter area while walking (see Exploration). The story effect Start Battle starts one from a dialog or a cutscene. Everything funnels into the exploration subsystem, which resolves the mode, finds the battle site and calls the right director.

9.2.6 The battle mode override

The content decides each encounter's mode. The battle mode override is a session setting that fights every encounter that allows it in a different mode. It exists for demo menus, test tools and showcase captures. A story game leaves it empty.

Set it:

FromHow
BlueprintGet Game Instance Subsystem (RSE Battle Mode Override Subsystem) → Set Battle Mode Override (Battle.Mode.*), or Clear Battle Mode Override
ConsoleRSE.Battle.ModeOverride Tactics, Live, ATB, Turn, Action or None (no argument shows the current value)
Command line-RSEBattleMode=ATB
C++URSEBattleModeOverrideSubsystem::Get(this)->SetBattleModeOverride(Tag)

It lasts as long as the game instance and is never saved. The encounter's side has three fields: Allow Mode Override (off for scripted battles that only work in one mode), Override Modes (the modes it may use; empty means every mode its data supports) and Mode Rules Profiles (a rules profile per mode).

A battle in another mode runs on a transient copy of the encounter: the mode set, the mode's rules profile applied, the same name (so ids, quests and bestiary entries stay the same). What each mode needs, and what happens when the encounter does not author it:

ModeNeedsAutomatic fallback
Grid TacticsA Tactics zone with its director (from the site)Deployment tiles along the zone's near edge. Enemies are laid out on the far rows when their tiles are off the grid, blocked or shared.
ATB / turn-basedAn ATB director with a stageEvery enemy in front. Those after Auto Front Row Size go to the back row.
Free TacticsNothing (where the party stands)The site's live area. A party outside Live Area Radius is brought to Party Start first.
ActionThe action presentationThe live area, or the site's Action Area.

Technical values are in Project Settings → RPG Saga Engine → Battle Mode Override (overridable modes, Fall Back To Own Mode, fallback sizes). When an override cannot run, the encounter is fought in its own mode with a warning. A site's Log Mode Report button, or URSEBattleModeOverrideLibrary::GetEncounterModeReport, lists for every mode whether the encounter can run in it and which fallbacks it uses.

9.2.7 Encounter sites

ARSEEncounterSite holds one encounter's places for every mode: Tactics Director (its zone is the grid), ATB Director (its stage), the live area (Live Area Radius, Party Start) and an optional Action Area. Encounter triggers point at a site (Site), and the Start Battle effect and StartEncounter find the site of their encounter. Use it when one encounter should work in several modes.

9.3 Key classes and assets

Class / assetModuleRole
URSEEncounterDataRSECoreThe encounter data asset.
URSEBattleRSECoreHeadless core: units, resources, damage, statuses, reactions, objectives, events.
URSETacticsBattle, URSEATBBattle, URSETurnBattleRSECoreMode rules (grid, ATB, turn-based).
URSEBattleConditionRSECoreBase of objectives (URSECondition_TeamDefeated, _CharacterDefeated, _TurnsElapsed, _ReachTiles).
URSEBattleModeOverrideSubsystem, URSEBattleModeOverrideLibraryRSECoreThe mode override, resolution and reports.
URSECombatFormulaSettingsRSECoreThe damage and hit formulas as data.
FRSEBattleRewardsRSECoreWhat a victory gives (experience, items, money, growth, statistics).
ARSETacticsBattleDirector, ARSEATBBattleDirectorRSERun a battle on the map and present its events.
ARSEEncounterTrigger, ARSEEncounterSiteRSEStart a battle from the world, and hold its places per mode.
URSEBattleFlowSubsystemRSEResults and game-over screens.
URSERewardFeedSubsystemRSEThe notification feed after a win.
RSEBattleSim (namespace)RSECoreThe headless simulator: LoadScenarioFile, Run, Summarize, RunAndWriteReport.

9.4 Settings

Project Settings → RPG Saga Engine:

GroupWhat it holds
BattleTactics turn order (CT threshold 100, CT costs), KO countdown, damage constants (Defense Constant 100, Min Damage 1, Variance Min/Max 0.9 to 1.1, Critical Multiplier 1.5, Base Critical Chance 5), hit chance limits (5 to 100), flank and height rules, ATB timing and flee chance, Default Encounter Mode. A rules-profile section.
Combat FormulasThe offense and defense stats and every formula. A rules-profile section.
Battle Mode OverrideOverridable modes and fallbacks.
Battle Flow UIResults and game-over screens, delays (Results Delay 1.2 s), Results Presentation.
Reward FeedThe notification style, widget class and icons.
Battle SimulatorDefaults for the simulator (below).
BalanceBands and levels for Balance My Campaign.
EncountersContact rules (preemptive and back attacks), default teams, random encounter options.

See Appendix B for the full lists.

9.5 Blueprint usage

From the RPG Saga | Battle library:

  • Start Encounter (Encounter, Source): starts a battle as if the player walked in.
  • Is In Battle, Get Current Battle, Get Current Encounter, Get Current Battle Outcome.
  • Find Tactics Director, Find ATB Director.

Start a boss fight from a lever:

  1. Event On Interact (a lever) → Start Encounter (Encounter = DA_Encounter_Starter_Z1_Boss).
  2. Bind the exploration subsystem's On Encounter Ended to continue the story.

React to a battle event on a director: bind On Event Presented (it carries the battle event) to show custom numbers or play a sound.

Write a battle effect (for custom abilities): the battle nodes are Deal Damage, Heal Unit, Change Resource, Apply Status, Remove Statuses, Shift Turn, Defeat Unit, Scan Unit, Roll Int and Roll Percent, plus the getters Get Unit, Get Unit Ids, Get Unit Attribute, Get Unit Resource, Unit Has Status, Are Units Enemies and Get Outcome.

See Appendix A for every node.

9.6 C++ usage

Running a battle headless, as the simulator and the tests do:

URSEATBBattle* Battle = NewObject<URSEATBBattle>();
Battle->Initialize(Spawns, /*Seed*/ 42, /*bCanFlee*/ true);

while (!Battle->IsBattleOver())
{
    Battle->Advance(0.1f);
    for (int32 UnitId : Battle->GetReadyUnits())
    {
        // Choose a command (or let RSEATBAI::Act decide).
        Battle->IssueCommand(UnitId, Ability, TargetId);
    }
}
const ERSEBattleOutcome Outcome = Battle->GetOutcome();

To react to events without a director, bind to the battle's event delegate and switch on ERSEBattleEventType. A new objective is a subclass of URSEBattleCondition. A new kind of reward presentation is a URSERewardToastProvider.

9.7 Advanced

9.7.1 Rules profiles

A rules profile (URSERulesProfile) overrides chosen settings sections (Battle, Grid, Combat Formulas, Ability Toolkit, Turn-Based Battle, Inventory Rules, Economy, AI...) for one encounter, one mode, a chapter or the game. Set it on the encounter (Rules Profile), on a mode (Mode Rules Profiles), on a director (Rules Profile Override) or as the project default. Some properties are structure and stay global, such as tile size and the currency.

9.7.2 The battle simulator

The simulator plays whole battles with no rendering, AI against AI, many times with different seeds, and reports what happened. It is the fastest way to find a pushover or a brick wall.

Ways to run it:

  • Configurator → Tools → Battle Simulator: pick the encounter, a rules profile (empty means as in the game), the number of battles, the party's level and the AI intelligence of each side, optionally a sweep (a parameter from a start to an end by a step). Run, then Open report.
  • Console: RSE.Sim.Run <scenario.json> [Runs] (development builds).
  • Command line: UnrealEditor-Cmd.exe MyGame.uproject -run=RSEBattleSim -Scenario=Config/BattleSim/MyScenario.json -unattended -nullrhi with optional -Out= and -Runs=.

A scenario is a small JSON file:

{
  "Name": "Zone1_LevelSweep",
  "Encounter": "/RPGSagaEngine/Data/Encounters/DA_Encounter_Starter_Z1_Pack",
  "Runs": 40,
  "Seed": 2000,
  "PlayerIntelligence": 0.8,
  "Sweep": { "Parameter": "PartyLevel", "From": 1, "To": 10, "Step": 1 }
}

Fields you will use most: Encounter, Mode, Party, PartyItems, RulesProfile, RulesOverrides, Runs, Seed, PlayerIntelligence, EnemyIntelligence, PartyLevel, EnemyLevelMultiplier, ProgressionZone, MaxTurns, MaxSeconds, Grid and Sweep. Sweep parameters include PartyLevel, EnemyLevelMultiplier, the two intelligences, EnemyAIProfile and Rules:<Section>.<Field>, for example Rules:Battle.CriticalMultiplier.

Each run writes results.csv (one row per battle), summary.json and summary.html to Saved/BattleSim/<date-time>_<scenario>/. Read the win rate (how hard the fight is), the median turns and the HP left (tempo and how close the wins are), the performance score (which way adaptive difficulty would push) and the damage per ability (the candidates to tune). A battle that reaches the turn or time guard is a Timeout. The same scenario and seed always give the same results.

Grid battles need a grid. The simulator uses the zone of the map that runs the encounter, a named zone, or a generated grid (Width, Depth, optional height noise). The values are in Project Settings → RPG Saga Engine → Battle Simulator (default runs 50, seed 1, intelligence 0.8, guards 400 turns and 1200 seconds).

9.7.3 Balance My Campaign

RPG Saga Engine → Advanced tools → Balance My Campaign... plays every encounter of every progression zone with the simulator, flags what is too easy, too hard, too long, a stalemate, a one-shot or an ability cliff, proposes the smallest data changes that fix it, verifies each proposal on the same seeds and applies the ticked ones as one undoable step. It needs a campaign asset (the one the Progression Designer uses). Each encounter's Balance Role picks its win-rate band. Assets with Balance Locked are never touched. The command line is -run=RSEBalanceCampaign -Campaign=/Game/.../DA_Campaign_Main, with -Analyze, -Apply and -FailOnFlags for continuous integration. Reports go to Saved/Balance/.

9.7.4 Networking

The host runs the battle. Guests send commands. Because the rules are deterministic and the state is serializable, this needs no extra code from you (Co-op and online).

9.7.5 Performance

Battle logic does no per-frame allocation in the formulas (they are compiled expressions). Presentation cost is your main budget. The RSE.Battle.Presentation.Speed CVar scales presentation timings and RSE.Battle.Presentation.ReduceEffects trims effects (Platforms and performance).

9.7.6 Debugging commands

In development builds: RSE.Battle.StartEncounter <Asset>, RSE.Battle.Start, RSE.Battle.Restart, RSE.Battle.AutoPlay 1 (the AI plays your party too), RSE.Exploration.Encounter [Filter | ATB | Turn | Tactics] and RSE.Encounter.Debug 1. Appendix C lists them with help text.

9.8 Troubleshooting

SymptomCause and fix
Walking into the trigger does nothingThe trigger has no director or site, or a Repeat setting already used it. For classic battles the map needs an ATB Battle Director with an ATB Stage.
The encounter list in Encounter Layout is emptyOnly encounters of the target's kind show (grid for a zone, classic for a stage). Make one with New battle.
"Zone without a baked grid"Select the Tactics Zone and press Bake Grid.
A mode override seems ignoredThe encounter has Allow Mode Override off, or Override Modes excludes it. Check the mode report.
The override falls back to the encounter's own modeThe map has no zone or stage for the requested mode. Add a site, or turn off Fall Back To Own Mode to see the failure.
The same battle plays out differentlyCustom code uses engine random functions. Use the battle's Roll Int and Roll Percent.
Simulator says the grid is missingNo zone on the map runs this encounter. Pass Grid in the scenario, or use "Generate": true.
A boss is a one-shot for the partyRun Balance My Campaign, or lower the offense in the encounter's rules profile.
No results screen after a winResults Presentation is Feed, or the Free Tactics results mode is None.
A lost story battle shows game overTick Defeat Continues Story on the encounter.

9.9 See also