Skip to main content

API guide

Guide

Use the unified GCSApi to start battles, submit player actions, read live state, modify battle state safely, and subscribe to events

Project code accesses GCSApi through the TinyGiants.GCS.Runtime namespace. Battle start, player actions, state queries, runtime mutations, and event subscriptions all begin here

using TinyGiants.GCS.Runtime;

Check state before calling

GCSApi.IsReady is true once a GameCardManager exists in the scene. Without a Manager, most calls return empty lists, false, 0, or null, but UI should still guard paths that are valid only during an active battle

if (!GCSApi.IsReady)
{
return;
}

if (!GCSApi.IsBattleActive)
{
GCSApi.StartBattle(encounter);
}

Start a battle

StartBattle creates a live battle from a GameEncounter asset; any battle already in progress is disposed first, the opening hand is drawn, and OnBattleStarted is raised before the call returns, with enemy intents already resolved for the first turn

public GameEncounter Encounter;

public void Begin()
{
GCSApi.StartBattle(Encounter);
}
tip

After StartBattle returns, the opening hand, player energy, enemy lineup, and first resolved Intents are available to the presentation layer

Once the battle exists, use events to refresh UI precisely when state changes instead of polling continuously

private IDisposable _battleStarted;

void OnEnable()
{
_battleStarted = GCSApi.OnBattleStarted(RefreshAll);
}

void OnDisable()
{
_battleStarted?.Dispose();
}

Play a card

Read the hand, test playability, then call TryPlayCard

public void TryPlayFirstCard(UnitState target)
{
foreach (var card in GCSApi.Hand())
{
if (!GCSApi.CanPlayCard(card)) continue;
GCSApi.TryPlayCard(card, target);
return;
}
}

Not every card requires the player to choose a target. RequiresTarget returns true only for cards that specify one enemy unit. Runtime resolves self, all-enemy, and untargeted cards automatically, so they can be submitted without opening a picker

var activeCard = GCSApi.GetActiveCard(card);
if (GCSApi.RequiresTarget(activeCard))
{
ShowTargetPicker(card);
}
else
{
GCSApi.TryPlayCard(card);
}

TryPlayCard returns false when there is no active battle, the card is not in hand, energy is insufficient, the card has the Unplayable keyword, or a play Hook cancels the action

End the player turn

public void EndTurnButton()
{
if (GCSApi.Phase == BattlePhase.PlayerPhase)
{
GCSApi.EndPlayerTurn();
}
}

EndPlayerTurn advances to the enemy phase and does nothing outside PlayerPhase, so a stray click on a stale button cannot break the turn order

Read state required by UI

Common UI data has corresponding named query methods

turnLabel.text = GCSApi.TurnNumber.ToString();
energyLabel.text = $"{GCSApi.PlayerEnergy}/{GCSApi.MaxEnergy}";

var player = GCSApi.Player();
hpLabel.text = $"{GCSApi.Hp(player)}/{GCSApi.MaxHp(player)}";

foreach (var enemy in GCSApi.AliveEnemies())
{
RenderEnemy(enemy, GCSApi.CurrentIntentTag(enemy), GCSApi.CurrentIntentValue(enemy));
}

After an event fires, refresh from current API state. A cached copy may already be stale; runtime state is the source that UI should present

Mutate battle state safely

External systems modify state through named GCSApi methods. These calls enter the runtime Controller, so Hooks still run and events are published normally

var player = GCSApi.Player();
GCSApi.GainArmor(player, 5);
GCSApi.DrawCards(1);
GCSApi.ApplyStatus(player, shieldedStatus, 2, player);
Update state through the public mutation APIs

Writing to UnitState.CurrentHp, UnitState.Statuses, or pile lists directly skips Hooks and events, so status reactions and UI refreshes do not receive the change. Use GCSApi mutation methods or the Controller

Victory is checked by the phase machinery

Win and loss are evaluated on phase transitions and after card plays, not on every HP write; dropping an enemy to zero HP through a mutation helper registers at the next check rather than ending the battle on the spot

Subscribe to events

Built-in events provide shortcut subscription methods that return an IDisposable used to release the listener

private readonly List<IDisposable> _subs = new();

void OnEnable()
{
_subs.Add(GCSApi.OnEnergyChanged(_ => RefreshEnergy()));
_subs.Add(GCSApi.OnUnitHpChanged(_ => RefreshUnits()));
_subs.Add(GCSApi.OnCardDrawn(_ => RefreshHand()));
}

void OnDisable()
{
foreach (var sub in _subs) sub.Dispose();
_subs.Clear();
}

Use Subscribe<TArgs> for custom event names or direct access to the event channel

_subs.Add(GCSApi.Subscribe<DamageEventArgs>(
GCSEventNames.OnDamageDealt,
args => LogDamage(args.SourceUnitId, args.TargetUnitId, args.Amount)));

Parameterless events use Subscribe(string, Action); every built-in name is a constant on GCSEventNames, and the payload classes live in GCSEventArgs; custom events raised through RaiseGameEvent deliver their payload as IReadOnlyDictionary<string, object>, so subscribe with that as TArgs

Discover content

GCSApi enumerates the active databases and their entities

foreach (var card in GCSApi.Cards())
{
AddCardToBrowser(card);
}

var poison = GCSApi.FindStatus(poisonGuid);

Enumeration methods build fresh lists and can allocate; cache the result for menus that refresh every frame

Maintain the Master Deck

The Master Deck is the Run-level deck used to create card instances for later battles. StartBattle fills the draw pile from those instances

GCSApi.ClearMasterDeck();
GCSApi.AddCardToMasterDeck(strike);
GCSApi.AddCardToMasterDeck(defend, isUpgraded: true);
GCSApi.StartBattle(encounter);

Changing the Master Deck does not rewrite the piles of a battle that has already started

Extension registries

Tools and debug panels can read the four extension registries through these methods

var nodes = GCSApi.NodeTypes();
var cardTypes = GCSApi.CardTypes();
var tags = GCSApi.CardTags();
var tokens = GCSApi.DescriptionTokens();

The results include custom nodes, card types, tags, and description tokens discovered from every loaded assembly that references TinyGiants.GCS.Runtime

Use API Reference for the complete member signatures and return types.