Skip to content

Patch infinite money glitch by mutexing coin transfers and blackjack #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/commandDetails/coin/transfer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { SapphireClient, container } from '@sapphire/framework';
import { CommandInteraction, EmbedBuilder, Message, User, userMention } from 'discord.js';
import {
CodeyCommandOptionType,
CodeyCommandDetails,
CodeyCommandOptionType,
SapphireAfterReplyType,
SapphireMessageExecuteType,
getUserFromMessage,
SapphireMessageResponseWithMetadata,
SapphireAfterReplyType,
getUserFromMessage,
} from '../../codeyCommand';
import { getCoinBalanceByUserId, transferTracker } from '../../components/coin';
import { getCoinEmoji } from '../../components/emojis';
import { gamesByPlayerId } from '../../components/games/blackjack.js';

const coinTransferExecuteCommand: SapphireMessageExecuteType = async (
client,
Expand Down Expand Up @@ -57,6 +58,20 @@ const coinTransferExecuteCommand: SapphireMessageExecuteType = async (
return new SapphireMessageResponseWithMetadata(`You can't transfer less than 1 coin.`, {});
}

if (transferTracker.transferringUsers.has(sendingUser.id)) {
return new SapphireMessageResponseWithMetadata(
`Please finish your current transfer first.`,
{},
);
}

if (gamesByPlayerId.has(sendingUser.id)) {
return new SapphireMessageResponseWithMetadata(
`Please finish your current blackjack game before transferring coins.`,
{},
);
}

const transfer = await transferTracker.startTransfer(
sendingUser,
receivingUser,
Expand Down
36 changes: 23 additions & 13 deletions src/commandDetails/games/blackjack.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
import { container } from '@sapphire/framework';
import {
Colors,
EmbedBuilder,
ButtonBuilder,
ButtonStyle,
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
Colors,
ComponentType,
EmbedBuilder,
} from 'discord.js';
import {
CodeyCommandDetails,
CodeyCommandOptionType,
SapphireMessageExecuteType,
SapphireMessageResponse,
getUserFromMessage,
} from '../../codeyCommand';
import {
UserCoinEvent,
adjustCoinBalanceByUserId,
getCoinBalanceByUserId,
UserCoinEvent,
transferTracker,
} from '../../components/coin';
import { getCoinEmoji, getEmojiByName } from '../../components/emojis';
import {
BlackjackAction,
BlackjackHand,
BlackjackStage,
CardSuit,
endGame,
GameState,
endGame,
gamesByPlayerId,
performGameAction,
startGame,
} from '../../components/games/blackjack';
import { pluralize } from '../../utils/pluralize';
import {
CodeyCommandDetails,
CodeyCommandOptionType,
SapphireMessageExecuteType,
SapphireMessageResponse,
getUserFromMessage,
} from '../../codeyCommand';

// CodeyCoin constants
const DEFAULT_BET = 10;
Expand Down Expand Up @@ -223,6 +225,14 @@ const blackjackExecuteCommand: SapphireMessageExecuteType = async (
return `You don't have enough coins to place that bet. ${getEmojiByName('codey_sad')}`;
}

if (transferTracker.transferringUsers.has(author)) {
return `Please finish your current coin transfer before starting a game.`;
}

if (gamesByPlayerId.has(author)) {
return `Please finish your current game before starting another one!`;
}

// Initialize the game
let game = startGame(bet, author, channel);
if (!game) {
Expand Down
23 changes: 16 additions & 7 deletions src/components/coin.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import _, { uniqueId } from 'lodash';
import { openDB } from './db';
import { SapphireClient } from '@sapphire/framework';
import {
Colors,
ActionRowBuilder,
BaseMessageOptions,
ButtonBuilder,
ButtonStyle,
Colors,
EmbedBuilder,
User,
ButtonStyle,
BaseMessageOptions,
} from 'discord.js';
import _, { uniqueId } from 'lodash';
import { SapphireSentMessageType } from '../codeyCommand';
import { updateMessageEmbed } from '../utils/embeds';
import { pluralize } from '../utils/pluralize';
import { openDB } from './db';
import { getCoinEmoji, getEmojiByName } from './emojis';
import { updateMessageEmbed } from '../utils/embeds';

export enum BonusType {
Daily = 0,
Expand Down Expand Up @@ -323,9 +323,11 @@ const TransferTimeout = 600000; // in ms (600000 ms == 10 mins)

class TransferTracker {
transfers: Map<string, Transfer>; // id, transfer
transferringUsers: Set<string>;

constructor() {
this.transfers = new Map<string, Transfer>();
this.transferringUsers = new Set<string>();
}
getTransferFromId(id: string): Transfer | undefined {
return this.transfers.get(id);
Expand All @@ -351,8 +353,9 @@ class TransferTracker {
reason: reason,
result: TransferResult.Pending,
};
const transfer = new Transfer(channelId, client, transferId, transferState);
const transfer = new Transfer(channelId, client, transferId, transferState, this);
this.transfers.set(transferId, transfer);
this.transferringUsers.add(sender.id);
setTimeout(async () => {
if (transfer.state.result === TransferResult.Pending) {
transfer.state.result = TransferResult.Timeout;
Expand All @@ -370,6 +373,7 @@ class TransferTracker {
throw new Error(`No transfer with transfer ID ${transferId} found`);
}

this.transferringUsers.delete(transfer.state.sender.id);
if (transfer.state.result === TransferResult.Pending) return;
await transfer.handleTransaction();
}
Expand All @@ -383,17 +387,20 @@ export class Transfer {
state: TransferState;
transferId: string;
transferMessage!: SapphireSentMessageType;
tracker: TransferTracker;

constructor(
channelId: string,
client: SapphireClient<boolean>,
transferId: string,
transferState: TransferState,
tracker: TransferTracker,
) {
this.channelId = channelId;
this.state = transferState;
this.client = client;
this.transferId = transferId;
this.tracker = tracker;
}

// called if state is (believed to be) no longer pending. Transfers coins and updates balances if transfer is confirmed
Expand All @@ -420,6 +427,8 @@ export class Transfer {
<string>(this.state.reason ?? ''),
this.client.user?.id,
);

this.tracker.transferringUsers.delete(this.state.sender.id);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/games/blackjack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export enum CardSuit {
}

// keeps track of games by player's Discord IDs
const gamesByPlayerId = new Map<string, BlackjackGame>();
export const gamesByPlayerId = new Map<string, BlackjackGame>();

// maps action Enum to game action
const gameActionsMap = new Map<BlackjackAction, () => Action>([
Expand Down
Loading