trucoshi 11.0.1 → 12.0.0-rc.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 CHANGED
@@ -26,10 +26,66 @@ Proba la demo actual en [Trucoshi](https://trucoshi.com)
26
26
 
27
27
  `yarn build`
28
28
 
29
+ ### Full local dev stack
30
+
31
+ Run the three-repo local stack from this repo with:
32
+
33
+ `yarn dev:all:sudo-docker`
34
+
35
+ Do not prefix the command with `sudo`. The script only uses sudo for Docker commands, and running Yarn itself as root can corrupt local links and generated Prisma files.
36
+
29
37
  # Test
30
38
 
31
39
  `yarn test`
32
40
 
41
+ ### Docker e2e
42
+
43
+ Trucoshi e2e tests depend on a running Lightning Accounts e2e server. Start that first in one
44
+ terminal, then run the Trucoshi e2e suite in another:
45
+
46
+ ```bash
47
+ cd /home/fran/Workspace/trucoshi/lightning-accounts
48
+ NODE_ENV=test ./init-e2e.sh
49
+ ```
50
+
51
+ ```bash
52
+ cd /home/fran/Workspace/trucoshi/trucoshi
53
+ NODE_ENV=test ./init-e2e.sh
54
+ ```
55
+
56
+ Use `../lightning-accounts/init-test.sh` when you want to run the Lightning Accounts Jest/e2e
57
+ suite itself. Use `../lightning-accounts/init-e2e.sh` when Trucoshi needs the persistent API,
58
+ regtest bitcoind, and LND containers.
59
+
60
+ ### Docker migrations
61
+
62
+ Production and staging containers do not run database seeds. Run migrations explicitly before
63
+ starting a newly built app image.
64
+
65
+ Staging:
66
+
67
+ ```bash
68
+ docker compose -f docker-compose.staging.yml --env-file .env build server
69
+ docker compose -f docker-compose.staging.yml --env-file .env up -d postgres redis
70
+ yarn docker:staging:migrate
71
+ docker compose -f docker-compose.staging.yml --env-file .env up -d --build server
72
+ ```
73
+
74
+ Production uses the same sequence with `docker-compose.prod.yml` and `yarn docker:prod:migrate`.
75
+
76
+ ### Admin users
77
+
78
+ User roles live in Lightning Accounts. To promote an existing user by email:
79
+
80
+ ```bash
81
+ cd /home/fran/Workspace/trucoshi/lightning-accounts
82
+ yarn docker:staging:make-admin --email you@example.com
83
+ yarn docker:prod:make-admin --email you@example.com
84
+ ```
85
+
86
+ The command only updates an existing non-`APPLICATION` user to `ADMIN`; it does not create users or
87
+ run seeds.
88
+
33
89
  ### Play
34
90
 
35
91
  `yarn cli:play`
@@ -53,7 +109,7 @@ Proba la demo actual en [Trucoshi](https://trucoshi.com)
53
109
 
54
110
  # Donations
55
111
 
56
- Donate Bitcoin at [geyser.fund/project/trucoshi](https://geyser.fund/project/trucoshi)
112
+ Donate Bitcoin at [jfrader.com/tips](https://jfrader.com/tips)
57
113
 
58
114
  # License
59
115
 
package/dist/events.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SocketError } from "./server";
2
- import { EMatchState, IAccountDetails, IChatMessage, ILobbyOptions, IMatchDetails, IPlayerRanking, IPublicChatRoom, IPublicMatch, IPublicMatchInfo, IPublicMatchStats, IJoinQueueOptions, IQueueMatchFound, IQueueStatus, ITrucoshiStats, IUserData, IWaitingPlayData, IWaitingSayData } from "./types";
2
+ import { EMatchState, IAdminCreateChestRewardCodeInput, IAdminCreateChestRewardCodeResult, IAdminDashboard, IAdminSetNoticeBannerInput, IAdminSetNoticeBannerResult, IAccountDetails, CardSkinId, ICard, IChatMessage, IEquippedDeck, ILobbyOptions, IInventoryCardGroup, IMatchDetails, IPlayerRanking, IPublicChatRoom, IPublicMatch, IPublicMatchInfo, IPublicMatchStats, IJoinQueueOptions, IQueueMatchFound, IQueueStatus, IRewardCodeRedeemResult, IPublicNoticeBanner, ITreasureOpenResult, ITreasureStatus, ITrucoshiStats, IUserData, IWaitingPlayData, IWaitingSayData } from "./types";
3
3
  export type IEventCallback<T = {}> = (args: {
4
4
  success: boolean;
5
5
  error?: SocketError;
@@ -22,7 +22,8 @@ export declare enum EServerEvent {
22
22
  UPDATE_CHAT = "UPDAET_CHAT",
23
23
  UPDATE_STATS = "UPDATE_STATS",
24
24
  QUEUE_UPDATE = "QUEUE_UPDATE",
25
- QUEUE_MATCH_FOUND = "QUEUE_MATCH_FOUND"
25
+ QUEUE_MATCH_FOUND = "QUEUE_MATCH_FOUND",
26
+ UPDATE_NOTICE_BANNER = "UPDATE_NOTICE_BANNER"
26
27
  }
27
28
  export interface ServerToClientEvents {
28
29
  [EServerEvent.PONG]: (serverTime: number, clientTime: number) => void;
@@ -30,6 +31,7 @@ export interface ServerToClientEvents {
30
31
  [EServerEvent.UPDATE_STATS]: (room: ITrucoshiStats) => void;
31
32
  [EServerEvent.QUEUE_UPDATE]: (status: IQueueStatus) => void;
32
33
  [EServerEvent.QUEUE_MATCH_FOUND]: (match: IQueueMatchFound) => void;
34
+ [EServerEvent.UPDATE_NOTICE_BANNER]: (noticeBanner: IPublicNoticeBanner | null) => void;
33
35
  [EServerEvent.NEW_MESSAGE]: (roomId: string, message?: IChatMessage) => void;
34
36
  [EServerEvent.UPDATE_ACTIVE_MATCHES]: (activeMatches: IPublicMatchInfo[]) => void;
35
37
  [EServerEvent.UPDATE_PUBLIC_MATCHES]: (publicMatches: IPublicMatchInfo[]) => void;
@@ -60,6 +62,16 @@ export declare enum EClientEvent {
60
62
  PLAY_AGAIN = "PLAY_AGAIN",
61
63
  JOIN_QUEUE = "JOIN_QUEUE",
62
64
  LEAVE_QUEUE = "LEAVE_QUEUE",
65
+ FETCH_INVENTORY = "FETCH_INVENTORY",
66
+ SET_DECK_CARD_SKIN = "SET_DECK_CARD_SKIN",
67
+ FETCH_TREASURE_STATUS = "FETCH_TREASURE_STATUS",
68
+ OPEN_TREASURE_CHEST = "OPEN_TREASURE_CHEST",
69
+ DEV_GRANT_TREASURE_CHEST = "DEV_GRANT_TREASURE_CHEST",
70
+ FETCH_NOTICE_BANNER = "FETCH_NOTICE_BANNER",
71
+ ADMIN_FETCH_DASHBOARD = "ADMIN_FETCH_DASHBOARD",
72
+ ADMIN_CREATE_CHEST_REWARD_CODE = "ADMIN_CREATE_CHEST_REWARD_CODE",
73
+ ADMIN_SET_NOTICE_BANNER = "ADMIN_SET_NOTICE_BANNER",
74
+ REDEEM_REWARD_CODE = "REDEEM_REWARD_CODE",
63
75
  ADD_BOT = "ADD_BOT",
64
76
  FETCH_MATCH = "FETCH_MATCH",
65
77
  FETCH_CHAT_ROOM = "FETCH_CHAT_ROOM",
@@ -111,6 +123,35 @@ export interface ClientToServerEvents {
111
123
  status?: IQueueStatus;
112
124
  }>) => void;
113
125
  [EClientEvent.LEAVE_QUEUE]: (callback?: IEventCallback<{}>) => void;
126
+ [EClientEvent.FETCH_INVENTORY]: (callback: IEventCallback<{
127
+ inventory: IInventoryCardGroup[];
128
+ equippedDeck: IEquippedDeck;
129
+ }>) => void;
130
+ [EClientEvent.SET_DECK_CARD_SKIN]: (card: ICard, cardSkinId: CardSkinId | null, callback: IEventCallback<{
131
+ inventory: IInventoryCardGroup[];
132
+ equippedDeck: IEquippedDeck;
133
+ }>) => void;
134
+ [EClientEvent.FETCH_TREASURE_STATUS]: (callback: IEventCallback<{
135
+ treasureStatus: ITreasureStatus;
136
+ }>) => void;
137
+ [EClientEvent.OPEN_TREASURE_CHEST]: (chestId: number, callback: IEventCallback<{
138
+ treasureStatus: ITreasureStatus;
139
+ treasureResult: ITreasureOpenResult;
140
+ inventory: IInventoryCardGroup[];
141
+ equippedDeck: IEquippedDeck;
142
+ }>) => void;
143
+ [EClientEvent.DEV_GRANT_TREASURE_CHEST]: (callback: IEventCallback<{
144
+ treasureStatus: ITreasureStatus;
145
+ }>) => void;
146
+ [EClientEvent.FETCH_NOTICE_BANNER]: (callback: IEventCallback<{
147
+ noticeBanner: IPublicNoticeBanner | null;
148
+ }>) => void;
149
+ [EClientEvent.ADMIN_FETCH_DASHBOARD]: (callback: IEventCallback<{
150
+ dashboard: IAdminDashboard;
151
+ }>) => void;
152
+ [EClientEvent.ADMIN_CREATE_CHEST_REWARD_CODE]: (input: IAdminCreateChestRewardCodeInput, callback: IEventCallback<IAdminCreateChestRewardCodeResult>) => void;
153
+ [EClientEvent.ADMIN_SET_NOTICE_BANNER]: (input: IAdminSetNoticeBannerInput, callback: IEventCallback<IAdminSetNoticeBannerResult>) => void;
154
+ [EClientEvent.REDEEM_REWARD_CODE]: (code: string, callback: IEventCallback<IRewardCodeRedeemResult>) => void;
114
155
  [EClientEvent.FETCH_MATCH]: (matchSessionId: string, callback: IEventCallback<{
115
156
  match: IPublicMatch | null;
116
157
  }>) => void;
package/dist/events.js CHANGED
@@ -18,6 +18,7 @@ export var EServerEvent;
18
18
  EServerEvent["UPDATE_STATS"] = "UPDATE_STATS";
19
19
  EServerEvent["QUEUE_UPDATE"] = "QUEUE_UPDATE";
20
20
  EServerEvent["QUEUE_MATCH_FOUND"] = "QUEUE_MATCH_FOUND";
21
+ EServerEvent["UPDATE_NOTICE_BANNER"] = "UPDATE_NOTICE_BANNER";
21
22
  })(EServerEvent || (EServerEvent = {}));
22
23
  export var EClientEvent;
23
24
  (function (EClientEvent) {
@@ -36,6 +37,16 @@ export var EClientEvent;
36
37
  EClientEvent["PLAY_AGAIN"] = "PLAY_AGAIN";
37
38
  EClientEvent["JOIN_QUEUE"] = "JOIN_QUEUE";
38
39
  EClientEvent["LEAVE_QUEUE"] = "LEAVE_QUEUE";
40
+ EClientEvent["FETCH_INVENTORY"] = "FETCH_INVENTORY";
41
+ EClientEvent["SET_DECK_CARD_SKIN"] = "SET_DECK_CARD_SKIN";
42
+ EClientEvent["FETCH_TREASURE_STATUS"] = "FETCH_TREASURE_STATUS";
43
+ EClientEvent["OPEN_TREASURE_CHEST"] = "OPEN_TREASURE_CHEST";
44
+ EClientEvent["DEV_GRANT_TREASURE_CHEST"] = "DEV_GRANT_TREASURE_CHEST";
45
+ EClientEvent["FETCH_NOTICE_BANNER"] = "FETCH_NOTICE_BANNER";
46
+ EClientEvent["ADMIN_FETCH_DASHBOARD"] = "ADMIN_FETCH_DASHBOARD";
47
+ EClientEvent["ADMIN_CREATE_CHEST_REWARD_CODE"] = "ADMIN_CREATE_CHEST_REWARD_CODE";
48
+ EClientEvent["ADMIN_SET_NOTICE_BANNER"] = "ADMIN_SET_NOTICE_BANNER";
49
+ EClientEvent["REDEEM_REWARD_CODE"] = "REDEEM_REWARD_CODE";
39
50
  EClientEvent["ADD_BOT"] = "ADD_BOT";
40
51
  EClientEvent["FETCH_MATCH"] = "FETCH_MATCH";
41
52
  EClientEvent["FETCH_CHAT_ROOM"] = "FETCH_CHAT_ROOM";
@@ -0,0 +1,10 @@
1
+ import type { ICardSkin } from "../types";
2
+ export type ICardSkinRelease = {
3
+ release: string;
4
+ grantOnInventory: boolean;
5
+ grantSource?: string;
6
+ skins: ICardSkin[];
7
+ };
8
+ export declare const SKIN_RELEASES: ICardSkinRelease[];
9
+ export declare const CARD_SKINS: ICardSkin[];
10
+ export declare const INVENTORY_GRANT_RELEASES: ICardSkinRelease[];
@@ -0,0 +1,10 @@
1
+ import skinReleases from "../cosmetics/skins.json";
2
+ const normalizeSkin = (release, skin) => (Object.assign({ enabled: true, unlockable: true, release }, skin));
3
+ export const SKIN_RELEASES = skinReleases.map(({ release, grantOnInventory = false, grantSource, skins }) => ({
4
+ release,
5
+ grantOnInventory,
6
+ grantSource,
7
+ skins: skins.map((skin) => normalizeSkin(release, skin)),
8
+ }));
9
+ export const CARD_SKINS = SKIN_RELEASES.flatMap(({ skins }) => skins);
10
+ export const INVENTORY_GRANT_RELEASES = SKIN_RELEASES.filter(({ grantOnInventory }) => grantOnInventory);
@@ -0,0 +1,3 @@
1
+ import type { CardSkinRarity, ITreasureConfig } from "../types";
2
+ export declare const TREASURE_CONFIG: ITreasureConfig;
3
+ export declare const TREASURE_RARITIES: CardSkinRarity[];
@@ -0,0 +1,7 @@
1
+ import treasureConfig from "../cosmetics/treasures.json";
2
+ const rarityWeights = treasureConfig.rarityWeights;
3
+ export const TREASURE_CONFIG = {
4
+ eligibleMatchesPerChest: treasureConfig.eligibleMatchesPerChest,
5
+ rarityWeights,
6
+ };
7
+ export const TREASURE_RARITIES = Object.keys(rarityWeights);
@@ -85,10 +85,12 @@ export function shuffleArray(array, getRandom = defaultGetRandom) {
85
85
  return array;
86
86
  }
87
87
  export function PlayedCard(player, card, burn) {
88
+ var _a;
88
89
  const pc = {
89
90
  player,
90
91
  card,
91
92
  key: card + player.idx,
93
+ cardSkinId: (_a = player.deckSkinByCard) === null || _a === void 0 ? void 0 : _a[card],
92
94
  };
93
95
  if (burn) {
94
96
  pc.card = BURNT_CARD;
@@ -3,6 +3,8 @@ import { IRandom } from "../../types";
3
3
  import { AxiosResponse } from "axios";
4
4
  export declare const rng: IRng;
5
5
  export declare const Random: () => IRandom;
6
+ export type RandomIndexPicker = (max: number) => number;
7
+ export declare const createRandomIndexPicker: (clientSeed: string) => RandomIndexPicker;
6
8
  export interface IRng {
7
9
  combine(client: string, server: string, bitcoinHash: string, nonce: number): string;
8
10
  sha512(string: string): string;
@@ -42,6 +42,16 @@ export const Random = () => {
42
42
  };
43
43
  return random;
44
44
  };
45
+ export const createRandomIndexPicker = (clientSeed) => {
46
+ const random = Random();
47
+ random.clients[0] = clientSeed;
48
+ return (max) => {
49
+ if (!Number.isInteger(max) || max <= 0) {
50
+ throw new Error("Random max must be a positive integer");
51
+ }
52
+ return random.pick(0, max);
53
+ };
54
+ };
45
55
  function Rng() {
46
56
  return {
47
57
  /**
@@ -1,2 +1,4 @@
1
1
  export * from "./classes";
2
2
  export * from "./constants";
3
+ export * from "./Skins";
4
+ export * from "./Treasures";
package/dist/lib/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./classes";
2
2
  export * from "./constants";
3
+ export * from "./Skins";
4
+ export * from "./Treasures";
package/dist/types.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export * from "./events";
2
2
  export { CARDS, CARDS_HUMAN_READABLE, SUITS_HUMAN_READABLE, BURNT_CARD } from "./lib/constants";
3
+ export { CARD_SKINS, INVENTORY_GRANT_RELEASES, SKIN_RELEASES } from "./lib/Skins";
4
+ export { TREASURE_CONFIG, TREASURE_RARITIES } from "./lib/Treasures";
3
5
  import { RequestParams, User } from "lightning-accounts";
4
6
  import { Match, MatchPlayer, MatchHand, UserStats, MatchBet } from "@trucoshi/prisma";
5
7
  import { IHand, IPlayInstance } from "./truco";
6
8
  import { CARDS, ITable } from "./lib";
7
9
  import { AxiosResponse } from "axios";
8
- import { ITrucoshi } from "./server";
10
+ import type { ITrucoshi } from "./server";
9
11
  import { BotProfile } from "./truco/Bot";
10
12
  export declare enum EMatchState {
11
13
  UNREADY = "UNREADY",
@@ -17,13 +19,13 @@ export declare enum EMatchState {
17
19
  export type IPublicUserStats = Omit<UserStats, "elo">;
18
20
  export type IPlayerRanking = Omit<IPublicUserStats, "id" | "satsBet" | "satsWon" | "satsLost"> & Pick<User, "name" | "avatarUrl">;
19
21
  export interface IMatchDetails extends Match {
20
- players: Array<Pick<MatchPlayer, "accountId" | "teamIdx" | "name" | "idx" | "bot">>;
22
+ players: Array<Pick<MatchPlayer, "accountId" | "teamIdx" | "name" | "idx" | "bot" | "deckSkinByCard">>;
21
23
  hands: Array<MatchHand>;
22
24
  }
23
25
  export interface IAccountDetails {
24
26
  stats: IPublicUserStats | null;
25
27
  matches: Array<Match & {
26
- players: Pick<MatchPlayer, "accountId" | "idx" | "teamIdx" | "bot" | "name">[];
28
+ players: Pick<MatchPlayer, "accountId" | "idx" | "teamIdx" | "bot" | "name" | "deckSkinByCard">[];
27
29
  bet: Pick<MatchBet, "id" | "satsPerPlayer"> | null;
28
30
  }>;
29
31
  account: User | null;
@@ -45,15 +47,16 @@ export interface ILobbyOptions {
45
47
  satsPerPlayer: number;
46
48
  }
47
49
  export interface IJoinQueueOptions {
48
- maxPlayers: 2 | 4 | 6;
50
+ maxPlayers: 0 | 2 | 4 | 6;
49
51
  allowBots: boolean;
50
52
  }
51
53
  export interface IQueueStatus {
52
54
  requestId: string;
53
- maxPlayers: 2 | 4 | 6;
55
+ maxPlayers: 0 | 2 | 4 | 6;
54
56
  queuedPlayers: number;
55
57
  requiredPlayers: number;
56
58
  position: number;
59
+ queuedAt: number;
57
60
  botFallbackAt?: number;
58
61
  }
59
62
  export interface IQueueMatchFound {
@@ -101,6 +104,8 @@ export interface IMatchPreviousHand {
101
104
  export interface IPublicMatch {
102
105
  id?: number;
103
106
  options: ILobbyOptions;
107
+ createdFromQueue: boolean;
108
+ queueOptions?: IJoinQueueOptions;
104
109
  busy: boolean;
105
110
  state: EMatchState;
106
111
  handState: EHandState | null;
@@ -129,6 +134,8 @@ export interface IPublicMatchInfo {
129
134
  options: ILobbyOptions;
130
135
  state: EMatchState;
131
136
  winnerTeamIdx: 0 | 1 | undefined;
137
+ createdFromQueue: boolean;
138
+ queueOptions?: IJoinQueueOptions;
132
139
  }
133
140
  export type IPublicChatRoom = Pick<IChatRoom, "id" | "messages">;
134
141
  export interface IChatMessage {
@@ -218,6 +225,7 @@ export declare enum GAME_ERROR {
218
225
  INVALID_IDENTITY = "INVALID_IDENTITY",
219
226
  UNEXPECTED_ERROR = "UNEXPECTED_ERROR",
220
227
  FORBIDDEN = "FORBIDDEN",
228
+ INVALID_INPUT = "INVALID_INPUT",
221
229
  NOT_FOUND = "NOT_FOUND",
222
230
  MATCH_ALREADY_STARTED = "MATCH_ALREADY_STARTED",
223
231
  LOBBY_IS_FULL = "LOBBY_IS_FULL",
@@ -231,7 +239,9 @@ export declare enum GAME_ERROR {
231
239
  NO_FLOR = "NO_FLOR",
232
240
  INVALID_SESSION = "INVALID_SESSION",
233
241
  PAYMENT_REQUIRED = "PAYMENT_REQUIRED",
234
- PAYMENT_ERROR = "PAYMENT_ERROR"
242
+ PAYMENT_ERROR = "PAYMENT_ERROR",
243
+ REWARD_CODE_INVALID = "REWARD_CODE_INVALID",
244
+ REWARD_CODE_REDEEMED = "REWARD_CODE_REDEEMED"
235
245
  }
236
246
  export interface EnvidoState {
237
247
  accept: number;
@@ -263,10 +273,117 @@ export interface IDeck {
263
273
  shuffle(dealerIdx: number): IDeck;
264
274
  }
265
275
  export type ICard = keyof typeof CARDS;
276
+ export type CardSkinId = string;
277
+ export type CardSkinRarity = "COMMON" | "RARE" | "EPIC" | "LEGENDARY" | "PROMO";
278
+ export interface ICardSkin {
279
+ id: CardSkinId;
280
+ release: string;
281
+ card: ICard;
282
+ description?: string | null;
283
+ fileName: string;
284
+ assetPath: string;
285
+ rarity: CardSkinRarity;
286
+ enabled: boolean;
287
+ unlockable: boolean;
288
+ }
289
+ export type IEquippedDeck = Partial<Record<ICard, CardSkinId>>;
290
+ export interface IInventoryCardSkin extends ICardSkin {
291
+ unlocked: boolean;
292
+ equipped: boolean;
293
+ }
294
+ export interface IInventoryCardGroup {
295
+ card: ICard;
296
+ skins: IInventoryCardSkin[];
297
+ equippedCardSkinId?: CardSkinId;
298
+ }
299
+ export interface ITreasureConfig {
300
+ eligibleMatchesPerChest: number;
301
+ rarityWeights: Record<CardSkinRarity, number>;
302
+ }
303
+ export interface ITreasureChest {
304
+ id: number;
305
+ sourceMatchId?: number | null;
306
+ earnedAt: string;
307
+ }
308
+ export interface ITreasureStatus {
309
+ progress: number;
310
+ threshold: number;
311
+ unopenedChests: ITreasureChest[];
312
+ }
313
+ export interface ITreasureOpenResult {
314
+ chestId: number;
315
+ rarity: CardSkinRarity | null;
316
+ cardSkin: ICardSkin | null;
317
+ duplicate: boolean;
318
+ granted: boolean;
319
+ }
320
+ export interface IAdminOnlineAccount {
321
+ accountId: number;
322
+ name: string;
323
+ avatarUrl?: string | null;
324
+ role?: User["role"];
325
+ online: boolean;
326
+ }
327
+ export interface IAdminRewardCodeSummary {
328
+ id: number;
329
+ codePreview: string;
330
+ createdByAccountId: number;
331
+ intendedAccountId?: number | null;
332
+ note?: string | null;
333
+ createdAt: string;
334
+ redeemedAt?: string | null;
335
+ redeemedByAccountId?: number | null;
336
+ treasureChestId?: number | null;
337
+ }
338
+ export type NoticeBannerSeverity = "info" | "warning" | "error" | "success";
339
+ export interface IPublicNoticeBanner {
340
+ id: number;
341
+ text: string;
342
+ severity: NoticeBannerSeverity;
343
+ buttonText?: string | null;
344
+ buttonHref?: string | null;
345
+ updatedAt: string;
346
+ }
347
+ export interface IAdminNoticeBanner extends IPublicNoticeBanner {
348
+ active: boolean;
349
+ updatedByAccountId: number;
350
+ createdAt: string;
351
+ }
352
+ export interface IAdminDashboard {
353
+ onlineAccounts: IAdminOnlineAccount[];
354
+ liveGames: IPublicMatchInfo[];
355
+ rewardCodes: IAdminRewardCodeSummary[];
356
+ noticeBanner: IAdminNoticeBanner | null;
357
+ }
358
+ export interface IAdminCreateChestRewardCodeInput {
359
+ intendedAccountId?: number | null;
360
+ note?: string | null;
361
+ }
362
+ export interface IAdminCreateChestRewardCodeResult {
363
+ code: string;
364
+ link: string;
365
+ rewardCode: IAdminRewardCodeSummary;
366
+ }
367
+ export interface IAdminSetNoticeBannerInput {
368
+ active: boolean;
369
+ text?: string | null;
370
+ severity?: NoticeBannerSeverity | null;
371
+ buttonText?: string | null;
372
+ buttonHref?: string | null;
373
+ }
374
+ export interface IAdminSetNoticeBannerResult {
375
+ noticeBanner: IAdminNoticeBanner | null;
376
+ publicNoticeBanner: IPublicNoticeBanner | null;
377
+ }
378
+ export interface IRewardCodeRedeemResult {
379
+ grantedChest: ITreasureChest;
380
+ treasureStatus: ITreasureStatus;
381
+ }
266
382
  export interface IPlayedCard {
267
383
  key: string;
268
384
  player: IPlayer | IPublicPlayer;
269
385
  card: ICard;
386
+ cardSkinId?: CardSkinId;
270
387
  }
271
388
  export interface IHandPoints {
272
389
  0: number;
@@ -291,7 +408,7 @@ export interface IRandom {
291
408
  bitcoinHeight: number;
292
409
  };
293
410
  }
294
- export type IPublicPlayer = Pick<IPlayer, "idx" | "key" | "name" | "bot" | "abandonedTime" | "accountId" | "avatarUrl" | "disabled" | "abandoned" | "ready" | "hand" | "usedHand" | "prevHand" | "teamIdx" | "isTurn" | "turnExpiresAt" | "turnExtensionExpiresAt" | "isEnvidoTurn" | "isOwner" | "hasSaidFlor" | "hasSaidEnvidoPoints" | "hasSaidTruco" | "hasPassed"> & ({
411
+ export type IPublicPlayer = Pick<IPlayer, "idx" | "key" | "name" | "bot" | "abandonedTime" | "accountId" | "avatarUrl" | "disabled" | "abandoned" | "ready" | "hand" | "usedHand" | "prevHand" | "teamIdx" | "isTurn" | "turnExpiresAt" | "turnExtensionExpiresAt" | "isEnvidoTurn" | "isOwner" | "hasSaidFlor" | "hasSaidEnvidoPoints" | "hasSaidTruco" | "hasPassed" | "deckSkinByCard"> & ({
295
412
  isMe?: true;
296
413
  commands: IPlayer["commands"];
297
414
  hasFlor: IPlayer["hasFlor"];
@@ -356,6 +473,9 @@ export interface IPlayer {
356
473
  abandoned: boolean;
357
474
  ready: boolean;
358
475
  opponentProfiles: Record<string, OpponentProfile>;
476
+ deckSkinByCard: IEquippedDeck;
477
+ getCardSkinId(card: ICard): CardSkinId | undefined;
478
+ setDeckSkinByCard(deck: IEquippedDeck): void;
359
479
  getRandomCard(): [number, ICard];
360
480
  getHighestCard(): [number, ICard];
361
481
  getLowestCard(): [number, ICard];
package/dist/types.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./events";
2
2
  export { CARDS, CARDS_HUMAN_READABLE, SUITS_HUMAN_READABLE, BURNT_CARD } from "./lib/constants";
3
+ export { CARD_SKINS, INVENTORY_GRANT_RELEASES, SKIN_RELEASES } from "./lib/Skins";
4
+ export { TREASURE_CONFIG, TREASURE_RARITIES } from "./lib/Treasures";
3
5
  export var EMatchState;
4
6
  (function (EMatchState) {
5
7
  EMatchState["UNREADY"] = "UNREADY";
@@ -63,6 +65,7 @@ export var GAME_ERROR;
63
65
  GAME_ERROR["INVALID_IDENTITY"] = "INVALID_IDENTITY";
64
66
  GAME_ERROR["UNEXPECTED_ERROR"] = "UNEXPECTED_ERROR";
65
67
  GAME_ERROR["FORBIDDEN"] = "FORBIDDEN";
68
+ GAME_ERROR["INVALID_INPUT"] = "INVALID_INPUT";
66
69
  GAME_ERROR["NOT_FOUND"] = "NOT_FOUND";
67
70
  GAME_ERROR["MATCH_ALREADY_STARTED"] = "MATCH_ALREADY_STARTED";
68
71
  GAME_ERROR["LOBBY_IS_FULL"] = "LOBBY_IS_FULL";
@@ -77,6 +80,8 @@ export var GAME_ERROR;
77
80
  GAME_ERROR["INVALID_SESSION"] = "INVALID_SESSION";
78
81
  GAME_ERROR["PAYMENT_REQUIRED"] = "PAYMENT_REQUIRED";
79
82
  GAME_ERROR["PAYMENT_ERROR"] = "PAYMENT_ERROR";
83
+ GAME_ERROR["REWARD_CODE_INVALID"] = "REWARD_CODE_INVALID";
84
+ GAME_ERROR["REWARD_CODE_REDEEMED"] = "REWARD_CODE_REDEEMED";
80
85
  })(GAME_ERROR || (GAME_ERROR = {}));
81
86
  export const DANGEROUS_COMMANDS = [
82
87
  ESayCommand.MAZO,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trucoshi",
3
- "version": "11.0.1",
3
+ "version": "12.0.0-rc.1",
4
4
  "description": "Truco Game Server",
5
5
  "main": "dist/types.js",
6
6
  "license": "GPL-3.0",
@@ -25,9 +25,13 @@
25
25
  "start:prod": "node bin/trucoshi-server",
26
26
  "start:prod:migrate": "prisma migrate deploy && yarn start:prod",
27
27
  "docker": "NODE_ENV=development ./init-dev.sh",
28
+ "dev:all": "NODE_ENV=development ./init-dev-all.sh",
29
+ "dev:all:sudo-docker": "DEV_ALL_DOCKER_SUDO=1 NODE_ENV=development ./init-dev-all.sh",
28
30
  "docker:exec": "docker compose exec -i server yarn",
29
31
  "docker:prod": "NODE_ENV=production && docker compose -f docker-compose.prod.yml down && docker compose -f docker-compose.prod.yml --env-file .env up --build",
32
+ "docker:prod:migrate": "docker compose -f docker-compose.prod.yml --env-file .env run --rm --no-deps server yarn prisma migrate deploy",
30
33
  "docker:staging": "docker compose -f docker-compose.staging.yml down && docker compose -f docker-compose.staging.yml --env-file .env up --build",
34
+ "docker:staging:migrate": "docker compose -f docker-compose.staging.yml --env-file .env run --rm --no-deps server yarn prisma migrate deploy",
31
35
  "docker:e2e": "NODE_ENV=test ./init-e2e.sh",
32
36
  "test": "dotenv -e .env.test -- ts-mocha --exit ./test/lib/**/*.ts -t 50000",
33
37
  "test:e2e": "prisma db push --force-reset && dotenv -e .env.test -- ts-mocha --reporter spec --verbose --exit ./test/**/*.ts -t 60000",
@@ -79,7 +83,7 @@
79
83
  "dotenv-cli": "^7.3.0",
80
84
  "form-data": "^4.0.5",
81
85
  "jsonwebtoken": "^9.0.3",
82
- "lightning-accounts": "6.2.1",
86
+ "lightning-accounts": "6.3.0",
83
87
  "lodash.debounce": "^4.0.8",
84
88
  "lodash.memoize": "^4.1.2",
85
89
  "lodash.partialright": "^4.2.1",
@@ -93,7 +97,6 @@
93
97
  "sanitize-html": "^2.17.1",
94
98
  "socket.io": "4.8.3",
95
99
  "ts-node": "^10.9.1",
96
- "tsc": "^2.0.4",
97
100
  "typescript": "^5.9.3"
98
101
  }
99
102
  }