Entity scripts and instances
Running the script an entity holds in a code field, what `self` and a field's roles are, spawning entity copies that change on their own, and saving both.
All documentation — Playing a story
Start here
Concepts
Authoring for a build
Playing a story
The generated assets and code
The plugin and sync
An entity can hold a script. A writer gives a definition a code field — On use on an Item —
writes target.hp -= self.damage in it on the Rusty Dagger, and the same text works on every item
that holds it. A condition field is the same for a gate: Usable, target.hp > 0. The sync
ships each one as a parsed tree beside its text, so your game never handles source and never needs
a parser. Your game decides when a script runs — a card is played, an item is used — and hands
it the entities it acts on.
Running a script
The session runs one by the entity and the field’s name:
| Function | |
|---|---|
| Execute Entity Script | runs a code field. True when it ran; false with the reason when it was refused or failed |
| Evaluate Entity Condition | evaluates a condition field and answers the gate. A blank gate is true; one that cannot be evaluated is false, with the reason |
Entity is the entity’s code or its id — a spawned copy’s id included, below. Field is the field’s name as the definition declares it. A blank field runs and does nothing.
#include "TaleShardSession.h"#include "TaleShardValue.h"
namespace TaleShardDocSamples{ // Plays one card from the player's hand: a copy of the Rusty Dagger, used on an enemy. bool PlayDagger(UTaleShardSession* Session, const FString& EnemyId, const FString& HeroId, FText& OutReason) { // The player's own copy. Its writes are its own: sharpening it leaves the enemy's dagger, // and the row itself, as they were. FString DaggerId; if (!Session->SpawnEntity(TEXT("rusty_dagger"), DaggerId, OutReason)) { return false; }
// Each role the field declares, by an entity's code or id. `self` is never passed: it is // always the entity the script runs on. TMap<FString, FString> Roles; Roles.Add(TEXT("target"), EnemyId); if (!Session->EvaluateEntityCondition(DaggerId, TEXT("usable"), Roles, OutReason)) { return false; }
Roles.Add(TEXT("user"), HeroId); if (!Session->ExecuteEntityScript(DaggerId, TEXT("onUse"), Roles, OutReason)) { return false; }
// The copy is read by its id; it has no tag of its own. const double Damage = UTaleShardValueLibrary::Conv_ValueToDouble(Session->GetEntityFieldValueById(DaggerId, TEXT("damage"))); UE_LOG(LogTemp, Log, TEXT("The dagger now deals %.0f."), Damage); return true; }}With Generate C++ Types on, each scripted field also gets a typed C++ call — a trait’s scripted
field too, named from its path (RunStatsOnHit runs stats.onHit) — that takes its roles as
parameters — RunOnUse(Session, Item, Target, User, OutReason) — described in
The typed C++ layer. Prefer it: a role the writer
renames then breaks your build, not your fight.
self and roles
Inside the script, self is the entity the script runs on. It is a name the runtime gives the
script, not something you pass: passing it is refused. The same text on two entities is one tree,
and self is what differs between them, so a writer who duplicates the Rusty Dagger into an Iron
Dagger changes the numbers and never the script.
A field’s settings in the editor list the roles its script receives — target and user, each
an entity of a definition the writer picks. Pass each one, by the entity’s code or id. A run is
refused, with a sentence naming the problem, when:
| The reason contains | |
|---|---|
| a declared role is left out | needs a value for 'user' |
| a role is passed that the field does not declare | isn't something this script receives |
self is passed | can't be passed in |
| a role names no entity | must name an entity |
| a role names an entity of another kind — neither the role’s definition nor one inheriting from it | is another kind |
| the script calls a command registered as awaited, anywhere in it | only the story can wait |
| the field is a code field run as a condition, or the other way round | is not a condition field / is not a script field |
A role accepts an entity of the definition it names or of one inheriting from it, so a Boss made from Villain may be the target of a script whose target is a Villain; a copy is its template’s kind. The check is the same through the typed call and through Execute Entity Script.
Neither self nor a role can be reassigned by the script: self = "kim" fails with
can't be changed. What a script can do is write through them — target.hp -= 3 writes the
target’s field into the playthrough, exactly as kim.hp -= 3 would.
The same goes for any local whose value names an entity: inside for (w in user.weapons), w holds
each weapon’s id and w.damage += 1 writes that weapon. A local that names no entity reads as
nothing, and a write through it is refused with doesn't name an entity rather than stored
somewhere a save would carry.
A command an entity script calls runs as it does from the story, except an awaited one: only the story can wait on a command, so Execute Entity Script refuses a script that calls one — on any branch, taken or not — before it runs, and names it. Nothing the script would have done has happened, so the refusal is safe to retry once the command is registered as an instant one.
Instances: copies that change on their own
An entity is one authored row, and what the playthrough writes on it is one set of values. Two
Rusty Daggers in a backpack — or the player’s and the enemy’s — would share one damage. When a
copy must change on its own, spawn it:
| Function | |
|---|---|
| Spawn Entity | a copy of the entity named by Template, its code or id. Answers the copy’s id, <template id>#<n> |
| Despawn Entity | removes a copy and everything written on it. False for an authored entity |
| Get Entity Instances | every live copy’s id, mapped to the id of the entity it copies |
| Get Entity Field Value By Id, Set Entity Field Value By Id | read and write any entity’s field by code or id, a copy’s included |
A copy reads its template’s authored values until something writes it, and a write lands on the
copy alone. Its code field answers its template’s code — which kind of thing it is — but authored
text can never name it: a script reaches a copy only through self, a role or a local holding its
id. rusty_dagger.damage in a dialogue line still reads the row.
With generated types on, each definition’s library also carries a typed C++ spawn,
SpawnItem(Session, Template, OutInstance, OutReason), whose result you pass straight to the typed
calls.
Spawned copies are part of the playthrough: they are saved, a rollback takes them back to what they were at that moment, and Restart clears them. Saving a playthrough has what a restore does with one whose template the project no longer has.