Server-side scenario execution: new Rudder.Core.dll, realtime/planstore glue removed
- Rudder.Core.dll rebuilt from csharp-sdk effects rewrite (96,768 bytes) - Realtime/ adapters, UnityRealtimeTransportFactory, UnityPlanStateStore, UnityPlanScheduler, WebGL jslib and RealtimeUrl config deleted (with .meta) - Scenarios sample rewired to client.Effects; samples login path updated - AGENTS.md/README/CHANGELOG/package docs + agent skill updated; realtime.md skill doc removed
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: rudder-unity-sdk
|
||||
description: Use when working with the Rudder Unity SDK rudder.sdk in a Unity project — install, bootstrap, auth, remote config, storage, stores, inventory, leaderboards, quests, battle pass, scenarios, realtime.
|
||||
description: Use when working with the Rudder Unity SDK rudder.sdk in a Unity project — install, bootstrap, auth, remote config, storage, stores, inventory, leaderboards, quests, battle pass, scenarios.
|
||||
---
|
||||
|
||||
# Rudder Unity SDK (`rudder.sdk`)
|
||||
@@ -13,7 +13,7 @@ adapters in `RudderSdk.Unity`. Public API surface:
|
||||
`RudderState`, platform adapters.
|
||||
- `RudderSdk.Core` — `RudderClient` and feature services (`Auth`, `Player`,
|
||||
`RemoteConfig`, `Storage`, `ProjectStorage`, `Stores`, `Inventory`,
|
||||
`Leaderboards`, `Quests`, `BattlePass`, `Scenario`, `Realtime`).
|
||||
`Leaderboards`, `Quests`, `BattlePass`, `Scenario`, `Effects`).
|
||||
- `RudderSdk.Core.Models.*` — DTOs (`PlayerProfile`, `Wallet`, `Offer`,
|
||||
`Store`, `StorageItem`, `RankEntry`, `Quest`, `Reward`, ...).
|
||||
|
||||
@@ -47,7 +47,7 @@ the package does not bundle Newtonsoft.Json):
|
||||
|
||||
1. Create the configuration asset: `Assets > Create > Rudder > Configuration`.
|
||||
2. Set `ProjectKey` (required). Defaults: `BaseUrl` `https://api.rudder.build`,
|
||||
`RealtimeUrl` `wss://api.rudder.build/api/realtime/ws`, `TimeoutSeconds` 10.
|
||||
`TimeoutSeconds` 10.
|
||||
3. Add the `Rudder` component to a startup scene object and assign the asset.
|
||||
`Rudder` is `[DefaultExecutionOrder(-1500), DisallowMultipleComponent]`,
|
||||
uses `DontDestroyOnLoad`, and pumps `client.Update(Time.deltaTime)` every
|
||||
@@ -58,7 +58,7 @@ the package does not bundle Newtonsoft.Json):
|
||||
using RudderSdk.Unity;
|
||||
|
||||
var client = Rudder.Initialize(); // synchronous, idempotent
|
||||
await client.AuthorizeWithDeviceAsync("global", "en", nickname: "Player");
|
||||
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||
```
|
||||
|
||||
`Rudder.Initialize()` throws if the component or configuration is missing.
|
||||
@@ -79,9 +79,7 @@ you with `PlayerPrefsUnityKeyValueStore` + `UnityDebugLoggerSink`:
|
||||
| `ITokenStore` | `UnityTokenStoreAdapter` | tokens in PlayerPrefs |
|
||||
| `IDeviceIdProvider` | `UnityDeviceIdProvider` | GUID in PlayerPrefs (`rudder_device_id`) — not `SystemInfo.deviceUniqueIdentifier` |
|
||||
| `IClock` | `UnityClock` | `DateTimeOffset.UtcNow` |
|
||||
| `IPlanStateStore` | `UnityPlanStateStore` | scenario runs persist in PlayerPrefs |
|
||||
| `IRudderLogger` | `UnityLoggerAdapter` → `IUnityLoggerSink` | default `UnityDebugLoggerSink` → `Debug.Log*` |
|
||||
| `IRealtimeTransportFactory` | `UnityRealtimeTransportFactory` | WebGL: `JsWebSocketAdapter` (jslib); else `NativeWebSocketAdapter` |
|
||||
|
||||
## Capabilities
|
||||
|
||||
@@ -90,15 +88,14 @@ then read the reference file for exact signatures.
|
||||
|
||||
| Task | API | Reference |
|
||||
| --- | --- | --- |
|
||||
| Init, sign in, profile, wallets, errors | `Rudder.Initialize`, `client.AuthorizeWithDeviceAsync`, `Auth.*`, `Player.GetProfileAsync` | [reference/bootstrap.md](reference/bootstrap.md), [reference/authentication.md](reference/authentication.md) |
|
||||
| Init, sign in, profile, wallets, errors | `Rudder.Initialize`, `client.Auth.LoginWithDeviceAsync`, `Auth.*`, `Player.GetProfileAsync` | [reference/bootstrap.md](reference/bootstrap.md), [reference/authentication.md](reference/authentication.md) |
|
||||
| Remote tunables | `RemoteConfig.LoadAsync`, `Get<T>(key, fallback)`, `GetAsync` | [reference/remote-config.md](reference/remote-config.md) |
|
||||
| Player cloud save / project storage | `Storage.GetAsync`/`SaveAsync`/`DeleteAsync`, `ProjectStorage.*` | [reference/storage.md](reference/storage.md) |
|
||||
| Shop + inventory | `Stores.ListAsync`/`GetAsync`/`PurchaseAsync`, `Inventory.GetAsync` | [reference/stores-inventory.md](reference/stores-inventory.md) |
|
||||
| High scores | `Leaderboards.FindBySlug(slug)` → `SubmitAsync`/`ListAsync` | [reference/leaderboards.md](reference/leaderboards.md) |
|
||||
| LiveOps scenarios | `Scenario.TriggerAsync`, `OnNotification`/`OnStoreOffer`/... | [reference/scenarios.md](reference/scenarios.md) |
|
||||
| LiveOps scenarios | `Scenario.TriggerAsync`, `Effects.OnNotification`/`OnStoreOffer`/... | [reference/scenarios.md](reference/scenarios.md) |
|
||||
| Global quests | `Quests.ListAsync`/`ClaimAsync`/`ReportProgressAsync` | [reference/quests.md](reference/quests.md) |
|
||||
| Battle pass | `BattlePass.*` with scenario/node ids, `BattlePassSession` | [reference/battlepass.md](reference/battlepass.md) |
|
||||
| Realtime websocket | `Realtime.ConnectAsync`/`DisconnectAsync` (relay not deployed) | [reference/realtime.md](reference/realtime.md) |
|
||||
| Battle pass | `BattlePass.*` with scenario/node ids, `BattlePassEffect` | [reference/battlepass.md](reference/battlepass.md) |
|
||||
|
||||
## Hard rules
|
||||
|
||||
|
||||
@@ -7,16 +7,16 @@ and `Samples~/Authentication/AuthenticationSample.cs`.
|
||||
|
||||
```csharp
|
||||
var client = Rudder.Initialize();
|
||||
await client.AuthorizeWithDeviceAsync("global", "en", nickname: "Player");
|
||||
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||
```
|
||||
|
||||
`RudderClient.AuthorizeWithDeviceAsync(string region, string language,
|
||||
`AuthService.LoginWithDeviceAsync(string region, string language,
|
||||
string nickname = null, CancellationToken cancellationToken = default)`
|
||||
returns `LoginViaDeviceResponse` (`AccessToken`, `RefreshToken`). It performs
|
||||
device login and then restores persisted scenario runs. Tokens are saved to
|
||||
PlayerPrefs by the Unity adapter.
|
||||
returns `LoginViaDeviceResponse` (`AccessToken`, `RefreshToken`). Tokens are
|
||||
saved to PlayerPrefs by the Unity adapter. Pending scenario effects are
|
||||
fetched after sign-in by the effects heartbeat.
|
||||
|
||||
Lower-level alternative on `client.Auth` (login only, no scenario restore):
|
||||
Auth service:
|
||||
|
||||
- `Task<LoginViaDeviceResponse> LoginWithDeviceAsync(string region, string language, string nickname = null, CancellationToken = default)`
|
||||
- `Task<bool> RefreshAsync()` — explicit token refresh.
|
||||
|
||||
@@ -3,31 +3,31 @@
|
||||
`client.BattlePass` (`BattlePassService`), verified against the bundled
|
||||
`Rudder.Core.dll`. There is no sample scene for this feature.
|
||||
|
||||
Progress is tied to a scenario battle-pass node. Prefer `BattlePassSession`
|
||||
delivered by `Scenario.OnBattlePass` — it binds `scenarioId`/`nodeId`/
|
||||
Progress is tied to a scenario battle-pass node. Prefer `BattlePassEffect`
|
||||
delivered by `Effects.OnBattlePass` — it binds `scenarioId`/`nodeId`/
|
||||
`runId` for you. Direct `BattlePassService` calls need those ids manually.
|
||||
|
||||
## BattlePassSession (preferred)
|
||||
## BattlePassEffect (preferred)
|
||||
|
||||
```csharp
|
||||
client.Scenario.OnBattlePass += async session =>
|
||||
client.Effects.OnBattlePass += async effect =>
|
||||
{
|
||||
var progress = await session.GetProgressAsync();
|
||||
await session.AddXpAsync("kills", 100);
|
||||
var claim = await session.ClaimRewardAsync(level, BattlePassService.TrackFree);
|
||||
var progress = await effect.GetProgressAsync();
|
||||
await effect.AddXpAsync("kills", 100);
|
||||
var claim = await effect.ClaimRewardAsync(level, BattlePassService.TrackFree);
|
||||
};
|
||||
```
|
||||
|
||||
- `Task<GetBattlePassProgressResponse> GetProgressAsync(CancellationToken = default)`
|
||||
- `Task<AddBattlePassXpResponse> AddXpAsync(string source, long amount, CancellationToken = default)`
|
||||
- `Task<ClaimBattlePassRewardResponse> ClaimRewardAsync(int level, string track, CancellationToken = default)`
|
||||
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(CancellationToken = default)`
|
||||
- `LevelUp()` / `LevelUpAsync()`, `MaxLevel()` / `MaxLevelAsync()`,
|
||||
`Complete()` / `CompleteAsync()` — resolve the scenario node.
|
||||
- `Get<T>(key, fallback)`, `Id`, `Context` like other sessions.
|
||||
- `Task<PurchaseBattlePassPremiumResponse> PurchasePremiumAsync(CancellationToken = default)` — posts `onPremiumPurchase` on success
|
||||
- `LevelUp()` / `LevelUpAsync()` — posts `onLevelUp`
|
||||
- `End()` / `EndAsync()` — posts `onComplete`
|
||||
- `Get<T>(key, fallback)`, `RunId`, `ScenarioId`, `NodeId`, `Data` like other effects
|
||||
|
||||
`BattlePassLevelSession` (from `OnBattlePassLevel`) has `Claim()` /
|
||||
`ClaimAsync()` and a `Level` property.
|
||||
`BattlePassLevelEffect` (from `OnBattlePassLevel`) has `Claim()` /
|
||||
`ClaimAsync()` (posts `onComplete`) and a `Level` property.
|
||||
|
||||
## BattlePassService (direct)
|
||||
|
||||
@@ -46,11 +46,10 @@ Request models carry `ScenarioId`, `NodeId`, and (except progress)
|
||||
`PremiumOwned` (`bool?`), `ClaimedTiers` (`List<ClaimedTier>`: `Level`,
|
||||
`Track`).
|
||||
- `AddBattlePassXpResponse` — `Xp` (`long?`), `Level` (`int?`),
|
||||
`LeveledUp` (`bool?`), `MaxLevel` (`bool?`), `Plan` (`ExecutionPlan`).
|
||||
`LeveledUp` (`bool?`), `MaxLevel` (`bool?`).
|
||||
- `ClaimBattlePassRewardResponse` — `Success` (`bool?`),
|
||||
`AlreadyClaimed` (`bool?`), `Granted` (`List<Reward>`), `Error`.
|
||||
- `PurchaseBattlePassPremiumResponse` — `Success` (`bool?`), `Error`,
|
||||
`Plan` (`ExecutionPlan`).
|
||||
- `PurchaseBattlePassPremiumResponse` — `Success` (`bool?`), `Error`.
|
||||
|
||||
Business failures come back in the response (`Success != true`, `Error`)
|
||||
with codes from `RudderErrorCodes` (`LevelNotReached`,
|
||||
|
||||
@@ -12,7 +12,6 @@ Create via `Assets > Create > Rudder > Configuration`.
|
||||
| --- | --- | --- | --- |
|
||||
| `ProjectKey` | `string` | — | Required. Issued in the Rudder admin panel. Empty key logs a warning in `OnValidate`. |
|
||||
| `BaseUrl` | `string` | `https://api.rudder.build` | API base URL. |
|
||||
| `RealtimeUrl` | `string` | `wss://api.rudder.build/api/realtime/ws` | Realtime websocket URL. The relay is not deployed yet; leave as-is. |
|
||||
| `TimeoutSeconds` | `int` (`[Min(1)]`) | `10` | HTTP request timeout. |
|
||||
|
||||
Keep the asset with the real key out of git (this repo uses the git-ignored
|
||||
@@ -23,8 +22,8 @@ Keep the asset with the real key out of git (this repo uses the git-ignored
|
||||
`[DefaultExecutionOrder(-1500), DisallowMultipleComponent]`. Put it on a
|
||||
startup scene object, assign the configuration asset. `Awake` keeps the first
|
||||
instance (`DontDestroyOnLoad`) and destroys duplicates. `Update` pumps
|
||||
`_client.Update(Time.deltaTime)` (scenarios and realtime need it — do not
|
||||
call `client.Update` yourself). On quit/destroy it disconnects realtime.
|
||||
`_client.Update(Time.deltaTime)` (effects heartbeat and wait-deadline checks
|
||||
need it — do not call `client.Update` yourself).
|
||||
|
||||
Static API:
|
||||
|
||||
@@ -56,18 +55,21 @@ public class Bootstrap : MonoBehaviour
|
||||
private async void Start()
|
||||
{
|
||||
var client = Rudder.Initialize();
|
||||
await client.AuthorizeWithDeviceAsync("global", "en", nickname: "Player");
|
||||
await client.Auth.LoginWithDeviceAsync("global", "en", nickname: "Player");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`AuthorizeWithDeviceAsync` logs in with the SDK device id (a GUID persisted
|
||||
in PlayerPrefs under `rudder_device_id`) and restores persisted scenario
|
||||
runs. It is the first awaited call; afterwards use `client.*` /
|
||||
`Rudder.Client.*`. Tokens persist in PlayerPrefs, so a second launch can call
|
||||
it again (it refreshes the session). See
|
||||
`LoginWithDeviceAsync` logs in with the SDK device id (a GUID persisted
|
||||
in PlayerPrefs under `rudder_device_id`). It is the first awaited call;
|
||||
afterwards use `client.*` / `Rudder.Client.*`. Tokens persist in PlayerPrefs,
|
||||
so a second launch can call it again (it refreshes the session). See
|
||||
[authentication.md](authentication.md) for the Auth service.
|
||||
|
||||
Pending scenario effects are fetched after sign-in by the effects heartbeat
|
||||
(`client.Update`, pumped by `Rudder`). Subscribe to `client.Effects.On*`
|
||||
before `Scenario.TriggerAsync`.
|
||||
|
||||
## Errors
|
||||
|
||||
All API errors are `RudderApiException` subclasses from `RudderSdk.Core`:
|
||||
@@ -85,5 +87,5 @@ token refresh — game code does not implement that retry. Catch and surface
|
||||
|
||||
Removed APIs (do not use): `Rudder.Initialize(configuration)`,
|
||||
`Rudder.Auth`-style static service shortcuts, `client.Scenarios`,
|
||||
`BuyAsync`, `LoginViaDeviceAsync`, `Scenario.RestoreAsync` (folded into
|
||||
`AuthorizeWithDeviceAsync`).
|
||||
`BuyAsync`, `LoginViaDeviceAsync`, `AuthorizeWithDeviceAsync`,
|
||||
`Scenario.RestoreAsync`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Quests (global)
|
||||
|
||||
`client.Quests` (`QuestsService`) — the player's global quest list, distinct
|
||||
from scenario `OnQuest` nodes (`QuestSession`, see
|
||||
from scenario `OnQuest` nodes (`QuestEffect`, see
|
||||
[scenarios.md](scenarios.md)). Verified against the bundled
|
||||
`Rudder.Core.dll`. There is no sample scene for this feature.
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Realtime
|
||||
|
||||
`client.Realtime` (`RealtimeService`), verified against the bundled
|
||||
`Rudder.Core.dll` and
|
||||
`Packages/rudder.sdk/Runtime/Sources/Realtime/`.
|
||||
|
||||
**The realtime relay is not deployed yet.** `ConnectAsync` is available, but
|
||||
do not build product features on it unless the user explicitly asks.
|
||||
|
||||
```csharp
|
||||
var session = await client.Realtime.ConnectAsync();
|
||||
session.MessageReceived += data => { /* ArraySegment<byte> payload */ };
|
||||
await session.SendAsync(payload);
|
||||
await client.Realtime.DisconnectAsync();
|
||||
```
|
||||
|
||||
API:
|
||||
|
||||
- `Task<RealtimeSession> ConnectAsync(TimeSpan? timeout = null, CancellationToken = default)` — connects to `RudderConfiguration.RealtimeUrl` (default `wss://api.rudder.build/api/realtime/ws`).
|
||||
- `Task<RealtimeSession> ConnectAsync(Uri uri, TimeSpan? timeout = null, CancellationToken = default)` — explicit URI.
|
||||
- `Task DisconnectAsync(CancellationToken = default)` — also called by the
|
||||
`Rudder` component on quit/destroy.
|
||||
- `RealtimeSession Session { get; }`, `bool IsConnected { get; }`.
|
||||
- `void Update(float deltaTime)` — pumped by the `Rudder` component.
|
||||
|
||||
`RealtimeSession`:
|
||||
|
||||
- `Task SendAsync(byte[] payload, CancellationToken = default)`
|
||||
- `Task DisconnectAsync(CancellationToken = default)`
|
||||
- `bool IsConnected { get; }`
|
||||
- `event Action Closed`, `event Action<Exception> Error`,
|
||||
`event Action<ArraySegment<byte>> MessageReceived`.
|
||||
|
||||
Transport selection is automatic (`UnityRealtimeTransportFactory`):
|
||||
WebGL builds use a JavaScript websocket via `RudderWebSocket.jslib`
|
||||
(`JsWebSocketAdapter`); all other platforms use `NativeWebSocketAdapter`.
|
||||
@@ -21,6 +21,5 @@ API:
|
||||
`Description`, `Environment`, `ProjectId`, `Active` (`bool?`), `CreatedAt`,
|
||||
`UpdatedAt`.
|
||||
|
||||
Scenario `OnConfigChanged` sessions mean the cache was already patched —
|
||||
re-read with `Get`, do not reload. A missing key returns the fallback; the
|
||||
key must exist in the dashboard to have a server value.
|
||||
A missing key returns the fallback; the key must exist in the dashboard to
|
||||
have a server value.
|
||||
|
||||
@@ -1,52 +1,47 @@
|
||||
# Scenarios
|
||||
|
||||
`client.Scenario` (`ScenarioService`) — the LiveOps plan runtime. Verified
|
||||
against the bundled `Rudder.Core.dll` and
|
||||
Scenario graphs execute on the server. `client.Scenario` (`ScenarioService`)
|
||||
only triggers an event; pending nodes surface through `client.Effects`
|
||||
(`EffectsService`). Verified against the bundled `Rudder.Core.dll` and
|
||||
`Samples~/Scenarios/ScenariosSample.cs`.
|
||||
|
||||
## Flow
|
||||
|
||||
Subscribe to `On*` events **before** triggering, then trigger an event name
|
||||
Subscribe to `Effects.On*` **before** triggering, then trigger an event name
|
||||
configured in the dashboard (examples: `player_login`,
|
||||
`demo_round_finished`). Unsubscribe on destroy.
|
||||
|
||||
```csharp
|
||||
client.Scenario.OnNotification += session => { /* show UI, then session.Complete() */ };
|
||||
var runs = await client.Scenario.TriggerAsync("player_login");
|
||||
client.Effects.OnNotification += effect => { /* show UI, then effect.Done() */ };
|
||||
await client.Scenario.TriggerAsync("player_login");
|
||||
```
|
||||
|
||||
API:
|
||||
|
||||
- `Task<IReadOnlyList<PlanRun>> TriggerAsync(string eventName, CancellationToken = default)` — returns the runs it started.
|
||||
- `Task RestoreAsync(CancellationToken = default)` — restore persisted runs.
|
||||
Already called by `client.AuthorizeWithDeviceAsync`; do not call both.
|
||||
- `void Update(float deltaTime)` — pumped by the `Rudder` component.
|
||||
- `IReadOnlyList<PlanRun> ActiveRuns { get; }` — `PlanRun` exposes `RunId`,
|
||||
`PlanId`, `ScenarioId`, `UserId`, `ActiveNodeIds`, `Plan`.
|
||||
- `Task ScenarioService.TriggerAsync(string eventName, CancellationToken = default)` — posts the event and ingests any effects the response contains. Does not return runs.
|
||||
- `void RudderClient.Update(float deltaTime)` — pumped by the `Rudder` component. Drives the 30s pending-effects heartbeat and wait-deadline checks. Do not call it from game code.
|
||||
|
||||
Run state persists in PlayerPrefs (`UnityPlanStateStore`), so sessions
|
||||
survive restarts.
|
||||
After `Auth.LoginWithDeviceAsync`, the heartbeat fetches pending effects for
|
||||
the signed-in player. Effects that arrive later (or after a wait deadline)
|
||||
are dispatched the same way.
|
||||
|
||||
## Events and sessions
|
||||
## Events and effects
|
||||
|
||||
All session types expose `Id`, `ScenarioNodeContext Context`, and
|
||||
`T Get<T>(string key, T defaultValue = default)` for node data.
|
||||
`ScenarioNodeContext` exposes `Run`, `Node`, `RunId`, `PlanId`, `ScenarioId`,
|
||||
`NodeId`, `Type`, `Data` (`JObject`), `AsObjectDictionary()`.
|
||||
All effect types expose `RunId`, `ScenarioId`, `NodeId`, `Data` (`JObject`),
|
||||
and `T Get<T>(string key, T defaultValue = default)` for node data.
|
||||
|
||||
| Event | Session | Game must |
|
||||
| Event | Effect | Game must |
|
||||
| --- | --- | --- |
|
||||
| `OnNotification` | `NotificationSession` | Show UI, then `Complete()` / `CompleteAsync()`. |
|
||||
| `OnStoreOffer` | `StoreOfferSession` | Show offer (`session.Data`, `session.Get("storeSlug", ...)`); on buy call `Stores.PurchaseAsync` then `Purchase()` / `PurchaseAsync()`, else `Decline()` / `DeclineAsync()`. `IsResolved` marks resolution. |
|
||||
| `OnConfigChanged` | `ConfigChangedSession` | Re-read `RemoteConfig.Get` — patches are already applied to the cache. |
|
||||
| `OnWait` | `WaitSession` | Deadline only (`DeadlineUtc`); the runtime completes it. |
|
||||
| `OnQuest` | `QuestSession` | Scenario quest node: `AddProgress(counterKey, amount)` / `AddProgressAsync`, `Complete()` / `CompleteAsync()`, `Fail()` / `FailAsync()`. Not `client.Quests`. |
|
||||
| `OnLeaderboard` | `LeaderboardSession` | `End()` / `EndAsync()`, `RewardClaimed()` / `RewardClaimedAsync()`. |
|
||||
| `OnBattlePass` | `BattlePassSession` | Drive the session — see [battlepass.md](battlepass.md). |
|
||||
| `OnBattlePassLevel` | `BattlePassLevelSession` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. |
|
||||
| `OnScenarioCompleted` | `PlanRun` | Log / surface. |
|
||||
| `OnScenarioFailed` | `ScenarioFailedEvent` | `Run`, `NodeId`, `Exception`. Log / surface. |
|
||||
| `OnNotification` | `NotificationEffect` | Show UI (`Title`, `Message`), then `Done()` / `DoneAsync()`. |
|
||||
| `OnStoreOffer` | `StoreOfferEffect` | Show offer (`StoreSlug`, `Get("storeSlug", ...)`); on buy call `Stores.PurchaseAsync` then `Purchase()` / `PurchaseAsync()`, else `Decline()` / `DeclineAsync()`. `IsResolved` marks resolution. |
|
||||
| `OnWait` | `WaitEffect` | Deadline only (`DeadlineUtc`); the server advances the run. |
|
||||
| `OnQuest` | `QuestEffect` | Scenario quest node: `ReportProgress(objectiveId, amount)` / `ReportProgressAsync`. The server auto-completes the node once every objective is satisfied. Not `client.Quests`. |
|
||||
| `OnLeaderboard` | `LeaderboardEffect` | `End()` / `EndAsync()`, `Claim()` / `ClaimAsync()`. `IsResolved` marks resolution. |
|
||||
| `OnBattlePass` | `BattlePassEffect` | Drive the effect — see [battlepass.md](battlepass.md). |
|
||||
| `OnBattlePassLevel` | `BattlePassLevelEffect` | `Claim()` / `ClaimAsync()` for the level reward; `Level` prop. |
|
||||
| `OnScenarioCompleted` | `ScenarioCompletedEffect` | `RunId`, `ScenarioId`. Log / surface. |
|
||||
| `OnScenarioFailed` | `ScenarioFailedEffect` | `RunId`, `ScenarioId`, `NodeId`, `Exception`. Log / surface. |
|
||||
|
||||
Async and sync variants are equivalent; the sync variants fire-and-forget
|
||||
the same server callback. The event name must match a scenario trigger
|
||||
configured in the dashboard — an unknown event simply starts zero runs.
|
||||
configured in the dashboard — an unknown event simply produces no effects.
|
||||
|
||||
Reference in New Issue
Block a user