added more achievments

This commit is contained in:
DeSqBlocki
2026-08-22 22:10:41 +02:00
parent ae35f61522
commit ca823726e3
12 changed files with 4977 additions and 1998 deletions
-60
View File
@@ -1,60 +0,0 @@
const {
EmbedBuilder
} = require("discord.js");
function delay(ms) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}
module.exports = {
name:
"abort",
description:
"abort it",
async execute(
interaction
) {
const embed =
new EmbedBuilder()
.setTitle(
"Close"
)
.setDescription(
"Closing..."
);
await interaction.update({
embeds: [
embed
],
components: []
});
await delay(
2000
);
try {
await interaction.deleteReply();
}
catch {
// Ignore if already deleted/expired.
}
}
}
+91
View File
@@ -0,0 +1,91 @@
const {
EmbedBuilder
} = require(
"discord.js"
);
const {
temporaryEphemeralReply
} = require(
"../../utils/cookieEconomy"
);
const {
getCrumbSaucerSessionOwner
} = require(
"../../utils/crumbSaucer"
);
function delay(
ms
) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}
module.exports = {
name:
"crumbSaucerClose",
async execute(
interaction
) {
const ownerId =
getCrumbSaucerSessionOwner(
interaction
);
if (
ownerId !==
interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
}
const embed =
new EmbedBuilder()
.setTitle(
"Closed"
)
.setDescription(
"Closing Crumb Saucer..."
);
await interaction.update({
embeds: [
embed
],
components: []
});
await delay(
1500
);
try {
await interaction.deleteReply();
}
catch {}
}
};
+3 -3
View File
@@ -1,7 +1,7 @@
const { const {
collectCookies collectCookiesFromSaucer
} = require( } = require(
"../../commands/applications/cookies" "../../utils/crumbCollect"
); );
@@ -13,7 +13,7 @@ module.exports = {
async execute( async execute(
interaction interaction
) { ) {
return collectCookies( return collectCookiesFromSaucer(
interaction interaction
); );
} }
+20
View File
@@ -0,0 +1,20 @@
const {
showCollectPage
} = require(
"../../utils/crumbCollect"
);
module.exports = {
name:
"crumbSaucerCollectMenu",
async execute(
interaction
) {
return showCollectPage(
interaction
);
}
};
File diff suppressed because it is too large Load Diff
+127 -204
View File
@@ -19,83 +19,86 @@ const {
} = require("./profileSystem"); } = require("./profileSystem");
/*
* ============================================================
* CONFIG
* ============================================================
*/
const HEIST_COOLDOWN = const HEIST_COOLDOWN =
60 * 60; // 1 hour 60 * 60;
/*
* All three approaches use the SAME cooldown:
*
* cooldowns_cookies.heist
*
* Sneak does NOT have its own cooldown.
* Hack does NOT have its own cooldown.
* Smash does NOT have its own cooldown.
*/
const HEIST_METHODS = { const HEIST_METHODS = {
Sneak: { Sneak: {
baseChance: 0.65, baseChance:
0.65,
minStealPercent: 0.01, minStealPercent:
maxStealPercent: 0.03, 0.01,
maxStealAmount: 1500,
minPenaltyPercent: 0.01, maxStealPercent:
maxPenaltyPercent: 0.04, 0.03,
maxPenaltyAmount: 750
maxStealAmount:
1500,
minPenaltyPercent:
0.01,
maxPenaltyPercent:
0.04,
maxPenaltyAmount:
750
}, },
Hack: { Hack: {
baseChance: 0.45, baseChance:
0.45,
minStealPercent: 0.03, minStealPercent:
maxStealPercent: 0.06, 0.03,
maxStealAmount: 3500,
minPenaltyPercent: 0.03, maxStealPercent:
maxPenaltyPercent: 0.07, 0.06,
maxPenaltyAmount: 2000
maxStealAmount:
3500,
minPenaltyPercent:
0.03,
maxPenaltyPercent:
0.07,
maxPenaltyAmount:
2000
}, },
Smash: { Smash: {
baseChance: 0.25, baseChance:
0.25,
minStealPercent: 0.06, minStealPercent:
maxStealPercent: 0.12, 0.06,
maxStealAmount: 7500,
minPenaltyPercent: 0.06, maxStealPercent:
maxPenaltyPercent: 0.12, 0.12,
maxPenaltyAmount: 5000
maxStealAmount:
7500,
minPenaltyPercent:
0.06,
maxPenaltyPercent:
0.12,
maxPenaltyAmount:
5000
} }
}; };
/*
* ============================================================
* RESOLVE HEIST
* ============================================================
*/
async function resolveCookieHeist( async function resolveCookieHeist(
interaction, interaction,
methodName methodName
) { ) {
/*
* Validate ownership of this Heist menu.
*
* Footer:
*
* cookieHeist:<userId>
*/
const footer = const footer =
interaction.message interaction.message
?.embeds?.[0] ?.embeds?.[0]
@@ -129,10 +132,6 @@ async function resolveCookieHeist(
} }
/*
* Validate method.
*/
const method = const method =
HEIST_METHODS[ HEIST_METHODS[
methodName methodName
@@ -150,12 +149,6 @@ async function resolveCookieHeist(
} }
/*
* ========================================================
* SHARED HEIST COOLDOWN
* ========================================================
*/
const cooldown = const cooldown =
await claimCooldown( await claimCooldown(
interaction.user.id, interaction.user.id,
@@ -179,10 +172,6 @@ async function resolveCookieHeist(
} }
/*
* Lock this Heist interaction immediately.
*/
await interaction.update({ await interaction.update({
embeds: [ embeds: [
new EmbedBuilder() new EmbedBuilder()
@@ -212,11 +201,6 @@ async function resolveCookieHeist(
interaction.client.user.id; interaction.client.user.id;
/*
* Re-fetch balances because they may have
* changed since the menu was opened.
*/
const [ const [
playerCookies, playerCookies,
vaultCookies vaultCookies
@@ -267,21 +251,11 @@ async function resolveCookieHeist(
} }
/*
* Short theatrical delay.
*/
await sleep( await sleep(
1200 1200
); );
/*
* ========================================================
* DYNAMIC VAULT SECURITY
* ========================================================
*/
const securityPenalty = const securityPenalty =
getVaultSecurityPenalty( getVaultSecurityPenalty(
vaultCookies vaultCookies
@@ -302,12 +276,6 @@ async function resolveCookieHeist(
successChance; successChance;
/*
* ========================================================
* SUCCESS
* ========================================================
*/
if (success) { if (success) {
const rolledPercent = const rolledPercent =
randomFloat( randomFloat(
@@ -377,30 +345,6 @@ async function resolveCookieHeist(
} }
const [
playerAfter,
vaultAfter
] =
await Promise.all([
getCookieBalance(
playerId
),
getCookieBalance(
vaultId
)
]);
const actualPercentage =
(
amount /
vaultCookies
)
*
100;
await trackProfileEvent( await trackProfileEvent(
interaction, interaction,
playerId, playerId,
@@ -412,7 +356,10 @@ async function resolveCookieHeist(
"heist", "heist",
result: result:
"win" "win",
profit:
amount
} }
); );
@@ -441,6 +388,30 @@ async function resolveCookieHeist(
); );
const [
playerAfter,
vaultAfter
] =
await Promise.all([
getCookieBalance(
playerId
),
getCookieBalance(
vaultId
)
]);
const actualPercentage =
(
amount /
vaultCookies
)
*
100;
const embed = const embed =
new EmbedBuilder() new EmbedBuilder()
.setTitle( .setTitle(
@@ -551,12 +522,6 @@ async function resolveCookieHeist(
} }
/*
* ========================================================
* FAILURE
* ========================================================
*/
const penaltyPercent = const penaltyPercent =
randomFloat( randomFloat(
method.minPenaltyPercent, method.minPenaltyPercent,
@@ -582,11 +547,6 @@ async function resolveCookieHeist(
try { try {
/*
* Heist fines always go into
* ShaiBot's central vault.
*/
await transferCookies( await transferCookies(
playerId, playerId,
vaultId, vaultId,
@@ -630,6 +590,38 @@ async function resolveCookieHeist(
} }
await trackProfileEvent(
interaction,
playerId,
{
type:
"game_result",
game:
"heist",
result:
"loss",
profit:
-penalty
}
);
await trackProfileEvent(
interaction,
playerId,
{
type:
"cookie_loss",
amount:
penalty
}
);
const [ const [
playerAfter, playerAfter,
vaultAfter vaultAfter
@@ -654,35 +646,6 @@ async function resolveCookieHeist(
100; 100;
await trackProfileEvent(
interaction,
playerId,
{
type:
"game_result",
game:
"heist",
result:
"loss"
}
);
await trackProfileEvent(
interaction,
playerId,
{
type:
"cookie_loss",
amount:
penalty
}
);
const embed = const embed =
new EmbedBuilder() new EmbedBuilder()
.setTitle( .setTitle(
@@ -793,12 +756,6 @@ async function resolveCookieHeist(
} }
/*
* ============================================================
* RETURN TO CRUMB SAUCER
* ============================================================
*/
function buildReturnButton() { function buildReturnButton() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
@@ -821,25 +778,9 @@ function buildReturnButton() {
} }
/*
* ============================================================
* SECURITY
* ============================================================
*/
function getVaultSecurityPenalty( function getVaultSecurityPenalty(
vaultCookies vaultCookies
) { ) {
/*
* Approximately:
*
* 100 -> 5%
* 1,000 -> 7.5%
* 10,000 -> 10%
* 100,000 -> 12.5%
* 1,000,000 -> 15%
*/
return Math.min( return Math.min(
Math.log10( Math.log10(
vaultCookies + vaultCookies +
@@ -853,12 +794,6 @@ function getVaultSecurityPenalty(
} }
/*
* ============================================================
* PROGRESS TEXT
* ============================================================
*/
function getHeistProgressText( function getHeistProgressText(
methodName methodName
) { ) {
@@ -866,6 +801,7 @@ function getHeistProgressText(
methodName methodName
) { ) {
case "Sneak": case "Sneak":
return [ return [
"🥷 You slip through the service entrance...", "🥷 You slip through the service entrance...",
"", "",
@@ -874,6 +810,7 @@ function getHeistProgressText(
case "Hack": case "Hack":
return [ return [
"💻 You connect to the vault terminal...", "💻 You connect to the vault terminal...",
"", "",
@@ -882,6 +819,7 @@ function getHeistProgressText(
case "Smash": case "Smash":
return [ return [
"💥 You charge directly at the vault...", "💥 You charge directly at the vault...",
"", "",
@@ -890,17 +828,12 @@ function getHeistProgressText(
default: default:
return "The heist is underway..."; return "The heist is underway...";
} }
} }
/*
* ============================================================
* SUCCESS TEXT
* ============================================================
*/
function getSuccessText( function getSuccessText(
methodName, methodName,
user, user,
@@ -914,24 +847,28 @@ function getSuccessText(
methodName methodName
) { ) {
case "Sneak": case "Sneak":
return ( return (
`🥷 ${user} slipped through security and quietly escaped with ${cookies}.` `🥷 ${user} slipped through security and quietly escaped with ${cookies}.`
); );
case "Hack": case "Hack":
return ( return (
`💻 ${user} convinced the vault terminal that ${cookies} belonged to them.` `💻 ${user} convinced the vault terminal that ${cookies} belonged to them.`
); );
case "Smash": case "Smash":
return ( return (
`💥 ${user} smashed through the Cookie Vault and escaped with ${cookies}!` `💥 ${user} smashed through the Cookie Vault and escaped with ${cookies}!`
); );
default: default:
return ( return (
`${user} escaped with ${cookies}.` `${user} escaped with ${cookies}.`
); );
@@ -939,12 +876,6 @@ function getSuccessText(
} }
/*
* ============================================================
* FAILURE TEXT
* ============================================================
*/
function getFailureText( function getFailureText(
methodName, methodName,
user, user,
@@ -958,6 +889,7 @@ function getFailureText(
methodName methodName
) { ) {
case "Sneak": case "Sneak":
return [ return [
`🚨 ${user} stepped directly on the world's loudest Cookie wrapper.`, `🚨 ${user} stepped directly on the world's loudest Cookie wrapper.`,
"", "",
@@ -966,6 +898,7 @@ function getFailureText(
case "Hack": case "Hack":
return [ return [
`🚨 ${user} entered the password incorrectly one too many times.`, `🚨 ${user} entered the password incorrectly one too many times.`,
"", "",
@@ -974,6 +907,7 @@ function getFailureText(
case "Smash": case "Smash":
return [ return [
`🚨 ${user} discovered that the vault door is significantly stronger than expected.`, `🚨 ${user} discovered that the vault door is significantly stronger than expected.`,
"", "",
@@ -982,6 +916,7 @@ function getFailureText(
default: default:
return ( return (
`The heist failed and cost ${user} ${cookies}.` `The heist failed and cost ${user} ${cookies}.`
); );
@@ -989,12 +924,6 @@ function getFailureText(
} }
/*
* ============================================================
* UTIL
* ============================================================
*/
function sleep( function sleep(
ms ms
) { ) {
@@ -1008,12 +937,6 @@ function sleep(
} }
/*
* ============================================================
* EXPORTS
* ============================================================
*/
module.exports = { module.exports = {
resolveCookieHeist, resolveCookieHeist,
HEIST_COOLDOWN HEIST_COOLDOWN
+21 -271
View File
@@ -22,12 +22,6 @@ const {
} = require("./profileSystem"); } = require("./profileSystem");
/*
* ============================================================
* CONFIG
* ============================================================
*/
const GAME_COLLECTION = const GAME_COLLECTION =
"items_games"; "items_games";
@@ -41,12 +35,6 @@ const BLACKJACK_BETS = [
]; ];
/*
* ============================================================
* SHOW BLACKJACK MENU
* ============================================================
*/
async function showBlackjack( async function showBlackjack(
interaction interaction
) { ) {
@@ -70,10 +58,6 @@ async function showBlackjack(
} }
/*
* Resume an unfinished game.
*/
const active = const active =
await getActiveBlackjack( await getActiveBlackjack(
interaction.user.id, interaction.user.id,
@@ -146,26 +130,12 @@ async function showBlackjack(
} }
/*
* ============================================================
* START GAME
* ============================================================
*/
async function startBlackjack( async function startBlackjack(
interaction, interaction,
bet, bet,
allowCustomBet = allowCustomBet =
false false
) { ) {
/*
* Normal wager buttons must use one of our
* predefined values.
*
* All In and Run It Back are allowed to use
* custom wager amounts.
*/
if ( if (
!allowCustomBet !allowCustomBet
&& &&
@@ -183,11 +153,6 @@ async function startBlackjack(
} }
/*
* Every wager, predefined or custom, must
* still be a positive safe integer.
*/
if ( if (
!Number.isSafeInteger( !Number.isSafeInteger(
bet bet
@@ -238,10 +203,6 @@ async function startBlackjack(
); );
/*
* Prevent multiple active hands.
*/
const existing = const existing =
await getActiveBlackjack( await getActiveBlackjack(
interaction.user.id, interaction.user.id,
@@ -297,18 +258,6 @@ async function startBlackjack(
} }
/*
* Natural Blackjack returns:
*
* wager + 3:2 winnings
*
* = 2.5x total payout.
*
* The wager will move into the house before
* resolution, so the house currently needs
* enough to cover the additional 1.5x.
*/
if ( if (
houseBalance < houseBalance <
Math.ceil( Math.ceil(
@@ -326,10 +275,6 @@ async function startBlackjack(
} }
/*
* Take wager.
*/
try { try {
await transferCookies( await transferCookies(
playerId, playerId,
@@ -356,12 +301,6 @@ async function startBlackjack(
} }
/*
* ========================================================
* REAL 52-CARD DECK
* ========================================================
*/
const deck = const deck =
createDeck(); createDeck();
@@ -426,12 +365,6 @@ async function startBlackjack(
); );
/*
* ========================================================
* NATURAL BLACKJACK CHECK
* ========================================================
*/
const playerBlackjack = const playerBlackjack =
isBlackjack( isBlackjack(
playerCards playerCards
@@ -448,10 +381,6 @@ async function startBlackjack(
playerBlackjack || playerBlackjack ||
dealerBlackjack dealerBlackjack
) { ) {
/*
* Both natural Blackjack = Push.
*/
if ( if (
playerBlackjack && playerBlackjack &&
dealerBlackjack dealerBlackjack
@@ -496,12 +425,6 @@ async function startBlackjack(
} }
/*
* ============================================================
* ALL IN
* ============================================================
*/
async function startBlackjackAllIn( async function startBlackjackAllIn(
interaction interaction
) { ) {
@@ -525,10 +448,6 @@ async function startBlackjackAllIn(
} }
/*
* Resolve balance at click time.
*/
const balance = const balance =
await getCookieBalance( await getCookieBalance(
interaction.user.id interaction.user.id
@@ -549,11 +468,6 @@ async function startBlackjackAllIn(
} }
/*
* true allows the balance to be used even though
* it likely isn't one of BLACKJACK_BETS.
*/
return startBlackjack( return startBlackjack(
interaction, interaction,
balance, balance,
@@ -562,16 +476,6 @@ async function startBlackjackAllIn(
} }
/*
* ============================================================
* RUN IT BACK
* ============================================================
*
* Reuses only the previous BET.
*
* A completely new shuffled deck and hand are created.
*/
async function runBlackjackBack( async function runBlackjackBack(
interaction interaction
) { ) {
@@ -588,12 +492,6 @@ async function runBlackjackBack(
[]; [];
/*
* Expected:
*
* crumbBlackjackResult:<userId>:<bet>
*/
if ( if (
parts[0] !== parts[0] !==
"crumbBlackjackResult" "crumbBlackjackResult"
@@ -650,13 +548,6 @@ async function runBlackjackBack(
} }
/*
* Reuse the exact previous wager.
*
* true is required because a previous All In bet
* may not be part of BLACKJACK_BETS.
*/
return startBlackjack( return startBlackjack(
interaction, interaction,
bet, bet,
@@ -665,12 +556,6 @@ async function runBlackjackBack(
} }
/*
* ============================================================
* HIT
* ============================================================
*/
async function hitBlackjack( async function hitBlackjack(
interaction interaction
) { ) {
@@ -748,13 +633,11 @@ async function hitBlackjack(
getCollections(); getCollections();
const games = await db
db.collection( .collection(
GAME_COLLECTION GAME_COLLECTION
); )
.updateOne(
await games.updateOne(
{ {
gameId: gameId:
game.gameId, game.gameId,
@@ -778,10 +661,6 @@ async function hitBlackjack(
}; };
/*
* Bust.
*/
if ( if (
score > score >
21 21
@@ -794,10 +673,6 @@ async function hitBlackjack(
} }
/*
* Automatically stand on exactly 21.
*/
if ( if (
score === score ===
21 21
@@ -824,12 +699,6 @@ async function hitBlackjack(
} }
/*
* ============================================================
* STAND
* ============================================================
*/
async function standBlackjack( async function standBlackjack(
interaction, interaction,
suppliedGame = suppliedGame =
@@ -884,10 +753,6 @@ async function standBlackjack(
]; ];
/*
* Dealer stands on 17.
*/
while ( while (
getHandValue( getHandValue(
dealerCards dealerCards
@@ -950,10 +815,6 @@ async function standBlackjack(
} }
/*
* Same value = Push.
*/
else { else {
result = result =
"Push"; "Push";
@@ -968,12 +829,6 @@ async function standBlackjack(
} }
/*
* ============================================================
* DOUBLE
* ============================================================
*/
async function doubleBlackjack( async function doubleBlackjack(
interaction interaction
) { ) {
@@ -1008,10 +863,6 @@ async function doubleBlackjack(
} }
/*
* Double only on initial two cards.
*/
if ( if (
game.playerCards.length !== game.playerCards.length !==
2 2
@@ -1086,10 +937,6 @@ async function doubleBlackjack(
]; ];
/*
* Double receives exactly one additional card.
*/
playerCards.push( playerCards.push(
deck.pop() deck.pop()
); );
@@ -1158,10 +1005,6 @@ async function doubleBlackjack(
} }
/*
* Double automatically stands.
*/
return standBlackjack( return standBlackjack(
interaction, interaction,
updated updated
@@ -1169,12 +1012,6 @@ async function doubleBlackjack(
} }
/*
* ============================================================
* FINISH GAME
* ============================================================
*/
async function finishBlackjack( async function finishBlackjack(
interaction, interaction,
game, game,
@@ -1188,11 +1025,9 @@ async function finishBlackjack(
0; 0;
switch (result) { switch (
/* result
* Normal win. ) {
*/
case "Win": case "Win":
case "DealerBust": case "DealerBust":
@@ -1203,12 +1038,6 @@ async function finishBlackjack(
break; break;
/*
* Push:
*
* Return exactly the wager.
*/
case "Push": case "Push":
payout = payout =
@@ -1217,12 +1046,6 @@ async function finishBlackjack(
break; break;
/*
* Natural Blackjack:
*
* stake + 3:2 winnings.
*/
case "Blackjack": case "Blackjack":
payout = payout =
@@ -1234,10 +1057,6 @@ async function finishBlackjack(
break; break;
/*
* Bust / Loss / Dealer Blackjack.
*/
default: default:
payout = payout =
@@ -1245,10 +1064,6 @@ async function finishBlackjack(
} }
/*
* Pay player.
*/
if ( if (
payout > payout >
0 0
@@ -1281,7 +1096,7 @@ async function finishBlackjack(
.setFooter({ .setFooter({
text: text:
`crumbBlackjackResult:${game.userId}:${game.originalBet ?? game.bet}` `crumbBlackjackResult:${game.userId}:${game.originalBet}`
}) })
], ],
@@ -1299,10 +1114,6 @@ async function finishBlackjack(
} }
/*
* Mark game finished.
*/
const { const {
db db
} = } =
@@ -1356,6 +1167,7 @@ async function finishBlackjack(
let profileResult; let profileResult;
if ( if (
result === result ===
"Push" "Push"
@@ -1364,6 +1176,7 @@ async function finishBlackjack(
"push"; "push";
} }
else if ( else if (
[ [
"Win", "Win",
@@ -1377,6 +1190,7 @@ async function finishBlackjack(
"win"; "win";
} }
else { else {
profileResult = profileResult =
"loss"; "loss";
@@ -1394,7 +1208,9 @@ async function finishBlackjack(
"blackjack", "blackjack",
result: result:
profileResult profileResult,
profit
} }
); );
@@ -1416,6 +1232,7 @@ async function finishBlackjack(
); );
} }
else if ( else if (
profit < profit <
0 0
@@ -1520,12 +1337,15 @@ async function finishBlackjack(
) )
/* /*
* Owner + previous exact wager. * Important:
*
* Run It Back uses the original wager,
* not the doubled total.
*/ */
.setFooter({ .setFooter({
text: text:
`crumbBlackjackResult:${game.userId}:${game.originalBet ?? game.bet}` `crumbBlackjackResult:${game.userId}:${game.originalBet}`
}); });
@@ -1540,12 +1360,6 @@ async function finishBlackjack(
} }
/*
* ============================================================
* GAME EMBED
* ============================================================
*/
function buildGameEmbed( function buildGameEmbed(
game game
) { ) {
@@ -1608,12 +1422,6 @@ function buildGameEmbed(
} }
/*
* ============================================================
* BET BUTTONS
* ============================================================
*/
function buildBetButtons() { function buildBetButtons() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
@@ -1624,18 +1432,14 @@ function buildBetButtons() {
.setCustomId( .setCustomId(
`crumbBlackjackBet${bet}` `crumbBlackjackBet${bet}`
) )
.setLabel( .setLabel(
bet.toLocaleString() bet.toLocaleString()
) )
.setEmoji( .setEmoji(
"🍪" "🍪"
) )
.setStyle( .setStyle(
bet >= bet >= 500
500
? ButtonStyle.Primary ? ButtonStyle.Primary
: ButtonStyle.Secondary : ButtonStyle.Secondary
) )
@@ -1676,12 +1480,6 @@ function buildBetButtons() {
} }
/*
* ============================================================
* ACTIVE GAME BUTTONS
* ============================================================
*/
function buildGameButtons( function buildGameButtons(
game game
) { ) {
@@ -1744,12 +1542,6 @@ function buildGameButtons(
} }
/*
* ============================================================
* RESULT BUTTONS
* ============================================================
*/
function buildResultButtons() { function buildResultButtons() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
@@ -1800,12 +1592,6 @@ function buildResultButtons() {
} }
/*
* ============================================================
* CARDS
* ============================================================
*/
function createDeck() { function createDeck() {
const suits = [ const suits = [
"♠️", "♠️",
@@ -1988,12 +1774,6 @@ function formatCards(
} }
/*
* ============================================================
* DATABASE LOOKUPS
* ============================================================
*/
async function getActiveBlackjack( async function getActiveBlackjack(
userId, userId,
guildId guildId
@@ -2072,12 +1852,6 @@ async function getGameFromInteraction(
} }
/*
* ============================================================
* SESSION OWNER
* ============================================================
*/
function getBlackjackSessionOwner( function getBlackjackSessionOwner(
interaction interaction
) { ) {
@@ -2110,12 +1884,6 @@ function getBlackjackSessionOwner(
} }
/*
* Active hand:
*
* crumbBlackjack:<gameId>:<userId>
*/
if ( if (
parts[0] === parts[0] ===
"crumbBlackjack" "crumbBlackjack"
@@ -2124,12 +1892,6 @@ function getBlackjackSessionOwner(
} }
/*
* Finished result:
*
* crumbBlackjackResult:<userId>:<bet>
*/
if ( if (
parts[0] === parts[0] ===
"crumbBlackjackResult" "crumbBlackjackResult"
@@ -2142,12 +1904,6 @@ function getBlackjackSessionOwner(
} }
/*
* ============================================================
* RESULT TITLES
* ============================================================
*/
function getResultTitle( function getResultTitle(
result result
) { ) {
@@ -2191,12 +1947,6 @@ function getResultTitle(
} }
/*
* ============================================================
* EXPORTS
* ============================================================
*/
module.exports = { module.exports = {
showBlackjack, showBlackjack,
+400
View File
@@ -0,0 +1,400 @@
const {
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle
} = require("discord.js");
const {
getCookieBalance,
changeCookies,
claimCooldown,
temporaryEphemeralReply
} = require("./cookieEconomy");
const {
trackProfileEvent
} = require("./profileSystem");
/*
* ============================================================
* CONFIG
* ============================================================
*/
const COLLECT_COOLDOWN =
60 * 60;
/*
* IMPORTANT:
*
* Always use exactly the same cooldown key.
*/
const COLLECT_COOLDOWN_KEY =
"collect";
/*
* ============================================================
* SHOW COLLECT PAGE
* ============================================================
*/
async function showCollectPage(
interaction
) {
const ownerId =
getCollectOwner(
interaction
);
if (
ownerId !==
interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
}
const balance =
await getCookieBalance(
interaction.user.id
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Crumb Saucer Cookie Bakery"
)
.setDescription(
[
"Fresh Cookies are prepared regularly.",
"",
"Press **Collect Cookies** to claim your batch.",
"",
"Your collection cooldown is shared with `/cookies collect`."
].join("\n")
)
.addFields({
name:
"Your Vault",
value:
`**${balance.toLocaleString()} 🍪**`
})
.setFooter({
text:
`crumbSaucerCollect:${interaction.user.id}`
});
return interaction.update({
embeds: [
embed
],
components:
buildCollectButtons()
});
}
/*
* ============================================================
* COLLECT
* ============================================================
*/
async function collectCookiesFromSaucer(
interaction
) {
const ownerId =
getCollectOwner(
interaction
);
if (
ownerId !==
interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
}
/*
* Mongo-backed cooldown.
*
* Same pattern as Cookie Heist:
*
* userId
* guildId
* cooldown key
* duration
*/
const cooldown =
await claimCooldown(
interaction.user.id,
interaction.guildId,
COLLECT_COOLDOWN_KEY,
COLLECT_COOLDOWN
);
/*
* Already collected.
*/
if (
!cooldown.claimed
) {
const balance =
await getCookieBalance(
interaction.user.id
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookie Bakery"
)
.setDescription(
[
"Your next batch isn't ready yet.",
"",
`Come back <t:${cooldown.next}:R>.`
].join("\n")
)
.addFields({
name:
"Your Vault",
value:
`**${balance.toLocaleString()} 🍪**`
})
.setFooter({
text:
`crumbSaucerCollect:${interaction.user.id}`
});
return interaction.update({
embeds: [
embed
],
components:
buildCollectButtons()
});
}
/*
* ========================================================
* GENERATE BATCH
* ========================================================
*/
const amount =
Math.floor(
Math.random() *
1000
)
+
1;
await changeCookies(
interaction.user.id,
amount
);
/*
* Profile / achievements.
*/
await trackProfileEvent(
interaction,
interaction.user.id,
{
type:
"cookie_gain",
amount
}
);
const balance =
await getCookieBalance(
interaction.user.id
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Fresh Batch!"
)
.setDescription(
[
`You collected **${amount.toLocaleString()} Cookies**! 🍪`,
"",
`Your next batch will be ready <t:${cooldown.next}:R>.`
].join("\n")
)
.addFields({
name:
"Your Vault",
value:
`**${balance.toLocaleString()} 🍪**`
})
.setFooter({
text:
`crumbSaucerCollect:${interaction.user.id}`
});
return interaction.update({
embeds: [
embed
],
components:
buildCollectButtons()
});
}
/*
* ============================================================
* BUTTONS
* ============================================================
*/
function buildCollectButtons() {
return [
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"crumbSaucerCollect"
)
.setLabel(
"Collect Cookies"
)
.setEmoji(
"🍪"
)
.setStyle(
ButtonStyle.Success
),
new ButtonBuilder()
.setCustomId(
"crumbSaucerBack"
)
.setLabel(
"Crumb Saucer"
)
.setEmoji(
"◀️"
)
.setStyle(
ButtonStyle.Secondary
)
)
];
}
/*
* ============================================================
* OWNER
* ============================================================
*/
function getCollectOwner(
interaction
) {
const footer =
interaction.message
?.embeds?.[0]
?.footer
?.text;
const parts =
footer?.split(":")
??
[];
/*
* Main menu -> Cookie Bakery.
*/
if (
parts[0] ===
"crumbSaucer"
) {
return parts[1];
}
/*
* Bakery page/result.
*/
if (
parts[0] ===
"crumbSaucerCollect"
) {
return parts[1];
}
return null;
}
/*
* ============================================================
* EXPORTS
* ============================================================
*/
module.exports = {
showCollectPage,
collectCookiesFromSaucer
};
+8 -224
View File
@@ -16,12 +16,6 @@ const {
} = require("./profileSystem"); } = require("./profileSystem");
/*
* ============================================================
* CONFIG
* ============================================================
*/
const ROULETTE_BETS = [ const ROULETTE_BETS = [
10, 10,
50, 50,
@@ -64,10 +58,6 @@ const ROULETTE_CHOICES = {
}; };
/*
* European Roulette red numbers.
*/
const RED_NUMBERS = const RED_NUMBERS =
new Set([ new Set([
1, 1,
@@ -91,12 +81,6 @@ const RED_NUMBERS =
]); ]);
/*
* ============================================================
* SHOW ROULETTE
* ============================================================
*/
async function showRoulette( async function showRoulette(
interaction interaction
) { ) {
@@ -171,12 +155,6 @@ async function showRoulette(
} }
/*
* ============================================================
* CHOOSE NORMAL WAGER
* ============================================================
*/
async function chooseRouletteBet( async function chooseRouletteBet(
interaction, interaction,
bet bet
@@ -223,12 +201,6 @@ async function chooseRouletteBet(
} }
/*
* ============================================================
* ALL IN
* ============================================================
*/
async function startRouletteAllIn( async function startRouletteAllIn(
interaction interaction
) { ) {
@@ -252,10 +224,6 @@ async function startRouletteAllIn(
} }
/*
* Resolve the balance at click time.
*/
const balance = const balance =
await getCookieBalance( await getCookieBalance(
interaction.user.id interaction.user.id
@@ -276,13 +244,6 @@ async function startRouletteAllIn(
} }
/*
* All In only selects the wager.
*
* Nothing is transferred until the player
* chooses Red / Black / Odd / Even / Zero.
*/
return showRouletteChoices( return showRouletteChoices(
interaction, interaction,
balance balance
@@ -290,18 +251,6 @@ async function startRouletteAllIn(
} }
/*
* ============================================================
* SHOW CHOICE MENU
* ============================================================
*
* Shared by:
*
* - normal wager buttons
* - All In
* - Run It Back
*/
async function showRouletteChoices( async function showRouletteChoices(
interaction, interaction,
bet bet
@@ -389,16 +338,6 @@ async function showRouletteChoices(
} }
/*
* ============================================================
* RUN IT BACK
* ============================================================
*
* Remembers ONLY the previous wager.
*
* It does NOT remember Red / Black / Odd / Even / Zero.
*/
async function runRouletteBack( async function runRouletteBack(
interaction interaction
) { ) {
@@ -415,12 +354,6 @@ async function runRouletteBack(
[]; [];
/*
* Expected:
*
* crumbRouletteResult:<userId>:<bet>
*/
if ( if (
parts[0] !== parts[0] !==
"crumbRouletteResult" "crumbRouletteResult"
@@ -477,10 +410,6 @@ async function runRouletteBack(
} }
/*
* Same wager, fresh choice.
*/
return showRouletteChoices( return showRouletteChoices(
interaction, interaction,
bet bet
@@ -488,12 +417,6 @@ async function runRouletteBack(
} }
/*
* ============================================================
* PLAY ROULETTE
* ============================================================
*/
async function playRoulette( async function playRoulette(
interaction, interaction,
choiceKey choiceKey
@@ -566,15 +489,6 @@ async function playRoulette(
} }
/*
* IMPORTANT:
*
* Do NOT require ROULETTE_BETS.includes(bet) here.
*
* All In and Run It Back may legitimately use
* amounts outside the predefined wager buttons.
*/
if ( if (
!Number.isSafeInteger( !Number.isSafeInteger(
bet bet
@@ -616,11 +530,6 @@ async function playRoulette(
]); ]);
/*
* Balance may have changed while the player
* was choosing their bet type.
*/
if ( if (
playerBalance < playerBalance <
bet bet
@@ -635,20 +544,6 @@ async function playRoulette(
} }
/*
* Worst payout depends on the selected wager.
*
* Red / Black / Odd / Even:
* x2
*
* Zero:
* x36
*
* The player's wager is transferred to the house
* before payout, so houseBalance + bet is available
* after accepting the wager.
*/
const maximumPayout = const maximumPayout =
bet * bet *
choice.multiplier; choice.multiplier;
@@ -669,12 +564,6 @@ async function playRoulette(
} }
/*
* ========================================================
* TAKE WAGER
* ========================================================
*/
try { try {
await transferCookies( await transferCookies(
playerId, playerId,
@@ -701,12 +590,6 @@ async function playRoulette(
} }
/*
* ========================================================
* PSEUDO ANIMATION
* ========================================================
*/
await interaction.update({ await interaction.update({
embeds: [ embeds: [
buildSpinEmbed( buildSpinEmbed(
@@ -764,12 +647,6 @@ async function playRoulette(
); );
/*
* ========================================================
* ACTUAL RESULT
* ========================================================
*/
const result = const result =
randomRouletteNumber(); randomRouletteNumber();
@@ -788,12 +665,6 @@ async function playRoulette(
: 0; : 0;
/*
* ========================================================
* PAYOUT
* ========================================================
*/
if ( if (
payout > payout >
0 0
@@ -871,7 +742,9 @@ async function playRoulette(
? "win" ? "win"
: profit < 0 : profit < 0
? "loss" ? "loss"
: "push" : "push",
profit
} }
); );
@@ -893,6 +766,7 @@ async function playRoulette(
); );
} }
else if ( else if (
profit < profit <
0 0
@@ -992,13 +866,6 @@ async function playRoulette(
} }
) )
/*
* Remember only:
*
* owner
* wager
*/
.setFooter({ .setFooter({
text: text:
`crumbRouletteResult:${playerId}:${bet}` `crumbRouletteResult:${playerId}:${bet}`
@@ -1016,17 +883,13 @@ async function playRoulette(
} }
/*
* ============================================================
* RESULT LOGIC
* ============================================================
*/
function didRouletteBetWin( function didRouletteBetWin(
choice, choice,
number number
) { ) {
switch (choice) { switch (
choice
) {
case "Red": case "Red":
return ( return (
@@ -1090,12 +953,6 @@ function didRouletteBetWin(
} }
/*
* ============================================================
* DISPLAY
* ============================================================
*/
function buildSpinEmbed( function buildSpinEmbed(
choice, choice,
bet, bet,
@@ -1167,12 +1024,6 @@ function formatRouletteNumber(
} }
/*
* ============================================================
* WAGER BUTTONS
* ============================================================
*/
function buildBetButtons() { function buildBetButtons() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
@@ -1183,18 +1034,14 @@ function buildBetButtons() {
.setCustomId( .setCustomId(
`crumbRouletteBet${bet}` `crumbRouletteBet${bet}`
) )
.setLabel( .setLabel(
bet.toLocaleString() bet.toLocaleString()
) )
.setEmoji( .setEmoji(
"🍪" "🍪"
) )
.setStyle( .setStyle(
bet >= bet >= 500
500
? ButtonStyle.Primary ? ButtonStyle.Primary
: ButtonStyle.Secondary : ButtonStyle.Secondary
) )
@@ -1235,12 +1082,6 @@ function buildBetButtons() {
} }
/*
* ============================================================
* CHOICE BUTTONS
* ============================================================
*/
function buildChoiceButtons() { function buildChoiceButtons() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
@@ -1330,22 +1171,10 @@ function buildChoiceButtons() {
} }
/*
* ============================================================
* RESULT BUTTONS
* ============================================================
*/
function buildResultButtons() { function buildResultButtons() {
return [ return [
new ActionRowBuilder() new ActionRowBuilder()
.addComponents( .addComponents(
/*
* Same wager.
*
* Player chooses Red/Black/etc again.
*/
new ButtonBuilder() new ButtonBuilder()
.setCustomId( .setCustomId(
"crumbRouletteRunItBack" "crumbRouletteRunItBack"
@@ -1360,10 +1189,6 @@ function buildResultButtons() {
ButtonStyle.Success ButtonStyle.Success
), ),
/*
* Return to wager selection.
*/
new ButtonBuilder() new ButtonBuilder()
.setCustomId( .setCustomId(
"crumbSaucerRoulette" "crumbSaucerRoulette"
@@ -1378,10 +1203,6 @@ function buildResultButtons() {
ButtonStyle.Primary ButtonStyle.Primary
), ),
/*
* Main Crumb Saucer.
*/
new ButtonBuilder() new ButtonBuilder()
.setCustomId( .setCustomId(
"crumbSaucerBack" "crumbSaucerBack"
@@ -1400,12 +1221,6 @@ function buildResultButtons() {
} }
/*
* ============================================================
* SESSION OWNER
* ============================================================
*/
function getRouletteSessionOwner( function getRouletteSessionOwner(
interaction interaction
) { ) {
@@ -1422,10 +1237,6 @@ function getRouletteSessionOwner(
[]; [];
/*
* Main Saucer
*/
if ( if (
parts[0] === parts[0] ===
"crumbSaucer" "crumbSaucer"
@@ -1434,10 +1245,6 @@ function getRouletteSessionOwner(
} }
/*
* Roulette wager menu
*/
if ( if (
parts[0] === parts[0] ===
"crumbRouletteMenu" "crumbRouletteMenu"
@@ -1446,10 +1253,6 @@ function getRouletteSessionOwner(
} }
/*
* Roulette choice menu.
*/
if ( if (
parts[0] === parts[0] ===
"crumbRouletteBet" "crumbRouletteBet"
@@ -1458,10 +1261,6 @@ function getRouletteSessionOwner(
} }
/*
* Roulette result.
*/
if ( if (
parts[0] === parts[0] ===
"crumbRouletteResult" "crumbRouletteResult"
@@ -1474,12 +1273,6 @@ function getRouletteSessionOwner(
} }
/*
* ============================================================
* RANDOM
* ============================================================
*/
function randomRouletteNumber() { function randomRouletteNumber() {
return Math.floor( return Math.floor(
Math.random() * Math.random() *
@@ -1501,20 +1294,11 @@ function sleep(
} }
/*
* ============================================================
* EXPORTS
* ============================================================
*/
module.exports = { module.exports = {
showRoulette, showRoulette,
chooseRouletteBet, chooseRouletteBet,
startRouletteAllIn, startRouletteAllIn,
showRouletteChoices, showRouletteChoices,
playRoulette, playRoulette,
runRouletteBack runRouletteBack
}; };
+238 -63
View File
@@ -10,115 +10,271 @@ const {
temporaryEphemeralReply temporaryEphemeralReply
} = require("./cookieEconomy"); } = require("./cookieEconomy");
async function showCrumbSaucer(interaction) {
if (interaction.isButton()) { /*
const ownerId = getCrumbSaucerSessionOwner(interaction); * ============================================================
if (ownerId !== interaction.user.id) { * MAIN MENU
return temporaryEphemeralReply(interaction, { * ============================================================
content: "This isn't your Crumb Saucer session." */
});
async function showCrumbSaucer(
interaction
) {
/*
* Button navigation must preserve ownership.
*/
if (
interaction.isButton()
) {
const ownerId =
getCrumbSaucerSessionOwner(
interaction
);
if (
ownerId !==
interaction.user.id
) {
return temporaryEphemeralReply(
interaction,
{
content:
"This isn't your Crumb Saucer session."
}
);
} }
} }
const balance = await getCookieBalance(interaction.user.id);
const embed = buildMainMenuEmbed(interaction.user, balance);
const components = buildMainMenuButtons();
if (interaction.isButton()) { const balance =
await getCookieBalance(
interaction.user.id
);
const embed =
buildMainMenuEmbed(
interaction.user,
balance
);
if (
interaction.isButton()
) {
return interaction.update({ return interaction.update({
embeds: [embed], embeds: [
components embed
],
components:
buildMainMenuButtons()
}); });
} }
return interaction.reply({ return interaction.reply({
embeds: [embed], embeds: [
components embed
],
components:
buildMainMenuButtons()
}); });
} }
function buildMainMenuEmbed(user, balance) {
/*
* ============================================================
* MAIN MENU EMBED
* ============================================================
*/
function buildMainMenuEmbed(
user,
balance
) {
return new EmbedBuilder() return new EmbedBuilder()
.setTitle("🎰 The Crumb Saucer") .setTitle(
.setDescription([ "🎰 The Crumb Saucer"
)
.setDescription(
[
`Welcome, ${user}!`, `Welcome, ${user}!`,
"", "",
"Where Cookies are won, lost,", "Where Cookies are won, lost,",
"and occasionally confiscated by ShaiBot.", "and occasionally confiscated by ShaiBot.",
"", "",
"### 🍪 Cookie Bakery",
"Collect your regular batch of Cookies.",
"",
"### 🎰 Slots", "### 🎰 Slots",
"Spin the reels and chase the Cookie jackpot.", "Spin the reels and chase the Cookie jackpot.",
"", "",
"### 🏦 Vault Heist",
"Attempt to breach ShaiBot's Cookie Vault.",
"",
"### 🃏 Blackjack", "### 🃏 Blackjack",
"Beat the dealer without going over 21.", "Beat the dealer without going over 21.",
"", "",
"### 🎡 Roulette", "### 🎡 Roulette",
"Bet on Red, Black, Odd, Even, or Zero.", "Bet on Red, Black, Odd, Even, or Zero.",
"", "",
"*More attractions coming soon... make a lala offering or buy me a white monster*" "### 🏦 Vault Heist",
].join("\n")) "Attempt to breach ShaiBot's Cookie Vault."
].join("\n")
)
.addFields({ .addFields({
name: "Your Vault", name:
value: `**${balance.toLocaleString()} 🍪**` "Your Vault",
value:
`**${balance.toLocaleString()} 🍪**`
}) })
.setFooter({ .setFooter({
text: `crumbSaucer:${user.id}` text:
`crumbSaucer:${user.id}`
}); });
} }
/*
* ============================================================
* MAIN MENU BUTTONS
* ============================================================
*/
function buildMainMenuButtons() { function buildMainMenuButtons() {
return [ return [
new ActionRowBuilder().addComponents( new ActionRowBuilder()
.addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerSlots") .setCustomId(
.setLabel("Slots") "crumbSaucerSlots"
.setEmoji("🎰") )
.setStyle(ButtonStyle.Primary), .setLabel(
"Slots"
new ButtonBuilder() )
.setCustomId("crumbSaucerBlackjack") .setEmoji(
.setLabel("Blackjack") "🎰"
.setEmoji("🃏") )
.setStyle(ButtonStyle.Success), .setStyle(
ButtonStyle.Primary
new ButtonBuilder()
.setCustomId("crumbSaucerRoulette")
.setLabel("Roulette")
.setEmoji("🎡")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId("crumbSaucerHeist")
.setLabel("Vault Heist")
.setEmoji("🏦")
.setStyle(ButtonStyle.Danger)
), ),
new ActionRowBuilder().addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId("abort") .setCustomId(
.setLabel("Close") "crumbSaucerBlackjack"
.setEmoji("✖️") )
.setStyle(ButtonStyle.Secondary), .setLabel(
"Blackjack"
)
.setEmoji(
"🃏"
)
.setStyle(
ButtonStyle.Success
),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("crumbSaucerCollect") .setCustomId(
.setLabel("Collect") "crumbSaucerRoulette"
.setEmoji("🍪") )
.setStyle(ButtonStyle.Success) .setLabel(
"Roulette"
)
.setEmoji(
"🎡"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder()
.setCustomId(
"crumbSaucerHeist"
)
.setLabel(
"Vault Heist"
)
.setEmoji(
"🏦"
)
.setStyle(
ButtonStyle.Danger
)
),
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"crumbSaucerCollectMenu"
)
.setLabel(
"Cookie Bakery"
)
.setEmoji(
"🍪"
)
.setStyle(
ButtonStyle.Success
),
/*
* Do NOT use global "abort" here.
*
* The dedicated handler validates
* Crumb Saucer ownership first.
*/
new ButtonBuilder()
.setCustomId(
"crumbSaucerClose"
)
.setLabel(
"Close"
)
.setEmoji(
"✖️"
)
.setStyle(
ButtonStyle.Secondary
)
) )
]; ];
} }
function getCrumbSaucerSessionOwner(interaction) {
const footer = interaction.message?.embeds?.[0]?.footer?.text;
const parts = footer?.split(":") ?? [];
switch (parts[0]) { /*
* ============================================================
* SESSION OWNER
* ============================================================
*/
function getCrumbSaucerSessionOwner(
interaction
) {
const footer =
interaction.message
?.embeds?.[0]
?.footer
?.text;
const parts =
footer?.split(":")
??
[];
switch (
parts[0]
) {
case "crumbSaucer": case "crumbSaucer":
case "crumbSaucerCollect":
case "crumbSaucerSlots": case "crumbSaucerSlots":
case "crumbSaucerSlotsResult": case "crumbSaucerSlotsResult":
case "crumbSaucerBlackjack": case "crumbSaucerBlackjack":
@@ -127,16 +283,35 @@ function getCrumbSaucerSessionOwner(interaction) {
case "crumbRouletteBet": case "crumbRouletteBet":
case "crumbRouletteResult": case "crumbRouletteResult":
case "cookieHeist": case "cookieHeist":
return parts[1]; return parts[1];
/*
* Active Blackjack:
*
* crumbBlackjack:<gameId>:<userId>
*/
case "crumbBlackjack": case "crumbBlackjack":
return parts[2]; return parts[2];
default: default:
return null; return null;
} }
} }
/*
* ============================================================
* EXPORTS
* ============================================================
*/
module.exports = { module.exports = {
showCrumbSaucer showCrumbSaucer,
getCrumbSaucerSessionOwner
}; };
+875 -179
View File
File diff suppressed because it is too large Load Diff
+2234 -401
View File
File diff suppressed because it is too large Load Diff