@indiegems/gem-web-sdk 7.3.1 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/game.d.ts +156 -22
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +2 -2
- package/dist/gem-web-sdk.game.js +2 -2
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +89 -6
- package/dist/sdk.d.ts +156 -22
- package/docs/achievements.md +232 -0
- package/docs/analytics.md +271 -0
- package/docs/api/api-reference-game.md +15308 -0
- package/docs/api/api-reference-server.md +4397 -0
- package/docs/api/reference.json +24130 -0
- package/docs/arcade-economy.md +263 -0
- package/docs/async-opponents.md +396 -0
- package/docs/brand-art.md +334 -0
- package/docs/cross-promotion.md +137 -0
- package/docs/daily-challenges.md +530 -0
- package/docs/dedicated-server-flow.md +879 -0
- package/docs/deprecation-policy.md +124 -0
- package/docs/friends.md +233 -0
- package/docs/game-economy-tutorial.md +326 -0
- package/docs/getting-started.md +552 -0
- package/docs/inventory-and-catalogs.md +1035 -0
- package/docs/leaderboards.md +277 -0
- package/docs/manifest.json +135 -0
- package/docs/match-session.md +288 -0
- package/docs/p2p-and-parties.md +783 -0
- package/docs/platform-rules.md +372 -0
- package/docs/player-sign-in.md +89 -0
- package/docs/room-shapes.md +195 -0
- package/docs/rooms-and-matches.md +779 -0
- package/docs/saving-player-progress.md +457 -0
- package/docs/server-bundles.md +1041 -0
- package/docs/serverless-functions.md +748 -0
- package/docs/trading-and-gifting.md +98 -0
- package/docs/what-a-game-must-do.md +1231 -0
- package/package.json +7 -2
- package/schema/bridge.v1.json +440 -7
- package/schema/capabilities.public.json +189 -0
package/dist/sdk.d.ts
CHANGED
|
@@ -205,6 +205,7 @@ type GameConfigSnapshot = {
|
|
|
205
205
|
"async_opponent_types"?: SnapshotAsyncOpponentType[];
|
|
206
206
|
"catalogs": SnapshotCatalog[];
|
|
207
207
|
"currencies": SnapshotCurrency[];
|
|
208
|
+
"friend_challenge_templates"?: SnapshotFriendChallengeTemplate[];
|
|
208
209
|
"game_room_templates": SnapshotGameRoomTemplate[];
|
|
209
210
|
"hash": string;
|
|
210
211
|
"items": SnapshotItem[];
|
|
@@ -212,6 +213,7 @@ type GameConfigSnapshot = {
|
|
|
212
213
|
"leaderboards"?: SnapshotLeaderboard[];
|
|
213
214
|
"loot_tables": SnapshotLootTable[];
|
|
214
215
|
"progressions": SnapshotProgression[];
|
|
216
|
+
"rewarded_placements"?: SnapshotRewardedPlacement[];
|
|
215
217
|
"run_types"?: SnapshotRunType[];
|
|
216
218
|
"version": number;
|
|
217
219
|
};
|
|
@@ -419,6 +421,21 @@ type SnapshotCurrency = {
|
|
|
419
421
|
"min_balance": number;
|
|
420
422
|
"slug"?: string | null;
|
|
421
423
|
};
|
|
424
|
+
type SnapshotFriendChallengeParam = {
|
|
425
|
+
"key": string;
|
|
426
|
+
"localized"?: Record<string, unknown> | null;
|
|
427
|
+
"max"?: unknown;
|
|
428
|
+
"min"?: unknown;
|
|
429
|
+
"pattern"?: string | null;
|
|
430
|
+
"type": string;
|
|
431
|
+
"values"?: Record<string, unknown> | null;
|
|
432
|
+
};
|
|
433
|
+
type SnapshotFriendChallengeTemplate = {
|
|
434
|
+
"key": string;
|
|
435
|
+
"localized"?: Record<string, unknown>;
|
|
436
|
+
"params"?: SnapshotFriendChallengeParam[];
|
|
437
|
+
"text": string;
|
|
438
|
+
};
|
|
422
439
|
type SnapshotGameRoomTemplate = {
|
|
423
440
|
"allow_host_transfer": boolean;
|
|
424
441
|
"allow_join_in_progress"?: boolean;
|
|
@@ -526,6 +543,25 @@ type SnapshotRecipe = {
|
|
|
526
543
|
"slug"?: string | null;
|
|
527
544
|
"sort_order": number;
|
|
528
545
|
};
|
|
546
|
+
type SnapshotRewardGrant = {
|
|
547
|
+
"item_definition_id": string;
|
|
548
|
+
"quantity": number;
|
|
549
|
+
"type": "item_grant";
|
|
550
|
+
} | {
|
|
551
|
+
"amount": number;
|
|
552
|
+
"currency_id": string;
|
|
553
|
+
"type": "currency_grant";
|
|
554
|
+
};
|
|
555
|
+
type SnapshotRewardedPlacement = {
|
|
556
|
+
"allowance_currency_id"?: string | null;
|
|
557
|
+
"cooldown_seconds": number;
|
|
558
|
+
"grants": SnapshotRewardGrant[];
|
|
559
|
+
"max_held"?: number | null;
|
|
560
|
+
"max_per_day": number;
|
|
561
|
+
"placement": string;
|
|
562
|
+
"reward_class": string;
|
|
563
|
+
"without_ads": string;
|
|
564
|
+
};
|
|
529
565
|
type SnapshotRunType = {
|
|
530
566
|
"bucket_1_allowed_values"?: string[] | null;
|
|
531
567
|
"bucket_1_name"?: string | null;
|
|
@@ -1211,10 +1247,35 @@ interface ListFriendsOptions {
|
|
|
1211
1247
|
readonly cursor?: string;
|
|
1212
1248
|
readonly signal?: AbortSignal;
|
|
1213
1249
|
}
|
|
1250
|
+
interface FriendChallengeRequest {
|
|
1251
|
+
readonly playerId: string;
|
|
1252
|
+
readonly template: string;
|
|
1253
|
+
readonly params?: Readonly<Record<string, string | number>>;
|
|
1254
|
+
}
|
|
1255
|
+
type FriendChallengeOutcomeKind = "accepted" | "cancelled" | "refused" | "unavailable";
|
|
1256
|
+
interface FriendChallengeOutcome {
|
|
1257
|
+
readonly outcome: FriendChallengeOutcomeKind;
|
|
1258
|
+
}
|
|
1259
|
+
type FriendChallengeErrorCode = "unknown_template" | "template_not_on_channel" | "param_invalid" | "param_undeclared" | "recipient_invalid" | (string & {});
|
|
1260
|
+
declare class FriendChallengeError extends GemApiError {
|
|
1261
|
+
readonly code?: FriendChallengeErrorCode;
|
|
1262
|
+
readonly param?: string;
|
|
1263
|
+
readonly channel?: string;
|
|
1264
|
+
readonly configVersion?: number;
|
|
1265
|
+
constructor(source: GemApiError, extra?: {
|
|
1266
|
+
readonly param?: string;
|
|
1267
|
+
readonly channel?: string;
|
|
1268
|
+
readonly configVersion?: number;
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
interface FriendsApiDependencies {
|
|
1272
|
+
readonly challenge?: (request: FriendChallengeRequest) => Promise<FriendChallengeOutcome>;
|
|
1273
|
+
}
|
|
1214
1274
|
declare class FriendsApi {
|
|
1215
1275
|
#private;
|
|
1216
|
-
constructor(api: GameApi$1);
|
|
1276
|
+
constructor(api: GameApi$1, dependencies?: FriendsApiDependencies);
|
|
1217
1277
|
list(options?: ListFriendsOptions): Promise<FriendsPage>;
|
|
1278
|
+
challenge(request: FriendChallengeRequest): Promise<FriendChallengeOutcome>;
|
|
1218
1279
|
}
|
|
1219
1280
|
type PlatformCurrencyAccess = "read" | "spend";
|
|
1220
1281
|
interface PlatformCurrency {
|
|
@@ -1231,9 +1292,35 @@ declare class PlatformCurrenciesApi {
|
|
|
1231
1292
|
constructor(api: GameApi$1);
|
|
1232
1293
|
get(options?: PlatformCurrencyReadOptions): Promise<readonly PlatformCurrency[]>;
|
|
1233
1294
|
}
|
|
1295
|
+
type EarnAllowancePeriod = "day" | "window";
|
|
1296
|
+
interface EarnAllowance {
|
|
1297
|
+
readonly currencySlug: string;
|
|
1298
|
+
readonly currencyId: string;
|
|
1299
|
+
readonly limit: number;
|
|
1300
|
+
readonly earned: number;
|
|
1301
|
+
readonly remaining: number;
|
|
1302
|
+
readonly period: EarnAllowancePeriod;
|
|
1303
|
+
readonly periodDays: number;
|
|
1304
|
+
readonly resetsAt?: number;
|
|
1305
|
+
}
|
|
1306
|
+
interface EarnAllowanceReadOptions {
|
|
1307
|
+
readonly signal?: AbortSignal;
|
|
1308
|
+
}
|
|
1309
|
+
interface EarnAllowanceSignal {
|
|
1310
|
+
on(listener: () => void): () => void;
|
|
1311
|
+
emit(): void;
|
|
1312
|
+
}
|
|
1313
|
+
type EarnAllowanceEvent = "changed";
|
|
1314
|
+
declare class PlatformEarnAllowanceApi {
|
|
1315
|
+
#private;
|
|
1316
|
+
constructor(api: GameApi$1, signal?: EarnAllowanceSignal, log?: ((line: string) => void) | null);
|
|
1317
|
+
get(options?: EarnAllowanceReadOptions): Promise<readonly EarnAllowance[]>;
|
|
1318
|
+
on(event: EarnAllowanceEvent, listener: () => void): () => void;
|
|
1319
|
+
}
|
|
1234
1320
|
declare class PlatformApi {
|
|
1235
1321
|
readonly currencies: PlatformCurrenciesApi;
|
|
1236
|
-
|
|
1322
|
+
readonly earnAllowance: PlatformEarnAllowanceApi;
|
|
1323
|
+
constructor(api: GameApi$1, earnAllowanceSignal?: EarnAllowanceSignal, log?: ((line: string) => void) | null);
|
|
1237
1324
|
}
|
|
1238
1325
|
interface ProfileShowcaseEntry$1 {
|
|
1239
1326
|
readonly key: string;
|
|
@@ -1856,6 +1943,12 @@ interface GemViewport$1 {
|
|
|
1856
1943
|
bottom: number;
|
|
1857
1944
|
left: number;
|
|
1858
1945
|
};
|
|
1946
|
+
readonly reserved?: {
|
|
1947
|
+
x: number;
|
|
1948
|
+
y: number;
|
|
1949
|
+
width: number;
|
|
1950
|
+
height: number;
|
|
1951
|
+
}[];
|
|
1859
1952
|
}
|
|
1860
1953
|
interface GemDeviceSettings {
|
|
1861
1954
|
readonly gem: typeof PROTOCOL;
|
|
@@ -1977,6 +2070,18 @@ interface GemSignInResult {
|
|
|
1977
2070
|
readonly requestId: string;
|
|
1978
2071
|
readonly outcome: "signed_in" | "declined" | "unavailable";
|
|
1979
2072
|
}
|
|
2073
|
+
interface GemFriendChallengeResult {
|
|
2074
|
+
readonly gem: typeof PROTOCOL;
|
|
2075
|
+
readonly type: "gem.friendChallengeResult";
|
|
2076
|
+
readonly requestId: string;
|
|
2077
|
+
readonly outcome: "accepted" | "cancelled" | "refused" | "unavailable" | "rate_limited" | "invalid";
|
|
2078
|
+
readonly code?: string;
|
|
2079
|
+
readonly param?: string;
|
|
2080
|
+
readonly channel?: string;
|
|
2081
|
+
readonly configVersion?: number;
|
|
2082
|
+
readonly retryAfterMs?: number;
|
|
2083
|
+
readonly detail?: string;
|
|
2084
|
+
}
|
|
1980
2085
|
interface ConnectOptions {
|
|
1981
2086
|
readonly clusterTargetId?: ClusterTargetLike;
|
|
1982
2087
|
readonly retry?: StartMatchRetryOptions;
|
|
@@ -3967,25 +4072,6 @@ interface SignInOutcome {
|
|
|
3967
4072
|
interface AccountApi {
|
|
3968
4073
|
requestSignIn(): Promise<SignInOutcome>;
|
|
3969
4074
|
}
|
|
3970
|
-
type AdFormat = "interstitial" | "rewarded";
|
|
3971
|
-
interface AdRequest {
|
|
3972
|
-
placement: string;
|
|
3973
|
-
format: AdFormat;
|
|
3974
|
-
}
|
|
3975
|
-
type AdOutcomeKind = "shown" | "no_ad" | "unavailable";
|
|
3976
|
-
interface AdOutcome {
|
|
3977
|
-
readonly outcome: AdOutcomeKind;
|
|
3978
|
-
}
|
|
3979
|
-
interface AdsApi {
|
|
3980
|
-
requestAd(req: AdRequest): Promise<AdOutcome>;
|
|
3981
|
-
}
|
|
3982
|
-
type DeviceSettings = Readonly<Record<string, unknown>>;
|
|
3983
|
-
type DeviceSettingsPutOutcome = "stored" | "too_large" | "unavailable";
|
|
3984
|
-
interface DeviceSettingsApi {
|
|
3985
|
-
readonly current: DeviceSettings | null;
|
|
3986
|
-
wait(timeoutMs?: number): Promise<DeviceSettings | null>;
|
|
3987
|
-
put(settings: DeviceSettings): Promise<DeviceSettingsPutOutcome>;
|
|
3988
|
-
}
|
|
3989
4075
|
interface PlayerBoostMultiplier {
|
|
3990
4076
|
readonly currencyId: string;
|
|
3991
4077
|
readonly currencySlug: string;
|
|
@@ -4016,6 +4102,7 @@ interface FrameCallbacks {
|
|
|
4016
4102
|
onPause?(msg: GemPause): void;
|
|
4017
4103
|
onPrefs?(msg: GemPrefs$1): void;
|
|
4018
4104
|
onCohorts?(msg: GemCohorts$1): void;
|
|
4105
|
+
onEarnAllowanceChanged?(): void;
|
|
4019
4106
|
onViewport?(msg: GemViewport$1): void;
|
|
4020
4107
|
onWillTerminate?(msg: GemWillTerminate): void;
|
|
4021
4108
|
onDevtools?(action: "open" | "close" | "toggle"): void;
|
|
@@ -4041,6 +4128,7 @@ interface FrameCallbacks {
|
|
|
4041
4128
|
onDeviceSettingsResult?(msg: GemDeviceSettingsResult): void;
|
|
4042
4129
|
onAdResult?(msg: GemAdResult): void;
|
|
4043
4130
|
onSignInResult?(msg: GemSignInResult): void;
|
|
4131
|
+
onFriendChallengeResult?(msg: GemFriendChallengeResult): void;
|
|
4044
4132
|
onError?(code: GemErrorCode, message?: string): void;
|
|
4045
4133
|
onDropped?(code: GemErrorCode, message?: string): void;
|
|
4046
4134
|
onStateChange?(state: FrameState): void;
|
|
@@ -4082,6 +4170,13 @@ declare class GemFrame {
|
|
|
4082
4170
|
requestFullscreen(enabled: boolean): void;
|
|
4083
4171
|
reportMuted(muted: boolean): boolean;
|
|
4084
4172
|
cueReward(cue: RewardCue): boolean;
|
|
4173
|
+
setWalletVisible(visible: boolean, options?: {
|
|
4174
|
+
only?: readonly string[];
|
|
4175
|
+
}): boolean;
|
|
4176
|
+
requestShare(params: ReadonlyArray<{
|
|
4177
|
+
readonly key: string;
|
|
4178
|
+
readonly value: string;
|
|
4179
|
+
}>): boolean;
|
|
4085
4180
|
reportFrameStats(stats: {
|
|
4086
4181
|
fps: number;
|
|
4087
4182
|
frameTimeP95Ms: number;
|
|
@@ -4094,6 +4189,7 @@ declare class GemFrame {
|
|
|
4094
4189
|
putDeviceSettings(requestId: string, settings: Readonly<Record<string, unknown>>): boolean;
|
|
4095
4190
|
requestAd(requestId: string, placement: string, format: "interstitial" | "rewarded"): boolean;
|
|
4096
4191
|
requestSignIn(requestId: string): boolean;
|
|
4192
|
+
requestFriendChallenge(requestId: string, playerId: string, template: string, params?: Readonly<Record<string, string | number>>): boolean;
|
|
4097
4193
|
close(): void;
|
|
4098
4194
|
private transition;
|
|
4099
4195
|
private refuseMalformed;
|
|
@@ -4105,6 +4201,32 @@ declare class GemFrame {
|
|
|
4105
4201
|
private dispatchInbound;
|
|
4106
4202
|
private screenshotDenied;
|
|
4107
4203
|
}
|
|
4204
|
+
interface ShareRequest {
|
|
4205
|
+
readonly params?: Readonly<Record<string, string>>;
|
|
4206
|
+
}
|
|
4207
|
+
interface ShareApi {
|
|
4208
|
+
readonly available: boolean;
|
|
4209
|
+
request(request?: ShareRequest): void;
|
|
4210
|
+
}
|
|
4211
|
+
type AdFormat = "interstitial" | "rewarded";
|
|
4212
|
+
interface AdRequest {
|
|
4213
|
+
placement: string;
|
|
4214
|
+
format: AdFormat;
|
|
4215
|
+
}
|
|
4216
|
+
type AdOutcomeKind = "shown" | "no_ad" | "unavailable";
|
|
4217
|
+
interface AdOutcome {
|
|
4218
|
+
readonly outcome: AdOutcomeKind;
|
|
4219
|
+
}
|
|
4220
|
+
interface AdsApi {
|
|
4221
|
+
requestAd(req: AdRequest): Promise<AdOutcome>;
|
|
4222
|
+
}
|
|
4223
|
+
type DeviceSettings = Readonly<Record<string, unknown>>;
|
|
4224
|
+
type DeviceSettingsPutOutcome = "stored" | "too_large" | "unavailable";
|
|
4225
|
+
interface DeviceSettingsApi {
|
|
4226
|
+
readonly current: DeviceSettings | null;
|
|
4227
|
+
wait(timeoutMs?: number): Promise<DeviceSettings | null>;
|
|
4228
|
+
put(settings: DeviceSettings): Promise<DeviceSettingsPutOutcome>;
|
|
4229
|
+
}
|
|
4108
4230
|
declare const DEVICE_SETTINGS_MAX_BYTES = 16384;
|
|
4109
4231
|
declare const ARCADE_EMBEDDERS: readonly string[];
|
|
4110
4232
|
interface GemOptions {
|
|
@@ -4165,6 +4287,16 @@ interface GemInsets {
|
|
|
4165
4287
|
}
|
|
4166
4288
|
interface GemViewport {
|
|
4167
4289
|
readonly insets: GemInsets;
|
|
4290
|
+
readonly reserved: readonly GemReservedArea[];
|
|
4291
|
+
}
|
|
4292
|
+
interface GemReservedArea {
|
|
4293
|
+
readonly x: number;
|
|
4294
|
+
readonly y: number;
|
|
4295
|
+
readonly width: number;
|
|
4296
|
+
readonly height: number;
|
|
4297
|
+
}
|
|
4298
|
+
interface WalletVisibleOptions {
|
|
4299
|
+
readonly only?: readonly string[];
|
|
4168
4300
|
}
|
|
4169
4301
|
interface GemTerminationNotice {
|
|
4170
4302
|
readonly reason: GemWillTerminate["reason"];
|
|
@@ -4189,6 +4321,7 @@ interface Gem {
|
|
|
4189
4321
|
readonly deviceSettings: DeviceSettingsApi;
|
|
4190
4322
|
readonly ads: AdsApi;
|
|
4191
4323
|
readonly account: AccountApi;
|
|
4324
|
+
readonly share: ShareApi;
|
|
4192
4325
|
readonly analytics: AnalyticsApi;
|
|
4193
4326
|
readonly leaderboards: LeaderboardsApi;
|
|
4194
4327
|
readonly challenges: ChallengesApi;
|
|
@@ -4204,6 +4337,7 @@ interface Gem {
|
|
|
4204
4337
|
awaitCohorts(timeoutMs?: number): Promise<GemCohorts | null>;
|
|
4205
4338
|
reportMuted(muted: boolean): void;
|
|
4206
4339
|
cueReward(cue: RewardCue): boolean;
|
|
4340
|
+
setWalletVisible(visible: boolean, options?: WalletVisibleOptions): boolean;
|
|
4207
4341
|
exit(reason?: string): Promise<void>;
|
|
4208
4342
|
readonly boot: BootTimeline;
|
|
4209
4343
|
readonly frame: GemFrame;
|
|
@@ -4229,4 +4363,4 @@ interface ExitAnalytics {
|
|
|
4229
4363
|
}
|
|
4230
4364
|
declare function leaveAndExit(rooms: ExitRooms, bridge: ExitBridge | null, reason?: string, analytics?: ExitAnalytics): Promise<void>;
|
|
4231
4365
|
declare function createGem(options?: GemOptions): Gem;
|
|
4232
|
-
export { ARCADE_EMBEDDERS, type AccountApi, type AdFormat, type AdOutcome, type AdOutcomeKind, type AdRequest, type AdsApi, AnalyticsApi, type AnalyticsFlushOptions, type AnalyticsFlushResult, type AnalyticsOptions, type AnalyticsRejection$1 as AnalyticsRejection, type AnalyticsValue, AuthError, type AuthFailure, type AuthState, type Base64String, type BootMark, type BootTimeline, type BroadcastOptions, type BytesResponse, CONDITIONAL_HEADERS, type Challenge, type ChallengeWindow, ChallengesApi, type ClientOptions, type Clock, type ConditionalRequestHeaders, type ConfiguredPurchasePayment, type ConfiguredPurchaseRequest, type ConfiguredPurchaseResult, DEVICE_SETTINGS_MAX_BYTES, DedicatedServerError, type DedicatedServerFailure, type DedicatedServerSession, type DevToolsOpener, type DeviceSettings, type DeviceSettingsApi, type DeviceSettingsPutOutcome, type DirectPurchaseRequest, type DirectPurchaseResult, type EntityLeaderboard, type EntityLeaderboardEntry, type EntityLeaderboardOptions, type EntityType$1 as EntityType, type ExitAnalytics, type ExitBridge, type ExitRooms, type FrameState, type Friend, FriendsApi, type FriendsPage, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameApi, type GameConfig, type GameServerLink, type Gem, GemApiError, GemClient, type GemCohort, type GemCohorts, GemConfigError, type GemDevTools, type GemErrorReason, GemFrame, GemFunctionError, type GemIdentity, type GemInsets, type GemMatchApi, type GemMatchServer, type GemMatchServerModule, type GemOptions, type GemPlayer, type GemPrefs, GemSaveError, type GemServer, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerIdentity, type GemServerLifecycle, type GemServerOptions, type GemServerProfile, type GemTerminationNotice, type GemViewport, type GetLeaderboardOptions, type HostTokenSource, type HostTokenSourceOptions, type JoinWelcome, type Leaderboard, type LeaderboardEntry, LeaderboardsApi, type ListChallengesOptions, type ListFriendsOptions, MAX_PERIODS_BACK, type MatchConnectOptions, type MatchConnection, type MatchHosting, type MatchHostingMode, type MatchServeOptions, type MatchTransport, type MessageMeta, type MultiTransportSession, PLATFORM_EVENTS, PlatformApi, PlatformCurrenciesApi, type PlatformCurrency, type PlatformCurrencyAccess, type PlatformCurrencyPurchasePayment, type PlatformCurrencyPurchaseRequest, type PlatformCurrencyPurchaseResult, type PlatformCurrencyReadOptions, type PlatformEventName, type PlayerBadge$1 as PlayerBadge, type PlayerBoost, type PlayerBoostMultiplier, type PlayerIdentity, type PlayerProfile, PlayerText, type ProfileCallOptions, type ProfileShowcaseEntry$1 as ProfileShowcaseEntry, ProfilesApi, type PurchaseApi, type PurchaseOptions, type PurchaseOutcome, PurchaseRefusal, type PurchaseRefusalCode, type PurchaseRequest, type PutBytesOptions, type PutSaveOptions, type RankedRow, type RecipeAvailability, type RecipeLock, type RequestOptions, type RewardCue, type RoomArgValue, type SavePage, type SaveRecord, type SendOptions$1 as SendOptions, type ServerApi, type ServerBundleHost, type ServerTokenGrant, type SessionEndReason, ShowcaseApi, type ShowcaseCallOptions, type ShowcaseFriendEntry, type ShowcaseFriends, type ShowcaseValue, type SignInOutcome, type SignInOutcomeKind, type SnapshotCurrency, type SnapshotItem, type TokenGrant, type Transport, type TransportAdvert, type TransportKind, TransportRefusal, type TransportRequest, type TransportResponse, UNRELIABLE_CHANNEL_LABEL, type VersionedGetOptions, type VersionedResponse, asPlayerText, createBootTimeline, createGem, createGemServer, createHostTokenSource, createMatchServer, defineServer, leaveAndExit, mapIdentity, markOnResolve, platformIconUrl, presentJoinCredential, readTemplateTransports, readTransportAdvert, reportOnResolve, systemClock, text, toBase64, webTransportAvailable };
|
|
4366
|
+
export { ARCADE_EMBEDDERS, type AccountApi, type AdFormat, type AdOutcome, type AdOutcomeKind, type AdRequest, type AdsApi, AnalyticsApi, type AnalyticsFlushOptions, type AnalyticsFlushResult, type AnalyticsOptions, type AnalyticsRejection$1 as AnalyticsRejection, type AnalyticsValue, AuthError, type AuthFailure, type AuthState, type Base64String, type BootMark, type BootTimeline, type BroadcastOptions, type BytesResponse, CONDITIONAL_HEADERS, type Challenge, type ChallengeWindow, ChallengesApi, type ClientOptions, type Clock, type ConditionalRequestHeaders, type ConfiguredPurchasePayment, type ConfiguredPurchaseRequest, type ConfiguredPurchaseResult, DEVICE_SETTINGS_MAX_BYTES, DedicatedServerError, type DedicatedServerFailure, type DedicatedServerSession, type DevToolsOpener, type DeviceSettings, type DeviceSettingsApi, type DeviceSettingsPutOutcome, type DirectPurchaseRequest, type DirectPurchaseResult, type EarnAllowance, type EarnAllowanceEvent, type EarnAllowancePeriod, type EarnAllowanceReadOptions, type EntityLeaderboard, type EntityLeaderboardEntry, type EntityLeaderboardOptions, type EntityType$1 as EntityType, type ExitAnalytics, type ExitBridge, type ExitRooms, type FrameState, type Friend, FriendChallengeError, type FriendChallengeErrorCode, type FriendChallengeOutcome, type FriendChallengeOutcomeKind, type FriendChallengeRequest, FriendsApi, type FriendsPage, type FunctionFailure, type FunctionInvokeOptions, type FunctionResult, FunctionsApi, type GameApi, type GameConfig, type GameServerLink, type Gem, GemApiError, GemClient, type GemCohort, type GemCohorts, GemConfigError, type GemDevTools, type GemErrorReason, GemFrame, GemFunctionError, type GemIdentity, type GemInsets, type GemMatchApi, type GemMatchServer, type GemMatchServerModule, type GemOptions, type GemPlayer, type GemPrefs, type GemReservedArea, GemSaveError, type GemServer, type GemServerBundle, type GemServerFactory, type GemServerHost, type GemServerHostProfile, type GemServerIdentity, type GemServerLifecycle, type GemServerOptions, type GemServerProfile, type GemTerminationNotice, type GemViewport, type GetLeaderboardOptions, type HostTokenSource, type HostTokenSourceOptions, type JoinWelcome, type Leaderboard, type LeaderboardEntry, LeaderboardsApi, type ListChallengesOptions, type ListFriendsOptions, MAX_PERIODS_BACK, type MatchConnectOptions, type MatchConnection, type MatchHosting, type MatchHostingMode, type MatchServeOptions, type MatchTransport, type MessageMeta, type MultiTransportSession, PLATFORM_EVENTS, PlatformApi, PlatformCurrenciesApi, type PlatformCurrency, type PlatformCurrencyAccess, type PlatformCurrencyPurchasePayment, type PlatformCurrencyPurchaseRequest, type PlatformCurrencyPurchaseResult, type PlatformCurrencyReadOptions, PlatformEarnAllowanceApi, type PlatformEventName, type PlayerBadge$1 as PlayerBadge, type PlayerBoost, type PlayerBoostMultiplier, type PlayerIdentity, type PlayerProfile, PlayerText, type ProfileCallOptions, type ProfileShowcaseEntry$1 as ProfileShowcaseEntry, ProfilesApi, type PurchaseApi, type PurchaseOptions, type PurchaseOutcome, PurchaseRefusal, type PurchaseRefusalCode, type PurchaseRequest, type PutBytesOptions, type PutSaveOptions, type RankedRow, type RecipeAvailability, type RecipeLock, type RequestOptions, type RewardCue, type RoomArgValue, type SavePage, type SaveRecord, type SendOptions$1 as SendOptions, type ServerApi, type ServerBundleHost, type ServerTokenGrant, type SessionEndReason, type ShareApi, type ShareRequest, ShowcaseApi, type ShowcaseCallOptions, type ShowcaseFriendEntry, type ShowcaseFriends, type ShowcaseValue, type SignInOutcome, type SignInOutcomeKind, type SnapshotCurrency, type SnapshotItem, type TokenGrant, type Transport, type TransportAdvert, type TransportKind, TransportRefusal, type TransportRequest, type TransportResponse, UNRELIABLE_CHANNEL_LABEL, type VersionedGetOptions, type VersionedResponse, type WalletVisibleOptions, asPlayerText, createBootTimeline, createGem, createGemServer, createHostTokenSource, createMatchServer, defineServer, leaveAndExit, mapIdentity, markOnResolve, platformIconUrl, presentJoinCredential, readTemplateTransports, readTransportAdvert, reportOnResolve, systemClock, text, toBase64, webTransportAvailable };
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Achievements
|
|
2
|
+
|
|
3
|
+
An **achievement** is a platform-wide recognition a player earns in your game: a name, a
|
|
4
|
+
description and some **achievement points**. The arcade lists your achievements on your store page, shows each player what they have unlocked, and
|
|
5
|
+
adds their points to their **Achievement Score**, a total that spans every game they play (§5).
|
|
6
|
+
|
|
7
|
+
There is no achievements API to call. An achievement sits on a **progression threshold your game
|
|
8
|
+
already has**, and it unlocks when your server, or a serverless function the player called, moves
|
|
9
|
+
that progression past it. Everything here is
|
|
10
|
+
built from things [inventory-and-catalogs.md](inventory-and-catalogs.md) §13 already covers — read
|
|
11
|
+
that first.
|
|
12
|
+
|
|
13
|
+
## 1. Declaring one
|
|
14
|
+
|
|
15
|
+
An achievement is a kind of `platform_grants` entry, on either of the two thresholds a progression
|
|
16
|
+
has: its `completion_threshold`, or one level of a track. The full schema and its validation rules
|
|
17
|
+
are in the gemctl `game-config` skill; the shape is:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
progressions:
|
|
21
|
+
- slug: boards_cleared
|
|
22
|
+
display_name: Boards cleared
|
|
23
|
+
config:
|
|
24
|
+
tracks:
|
|
25
|
+
- track_id: main
|
|
26
|
+
levels:
|
|
27
|
+
- level: 1
|
|
28
|
+
threshold: 10
|
|
29
|
+
platform_grants:
|
|
30
|
+
- achievement:
|
|
31
|
+
id: 91ac… # its own id, stable forever
|
|
32
|
+
points: 20
|
|
33
|
+
display_name: Getting the Hang of It
|
|
34
|
+
description: Clear 10 boards.
|
|
35
|
+
- level: 2
|
|
36
|
+
threshold: 100
|
|
37
|
+
platform_grants:
|
|
38
|
+
- achievement:
|
|
39
|
+
id: c47d…
|
|
40
|
+
points: 150
|
|
41
|
+
visibility: hidden
|
|
42
|
+
hidden_hint: See it through.
|
|
43
|
+
display_name: Every Door Opened
|
|
44
|
+
description: Clear all 100 boards.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
One counter with a level per tier is the usual shape: "clear 10, 50, 100 boards" is three levels on
|
|
48
|
+
one progression, not three progressions.
|
|
49
|
+
|
|
50
|
+
What the upload enforces, so you are not surprised by it:
|
|
51
|
+
|
|
52
|
+
- **A budget.** Your game's achievements may be worth at most **1,000 points** in total, and your
|
|
53
|
+
game may have at most **200 achievements** over its life — both counting ones you have since
|
|
54
|
+
removed, so retiring an achievement never frees room for another. Points are identity, not
|
|
55
|
+
currency — a player cannot spend them, and a game cannot mint them freely.
|
|
56
|
+
- **Nothing in your config may put value into its progression.** An achievement that awards
|
|
57
|
+
points or rewards may not sit on a progression that a recipe's cost or grant, a loot-table
|
|
58
|
+
entry, another progression's `rewards_on_complete` or a track level's `rewards` can raise — a
|
|
59
|
+
`progression_add` of a positive amount, or a `progression_set`. Unlocking counts the stored value
|
|
60
|
+
(§2), so any such write decides what your server's next write unlocks: a recipe priced in Gems
|
|
61
|
+
that adds 100 to `boards_cleared` would sell "Clear all 100 boards" to anyone with the Gems. A
|
|
62
|
+
reset or a spend (a negative add) is fine — it cannot buy an unlock that happens once. The
|
|
63
|
+
refusal is anchored at the achievement and names the operation that writes it:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
progressions[0] (boards_cleared): platform_grants[0]: source is written by an operation in this
|
|
67
|
+
config — catalogs[0] (shop) recipes[0] (board_pack) grants[0]. An achievement that awards points
|
|
68
|
+
or rewards is earned only by the game server's own progression_add or progression_max, …
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Keep the counter your server advances separate from anything a player can buy, open or claim.
|
|
72
|
+
A display-only achievement (`points: 0`, no rewards) may sit on such a progression.
|
|
73
|
+
|
|
74
|
+
- **Released achievements are fixed.** Once a release carries an achievement, its `points`,
|
|
75
|
+
`rewards`, `visibility` and _where it sits_ — which progression, which level, and that threshold —
|
|
76
|
+
cannot change. Its copy can. Removing it retires it: players who hold it keep it, and its points
|
|
77
|
+
still count against your budget.
|
|
78
|
+
- **The text is screened.** Copy that impersonates the platform or its staff is refused, and the
|
|
79
|
+
refusal names the rule it broke.
|
|
80
|
+
|
|
81
|
+
## 2. Who unlocks it
|
|
82
|
+
|
|
83
|
+
An achievement unlocks when an **eligible order** leaves the progression's value at or above the
|
|
84
|
+
threshold it sits on. Eligible means:
|
|
85
|
+
|
|
86
|
+
- written by **your game's server**, running as a platform-allocated server with its game-server
|
|
87
|
+
credential, **or by your serverless function, for the player who called it**. A browser write
|
|
88
|
+
never unlocks one, and neither does a server you started yourself with a developer credential —
|
|
89
|
+
its receipt says `suppressed`, with the reason `principal=developer-minted server token`;
|
|
90
|
+
- when a function wrote it, **on a progression that opts in** with `function_may_grant: true`
|
|
91
|
+
(otherwise `suppressed`, `functions_not_opted_in`), **for the player who called the function**,
|
|
92
|
+
and not for a guest (`function_invoker_is_guest`). A function a player called cannot write any
|
|
93
|
+
other player at all (`403 function_foreign_entity_write`), and a character never unlocks one.
|
|
94
|
+
A function your dedicated server called acts for your game, like the server. Every receipt a
|
|
95
|
+
function's order produces carries `principal: "function"`
|
|
96
|
+
([serverless-functions.md](serverless-functions.md#paying-the-player-who-called));
|
|
97
|
+
- on the **`release` channel**. Writes on `dev` and `test` do cross the threshold, and the receipt
|
|
98
|
+
says so as `suppressed` with the channel as the reason, but nothing unlocks: an unlock is
|
|
99
|
+
permanent, so testing must not spend one.
|
|
100
|
+
|
|
101
|
+
**The platform checks which credential wrote the progression. It cannot check why.** Your server's
|
|
102
|
+
or your function's own logic is the whole defence, and a function is the easier one to lean on: a
|
|
103
|
+
player can call it with any input they like, as often as their per-minute budget allows. Advance a progression that carries achievements only from what your
|
|
104
|
+
server observed or simulated itself — a match it ran, a board it scored — never from a player's
|
|
105
|
+
claim ("I cleared board 50"), and never through a handler that passes a client's request straight
|
|
106
|
+
through to a progression write. Apply the same plausibility and rate checks you would apply before
|
|
107
|
+
paying currency.
|
|
108
|
+
|
|
109
|
+
It unlocks **once per player, ever**. Resetting the progression and crossing the threshold again
|
|
110
|
+
does nothing, and neither the completion flag nor claiming a level's rewards plays any part. A player
|
|
111
|
+
whose value was already past the threshold before your release unlocks it on your server's **next
|
|
112
|
+
write** to that progression.
|
|
113
|
+
|
|
114
|
+
⚠️ **That catch-up counts the stored value, whoever wrote it.** That is why nothing in your config
|
|
115
|
+
may write the progression (§1), and why an achievement that pays points or rewards belongs on a
|
|
116
|
+
progression the browser has never been able to write. If a progression used to be
|
|
117
|
+
`client_writable`, or used to be written by a recipe, a loot table or a reward, its players' values
|
|
118
|
+
may not be your server's; make a new progression for the achievement rather than removing the other
|
|
119
|
+
writer from the old one. A release uploaded before this rule that still has such a writer unlocks
|
|
120
|
+
nothing there: the receipt says `suppressed`, with the reason `written_by_config`, until you upload
|
|
121
|
+
a config without it.
|
|
122
|
+
|
|
123
|
+
A progression the browser may write (`client_writable: true`) can carry only **display-only**
|
|
124
|
+
achievements: `points: 0` and no rewards. They never unlock on the platform — no record, no
|
|
125
|
+
receipt, nothing on the arcade — so a game that lists one works out its state itself, from
|
|
126
|
+
`currentValue >= threshold`.
|
|
127
|
+
|
|
128
|
+
The platform can switch achievements off for your game. When it has, the receipt says
|
|
129
|
+
`suppressed` with the reason `achievements_disabled` — read that before suspecting your config. It
|
|
130
|
+
can also switch off only what serverless functions pay and unlock; a function's unlock then reads
|
|
131
|
+
`suppressed` with `functions_disabled`.
|
|
132
|
+
|
|
133
|
+
## 3. On your server: what the order unlocked
|
|
134
|
+
|
|
135
|
+
Your server writes the progression as it always has, and the order it gets back carries a receipt for
|
|
136
|
+
each achievement threshold the write crossed, plus any achievement it unlocked by catching up. The
|
|
137
|
+
server entry's `achievementReceipts` reads them off; the package itself is installed as
|
|
138
|
+
[getting-started.md](getting-started.md) describes.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
import { achievementReceipts } from '@indiegems/gem-web-sdk/server';
|
|
142
|
+
|
|
143
|
+
const order = await gem.api.addEntityProgression('players', playerId, 'boards_cleared', {
|
|
144
|
+
amount: 1,
|
|
145
|
+
});
|
|
146
|
+
for (const r of achievementReceipts(order)) {
|
|
147
|
+
if (r.outcome === 'unlocked') log(`${playerId} unlocked ${r.achievementId} (+${r.points})`);
|
|
148
|
+
if (r.outcome === 'suppressed') log(`${r.achievementId} not unlocked: ${r.reason}`);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
| `outcome` | Means |
|
|
153
|
+
| ------------------ | -------------------------------------------------------------------------------- |
|
|
154
|
+
| `unlocked` | This order unlocked it, and `points` is what it credited |
|
|
155
|
+
| `already_unlocked` | The write crossed it again — after a reset — and the player already holds it |
|
|
156
|
+
| `suppressed` | It was not unlocked, and `reason` says why — for your logs, never for the player |
|
|
157
|
+
|
|
158
|
+
The list is empty when the write crossed no threshold, which is almost every write. For a batch,
|
|
159
|
+
read each entry of the result's `orders`. For a "best score" progression, use the `progression_max`
|
|
160
|
+
operation on `gem.api.createEntityOrder`; its result reads the same way.
|
|
161
|
+
|
|
162
|
+
**A receipt is a report, not a trigger.** A retried or replayed order reports nothing — the
|
|
163
|
+
threshold is already behind it — so a game-side reward keyed off `unlocked` is lost whenever the
|
|
164
|
+
first response never arrived. Put a game reward in the same threshold's `rewards`, which the
|
|
165
|
+
platform pays on the crossing itself, and read the player's standing from the progression. Those
|
|
166
|
+
rewards pay again if the progression resets and is crossed again; the achievement does not.
|
|
167
|
+
|
|
168
|
+
## 4. In the game: showing the player's standing
|
|
169
|
+
|
|
170
|
+
The progression reads your client already makes carry each achievement on the progression and
|
|
171
|
+
whether this player has it:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
const p = await gem.me.progressions.get('boards_cleared');
|
|
175
|
+
for (const a of p?.achievements ?? []) {
|
|
176
|
+
const state = a.revoked ? 'removed' : a.unlockedAt !== undefined ? 'unlocked' : 'locked';
|
|
177
|
+
// a.id, a.threshold, a.points, a.hidden
|
|
178
|
+
}
|
|
179
|
+
if (p?.achievementsUnavailable) {
|
|
180
|
+
// The list is right, but whether each is unlocked could not be read just now. Show no
|
|
181
|
+
// "locked" state from this read. A later get() asks the backend again rather than the cache.
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The standing comes from the platform's unlock record, not from the progression's value — so after a
|
|
186
|
+
reset, `isCompleted` is `false` and `currentValue` is back at zero, and the achievement still reads
|
|
187
|
+
as unlocked. `revoked` means the platform removed an unlock; it cannot be earned again. `points` is
|
|
188
|
+
what the achievement awards while locked, and what the unlock credited once held.
|
|
189
|
+
|
|
190
|
+
`achievements` is absent on a progression with none, and on a character's progressions: an
|
|
191
|
+
achievement belongs to the player. A player who has never written the progression has no record of
|
|
192
|
+
it, so `get` returns `null` and there is no list to read — to show locked achievements before the
|
|
193
|
+
first write, list them from your own game config.
|
|
194
|
+
|
|
195
|
+
The client caches what it reads, and an unlock happens on your server, so the client does not see
|
|
196
|
+
it by itself. When your server tells the game a player unlocked something, call
|
|
197
|
+
`gem.me.inventory.refresh()` before reading the progression again.
|
|
198
|
+
|
|
199
|
+
**This read is for display.** Never send it to your server as proof that a player holds something;
|
|
200
|
+
a modified client can report anything. Your server reads the standing itself.
|
|
201
|
+
|
|
202
|
+
**`hidden` is a presentation rule, not a secret.** Your game already knows a hidden achievement's
|
|
203
|
+
threshold — it is in your config. The arcade hides its name and description from players until they
|
|
204
|
+
unlock it; if your game shows its own achievement list, do the same.
|
|
205
|
+
|
|
206
|
+
## 5. Points are prestige: the Achievement Score
|
|
207
|
+
|
|
208
|
+
Achievement points are a player's permanent standing on the arcade. They are **never spent,
|
|
209
|
+
bought, boosted, decayed or expired**, and they are **removed only for fraud or moderation**.
|
|
210
|
+
|
|
211
|
+
- **Never bought.** There is no achievement write API, and nothing a player pays for may write an
|
|
212
|
+
achievement's progression (§1) — so no price, pack or recipe reaches a point.
|
|
213
|
+
- **Never boosted.** A Boost multiplies what some platform-currency grants pay. It never touches
|
|
214
|
+
achievement points: an unlock credits exactly the points your release registered for that
|
|
215
|
+
achievement, for every player, boosted or not.
|
|
216
|
+
- **Never spent, decayed or expired.** Nothing consumes points, and resetting the progression,
|
|
217
|
+
retiring the achievement in a later release or a player's time away never takes them back.
|
|
218
|
+
- **Removed only for fraud or moderation.** The platform can revoke an unlock — a player who
|
|
219
|
+
cheated, or a game whose server was compromised — and that removes its points. A revoked
|
|
220
|
+
achievement cannot be earned again. This is the platform's decision, never a game's.
|
|
221
|
+
|
|
222
|
+
The **Achievement Score** is the one number the arcade shows for them: every point a player holds
|
|
223
|
+
from every game's achievements, plus the points of the platform's own achievements. Your game never
|
|
224
|
+
computes it, and should not restate it as a currency of its own: it moves only when an unlock
|
|
225
|
+
credits points or a revocation removes them.
|
|
226
|
+
|
|
227
|
+
## What to read next
|
|
228
|
+
|
|
229
|
+
- [`inventory-and-catalogs.md`](inventory-and-catalogs.md) §13 — progressions and platform grants,
|
|
230
|
+
which this page builds on.
|
|
231
|
+
- [`server-bundles.md`](server-bundles.md) — how your server gets the credential that makes a write
|
|
232
|
+
eligible.
|