trucoshi 11.1.0 → 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;
@@ -223,6 +225,7 @@ export declare enum GAME_ERROR {
223
225
  INVALID_IDENTITY = "INVALID_IDENTITY",
224
226
  UNEXPECTED_ERROR = "UNEXPECTED_ERROR",
225
227
  FORBIDDEN = "FORBIDDEN",
228
+ INVALID_INPUT = "INVALID_INPUT",
226
229
  NOT_FOUND = "NOT_FOUND",
227
230
  MATCH_ALREADY_STARTED = "MATCH_ALREADY_STARTED",
228
231
  LOBBY_IS_FULL = "LOBBY_IS_FULL",
@@ -236,7 +239,9 @@ export declare enum GAME_ERROR {
236
239
  NO_FLOR = "NO_FLOR",
237
240
  INVALID_SESSION = "INVALID_SESSION",
238
241
  PAYMENT_REQUIRED = "PAYMENT_REQUIRED",
239
- PAYMENT_ERROR = "PAYMENT_ERROR"
242
+ PAYMENT_ERROR = "PAYMENT_ERROR",
243
+ REWARD_CODE_INVALID = "REWARD_CODE_INVALID",
244
+ REWARD_CODE_REDEEMED = "REWARD_CODE_REDEEMED"
240
245
  }
241
246
  export interface EnvidoState {
242
247
  accept: number;
@@ -268,10 +273,117 @@ export interface IDeck {
268
273
  shuffle(dealerIdx: number): IDeck;
269
274
  }
270
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
+ }
271
382
  export interface IPlayedCard {
272
383
  key: string;
273
384
  player: IPlayer | IPublicPlayer;
274
385
  card: ICard;
386
+ cardSkinId?: CardSkinId;
275
387
  }
276
388
  export interface IHandPoints {
277
389
  0: number;
@@ -296,7 +408,7 @@ export interface IRandom {
296
408
  bitcoinHeight: number;
297
409
  };
298
410
  }
299
- 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"> & ({
300
412
  isMe?: true;
301
413
  commands: IPlayer["commands"];
302
414
  hasFlor: IPlayer["hasFlor"];
@@ -361,6 +473,9 @@ export interface IPlayer {
361
473
  abandoned: boolean;
362
474
  ready: boolean;
363
475
  opponentProfiles: Record<string, OpponentProfile>;
476
+ deckSkinByCard: IEquippedDeck;
477
+ getCardSkinId(card: ICard): CardSkinId | undefined;
478
+ setDeckSkinByCard(deck: IEquippedDeck): void;
364
479
  getRandomCard(): [number, ICard];
365
480
  getHighestCard(): [number, ICard];
366
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.1.0",
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",
@@ -29,7 +29,9 @@
29
29
  "dev:all:sudo-docker": "DEV_ALL_DOCKER_SUDO=1 NODE_ENV=development ./init-dev-all.sh",
30
30
  "docker:exec": "docker compose exec -i server yarn",
31
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",
32
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",
33
35
  "docker:e2e": "NODE_ENV=test ./init-e2e.sh",
34
36
  "test": "dotenv -e .env.test -- ts-mocha --exit ./test/lib/**/*.ts -t 50000",
35
37
  "test:e2e": "prisma db push --force-reset && dotenv -e .env.test -- ts-mocha --reporter spec --verbose --exit ./test/**/*.ts -t 60000",
@@ -81,7 +83,7 @@
81
83
  "dotenv-cli": "^7.3.0",
82
84
  "form-data": "^4.0.5",
83
85
  "jsonwebtoken": "^9.0.3",
84
- "lightning-accounts": "6.2.1",
86
+ "lightning-accounts": "6.3.0",
85
87
  "lodash.debounce": "^4.0.8",
86
88
  "lodash.memoize": "^4.1.2",
87
89
  "lodash.partialright": "^4.2.1",
@@ -95,7 +97,6 @@
95
97
  "sanitize-html": "^2.17.1",
96
98
  "socket.io": "4.8.3",
97
99
  "ts-node": "^10.9.1",
98
- "tsc": "^2.0.4",
99
100
  "typescript": "^5.9.3"
100
101
  }
101
102
  }