The typed C++ layer
What a schema change generates into your game module — native tags, enums, entity structs, row structs, the command class, field accessors, variables.
All documentation — The generated assets and code
Start here
Concepts
Authoring for a build
Playing a story
The generated assets and code
The plugin and sync
On this page
A sync whose schema changed can emit C++ into your project’s own game module, under
Source/<YourModule>/TaleShardGenerated/, for your C++ code to use. A content change never
reaches this layer. Everything here is a view over the data layer: a generated accessor reads
the index, the store or the session, and holds no storage of its own.
When it generates
Generate C++ Types, in Project Settings > Plugins > TaleShard and in the sync window, takes three values:
| Value | Generates |
|---|---|
| Automatic — the default | when the project has a C++ game module, and not otherwise. A Blueprint-only project syncs with no C++ and no message about it |
| On | always. In a project with no C++ module nothing is written, because files with nothing to compile them would break the build of a module added later; the sync window says so (CPP034) and how to add one: Tools > New C++ Class |
| Off | never. Everything below is still reachable by tag and by key through the plugin’s own libraries; this layer is the typed spelling of the same data |
The generated code needs the module’s build rules to list GameplayTags, TaleShardCore and
TaleShardRuntime. The sync adds whichever are missing to your module’s Build.cs itself, at
its PublicDependencyModuleNames.AddRange(new string[] { ... }) line, before it writes the
generated files, and says so in the sync window (CPP036). The next build compiles them in. When
there is no such line to add to (CPP030), or the file cannot be written (CPP037), no C++ is
written and the message gives the exact line to add by hand. The story content has synced either
way: the sync window reads Synced — C++ not generated, with the reason beneath.
Names
Every generated name is the author’s code, minted into a C++ identifier by one set of rules:
| Rule | Example |
|---|---|
| PascalCase, split on punctuation and camel boundaries | loot_count → LootCount |
| a character outside ASCII is dropped, never transliterated | café → Caf |
a leading digit gets N | 3d model → N3dModel |
a C++ keyword gets Value | class → ClassValue |
a name a base class already holds gets Value | a field name on a node type → GetNameValue, because GetName is UObject’s |
| a second code minting to the same name gets an ordinal, in order of stable id | LootCount, LootCount2 |
| a code with nothing left is named from its id |
Every change is reported in the sync’s diagnostics, so a member nobody can guess the name of is at least a member somebody can look up.
The files
| File | Holds |
|---|---|
TaleShardGenerated.h/.cpp | native gameplay tags: TaleShardTags::Field_<Code>, Command_<Code>, Variable_<Context>_<Code>, Enum_<Enum>_<Option> and the rest — compile-checked symbols for the same tags the ini registers |
TaleShardEnums.h | one ETaleShard<Enum> : uint8 per reusable option set, with the authored values, plus a FTaleShard<Enum>Options helper that parses and prints option codes |
TaleShard<Definition>.h/.cpp | one FTaleShard<Definition> struct per entity definition, and a UTaleShard<Definition>Library that fills it from the entity index |
TaleShard<Definition>Scripts.h/.cpp | for a definition with a code or condition field, of its own or on a trait it applies, a UTaleShard<Definition>Scripts library with one typed call per such field |
TaleShard<Sheet>Row.h | one FTaleShard<Sheet>Row : FTableRowBase per typed spreadsheet, and the data table re-typed to it. A free-form sheet has no column contract, so it gets no row struct and no data table; its cells reach the game through its table store |
TaleShardProjectCommands.h/.cpp | the command handler class with one typed event per project command |
TaleShard<NodeType>Fields.h/.cpp | one function library per node type, with a typed accessor per field |
TaleShardVariablesLibrary.h/.cpp | a typed getter and setter per project variable |
TaleShardGeneratedHelpers.h | the conversions the generated code shares |
TaleShardGenerated.manifest.json | what was emitted, by source id, with the schema fingerprint and the plugin version |
Never hand-edit a generated file. The next schema change overwrites the folder as a whole.
Include a generated header by its folder, from anywhere in the module:
#include "TaleShardGenerated/TaleShardCharacter.h"Enums
A reusable option set with 255 options or fewer becomes a UENUM, with each entry’s authored value.
A bitmask set carries the Bitflags metadata. Beyond 255 the enum is not emitted and reported; the
tag form stays.
An option set is what a variable or an entity field of that type becomes. A node field
always reads as the stored option string, which is why the node accessors below return the enum by
parsing it through the Options helper. Its Try Parse answers false for an option the project no
longer declares; the plain Parse the accessors use answers the enum’s first entry for one.
Entity structs and libraries
FTaleShardCharacter carries one member per field the definition declares or inherits, typed from
the field’s kind: text and rich text as FString, a number as double, a boolean as bool, a
colour as FLinearColor, a vector as FVector, a time of day as FTaleShardTimeOfDay (Hour and
Minute, parsed from the stored HH:MM), a select as its enum (or an array of it), an entity
reference as a FGameplayTag (or a tag container), a node, board or tag reference as an FName. A trait’s fields arrive under the trait’s own key path, so two applications of one trait
keep their values apart, and each is named from that path: a trait applied as stats gives its hp
field the member StatsHp, beside the definition’s own Hp.
A formula field is generated as a BlueprintReadOnly member. Its value is worked out from the
entity’s other fields when the project syncs, and the session refuses a write to it: Set Entity
Field Value on a formula field fails with '<entity>.<field>' is a formula field. Write the fields
it is computed from instead.
UTaleShardCharacterLibrary:
| Function | |
|---|---|
GetCharacter(Index, Code, OutEntity, Result) | by code tag, answering Found or Not Found |
FindCharacter(Index, Code, OutEntity) | the same as a boolean |
FindCharacter(Session, Code, OutEntity) | the same, through the session’s entity index |
GetCharacterById(Index, EntityId, OutEntity) | by id: what a payload reference and a mention carry |
GetCharacterById(Session, EntityId, OutEntity) | the same, through the session’s entity index |
GetCharacterFromRow(Index, Row) | from a row the entity index already handed you |
SpawnCharacter(Session, Template, OutInstance, OutReason) | a copy of a Character on the session, which changes on its own — see below |
Every read fills the struct from a store row; the struct is a copy. A value the story changed at play time lives in the session’s overlay, which the session’s Get Entity reads — this library is the authored data.
No entity index has to be wired in. The payload asset names the entity index the same sync
generated, and the session’s GetEntityIndex() loads it, so the overloads that take a session
need nothing else. They answer false when the session is null or its payload names no index — a
session built from a sync output directory, or a payload from a sync that generated no assets. The
overloads that take an index are for code that holds one of its own.
FTaleShardCharacter Kim;if (UTaleShardCharacterLibrary::GetCharacterById(Session, KimId, Kim)){ UE_LOG(LogTemp, Display, TEXT("%s has %f hit points"), *Kim.Label, Kim.Hp);}SpawnCharacter takes the session and a Character from the reads above, spawns a copy of it,
and answers the struct again carrying the copy’s id. The members stay the template’s authored
values; the copy’s own are read through the session by that id. What a copy is and how it is saved
is Entity scripts and instances.
Entity scripts
A definition with a code or condition field gets UTaleShard<Definition>Scripts, holding
one call per such field: Run <Field> for a script, Check <Field> for a gate. A field a
trait brings counts too, a sub-trait’s and an inherited trait’s included, and is named from its whole
path, as its member is: a trait applied as stats gives its onHit script the call
RunStatsOnHit, which runs stats.onHit. Each takes
the session, the entity — the definition’s own struct, a spawned copy’s included — and one
parameter per role the field declares, typed by the struct of the role’s definition:
bool UTaleShardItemScripts::RunOnUse(UTaleShardSession* Session, const FTaleShardItem& Item, const FTaleShardCombatant& Target, const FTaleShardCombatant& User, FText& OutReason);bool UTaleShardItemScripts::CheckUsable(UTaleShardSession* Session, const FTaleShardItem& Item, const FTaleShardCombatant& Target, FText& OutReason);Each passes its structs’ ids to the session’s Execute Entity Script or Evaluate Entity Condition, so
everything Entity scripts and instances says about those holds here.
The point is the signature: a role the writer renames or removes changes the call, and your game
stops compiling where it passes the old one rather than refusing at play time. A role whose
definition has no generated struct takes an FString, an entity’s code or id. The calls are on a
library of their own because their parameters are other definitions’ structs: two definitions
whose scripts name each other would otherwise include each other’s headers.
Commands
UTaleShardProjectCommands derives the plugin’s TaleShardCommandHandler and adds one event per
command:
| Declared as | Generated as |
|---|---|
| ordinary | On<Command>(typed parameters) — a native event: override On<Command>_Implementation |
| awaited | On<Command>Async(typed parameters, Completion) — the story waits until you settle the completion |
Derive it in C++, override the events your game answers, and set your class as the Command Handler
Class on the TaleShard Player component or the TaleShard Story actor. Its Register Commands is
generated: every command is registered already, each through a private thunk that reads the
story’s arguments into the typed parameters, so a story calling playsound("alert") reaches
OnPlaysound with a string and no wiring from you.
The events are not offered in Blueprint. A Blueprint answers a command with a TaleShard Command event instead — implementing commands — and one placed in a Blueprint derived from this class takes that command over from the generated event.
The parameter list is the command’s fields, in the author’s order, mapped through the same kind table as everything else; an argument the story omitted arrives as its type’s default.
Node field accessors
UTaleShardDialogueFields, one per node type, is the typed read path for C++ code:
| Function | |
|---|---|
| Is Dialogue | whether a step is of this type |
| Get Text, Get Speaker, Get Mood, … | one per field, typed: text as FText, a number as double, a boolean as bool, a time of day as FTaleShardTimeOfDay, an entity as the step’s resolved entity, media as a media reference, a select as its enum, a list as an array |
| Get Where Picture Field | for a position or area field: the tag of the picture field it is placed on, so a handler finds the picture without spelling its code |
| Get Track Length Field | the tag of the node-level number field that ends the type’s track; only on a type that names one |
| Get Replies Blocks | one per repeater: the rows as an array of FTaleShardDialogueRepliesBlock structs, each carrying the repeater’s fields |
| Get Cues Start Field · Get Cues Duration Field | per repeater that places rows in time: the tags of the block’s start and duration fields; a duration never appears without a start |
| Get Lines Pause Field | per repeater drawn as a conversation (bubbles or screenplay): the tag of the number field a conversation waits after each row |
Every accessor takes the step and reads the field by code through the plugin’s Find Field, so a field the step does not carry reads as its type’s default. A Blueprint reads the same fields with Get Node Field.
Variables
UTaleShardVariablesLibrary carries Get<Variable>(Session) and Set<Variable>(Session, Value, Reason) per project variable, typed — a number as double, a select as its enum, a list as an
array of text. A scoped variable carries its context in the name: GetCombatHp. Every call reads
or writes the session, so a save captures it and a gate sees it. A Blueprint uses Get Story
Variable and Set Story Variable. Two variables whose codes differ
only in case get two accessors, the second numbered: GetGold and GetGold2, each with its own
tag (see the data layer). The accessor’s number and the tag’s come from two
different naming rules, so they need not agree: in a project holding gold, Gold and Gold2
where Gold2’s id sorts first, Gold2 takes GetGold2 and Gold’s accessor becomes GetGold3,
while Gold’s tag is still Gold_2. Do not pair an accessor with a tag by its number.
What a change does to your build
After emission the sync reports what changed — types added, changed and removed, by name — and leaves you in your normal C++ workflow: Live Coding, or a rebuild. It never drives a build.
Two rules worth knowing before the first schema change:
- A renamed code is a renamed type. The report says
FTaleShardGuard -> FTaleShardWardenand prints theCoreRedirectsline forDefaultEngine.inithat keeps existing assets loading; it never writes that file itself. - A changed enum needs an editor restart. A regenerated
UENUMcan corrupt Blueprints that reference it until the editor restarts; the report says so when the delta touched one.
Nothing is written unless the whole set validates. Files go to a scratch folder first, the destination is replaced as a whole, and the manifest is written last — so a run that fails part way leaves either the previous generation or nothing, never a mix.