API 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);
}
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);
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
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.