├── .eslintignore ├── .replit ├── src ├── util │ ├── admin.ts │ ├── data │ │ ├── boosts │ │ │ ├── luckboost.ts │ │ │ ├── boosts-shop.ts │ │ │ ├── spells.ts │ │ │ └── boosts.ts │ │ ├── events.ts │ │ ├── social-media.ts │ │ ├── craft.ts │ │ ├── drops.ts │ │ ├── item.ts │ │ ├── trivia.ts │ │ ├── quests.ts │ │ ├── shop.ts │ │ └── open-item.ts │ ├── database │ │ ├── banned-user.ts │ │ ├── database.ts │ │ ├── boosts.ts │ │ ├── genschema.ts │ │ └── pseudo.ts │ ├── guide.ts │ ├── levels.ts │ ├── util.ts │ └── format.ts ├── cmd │ ├── admin │ │ ├── echo.ts │ │ ├── deploy-commands.ts │ │ ├── database │ │ │ ├── update-db.ts │ │ │ ├── backup-db.ts │ │ │ ├── load-backup.ts │ │ │ ├── save-db.ts │ │ │ ├── README.md │ │ │ ├── database-maintain.ts │ │ │ ├── daily-maintain.ts │ │ │ ├── reset-user.ts │ │ │ └── set-user.ts │ │ ├── lock-down.ts │ │ ├── season1.ts │ │ ├── topgg-data.ts │ │ └── eval │ │ │ ├── eval-docs.ts │ │ │ └── eval.ts │ ├── shop │ │ ├── give.ts │ │ ├── spells.ts │ │ ├── social-media.ts │ │ ├── use-spell.ts │ │ ├── shop.ts │ │ ├── craft.ts │ │ ├── use-media.ts │ │ └── buy.ts │ ├── template.txt │ ├── cmd.ts │ ├── misc │ │ ├── coinflip.ts │ │ ├── dice-roll.ts │ │ └── whois.ts │ ├── game │ │ ├── vote.ts │ │ ├── daily.ts │ │ ├── code.ts │ │ └── post.ts │ ├── meta │ │ ├── version.ts │ │ ├── invite.ts │ │ ├── uptime.ts │ │ ├── feedback.ts │ │ ├── guide.ts │ │ └── delete-acc.ts │ ├── stats │ │ ├── info.ts │ │ ├── inv.ts │ │ ├── leaderboard.ts │ │ └── prof.ts │ ├── quests │ │ ├── quest.ts │ │ └── newquest.ts │ ├── sell │ │ ├── use.ts │ │ └── sell.ts │ └── minigame │ │ ├── casino.ts │ │ └── trivia.ts ├── idle.ts ├── cmd-parser.ts ├── README.md ├── server.ts ├── slashutil │ └── deploy-commands.ts ├── loader.ts ├── global.ts └── index.ts ├── img ├── cycle.png └── cycle-trans.png ├── update.sh ├── .vscode ├── settings.json └── extensions.json ├── .gitignore ├── .github ├── workflows │ └── main.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── tsconfig.json ├── LICENSE ├── package.json ├── CONTRIBUTING.md ├── .eslintrc.cjs └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | run = "npm run prod ; npm start" -------------------------------------------------------------------------------- /src/util/admin.ts: -------------------------------------------------------------------------------- 1 | export default ["456220387148169236", "636666248226275328"]; -------------------------------------------------------------------------------- /img/cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cursorweb/Cycle-Bot-Game/HEAD/img/cycle.png -------------------------------------------------------------------------------- /img/cycle-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cursorweb/Cycle-Bot-Game/HEAD/img/cycle-trans.png -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | # just for reference 2 | git fetch --all 3 | git reset --hard origin/master 4 | npm i 5 | npm run prod -------------------------------------------------------------------------------- /src/util/data/boosts/luckboost.ts: -------------------------------------------------------------------------------- 1 | export interface LBoost { 2 | name: string; 3 | description: string; 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.detectIndentation": false, 4 | "files.eol": "\n" 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # generated 2 | node_modules 3 | dist 4 | database.json 5 | 6 | # keys 7 | sdk-key.json 8 | .env 9 | run-command.txt 10 | -------------------------------------------------------------------------------- /src/util/database/banned-user.ts: -------------------------------------------------------------------------------- 1 | export const bannedUsers = [ 2 | "1080313902803460166", // sussy 3 | "812125215051743282" // anti... 4 | ]; -------------------------------------------------------------------------------- /src/util/data/events.ts: -------------------------------------------------------------------------------- 1 | export interface EventItm { 2 | month: Date; 3 | text: string; // the text to be displayed 4 | boost?: number; // the boost (defaults to 10) 5 | category: "tpc" | "tpm" | "cpp"; // the category 6 | } -------------------------------------------------------------------------------- /src/util/data/boosts/boosts-shop.ts: -------------------------------------------------------------------------------- 1 | import { BoostEnum } from "./boosts.js"; 2 | 3 | // SB = Shop Boost 4 | export interface SBItem { 5 | cost: number | string; 6 | ref: BoostEnum; 7 | } 8 | 9 | export const boostShop: SBItem[] = [{ 10 | cost: 10, 11 | ref: BoostEnum.SugarHigh 12 | }, { 13 | cost: 10, 14 | ref: BoostEnum.StylishShirts 15 | }]; -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint" 8 | ], 9 | } -------------------------------------------------------------------------------- /src/util/data/social-media.ts: -------------------------------------------------------------------------------- 1 | export const socialMedia = [ 2 | "Coding Club", 3 | "SMS", 4 | "Slack", 5 | "Discord", 6 | "Repl.it", 7 | "Glitch.com", 8 | "GitHub", 9 | "Dev.To", 10 | "YouTube", 11 | "Twitch", 12 | "Instagram", 13 | "Twitter", 14 | "Facebook", 15 | "Reddit", 16 | "Vimeo", 17 | "Hulu", 18 | "Netflix", 19 | "TikTok" 20 | ]; -------------------------------------------------------------------------------- /src/cmd/admin/echo.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["echo", "mimic", "repeat"]; 6 | help = "Repeats what you say"; 7 | examples = ["echo hi"]; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 13 | msg.channel.send(args.join(" ")); 14 | } 15 | } 16 | 17 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/shop/give.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as Discord from "discord.js"; 3 | import { Command, Colors } from "../../global.js"; 4 | 5 | class C extends Command { 6 | names = ["give", "g"]; 7 | help = "Give someone an item!"; 8 | examples = ["give @Coder100 apple"]; 9 | 10 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 11 | msg.channel.send("Coming... SOON!"); 12 | } 13 | } 14 | 15 | export const c = new C(); -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Push Check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v1 11 | with: 12 | node-version: "16" 13 | 14 | - name: Install packages 15 | run: npm ci 16 | 17 | - name: Build the project 18 | run: npm run prod 19 | 20 | - name: Eslint check 21 | run: npm run eslint 22 | -------------------------------------------------------------------------------- /src/cmd/template.txt: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database } from "../../global.js"; 4 | 5 | class C extends Command { 6 | names = [""]; 7 | help = ""; 8 | examples = [""]; 9 | isGame = "" as const; 10 | 11 | // don't forget to change! 12 | isAdmin = true; 13 | 14 | get cooldown() { return ; } 15 | 16 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 17 | 18 | } 19 | } 20 | 21 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/admin/deploy-commands.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import * as Discord from "discord.js"; 3 | import { Command, Colors, Bot, Database } from "../../global.js"; 4 | 5 | class C extends Command { 6 | names = ["admin-deploy-commands"]; 7 | help = "Deploy the slash commands."; 8 | examples = ["admin-deploy-commands"]; 9 | isGame = "n" as const; 10 | 11 | isAdmin = true; 12 | 13 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 14 | msg.channel.send("done!"); 15 | } 16 | } 17 | 18 | export const c = new C(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2020", "es2020.promise", "es2020.bigint", "es2020.string"], 4 | "module": "esnext", 5 | "target": "ES2020", 6 | 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | 15 | "outDir": "dist", 16 | "removeComments": true 17 | }, 18 | 19 | "include": ["src/**/*", "src/**/*.md"], 20 | "exclude": ["node_modules", "**/*.spec.ts"] 21 | } -------------------------------------------------------------------------------- /src/cmd/cmd.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | admin: "Admin commands only those whitelisted to the bot (like the developer) can access.", 3 | game: "This the main commands for gameplay, like **coding** and **posting**", 4 | minigame: "Small games to get easy cycles!", 5 | meta: "These are commands about the *bot*, like the current version.", 6 | misc: "These are miscellaneous commands not related to the Cycle game.", 7 | sell: "These are commands for selling/using items.", 8 | shop: "These are \"economy\" commands, like shopping and trading.", 9 | stats: "View your statistics, your quests, and the leaderboard.", 10 | quests: "Quests!" 11 | }; -------------------------------------------------------------------------------- /src/cmd/misc/coinflip.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, hidden, randomChoice } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["coinflip", "coin-flip"]; 6 | help = "Flip a coin!"; 7 | examples = ["coinflip", "coin-flip"]; 8 | isGame = "n" as const; 9 | 10 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 11 | msg.channel.send({ 12 | embeds: [{ 13 | color: Colors.PRIMARY, 14 | title: "Coin flip!", 15 | description: `You flipped a coin, and got ${hidden(randomChoice(["heads", "tails"])[0])}!` 16 | }] 17 | }); 18 | } 19 | } 20 | 21 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/guide.ts: -------------------------------------------------------------------------------- 1 | export type TermItem = { 2 | name: string, 3 | desc: string 4 | }; 5 | 6 | export const terms: TermItem[] = [{ 7 | name: "Text", 8 | desc: "Text can be posted to give you cycles! Use `&c` to get text!" 9 | }, { 10 | name: "Cycles", 11 | desc: "The primary purpose of the game, get as much of this as possible!" 12 | }, { 13 | name: "Chests", 14 | desc: "You have a chance of getting chests while coding, and they give you **items**! Use `&info` to see all items!" 15 | }, { 16 | name: "Posting", 17 | desc: "Use `&p` to post your text to get cycles!" 18 | }, { 19 | name: "Shop", 20 | desc: "The shop contains things you can buy to get cycles faster!" 21 | }]; -------------------------------------------------------------------------------- /src/cmd/admin/database/update-db.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Database } from "../../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-fetch", "admin-update-db"]; 6 | help = "'git fetch' from firebase."; 7 | isGame = "n" as const; 8 | 9 | isAdmin = true; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | Database.update().then(() => { 13 | msg.channel.send({ 14 | embeds: [{ 15 | color: Colors.SUCCESS, 16 | title: "Success!", 17 | description: "Successfully **updated** the database." 18 | }] 19 | }); 20 | }); 21 | } 22 | } 23 | 24 | export const c = new C(); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/cmd/admin/database/backup-db.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Database } from "../../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-stash", "admin-backup-db"]; 6 | help = "Backs up the database"; 7 | isGame = "n" as const; 8 | 9 | isAdmin = true; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | Database.saveBackup(); 13 | msg.channel.send({ 14 | embeds: [{ 15 | color: Colors.SUCCESS, 16 | title: "Successfully backed-up the database!", 17 | footer: { text: "Note: Use restore to actually restore the database." } 18 | }] 19 | }); 20 | } 21 | } 22 | 23 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/game/vote.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["vote", "v"]; 6 | help = "Vote for the bot <3"; 7 | isGame = "p" as const; 8 | 9 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 10 | msg.channel.send({ 11 | embeds: [{ 12 | color: Colors.PRIMARY, 13 | title: "Vote Link!", 14 | description: `**Vote rewards**: Cycles!! 15 | **Vote Link: ** [here](https://top.gg/bot/781939317450342470/vote)`, 16 | footer: { 17 | text: "You will be DMed once your vote is processed!" 18 | } 19 | }] 20 | }); 21 | } 22 | } 23 | 24 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/admin/database/load-backup.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Database } from "../../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-restore", "admin-load-backup"]; 6 | help = "Loads up the backup DB."; 7 | isGame = "n" as const; 8 | 9 | isAdmin = true; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | Database.updateBackup(); 13 | msg.channel.send({ 14 | embeds: [{ 15 | color: Colors.SUCCESS, 16 | title: "Successfully loaded the backup the database!", 17 | footer: { text: "Note: Use push to actually save the backup to main." } 18 | }] 19 | }); 20 | } 21 | } 22 | 23 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/data/boosts/spells.ts: -------------------------------------------------------------------------------- 1 | import { BoostEnum } from "./boosts.js"; 2 | 3 | export interface SpellItm { 4 | success: number; 5 | drops: BoostEnum; 6 | } 7 | /** 8 | * Every spell is actually a pointer to a boost, so nothing new there! 9 | * To get meta, we will need to follow the pointer, but that shouldn't be too slow. 10 | */ 11 | export const spells: SpellItm[] = [{ 12 | success: 50, 13 | drops: BoostEnum.Coinflip 14 | }, { 15 | success: 10, 16 | drops: BoostEnum.SOQues 17 | }, { 18 | success: 80, 19 | drops: BoostEnum.FriendDM 20 | }, { 21 | success: 25, 22 | drops: BoostEnum.GambleFever 23 | }, { 24 | success: 20, 25 | drops: BoostEnum.ClassicSpell 26 | }, { 27 | success: 60, 28 | drops: BoostEnum.SpellingBee 29 | }]; -------------------------------------------------------------------------------- /src/idle.ts: -------------------------------------------------------------------------------- 1 | import Big from "bignumber.js"; 2 | import { Database } from "./global.js"; 3 | import { boosts } from "./util/data/boosts/boosts.js"; 4 | 5 | setInterval(() => { 6 | for (const id in Database.pdb) { 7 | const user = Database.getUser(id); 8 | const userBoosts = Database.Boost.getUser(id); 9 | let tpm = new Big(user.tpm); 10 | if (tpm.eq(0)) continue; 11 | 12 | for (const index in userBoosts) { 13 | const itm = boosts[index]; 14 | if (!itm.tpm) continue; 15 | const amt = userBoosts[index].length; 16 | tpm = tpm.times(new Big(itm.tpm).plus(100).div(100)).times(amt).dp(0); 17 | } 18 | 19 | let text = new Big(user.text); 20 | text = text.plus(tpm); 21 | user.text = text.toString(); 22 | } 23 | }, 6e4); // 1 minute -------------------------------------------------------------------------------- /src/cmd/admin/lock-down.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, toggleLockDown, brackets } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-lock-down", "admin-ld"]; 6 | help = "Lock down the bot in case something goes wrong!"; 7 | examples = ["admin-lock-down"]; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 13 | const result = toggleLockDown(); 14 | 15 | msg.channel.send({ 16 | embeds: [{ 17 | color: Colors.SUCCESS, 18 | title: "Success!", 19 | description: `Successfully ${brackets(result ? "Locked down" : "Unlocked")} the bot!` 20 | }] 21 | }); 22 | } 23 | } 24 | 25 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/database/database.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs"; 2 | import { URL } from "node:url"; 3 | import admin from "firebase-admin"; 4 | 5 | if (process.env.FIREBASE) { 6 | admin.initializeApp({ 7 | credential: admin.credential.cert(JSON.parse(process.env.FIREBASE)) 8 | }); 9 | } else { 10 | const serviceAccount: admin.ServiceAccount = JSON.parse(fs.readFileSync(new URL("../../../sdk-key.json", import.meta.url), "utf8")); // i couldn't think of a better way lol 11 | 12 | admin.initializeApp({ 13 | credential: admin.credential.cert(serviceAccount) 14 | }); 15 | } 16 | 17 | export const db = admin.firestore(); 18 | db.settings({ ignoreUndefinedProperties: true }); 19 | 20 | export * from "./genschema.js"; 21 | export * from "./pseudo.js"; 22 | export * from "./boosts.js"; -------------------------------------------------------------------------------- /src/cmd/admin/database/save-db.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Database } from "../../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-save", "admin-save-db"]; 6 | help = "Save current db to firebase."; 7 | isGame = "n" as const; 8 | 9 | isAdmin = true; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | (process.env.NODE_ENV ? Database.saveBackup() as Promise : Database.save() as Promise).then(() => Database.update().then(() => { 13 | msg.channel.send({ 14 | embeds: [{ 15 | color: Colors.SUCCESS, 16 | title: "Success!", 17 | description: "Successfully **saved** and **updated** the database." 18 | }] 19 | }); 20 | })); 21 | } 22 | } 23 | 24 | export const c = new C(); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Use the command '...' 16 | 2. React with '...' 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **Contact Information** 25 | We can restore your data, just provide us with your Discord tag (for contact) and ID (for restore). You can also join our [Discord server](https://discord.gg/4vTPWdpjFz). 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /src/cmd/admin/season1.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | 3 | import * as fs from "fs"; 4 | import * as path from "path"; 5 | 6 | import { Command, Colors, Database } from "../../global.js"; 7 | 8 | class C extends Command { 9 | names = ["admin-new-season"]; 10 | help = "Starts a new season"; 11 | isGame = "n" as const; 12 | 13 | isAdmin = true; 14 | 15 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 16 | Database.db.collection("cycle-users").doc("users").set( 17 | JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "database.json"), "utf-8")) 18 | ); 19 | 20 | msg.channel.send({ 21 | embeds: [{ 22 | color: Colors.PRIMARY, 23 | title: "BOM BOM", 24 | description: "The new season has begun!" 25 | }] 26 | }); 27 | } 28 | } 29 | 30 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/meta/version.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["version", "announcements"]; 6 | help = "Get release notes of the current version!"; 7 | isGame = "n" as const; 8 | 9 | exec(msg: Discord.Message, _1: string[], _2: Discord.Client) { 10 | msg.channel.send({ 11 | embeds: [{ 12 | color: Colors.PRIMARY, 13 | title: "Version 0.1.19", 14 | description: "Quests update! Invest a certain amount of cycles and get a big payback! Get one now -> `&new-quest`", 15 | fields: [{ 16 | name: "Discord Server", 17 | value: "Talk to the developers, and get sneak peeks in the official [discord server](https://discord.gg/4vTPWdpjFz)!" 18 | }] 19 | }] 20 | }); 21 | } 22 | } 23 | 24 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd-parser.ts: -------------------------------------------------------------------------------- 1 | import { UserInput } from "./global.js"; 2 | 3 | function parse(prefix: string, command: string): UserInput | false { 4 | if (command.slice(0, prefix.length) != prefix) return false; 5 | 6 | const input = reformatStr(command.slice(prefix.length)); 7 | if (!/(\s+)|"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([^\s]+)/g.test(input)) return false; 8 | 9 | let output: string[] = []; 10 | 11 | // whitespace | "str" | 'str' | ident 12 | const r = /(\s+)|"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([^\s]+)/g; 13 | let m: RegExpExecArray | null; 14 | 15 | while ((m = r.exec(input)) != null) output.push((m[3] || m[2] || m[0]).replace(/\\('|"|\\)/g, "$1").trim()); 16 | 17 | output = output.filter(o => o != ""); 18 | 19 | return { command: output[0].toLowerCase(), args: output.slice(1) }; 20 | } 21 | 22 | function reformatStr(str: string) { 23 | return str 24 | .trim() 25 | .replace(/[‘’]/g, "'") 26 | .replace(/[“”]/g, "\"") 27 | ; 28 | } 29 | 30 | export { parse }; -------------------------------------------------------------------------------- /src/cmd/admin/database/README.md: -------------------------------------------------------------------------------- 1 | # Database Commands 2 | |Command|Explanation| 3 | |--|--| 4 | |[`admin-stash`](./backup-db.ts)|Stores the database into `database.json`, which can be loaded later for emergencies!| 5 | |[`admin-all`](./daily-maintain.ts)|Daily command to admin-stash and update topgg data.| 6 | |[`admin-update-schema`](./database-maintain.ts)|A server update that requires the database to be updated, like a new key, etc.| 7 | |[`admin-restore`](./load-backup.ts)|Loads the backup db into the memory. Validate the data before saving!| 8 | |[`admin-user-reset`](./reset-user.ts)|Resets an OP user (like you). Also saves the data to firestore.| 9 | |[`admin-save`](./save-db.ts)|Save the database to firestore. Then loads the data from firestore to memory.| 10 | |[`admin-user-set`](./set-user.ts)|Sets a user to have everything (for testing). Also saves the data to firestore.| 11 | |[`admin-fetch`](./update-db.ts)|Sets the memory from data from firestore.| 12 | 13 | That's all the admin commands out there! The rest should not be used. -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | The source code is divided into many sections. Hopefully this will help you (and me!) understand where everything is. 3 | ``` 4 | cmd/ 5 | # All the bot commands (each folder is a category) 6 | template.txt # A template file 7 | cmd.json # Info on all the files 8 | 9 | slashutil/ 10 | deploy-commands.ts # Run this to deploy commands 11 | 12 | util/ 13 | data/ 14 | boost/ 15 | # This contains data for boosts specifically 16 | # This contains typings for user data like items 17 | 18 | database/ 19 | # This is the wrapper for firebase 20 | 21 | admin.json # This is all the admin user IDs 22 | format.ts # These are text formatting utils 23 | util.ts # These are math/text utils 24 | levels.ts # This defines functions to be used for levels 25 | 26 | cmd-parser.ts # This parses commands 27 | global.ts # This exports things from 'util' to be easily importable 28 | loader.ts # This loads commands from 'cmd' 29 | server.ts # This creates a server for top.gg 30 | index.ts # Entry point 31 | ``` -------------------------------------------------------------------------------- /src/util/database/boosts.ts: -------------------------------------------------------------------------------- 1 | import { msBetween } from "../../global.js"; 2 | 3 | /** 4 | * An 'array' containing the boosts data. 5 | * i: the id of the boost 6 | * data: the data, it contains a time of creation and the amount to stack. 7 | */ 8 | export type BoostArr = { 9 | /** 10 | * Boosts last for 1 minute. 11 | */ 12 | [i: number]: Date[] 13 | } 14 | 15 | export const bdb: { [i: string]: BoostArr } = {}; 16 | 17 | export namespace Boost { 18 | export function setUser(key: string, val: BoostArr) { 19 | if (!bdb[key]) bdb[key] = val; 20 | Object.assign(bdb[key], val); 21 | } 22 | 23 | export function getUser(key: string) { 24 | if (!bdb[key]) bdb[key] = {}; 25 | else checkUser(key); 26 | return bdb[key]; 27 | } 28 | 29 | export function checkUser(key: string) { 30 | const user = bdb[key]; 31 | for (const boostKey in user) { 32 | const boost = user[boostKey]; 33 | for (const time of boost) { 34 | // 1 minute 35 | if (msBetween(time, new Date()) > 6e4) delete user[boostKey]; 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/util/levels.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Database, brackets, commanum } from "../global.js"; 4 | 5 | export function levelUp(user: Database.CycleUser): Discord.APIEmbedField | undefined { 6 | const level = new Big(user.level); 7 | let xp = new Big(user.xp); 8 | 9 | xp = xp.plus(1); 10 | 11 | if (xp.gte(level.times(5))) { 12 | xp = new Big(0); 13 | user.level = level.plus(1).toString(); 14 | 15 | const newCpp = new Big(user.cpp).times(1.01).dp(0); 16 | const newTpc = new Big(user.tpc).times(1.02).dp(0); 17 | const newCycles = new Big(user.cycles).times(1.1).dp(0); 18 | const newText = new Big(user.text).times(1.15).dp(0); 19 | 20 | user.cpp = newCpp.toString(); 21 | user.tpc = newTpc.toString(); 22 | user.cycles = newCycles.toString(); 23 | user.text = newText.toString(); 24 | 25 | user.xp = xp.toString(); 26 | 27 | return { 28 | name: "Level up!", 29 | value: `You are now level ${brackets(commanum(user.level))}!` 30 | }; 31 | } 32 | 33 | user.xp = xp.toString(); 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Junhao "Jerry" Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/cmd/meta/invite.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["invite", "about", "github"]; 6 | help = "Invite the bot to your server!"; 7 | isGame = "n" as const; 8 | 9 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 10 | msg.channel.send({ 11 | embeds: [{ 12 | color: Colors.PRIMARY, 13 | title: "Invite link!", 14 | description: `**Invite Link**: [Link](https://discord.com/api/oauth2/authorize?client_id=781939317450342470&permissions=272448&scope=bot) 15 | **Discord Server**: [Invite](https://discord.gg/4vTPWdpjFz) 16 | **Github**: [Link](https://github.com/cursorweb/Cycle-Bot-Game) 17 | **Replit**: [Link](https://replit.com/@Coder100/Cycle-Bot-Game-autogenerated) [@Coder100](https://repl.it/@Coder100) 18 | \n[**Terms of Service**](https://docs.google.com/document/d/e/2PACX-1vTpwHwEm39rR32JFBadDrkhhX5ql1wGsLCxc3U0l7cJvOAiSNZWx4DaD0LtbBnxxtxPTayhk7GaIZQm/pub) | [**Privacy Policy**](https://docs.google.com/document/d/e/2PACX-1vS5W_-EcOrl3vQstWHt0hVosuKXwWvIqT7iXaGE8zPeTIA8MmyaHXzf77qO6iOm2ArHdN6iTWvXskux/pub)` 19 | }] 20 | }); 21 | } 22 | } 23 | 24 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/admin/topgg-data.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import * as Discord from "discord.js"; 3 | import { Command, Colors, Bot, codestr } from "../../global.js"; 4 | 5 | class C extends Command { 6 | names = ["admin-update-topgg"]; 7 | help = "Update top.gg data."; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, _: string[], client: Discord.Client) { 13 | const guildCount = client.guilds.cache.size; 14 | 15 | fetch("https://top.gg/api/bots/781939317450342470/stats", { 16 | method: "POST", 17 | headers: { 18 | "Content-Type": "application/json", 19 | "Authorization": process.env.TOPGG || "" 20 | }, 21 | body: JSON.stringify({ 22 | // eslint-disable-next-line camelcase 23 | server_count: guildCount 24 | }) 25 | }).then(() => { 26 | msg.channel.send({ 27 | embeds: [{ 28 | color: Colors.SUCCESS, 29 | title: "Success!", 30 | description: "Successfully sent data to top.gg." 31 | }] 32 | }); 33 | }).catch(e => { 34 | Bot.errormsg(msg, `An error occured. 35 | **ERROR** 36 | ${codestr(e, "js")}`); 37 | }); 38 | } 39 | } 40 | 41 | export const c = new C(); -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import express from "express"; 4 | import { Webhook } from "@top-gg/sdk"; 5 | 6 | import { Database, Colors, brackets } from "./global.js"; 7 | import { ItemEnum } from "./util/data/item.js"; 8 | 9 | 10 | export function initiate(client: Discord.Client) { 11 | const app = express(); 12 | const webhook = new Webhook(process.env.TOPGG_AUTH); 13 | 14 | app.get("/", (_, res) => res.end("My prefix is '&'!")); 15 | 16 | app.post("/dblwebhook", webhook.listener(vote => { 17 | const id = vote.user; 18 | const user = Database.getUser(id); 19 | if (!user) return; 20 | 21 | const voteCrate = user.inv[ItemEnum.VoteCrate]; 22 | let amt = new Big(voteCrate || 0); 23 | amt = amt.plus(1); 24 | user.inv[ItemEnum.VoteCrate] = amt.toString(); 25 | 26 | client.users.cache.get(id)?.send({ 27 | embeds: [{ 28 | color: Colors.PRIMARY, 29 | title: "Thank you for voting!", 30 | description: `For voting, you get a ${brackets("vote crate")}!`, 31 | footer: { 32 | text: "To open a vote crate, use &open 'vote crate'" 33 | } 34 | }] 35 | }); 36 | })); 37 | 38 | app.listen(8080); 39 | } -------------------------------------------------------------------------------- /src/cmd/admin/database/database-maintain.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | // import Big from "bignumber.js"; 3 | import { Command, Colors, Database } from "../../../global.js"; 4 | 5 | class C extends Command { 6 | names = ["admin-update-schema", "admin-maintain-db"]; 7 | help = "Updates the database."; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 13 | Database.update().then(() => { 14 | for (const id in Database.pdb) { 15 | const user = Database.pdb[id]; 16 | 17 | // CODE ENTRY POINT 18 | if (typeof user.quest != "object") { 19 | user.quest = null; 20 | } 21 | // CODE ENTRY POINT 22 | } 23 | 24 | if (process.env.NODE_ENV) Database.updateBackup(); 25 | 26 | Database.save().then(() => { 27 | msg.channel.send({ 28 | embeds: [{ 29 | color: Colors.SUCCESS, 30 | title: "Successfully updated!", 31 | description: "The schema has successfully been updated!\nMake sure `genschema.ts` is up to date.", 32 | footer: { text: "Use &lb to find most information!" } 33 | }] 34 | }); 35 | }); 36 | }); 37 | } 38 | } 39 | 40 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/meta/uptime.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, brackets, plural, formatDate, commanum } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["uptime", "bot-about", "bot-servers", "bot-info"]; 6 | help = "View some general statistics about the bot."; 7 | isGame = "n" as const; 8 | 9 | exec(msg: Discord.Message, _: string[], client: Discord.Client) { 10 | const guildCount = client.guilds.cache.size; 11 | const userCount = client.guilds.cache.reduce((p, c) => p + c.memberCount, 0); 12 | 13 | msg.channel.send({ 14 | embeds: [{ 15 | color: Colors.PRIMARY, 16 | title: "Bot Statistics", 17 | description: "View bot statistics!", 18 | fields: [{ 19 | name: "Uptime", 20 | value: formatDate(client.uptime ?? 0) 21 | }, { 22 | name: "Servers", 23 | value: `In ${brackets(guildCount.toString())} server${plural(guildCount)}!` 24 | }, { 25 | name: "Users", 26 | value: `The bot is overseeing ${brackets(commanum(userCount.toString()))} user${plural(userCount)}.` 27 | }, { 28 | name: "Creator", 29 | value: "[Junhao Zhang](https://github.com/cursorweb) ([@Coder100](https://repl.it/@Coder100)) with TypeScript." 30 | }] 31 | }] 32 | }); 33 | } 34 | } 35 | 36 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/stats/info.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Bot, brackets } from "../../global.js"; 3 | import { items } from "../../util/data/item.js"; 4 | import { openItem } from "../../util/data/open-item.js"; 5 | 6 | class C extends Command { 7 | names = ["info", "item"]; 8 | help = "Get info about an item!"; 9 | examples = ["info coffee"]; 10 | 11 | isGame = "n" as const; 12 | 13 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 14 | if (args.length != 1) return Bot.argserror(msg, args.length, [1]); 15 | const name = args[0]; 16 | let itemIndex = items.findIndex(n => n.name.toLowerCase() == name.toLowerCase()); 17 | if (itemIndex == -1) itemIndex = items.findIndex(n => n.name.toLowerCase().indexOf(name.toLowerCase()) > -1); 18 | if (itemIndex == -1) { 19 | return Bot.usererr(msg, `The item ${brackets(name)} was not found! 20 | Check your spelling!`, "Item not found!"); 21 | } 22 | 23 | const item = items[itemIndex]; 24 | msg.channel.send({ 25 | embeds: [{ 26 | color: Colors.PRIMARY, 27 | title: "Item Info", 28 | description: `*${item.description}*`, 29 | fields: [{ 30 | name: "Stats", 31 | value: `**Drop Chance: ** ${item.dropChance}% 32 | **Openable? ** ${openItem[itemIndex] ? "yes" : "no"}` 33 | }] 34 | }] 35 | }); 36 | } 37 | } 38 | 39 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/shop/spells.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Bot, brackets, constrain, parseNumber, codestr } from "../../global.js"; 3 | import { boosts } from "../../util/data/boosts/boosts.js"; 4 | import { spells } from "../../util/data/boosts/spells.js"; 5 | 6 | class C extends Command { 7 | names = ["spells", "spell", "spell-book", "spellbook"]; 8 | help = "Look at all the spells or use a spell!"; 9 | examples = ["spells 3", "spells"]; 10 | 11 | exec(msg: Discord.Message, args: string[]) { 12 | if (args.length > 1) return Bot.argserror(msg, args.length, [0, 1]); 13 | const num = parseNumber(args[0]); 14 | if (args[1] && isNaN(num)) return Bot.usererr(msg, "The page must be a number!"); 15 | const page = constrain(num || 1, 1, Infinity); 16 | const data = spells.map(itm => { 17 | const boost = boosts[itm.drops]; 18 | return `[ ${boost.name} ][ ${itm.success}% success ] 19 | <+ ${boost.tpc || 0}% TPC> <+ ${boost.cpp || 0}% CPP> <+ ${boost.tpm || 0}% TPM> 20 | > ${boost.description}`; 21 | }); 22 | 23 | Bot.carousel(msg, data, 5, (page, i) => ({ 24 | color: Colors.PRIMARY, 25 | title: "Spells!", 26 | description: `Spellbook! Page ${brackets(page.toString())}. 27 | ${i.length > 0 ? codestr(i.join("\n\n"), "md") : codestr(`[ NO ][ MORE ][ SPELLS ] 28 | > You've gone far enough!`, "md")}` 29 | }), page); 30 | } 31 | } 32 | 33 | export const c = new C(); -------------------------------------------------------------------------------- /src/slashutil/deploy-commands.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { SlashCommandBuilder, Routes } from "discord.js"; 3 | import { REST } from "@discordjs/rest"; 4 | import { load } from "../loader.js"; 5 | 6 | 7 | const token = process.env.TOKEN || ""; 8 | const clientId = process.env.CLIENT_ID || ""; 9 | 10 | const rest = new REST({ version: "10" }).setToken(token); 11 | 12 | // node ... -d 13 | if (process.argv.length == 3 && process.argv[2] == "-d") { 14 | try { 15 | await rest.put(Routes.applicationCommands(clientId), { body: [] }); 16 | console.log("Successfully deleted all application commands."); 17 | } catch (err) { 18 | console.error(err); 19 | } 20 | 21 | process.exit(0); 22 | } 23 | 24 | const cmds = await load(); 25 | const slashCmds: SlashCommandBuilder[] = []; 26 | 27 | const names: string[] = []; 28 | 29 | for (const topic in cmds) { 30 | for (const cmd of cmds[topic].cmds) { 31 | const slashCmd = new SlashCommandBuilder(); 32 | if (names.includes(cmd.names[0])) { 33 | throw new Error(`duplicate: ${cmd.names[0]}`); 34 | } 35 | names.push(cmd.names[0]); 36 | slashCmd.setName(cmd.names[0]); 37 | slashCmd.setDescription(cmd.help); 38 | slashCmds.push(slashCmd); 39 | } 40 | } 41 | 42 | console.log(slashCmds.map(k => k.toJSON())); 43 | 44 | rest.put(Routes.applicationCommands(clientId), { body: slashCmds.map(k => k.toJSON()) }) 45 | .then(() => console.log("Successfully registered application commands.")) 46 | .catch(console.error); -------------------------------------------------------------------------------- /src/cmd/misc/dice-roll.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Bot, hidden, parseNumber } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["dice-roll", "roll-dice"]; 6 | help = "Roll a dice!"; 7 | examples = ["dice-roll min max", "dice-roll"]; 8 | isGame = "n" as const; 9 | 10 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 11 | if (args.length == 0) { 12 | msg.channel.send({ 13 | embeds: [{ 14 | color: Colors.PRIMARY, 15 | title: "Roll a dice!", 16 | description: `You rolled the dice, and you got... 17 | ${hidden(Math.floor(Math.random() * 6 + 1).toString())}` 18 | }] 19 | }); 20 | } else if (args.length == 2) { 21 | const min = parseNumber(args[0]); 22 | const max = parseNumber(args[1]); 23 | if (isNaN(min) || isNaN(max)) Bot.usererr(msg, "Please pass in a number for both arguments!"); 24 | else if (min >= max) Bot.usererr(msg, "The maximum number has to be greater than (and not equal to) the minimum number!"); 25 | else { 26 | msg.channel.send({ 27 | embeds: [{ 28 | color: Colors.PRIMARY, 29 | title: "Roll a dice!", 30 | description: `You rolled the dice, and you got... 31 | ${hidden(Math.floor(Math.random() * (max - min + 1) + min).toString())}` 32 | }] 33 | }); 34 | } 35 | } else Bot.argserror(msg, args.length, [0, 2]); 36 | } 37 | } 38 | 39 | export const c = new C(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cycle-bot", 3 | "version": "0.1.19", 4 | "description": "Fun Discord idle bot game", 5 | "exports": "./dist/index.js", 6 | "main": "dist/index.js", 7 | "type": "module", 8 | "scripts": { 9 | "start": "node .", 10 | "dev": "tsc -w", 11 | "prod": "tsc", 12 | "deploy": "node ./dist/slashutil/deploy-commands.js", 13 | "eslint": "eslint . --ext ts", 14 | "eslint-fix": "eslint . --ext ts --fix" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/cursorweb/Cycle-Bot-Game.git" 19 | }, 20 | "keywords": [ 21 | "bot", 22 | "discord", 23 | "idle" 24 | ], 25 | "author": "Coder100", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/cursorweb/Cycle-Bot-Game/issues" 29 | }, 30 | "homepage": "https://github.com/cursorweb/Cycle-Bot-Game#readme", 31 | "dependencies": { 32 | "@discordjs/rest": "^1.3.0", 33 | "@top-gg/sdk": "^3.1.3", 34 | "bignumber.js": "^9.0.2", 35 | "discord.js": "^14.6.0", 36 | "dotenv": "^10.0.0", 37 | "express": "^4.17.1", 38 | "firebase-admin": "^10.0.1", 39 | "node-fetch": "^3.1.0" 40 | }, 41 | "devDependencies": { 42 | "@types/express": "^4.17.13", 43 | "@types/node": "^17.0.5", 44 | "@typescript-eslint/eslint-plugin": "^5.8.1", 45 | "@typescript-eslint/parser": "^5.8.1", 46 | "eslint": "^8.5.0", 47 | "typescript": "^4.8.3" 48 | }, 49 | "engines": { 50 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/cmd/admin/database/daily-maintain.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import fetch from "node-fetch"; 3 | import { Command, Colors, Database, Bot, codestr } from "../../../global.js"; 4 | 5 | class C extends Command { 6 | names = ["admin-all"]; 7 | help = "Backs up the database, prunes users, and updates top.gg."; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, _: string[], client: Discord.Client) { 13 | Database.saveBackup(); 14 | const deleted = Database.pruneUsers().map(x => `\`${x.name}\`: ${x.reason}`).join("\n").toString(); 15 | 16 | const guildCount = client.guilds.cache.size; 17 | 18 | fetch("https://top.gg/api/bots/781939317450342470/stats", { 19 | method: "POST", 20 | headers: { 21 | "Content-Type": "application/json", 22 | "Authorization": process.env.TOPGG || "" 23 | }, 24 | body: JSON.stringify({ 25 | // eslint-disable-next-line camelcase 26 | server_count: guildCount 27 | }) 28 | }).then(() => { 29 | msg.channel.send({ 30 | embeds: [{ 31 | color: Colors.SUCCESS, 32 | title: `Successfully backed-up the database! Pruned users, and updated top.gg! 33 | Deleted: ${deleted == "" ? "**no one**" : deleted}`, 34 | footer: { text: "Note: Use restore to actually restore the database." } 35 | }] 36 | }); 37 | }).catch(e => { 38 | Bot.errormsg(msg, `An error occured. 39 | **ERROR** 40 | ${codestr(e, "js")}`); 41 | }); 42 | } 43 | } 44 | 45 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/meta/feedback.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Bot, brackets, cleanName } from "../../global.js"; 3 | 4 | import admins from "../../util/admin.js"; 5 | 6 | class C extends Command { 7 | names = ["feedback", "bug-report"]; 8 | help = "Report bugs or feedback on the bot!"; 9 | examples = ["bug-report \"something didn't work\""]; 10 | isGame = "n" as const; 11 | 12 | get cooldown() { 13 | return 60e3 * 10; 14 | } 15 | 16 | async exec(msg: Discord.Message, args: string[], client: Discord.Client) { 17 | if (args.length != 1) return Bot.argserror(msg, args.length, [1]); 18 | 19 | for (const id of admins) { 20 | const user = client.users.cache.get(id); 21 | user?.send({ 22 | embeds: [{ 23 | color: Colors.PRIMARY, 24 | title: "Incoming Feedback!", 25 | description: `${brackets(cleanName(msg.author.tag))} has sent a message!`, 26 | fields: [{ 27 | name: "Feedback", 28 | value: args[0] 29 | }] 30 | }] 31 | }); 32 | } 33 | 34 | msg.channel.send({ 35 | embeds: [{ 36 | color: Colors.SUCCESS, 37 | title: "Successfully sent!", 38 | description: "Your feedback is very important. Thanks for taking the time to write your feedback!" 39 | }] 40 | }); 41 | } 42 | 43 | cooldownError(msg: Discord.Message, ms: number) { 44 | Bot.errormsg(msg, `You still have ${brackets((ms / 60e3).toFixed(3))} minutes left before you can send feedback again!`, "No spamming!"); 45 | } 46 | } 47 | 48 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/quests/quest.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { quests, qDiff } from "../../util/data/quests.js"; 3 | import { Command, Colors, brackets, progress, formatDate } from "../../global.js"; 4 | import { getUser } from "../../util/database/database.js"; 5 | 6 | 7 | class C extends Command { 8 | names = ["quests", "q", "quest"]; 9 | help = "View your daily quest!"; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | const user = getUser(msg.author.id); 13 | if (user.quest) { 14 | const { name, end, difficulty, progress: prog } = user.quest; 15 | const quest = quests[name]; 16 | msg.channel.send({ 17 | embeds: [{ 18 | color: Colors.PRIMARY, 19 | title: "Quest info!", 20 | description: `Learn all about your **${qDiff[difficulty]}** quest: 21 | ${brackets(quest.name)}!`, 22 | fields: [{ 23 | name: "Description", 24 | value: quest.description 25 | }, { 26 | name: "Deadline", 27 | value: formatDate(new Date(end).getTime() - new Date().getTime()) 28 | }, { 29 | name: "Progress", 30 | value: `${progress(prog / quest.max * 10, 10)} (${prog.toLocaleString()} / ${quest.max.toLocaleString()})` 31 | }] 32 | }] 33 | }); 34 | } else { 35 | msg.channel.send({ 36 | embeds: [{ 37 | color: Colors.ERROR, 38 | title: "No quest!", 39 | description: "Go get a quest by doing `&new-quest`!" 40 | }] 41 | }); 42 | } 43 | } 44 | } 45 | 46 | export const c = new C(); 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Thanks for wanting to contribute! To make contributions as efficient and positive for everyone, please read these sections. 3 | 4 | ## Prequisites 5 | You should have `node` and `npm` installed on your system. 6 | Also run: 7 | ``` 8 | npm i -g typescript 9 | ``` 10 | for faster typescript development. 11 | 12 | If you have discord, please join the [discord sever](https://discord.gg/4vTPWdpjFz) to talk to devs. 13 | 14 | ## Tools 15 | To speed up development, we recommend using **VSCode** as your IDE and **git** for your SCM. 16 | 17 | ## Scripts 18 | |Script|Description| 19 | |:--:|:--| 20 | |`start`|Start and run the bot.| 21 | |`dev`|Use this command when developing. When updates are made (file saves), typescript will automatically recompile the program.| 22 | |`prod`|Compile the bot.| 23 | 24 | ## Development 25 | Check out [README.md](README.md) for more information. 26 | 27 | ## Pull Requests 28 | When creating a PR, please follow these guidelines: 29 | - The title should be a short description of what you added. 30 | - The PR content should describe in depth of what was accomplished. 31 | 32 | ## Commits 33 | Commits should be short and descriptive. They should be *past tense*. 34 | 35 | |Good Practice|Bad Practice| 36 | |:--|--:| 37 | |Added more items to the shop|Update shop.ts| 38 | |Fixed a bug with buying items|Fix bug| 39 | |Tweaked color theme|Change colors| 40 | 41 | ## Branches 42 | When naming your branches, please include information about who is using it. This is as simple as adding your name to the branch: 43 | ``` 44 | coder100-dev 45 | shop-item-cursorweb 46 | ``` 47 | 48 | When applicable, please also include what you aim to accomplish. -------------------------------------------------------------------------------- /src/util/data/craft.ts: -------------------------------------------------------------------------------- 1 | import { ItemEnum } from "./item.js"; 2 | 3 | export interface CraftItem { 4 | /** 5 | * The message when crafted 6 | */ 7 | message: string; 8 | creates: number; 9 | requires: { amt: number | string, type: number }[]; 10 | } 11 | 12 | export const craftItems: CraftItem[] = [{ 13 | message: "You smash all the idle-coins together with the cheap iPhone!", 14 | creates: ItemEnum.ApplePhone, 15 | requires: [{ amt: 1, type: ItemEnum.CheapPhone }, { amt: 5, type: ItemEnum.IdleCoin }] 16 | }, { 17 | message: "You glue the chest chests together...", 18 | creates: ItemEnum.ChestChestChest, 19 | requires: [{ amt: 5, type: ItemEnum.ChestChest }, { amt: 5, type: ItemEnum.Glue }] 20 | }, { 21 | message: "You glue glue together... what?", 22 | creates: ItemEnum.SuperGlue, 23 | requires: [{ amt: 2, type: ItemEnum.Glue }] 24 | }, { 25 | message: "You glue the crafting materials together...", 26 | creates: ItemEnum.ChestChest, 27 | requires: [{ amt: 3, type: ItemEnum.CraftingMat }, { amt: 1, type: ItemEnum.SuperGlue }] 28 | }, { 29 | message: "You ruin a phone :(", 30 | creates: ItemEnum.CraftingMat, 31 | requires: [{ amt: 1, type: ItemEnum.Glue }, { amt: 1, type: ItemEnum.CheapPhone }] 32 | }, { 33 | message: "You combine the golden cycles together! Somehow this makes idle-coins?", 34 | creates: ItemEnum.IdleCoinMaker, 35 | requires: [{ amt: 5, type: ItemEnum.GoldenCycle }, { amt: 5, type: ItemEnum.CraftingMat }] 36 | }, { 37 | message: "You combine the idle-coins together! Somehow this makes golden cycles?", 38 | creates: ItemEnum.GoldenCycleMaker, 39 | requires: [{ amt: 5, type: ItemEnum.IdleCoin }, { amt: 5, type: ItemEnum.CraftingMat }] 40 | }]; -------------------------------------------------------------------------------- /src/util/database/genschema.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | 3 | export const defaultSchema: CycleUser = { 4 | name: "", 5 | cycles: "0", text: "0", xp: "0", 6 | tpc: "1", cpp: "1", tpm: "0", 7 | level: "1", socialMedia: 0, 8 | 9 | inv: {}, badges: [], 10 | 11 | daily: "", 12 | quest: null, 13 | 14 | bought: { 15 | idle: {}, 16 | upgrades: {}, 17 | cpp: {} 18 | } 19 | }; 20 | 21 | export function genSchema(user: Discord.User): CycleUser { 22 | return { 23 | name: user.tag, 24 | cycles: "0", text: "0", xp: "0", 25 | tpc: "1", cpp: "1", tpm: "0", 26 | level: "1", socialMedia: 0, 27 | 28 | inv: {}, badges: [], 29 | 30 | daily: "", 31 | quest: null, 32 | 33 | bought: { 34 | idle: {}, 35 | upgrades: {}, 36 | cpp: {} 37 | } 38 | }; 39 | } 40 | 41 | export interface CycleUser { 42 | // meta 43 | name: string; // tag, used for identification 44 | 45 | // leaderboard 46 | cycles: string; // used from big.js 47 | text: string; // your code 48 | xp: string; // xp points 49 | // refer to desmos. 50 | // gain EXP by POSTING 51 | 52 | // boosts 53 | tpc: string; // text per code 54 | cpp: string; // cycles per post 55 | tpm: string; // text per minute 56 | 57 | // extra boosts 58 | level: string; // the level 59 | socialMedia: number; // social media platform (number) 60 | inv: { [i: number]: string | undefined }; // [item-index]: amount 61 | 62 | badges: string[]; // badges lol 63 | 64 | daily: string; 65 | 66 | quest: { 67 | name: number, // quest enum 68 | end: string, 69 | difficulty: number, // 0 = easy, 1 = med, 2 = hard 70 | progress: number 71 | } | null; 72 | 73 | // what the user has bought 74 | bought: { 75 | idle: { [i: number]: number | undefined }; // [item-index]: amount 76 | // this means we can only expand forwards 77 | upgrades: { [i: number]: number | undefined }; 78 | cpp: { [i: number]: number | undefined }; 79 | } 80 | } -------------------------------------------------------------------------------- /src/cmd/admin/database/reset-user.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Database, Bot, brackets, parseMention, cleanName } from "../../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-user-reset"]; 6 | help = "Test things out with *nothing*."; 7 | examples = ["admin-user-reset id"]; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 13 | if (args.length != 1) return Bot.argserror(msg, args.length, [1]); 14 | 15 | const parsed = parseMention(args[0]); 16 | let id: string; 17 | 18 | if (parsed.type == "id") { 19 | if (!Database.getUser(parsed.value)) return Bot.usererr(msg, `User ${brackets(`<@${ parsed.value }>`)} not found! Check your spelling.`, "Not found!!"); 20 | id = parsed.value; 21 | } else { 22 | const ids = Database.findUser(u => u.name.toLowerCase().includes(parsed.value.toLowerCase())); 23 | if (ids.length == 0) return Bot.usererr(msg, `User ${brackets(parsed.value)} not found! Check your spelling.`, "Not found!!"); 24 | if (ids.length > 1) { 25 | msg.channel.send({ 26 | embeds: [{ 27 | color: Colors.WARNING, 28 | title: "Warning", 29 | description: `Found ${brackets(ids.length.toString())} users. Using the first one.` 30 | }] 31 | }); 32 | } 33 | 34 | id = ids[0]; 35 | } 36 | 37 | const user = Database.getUser(id); 38 | Database.setUser(id, Object.assign({}, Database.defaultSchema, { name: user.name })); 39 | 40 | msg.channel.send({ 41 | embeds: [{ 42 | color: Colors.SUCCESS, 43 | title: "Success!", 44 | description: `Successfully **reset** user ${brackets(cleanName(user.name))} 45 | **Database Saved** 46 | > View profile to see more!` 47 | }] 48 | }); 49 | 50 | process.env.NODE_ENV ? Database.saveBackup() : Database.save(); 51 | } 52 | } 53 | 54 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/shop/social-media.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Database, brackets, codestr, commanum } from "../../global.js"; 4 | import { socialMedia } from "../../util/data/social-media.js"; 5 | 6 | class C extends Command { 7 | names = ["social-media", "media-shop", "sm"]; 8 | help = "View the next social media planned!"; 9 | examples = ["social-media"]; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | const user = Database.getUser(msg.author.id); 13 | 14 | // todo: remove 15 | if (!user.socialMedia) { 16 | user.socialMedia = 0; 17 | } 18 | 19 | const idx = user.socialMedia; 20 | const prestige = Math.log(idx + 1) + 0.5; 21 | 22 | const curr = socialMedia[idx]; 23 | const next = socialMedia[idx + 1]; 24 | 25 | // lnx + 0.5 26 | const nextTpc = new Big("1").times(prestige).dp(0); 27 | const nextCpp = new Big("1").times(prestige).dp(0); 28 | const nextTpm = new Big("0").plus(1).times(prestige).div(2).dp(0); 29 | 30 | const cyclesNeeded = new Big(100_000).pow(Math.ceil(Math.log(2 * (idx + 1)))); 31 | 32 | msg.channel.send({ 33 | embeds: [{ 34 | color: Colors.PRIMARY, 35 | title: "Social Media", 36 | description: "Social medias will let you get more cycles faster,\nbut all your clout is reset!", 37 | fields: [ 38 | { 39 | name: "Current Social Media", 40 | value: `You are currently using ${brackets(curr)} to share your posts.` 41 | }, { 42 | name: "Next Social Media", 43 | value: `Your next social media is ${brackets(next)}. 44 | ${codestr(`+ ${nextTpc}% base TPC 45 | + ${nextCpp}% base CPP 46 | + ${nextTpm}% base TPM`)} 47 | 48 | **Requirement: ** ${commanum(cyclesNeeded.toString())} cycles!` 49 | } 50 | ], 51 | footer: { 52 | text: "Use &next-media to go to the next social media!" 53 | } 54 | }] 55 | }); 56 | } 57 | } 58 | 59 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/data/boosts/boosts.ts: -------------------------------------------------------------------------------- 1 | export interface BoostItm { 2 | name: string; 3 | description: string; 4 | /** 5 | * The message when the user interacts with it 6 | */ 7 | message: string; 8 | 9 | // 0-100 10 | tpc?: number; 11 | cpp?: number; 12 | tpm?: number; 13 | } 14 | 15 | export enum BoostEnum { 16 | Coinflip, 17 | SOQues, 18 | FriendDM, 19 | GambleFever, 20 | ClassicSpell, 21 | SpellingBee, 22 | BackfiringSpell, 23 | SugarHigh, 24 | StylishShirts 25 | } 26 | 27 | export const boosts: BoostItm[] = [{ 28 | name: "Coinflip", 29 | description: "Flip a coin... see the result!", 30 | message: "You got 50% more text!", 31 | 32 | tpc: 50 33 | }, { 34 | name: "Stackoverflow Question", 35 | description: "This can either end really well or really badly...", 36 | message: "Everyone loves your question!", 37 | 38 | tpc: 100, 39 | cpp: 100, 40 | tpm: 100 41 | }, { 42 | name: "DM to friends", 43 | description: "You might get ignored...", 44 | message: "Your friends appreciate you!", 45 | 46 | cpp: 25 47 | }, { 48 | name: "Gambling Fever", 49 | description: "It's cheap, and the rewards are high!", 50 | message: "This isn't helping with your addiction...", 51 | 52 | tpc: 25, 53 | cpp: 25, 54 | tpm: 25 55 | }, { 56 | name: "Classic Spell", 57 | description: "Abracadabra!", 58 | message: "Your wizardry improves your life!", 59 | 60 | tpc: 75, 61 | cpp: 75, 62 | tpm: 75 63 | }, { 64 | name: "Spelling Bee", 65 | description: "Spell, 'antidisestablishmentarianism'", 66 | message: "You make typos 50% less!", 67 | 68 | tpc: 50, 69 | cpp: 25, 70 | tpm: 25 71 | }, { 72 | name: "Spell of Backfiring", 73 | description: "Your spell backfired!", 74 | message: "Bad luck hahaha", 75 | 76 | tpc: -50, 77 | cpp: -50, 78 | tpm: -50 79 | }, { 80 | name: "Sugar High", 81 | description: "You get a sugar high so you code more!", 82 | message: "You type 250 WPM!", 83 | 84 | tpc: 50 85 | }, { 86 | name: "Stylish Shirts", 87 | description: "Wear fashion for cycles!", 88 | message: "People are attracted by your shirt!", 89 | 90 | cpp: 50 91 | }]; -------------------------------------------------------------------------------- /src/cmd/admin/database/set-user.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors, Database, Bot, brackets, parseMention, cleanName } from "../../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["admin-user-set"]; 6 | help = "Test things out with *everything*."; 7 | examples = ["admin-user-set id"]; 8 | isGame = "n" as const; 9 | 10 | isAdmin = true; 11 | 12 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 13 | if (args.length != 1) return Bot.argserror(msg, args.length, [1]); 14 | 15 | const parsed = parseMention(args[0]); 16 | let id: string; 17 | 18 | if (parsed.type == "id") { 19 | if (!Database.getUser(parsed.value)) return Bot.usererr(msg, `User ${brackets(`<@${ parsed.value }>`)} not found! Check your spelling.`, "Not found!!"); 20 | id = parsed.value; 21 | } else { 22 | const ids = Database.findUser(u => u.name.toLowerCase().includes(parsed.value.toLowerCase())); 23 | if (ids.length == 0) return Bot.usererr(msg, `User ${brackets(cleanName(parsed.value))} not found! Check your spelling.`, "Not found!!"); 24 | if (ids.length > 1) { 25 | msg.channel.send({ 26 | embeds: [{ 27 | color: Colors.WARNING, 28 | title: "Warning", 29 | description: `Found ${brackets(ids.length.toString())} users. Using the first one.` 30 | }] 31 | }); 32 | } 33 | 34 | id = ids[0]; 35 | } 36 | 37 | const user = Database.getUser(id); 38 | Database.setUser(id, Object.assign(Database.defaultSchema, { 39 | cycles: "999999999", text: "999999999", 40 | tpc: "999999999", cpp: "999999999", tpm: "999999999" 41 | }, { name: user.name })); 42 | 43 | msg.channel.send({ 44 | embeds: [{ 45 | color: Colors.SUCCESS, 46 | title: "Success!", 47 | description: `Successfully **set** user ${brackets(cleanName(user.name))} 48 | **Database Saved** 49 | > View profile to see more!` 50 | }] 51 | }); 52 | 53 | process.env.NODE_ENV ? Database.saveBackup() : Database.save(); 54 | } 55 | } 56 | 57 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/shop/use-spell.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 3 | import { Command, Colors, Bot, Database, brackets } from "../../global.js"; 4 | import { boosts, BoostEnum } from "../../util/data/boosts/boosts.js"; 5 | import { spells } from "../../util/data/boosts/spells.js"; 6 | 7 | class C extends Command { 8 | names = ["use-spell", "cast-spell", "cast"]; 9 | help = "Use a spell!"; 10 | examples = ["use-spell"]; 11 | isGame = "y" as const; 12 | 13 | get cooldown() { 14 | return 6e4; 15 | } 16 | 17 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 18 | if (args.length != 1) Bot.argserror(msg, args.length, [1]); 19 | 20 | const user = Database.Boost.getUser(msg.author.id); 21 | const userInput = args[0]; 22 | 23 | // refer to data/boosts/spells.ts 24 | let itmIndex = spells.findIndex(s => boosts[s.drops].name.toLowerCase() == userInput.toLowerCase()); 25 | if (itmIndex == -1) itmIndex = spells.findIndex(s => boosts[s.drops].name.toLowerCase().indexOf(userInput.toLowerCase()) > -1); 26 | 27 | if (itmIndex == -1) { 28 | return Bot.usererr(msg, "Check your spelling!", "Spell not found!"); 29 | } 30 | 31 | const item = spells[itmIndex]; 32 | const boost = boosts[item.drops]; 33 | 34 | if (Math.random() * 100 < item.success) { // success 35 | msg.channel.send({ 36 | embeds: [{ 37 | color: Colors.PRIMARY, 38 | title: "Success!", 39 | description: `You cast the ${brackets(boost.name)}... 40 | It succeeds!` 41 | }] 42 | }); 43 | if (!user[item.drops]) user[item.drops] = []; 44 | user[item.drops].push(new Date()); 45 | } else { 46 | msg.channel.send({ 47 | embeds: [{ 48 | color: Colors.ERROR, 49 | title: "Failure!", 50 | description: `You cast the ${brackets(boost.name)}... 51 | It backfired!` 52 | }] 53 | }); 54 | if (!user[BoostEnum.BackfiringSpell]) user[BoostEnum.BackfiringSpell] = []; 55 | user[BoostEnum.BackfiringSpell].push(new Date()); 56 | } 57 | } 58 | } 59 | 60 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/admin/eval/eval-docs.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Bot, Colors, brackets, codestr, constrain } from "../../../global.js"; 3 | import { ItemEnum } from "../../../util/data/item.js"; 4 | 5 | class C extends Command { 6 | names = ["eval-docs"]; 7 | help = "Get quick code snippets"; 8 | examples = ["eval-docs", "eval-docs __INSERT_ID__ 1"]; 9 | isGame = "n" as const; 10 | 11 | isAdmin = true; 12 | 13 | exec(msg: Discord.Message, args: string[], _1: Discord.Client) { 14 | const page = constrain(Number(args[1]) || 1, 1, Infinity); 15 | const id = args[0] || "__INSERT_ID__"; 16 | 17 | const data: ({ desc: string, code: string })[] = [{ 18 | desc: "Set a user", 19 | code: `Database.setUser(${id}, Object.assign(Database.defaultSchema, { 20 | ___INSERT_CODE__ 21 | }, { name: user.name }))` 22 | }, { 23 | desc: "Get a specific user", 24 | code: `return Database.pdb["${id}"];` 25 | }, { 26 | desc: "Set Idle-Coins", 27 | code: `let inv = Database.pdb["${id}"].inv; 28 | let coins = new Big(inv[ItemEnum.IdleCoin] || 0); 29 | __INSERT_CODE__ 30 | inv[ItemEnum.IdleCoin] = coins.toString(); 31 | return coins;` 32 | }, { 33 | desc: "Preview TPM/Cycles/Text", 34 | code: `const user = Database.pdb["${id}"]; 35 | let ___ = new Big(user.cycles/text/tpc/cpp/tpm); 36 | __INSERT_CODE__ 37 | return ___;` 38 | }, { 39 | desc: "Set TPM/Cycles/Text", 40 | code: `const user = Database.pdb["${id}"]; 41 | let ___ = new Big(user.cycles/text/tpc/cpp/tpm); 42 | __INSERT_CODE__ 43 | user.___ = ___.toString(); 44 | return ___;` 45 | }, { 46 | desc: "ItemEnum Lookup Table", 47 | code: Object.keys(ItemEnum).filter(k => isNaN(Number(k))).join(" ") 48 | }]; 49 | 50 | Bot.carousel(msg, data, 4, (page, itms) => ({ 51 | color: Colors.PRIMARY, 52 | title: "Docs!", 53 | description: `Page: ${brackets(page.toString())}${itms.length == 0 ? "\nNo more items!\nYou've gone far enough!" : ""}`, 54 | fields: itms.map(({ desc, code }) => ({ 55 | name: desc, 56 | value: codestr(code, "js") 57 | })) 58 | }), page); 59 | } 60 | } 61 | 62 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/sell/use.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, brackets, parseNumber } from "../../global.js"; 4 | import { items } from "../../util/data/item.js"; 5 | import { openItem } from "../../util/data/open-item.js"; 6 | import { commanum } from "../../util/util.js"; 7 | 8 | class C extends Command { 9 | names = ["use", "eat", "u", "open", "drink", "open-item"]; 10 | help = "Use an item!"; 11 | examples = ["use coffee"]; 12 | 13 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 14 | if (args.length != 1 && args.length != 2) return Bot.argserror(msg, args.length, [1, 2]); 15 | const num = args[1] == "all" ? Infinity : parseNumber(args[1] || "1"); 16 | if (args[1] && isNaN(num)) return Bot.usererr(msg, "The amount must be a number!"); 17 | 18 | const name = args[0]; 19 | const user = Database.getUser(msg.author.id); 20 | 21 | let itmIndex = items.findIndex(o => o.name.toLowerCase() == name.toLowerCase()); 22 | if (itmIndex == -1) itmIndex = items.findIndex(i => i.name.toLowerCase().indexOf(name.toLowerCase()) > -1); 23 | 24 | const item = items[itmIndex]; 25 | const userAmt = new Big(user.inv[itmIndex] || 0); 26 | const amount = num == Infinity ? userAmt.toNumber() : num; 27 | const open = openItem[itmIndex]; 28 | 29 | if (itmIndex == -1) return Bot.usererr(msg, `Item ${brackets(name)} not found. Check your spelling!`, "Item not found!"); 30 | if (!user.inv[itmIndex] || userAmt.lt(amount)) return Bot.errormsg(msg, `You don't have enough of this item! You still need ${brackets(commanum(userAmt.negated().plus(amount).toString()))} more!`); 31 | if (!open) { 32 | return Bot.errormsg(msg, `This kind of item can't be used! 33 | It might be used in a shop, however.`, "Item can't be used!"); 34 | } 35 | 36 | user.inv[itmIndex] = userAmt.minus(amount).toString(); 37 | const result = open(user, amount); 38 | msg.channel.send({ 39 | embeds: [{ 40 | title: "Using item!", 41 | color: Colors.PRIMARY, 42 | description: `You use **x${commanum(amount.toString())}** ${brackets(item.name)} ...`, 43 | fields: [result] 44 | }] 45 | }); 46 | } 47 | } 48 | 49 | export const c = new C(); -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | node: true 5 | }, 6 | extends: [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | parser: "@typescript-eslint/parser", 11 | parserOptions: { 12 | ecmaVersion: 13, 13 | sourceType: "module" 14 | }, 15 | plugins: [ 16 | "@typescript-eslint" 17 | ], 18 | rules: { 19 | "no-unused-vars": 0, 20 | "indent": ["error", 2, { SwitchCase: 1 }], 21 | "quotes": [2, "double"], 22 | "semi": [2, "always"], 23 | "no-extra-parens": [2, "all"], 24 | "no-unreachable-loop": 2, 25 | "no-constant-condition": 2, 26 | "no-else-return": 2, 27 | "no-extra-bind": 2, 28 | "no-multi-spaces": 2, 29 | "no-useless-return": 2, 30 | "yoda": 2, 31 | "no-undef-init": 2, 32 | "prefer-const": 2, 33 | "prefer-template": 2, 34 | "prefer-arrow-callback": 2, 35 | "no-var": 2, 36 | "no-useless-constructor": 2, 37 | "spaced-comment": 2, 38 | "switch-colon-spacing": 2, 39 | "quote-props": [2, "consistent-as-needed"], 40 | "prefer-exponentiation-operator": 2, 41 | "no-unneeded-ternary": 2, 42 | "no-lonely-if": 2, 43 | "key-spacing": 2, 44 | "func-call-spacing": 2, 45 | "keyword-spacing": 2, 46 | "comma-dangle": 0, 47 | "brace-style": 2, 48 | "array-bracket-spacing": [2, "never", { objectsInArrays: false, arraysInArrays: false }], 49 | "camelcase": 2, 50 | "block-spacing": 2, 51 | "computed-property-spacing": 2, 52 | "dot-location": [2, "property"], 53 | "dot-notation": 2, 54 | "no-implicit-coercion": 2, 55 | "prefer-rest-params": 2, 56 | "space-before-function-paren": [2, { 57 | anonymous: "never", 58 | named: "never", 59 | asyncArrow: "always" 60 | }], 61 | "curly": [2, "multi-line"], 62 | "function-call-argument-newline": [2, "consistent"], 63 | "space-in-parens": 2, 64 | "space-before-blocks": 2, 65 | "no-trailing-spaces": 2, 66 | 67 | // typescript 68 | "no-shadow": 0, 69 | "@typescript-eslint/no-unused-vars": [2, { argsIgnorePattern: "^_" }], 70 | "@typescript-eslint/no-explicit-any": 0, 71 | "@typescript-eslint/no-non-null-assertion": 1, 72 | "@typescript-eslint/no-empty-function": 0, 73 | "@typescript-eslint/explicit-module-boundary-types": 0, 74 | "@typescript-eslint/no-namespace": 0 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/cmd/game/daily.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Database, Bot, formatDate, msBetween, brackets, commanum, addMs, randomChoice } from "../../global.js"; 4 | import { ItemEnum } from "../../util/data/item.js"; 5 | 6 | const rewards: ((user: Database.CycleUser) => Discord.APIEmbedField)[] = [ 7 | user => { 8 | let amt = new Big(user.inv[ItemEnum.DailyChest] || 0); 9 | amt = amt.plus(2); 10 | user.inv[ItemEnum.DailyChest] = amt.toString(); 11 | 12 | return { 13 | name: "Daily reward!", 14 | value: `You got a special ${brackets("Daily Chest")} **x2**!` 15 | }; 16 | }, 17 | user => { 18 | let cycles = new Big(user.cycles); 19 | const amt = new Big(user.cpp).times(20); 20 | 21 | cycles = cycles.plus(amt); 22 | user.cycles = cycles.toString(); 23 | 24 | return { 25 | name: "Daily post!", 26 | value: `You got ${brackets(commanum(amt.toString()))} cycles! 27 | You now have ${brackets(commanum(cycles.toString()))} cycles!` 28 | }; 29 | }, 30 | user => { 31 | let text = new Big(user.text); 32 | const amt = new Big(user.tpc).times(22); 33 | 34 | text = text.plus(amt); 35 | user.text = text.toString(); 36 | 37 | return { 38 | name: "Instant code!", 39 | value: `You got ${brackets(commanum(amt.toString()))} text! 40 | You now have ${brackets(commanum(text.toString()))} text!` 41 | }; 42 | } 43 | ]; 44 | 45 | class C extends Command { 46 | names = ["daily", "d"]; 47 | help = "Claim your daily award!"; 48 | isGame = "y" as const; 49 | 50 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 51 | const user = Database.getUser(msg.author.id); 52 | 53 | if (msBetween(new Date(), new Date(user.daily)) > 0) return Bot.errormsg(msg, `You still need to wait ${formatDate(msBetween(new Date(), new Date(user.daily)))}`, "Daily Cooldown!"); 54 | 55 | const func = randomChoice(rewards)[0]; 56 | const out = func(user); 57 | 58 | msg.channel.send({ 59 | embeds: [{ 60 | color: Colors.PRIMARY, 61 | title: "Daily Reward!", 62 | description: "You got your daily reward!", 63 | fields: [out], 64 | footer: { text: "Come back tomorrow for a daily reward!" } 65 | }] 66 | }); 67 | 68 | user.daily = addMs(new Date(), 60e3 * 60 * 10).toString(); 69 | } 70 | } 71 | 72 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/sell/sell.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, parseNumber, Database, brackets, commanum, constrain } from "../../global.js"; 4 | import { items } from "../../util/data/item.js"; 5 | 6 | class C extends Command { 7 | names = ["sell", "s"]; 8 | help = "Sell your items for cycles!"; 9 | examples = ["sell coffee", "sell 'chest chest' 2"]; 10 | 11 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 12 | if (args.length < 1 || args.length > 2) return Bot.argserror(msg, args.length, [1, 2]); 13 | 14 | const name = args[0]; 15 | const amt = parseNumber(args[1] || "1"); 16 | if (args[1] && isNaN(amt)) return Bot.usererr(msg, "The amount must be a number!"); 17 | const num = new Big(constrain(amt, 1, 50)); 18 | 19 | const user = Database.getUser(msg.author.id); 20 | let itemIndex = items.findIndex(n => n.name.toLowerCase() == name.toLowerCase()); 21 | if (itemIndex == -1) itemIndex = items.findIndex(n => n.name.toLowerCase().indexOf(name.toLowerCase()) > -1); 22 | if (itemIndex == -1) { 23 | return Bot.usererr(msg, `The item ${brackets(name)} was not found! 24 | Chock your spelling!`, "Item not found!"); 25 | } 26 | 27 | const item = items[itemIndex]; 28 | const userAmt = new Big(user.inv[itemIndex] || 0); 29 | const cycles = new Big(user.cycles); 30 | 31 | if (userAmt.lt(amt)) { 32 | return Bot.errormsg(msg, `You don't have enough of ${brackets(item.name)}! 33 | **You still need** ${brackets(commanum(num.minus(userAmt).toString()))} **more!**`); 34 | } 35 | 36 | if (item.currency) { 37 | return Bot.errormsg(msg, `You can't sell currency! 38 | These currencies cannot be converted into cycles!`); 39 | } 40 | 41 | const worth = new Big((1 - item.dropChance / 100) / 2); 42 | // every item is worth at most 100 43 | const cycleAmt = worth.times(100).times(amt).dp(0); 44 | user.inv[itemIndex] = userAmt.minus(num).toString(); 45 | user.cycles = cycles.plus(cycleAmt).toString(); 46 | 47 | msg.channel.send({ 48 | embeds: [{ 49 | color: Colors.PRIMARY, 50 | title: "Success!", 51 | description: `You successfully sold ${brackets(item.name)} x**${amt}**! 52 | It was worth ${brackets(commanum(cycleAmt.toString()))} cycles! 53 | You now have ${brackets(commanum(user.cycles))} cycles!` 54 | }] 55 | }); 56 | } 57 | } 58 | 59 | export const c = new C(); 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cycle Bot 2 | This is the code repository for cycle bot! You are playing as a coder who makes programs in order to get cycles. You can extend your reach to youtube, twitch, and dev.to to become the most cycled! 3 | 4 | You can invite the bot [here](https://discord.com/api/oauth2/authorize?client_id=781939317450342470&permissions=272448&scope=bot). Enjoy! 5 | 6 | 7 | ## How to play 8 | The prefix is `&`. Use `&help` to see all commands. You can also join the [discord server](https://discord.gg/4vTPWdpjFz) 9 | 10 | ## Building 11 | The program will try to read the `.env` file using `dotenv`. Your `.env` file should look something like: 12 | ```sh 13 | TOKEN=insert_token_here 14 | ``` 15 | Make sure not to commit `.env`! 16 | 17 | Afterwards, make sure you have typescript installed. To compile your bot during development, run 18 | ```sh 19 | npm run dev 20 | ``` 21 | 22 | Then do `node .` to run your bot. 23 | To run with extra logging, use `node . -d`. This includes things spit out by the discord.js library. 24 | 25 | If you are hosting this on [`replit`](https://replit.com/), a `.replit` file already has been made with everything you need! All you need to add is your `.env` file and make sure to install everything by doing 26 | ``` 27 | npm i 28 | ``` 29 | 30 | ### Slash Commands 31 | Use `npm run deploy` to deploy slash commands! 32 | > **Tip**: Use `npm run deploy -- -d` to delete all slash commands! 33 | 34 | ### Firebase 35 | This project *does* require firebase, so make sure you hvae the admin SDK ready. First, it will check if the `.env` file contains the `FIREBASE` key, which is just the JSON file on one line. 36 | ``` 37 | FIREBASE={"...": "..."} 38 | ``` 39 | Otherwise, it just reads the `sdk-key.json` file in the root directory. 40 | > Make sure not to commit `sdk-key.json`! 41 | 42 | ### Fake-DB 43 | The bot will periodically stash database info in `database.json`. When developing, make sure `database.json` is created! 44 | 45 | ## Contributions 46 | Suggestions are very much welcome! 47 | You can also use a fallback JSON database so you don't have to mess around with the actual database. To do this, just add a `NODE_ENV` to your `.env` file: 48 | ``` 49 | NODE_ENV=1 50 | ``` 51 | Note the value can be whatever you want as we only check for existence. 52 | 53 | Also read [CONTRIBUTING.md](CONTRIBUTING.md). 54 | 55 | ## Balance 56 | Most math can be found here: https://www.desmos.com/calculator/y0kqpajh2m 57 | Comments that say `refer to desmos` probably refers to that. 58 | -------------------------------------------------------------------------------- /src/cmd/stats/inv.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, brackets, commanum, parseMention, parseNumber, cleanName } from "../../global.js"; 4 | import { items } from "../../util/data/item.js"; 5 | 6 | class C extends Command { 7 | names = ["inventory", "inv", "i"]; 8 | help = "View your inventory."; 9 | examples = ["i 2"]; 10 | 11 | get cooldown() { 12 | return 5; 13 | } 14 | 15 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 16 | if (args.length > 1) return Bot.argserror(msg, args.length, [0, 1]); 17 | const arg0 = parseNumber(args[0], true); 18 | let user = Database.getUser(msg.author.id); 19 | 20 | // really stressing our NaN == false here 21 | if (!arg0) { 22 | const parsedId = parseMention(args[0] || msg.author.id); 23 | 24 | if (parsedId.type == "id") { 25 | user = Database.getUser(parsedId.value); 26 | if (!user) return Bot.errormsg(msg, `User ${brackets(`<@${parsedId.value}>`)} not found. Check your spelling!`, "User not found!"); 27 | } else { 28 | const userArr = Database.findUser(u => u.name.toLowerCase().indexOf(parsedId.value.toLowerCase()) > -1); 29 | if (userArr.length == 0) return Bot.usererr(msg, `User ${brackets(parsedId.value)} not found. Check your spelling!`, "User not found!"); 30 | else if (userArr.length > 1) { 31 | return Bot.errormsg(msg, `Found ${brackets(userArr.length.toString())} users. 32 | ${userArr.slice(0, 10).map(o => `${brackets(cleanName(Database.getUser(o).name))}: **${o}**`).join("\n")}`, "Multiple users found!"); 33 | } 34 | user = Database.getUser(userArr[0]); 35 | } 36 | } 37 | 38 | const page = arg0 || 1; 39 | // todo: emoji 40 | const data = Object.keys(user.inv).filter(i => new Big(user.inv[Number(i)] || 0).gt(0)).map(i => `x**${commanum(user.inv[Number(i)]!.toString())}** ${brackets(items[Number(i)].name)}`); 41 | 42 | Bot.carousel(msg, data, 10, (page, itm) => { 43 | if (itm.length == 0) { 44 | return { 45 | color: Colors.WARNING, 46 | title: "Empty Page", 47 | description: "No items here!", 48 | footer: { text: `Page: ${page}` } 49 | }; 50 | } 51 | return { 52 | color: Colors.PRIMARY, 53 | title: "Inventory", 54 | description: itm.join("\n"), 55 | footer: { text: `Page ${page}` } 56 | }; 57 | 58 | }, page); 59 | } 60 | } 61 | 62 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/stats/leaderboard.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, brackets, commanum, constrain, parseNumber, cleanName } from "../../global.js"; 4 | import { bannedUsers } from "../../util/database/banned-user.js"; 5 | 6 | class C extends Command { 7 | names = ["leaderboard", "scoreboard", "lb", "l"]; 8 | help = "Get the leaderboard!"; 9 | examples = ["lb 5", "lb"]; 10 | 11 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 12 | if (args.length > 1) return Bot.argserror(msg, args.length, [1]); 13 | const num = parseNumber(args[0]); 14 | if (args[0] && isNaN(num)) return Bot.usererr(msg, "The page must be a number!"); 15 | 16 | const page = constrain(num || 1, 1, Infinity); 17 | const data = Object.keys(Database.pdb) 18 | .filter(n => !bannedUsers.includes(n)) 19 | .map(n => ({ name: Database.pdb[n].name, cycles: Database.pdb[n].cycles })) 20 | .sort((a, b) => new Big(b.cycles).minus(new Big(a.cycles)).toNumber()); 21 | 22 | Bot.carousel(msg, data, 10, (pg, itm) => { 23 | let out: Discord.APIEmbedField[] = []; 24 | if (itm.length == 0) out = [{ name: "Empty Page", value: "No users here!" }]; 25 | else { 26 | const st = 10 * (pg - 1) + 1; // start 27 | 28 | const col1 = itm.slice(0, 5).map((n, i) => `${st + i}. ${n.name == msg.author.tag ? "**" : ""}${cleanName(n.name)}${n.name == msg.author.tag ? "**" : ""} ${brackets(commanum(n.cycles))}`); 29 | const col2 = itm.slice(5).map((n, i) => `${st + i + 5}. ${n.name == msg.author.tag ? "**" : ""}${cleanName(n.name)}${n.name == msg.author.tag ? "**" : ""} ${brackets(commanum(n.cycles))}`); 30 | 31 | if (col1.length > 0) out.push({ name: `${st}-${st + 4}`, value: col1.join("\n"), inline: true }); 32 | if (col2.length > 0) out.push({ name: `${st + 5}-${st + 9}`, value: col2.join("\n"), inline: true }); 33 | 34 | const user = Database.getUser(msg.author.id); 35 | 36 | if (itm.findIndex(n => n.name == user.name) < 0) { 37 | const index = data.findIndex(n => n.name == user.name); 38 | out.push({ 39 | name: "You", 40 | value: `${index + 1}. **${cleanName(user.name)}** ${brackets(commanum(user.cycles))}` 41 | }); 42 | } 43 | } 44 | 45 | return { 46 | color: Colors.PRIMARY, 47 | title: "Leaderboard", 48 | description: `View page ${brackets(pg.toString())}`, 49 | fields: out 50 | }; 51 | }, page); 52 | } 53 | } 54 | 55 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/meta/guide.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Colors } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["guide"]; 6 | help = "View a breakdown of the bot's commands!"; 7 | isGame = "n" as const; 8 | 9 | get cooldown() { 10 | return 10e3; 11 | } 12 | 13 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 14 | msg.channel.send({ 15 | embeds: [{ 16 | color: Colors.PRIMARY, 17 | title: "Cycle Guide!", 18 | description: `Welcome to the Cycle Bot! This guide explains everything you will need to know! 19 | Join the [Discord server](https://discord.gg/4vTPWdpjFz) for more perks! 20 | 21 | The main goal of the bot is to get as many **cycles** as possible! 22 | 23 | To get cycles, you code: 24 | **&code** Use this command to write some code! 25 | 26 | Then, you post the code: 27 | **&post** Once you make some code, you can post it for some *cycles*. 28 | 29 | To get text/cycles faster, visit the shop: 30 | **&shop** Use the shop to see the available upgrades you can buy. 31 | 32 | Look at the top players: 33 | **&lb** View the leaderboard, and see who has the most cycles! 34 | 35 | You can also do daily tasks for cycles: 36 | **&vote** Vote for the bot on top.gg :) 37 | **&daily** Get your daily reward! 38 | 39 | You can also do other tasks with \`quests\`! 40 | **&quest** View your current quest 41 | **&new-quest** Get a new quest! 42 | 43 | There's a lot more commands: 44 | **&help** View more help commands! 45 | 46 | [**Discord Server**](https://discord.gg/4vTPWdpjFz) | [**Wiki**](https://github.com/cursorweb/Cycle-Bot-Game/wiki) | [**Bot Invite**](https://discord.com/api/oauth2/authorize?client_id=781939317450342470&permissions=272448&scope=bot)`, 47 | fields: [{ 48 | name: "Code", 49 | value: "This is the stuff you can use to get **cycles**" 50 | }, { 51 | name: "Cycles", 52 | value: "This determines your position on the leaderboard and you can use it to buy upgrades!" 53 | }, { 54 | name: "Chests", 55 | value: `Sometimes you get chests from coding. They give you **items**! 56 | Use \`&info\` to see all items!` 57 | }, { 58 | name: "Golden Cycles", 59 | value: `Use these to buy boosts, which give you an advantage for a certain about of time! 60 | Buy boosts at \`&shop boosts\`` 61 | }, { 62 | name: "Idle-Coins", 63 | value: `Use these to buy idle machines, getting you passive **text**! 64 | Buy idle machines at \`&shop idle\`` 65 | }], 66 | footer: { text: "Join the Discord Server! https://discord.gg/4vTPWdpjFz" } 67 | }] 68 | }); 69 | } 70 | } 71 | 72 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/misc/whois.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Command, Bot, brackets, Colors, parseMention } from "../../global.js"; 3 | 4 | class C extends Command { 5 | names = ["whois", "who-is"]; 6 | help = "Get user info about anyone!"; 7 | examples = ["whois Hithere#6537", "whois"]; 8 | isGame = "n" as const; 9 | 10 | async exec(msg: Discord.Message, args: string[], client: Discord.Client) { 11 | if (args.length > 2) { 12 | return Bot.argserror(msg, args.length, [0, 1]); 13 | } 14 | 15 | let user: Discord.User | undefined; 16 | let member: Discord.GuildMember | null | undefined; 17 | 18 | if (args.length == 0) { 19 | user = msg.author; 20 | member = msg.member; 21 | } else if (args.length == 1) { 22 | const search = args[0]; 23 | 24 | const filter = (user: Discord.User | Discord.GuildMember) => { 25 | if (user.id == search) return true; 26 | const mention = parseMention(search); 27 | 28 | if (mention.type == "id") { 29 | return user.id == mention.value; 30 | } 31 | 32 | const duser = user instanceof Discord.User ? user : user.user; 33 | const val = mention.value.toLowerCase(); 34 | return duser.username.toLowerCase() == val || duser.tag.toLowerCase() == val; 35 | }; 36 | 37 | user = client.users.cache.find(filter); 38 | member = msg.guild?.members.cache.find(filter); 39 | try { 40 | if (!user) user = await client.users.fetch(search); 41 | if (!member) member = await msg.guild?.members.fetch(search); 42 | } catch { 43 | // don't care 44 | } 45 | } 46 | 47 | if (user) { 48 | msg.channel.send({ 49 | embeds: [{ 50 | color: Colors.PRIMARY, 51 | title: user.tag, 52 | description: `**Account Creation**: ${user.createdAt.toDateString()} 53 | **Joined At**: ${member?.joinedAt?.toDateString() || "*User not in server*"} 54 | **Id**: \`${user.id}\` 55 | **View more**: ${user}`, 56 | thumbnail: { 57 | url: user.displayAvatarURL(), 58 | }, 59 | fields: member ? [{ 60 | name: "Roles", 61 | value: member.roles.cache.map(r => `<@&${r.id}>`).join(" ") 62 | }] : [] 63 | }] 64 | }); 65 | } else { 66 | msg.channel.send({ 67 | embeds: [{ 68 | title: "User not found!", 69 | color: Colors.ERROR, 70 | description: `Your search, ${brackets(args[0])} 71 | did not turn up any results!`, 72 | footer: { 73 | text: "Make sure the user is in a server that cycle is in as well!" 74 | } 75 | }] 76 | }); 77 | } 78 | } 79 | } 80 | 81 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/database/pseudo.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises"; 2 | import { URL } from "node:url"; 3 | 4 | import Big from "bignumber.js"; 5 | import { msBetween } from "../util.js"; 6 | 7 | import { db } from "./database.js"; 8 | 9 | import { CycleUser } from "./genschema.js"; 10 | import { bannedUsers } from "./banned-user.js"; 11 | // Try to use `setUser` for everything 12 | 13 | // 'pseudo' db 14 | export let pdb: { [i: string]: CycleUser } = {}; 15 | 16 | export function setUser(key: string, val: CycleUser) { 17 | if (!pdb[key]) pdb[key] = val; 18 | Object.assign(pdb[key], val); 19 | } 20 | 21 | // return keys of IDs 22 | export function findUser(filter: (u: CycleUser) => boolean): string[] { 23 | return Object.keys(pdb).filter(k => filter(pdb[k])); 24 | } 25 | 26 | export function getUser(key: string) { 27 | return pdb[key]; 28 | } 29 | 30 | export function pruneUsers() { 31 | const deleted: { name: string, reason: string }[] = []; 32 | for (const key in pdb) { 33 | const user = pdb[key]; 34 | if (user.socialMedia > 0) continue; 35 | 36 | const tpc = new Big(user.tpc); 37 | const cpp = new Big(user.cpp); 38 | const tpm = new Big(user.tpm); 39 | 40 | if (tpc.plus(cpp).plus(tpm).gt(100)) continue; 41 | 42 | // greater than a week 43 | if (new Big(user.cycles).lt(50) && (user.daily && msBetween(new Date(user.daily), new Date()) > 1000 * 60 * 60 * 24 * 7)) { 44 | delete pdb[key]; 45 | 46 | let reason = ""; 47 | 48 | if (new Big(user.cycles).lt(50)) { 49 | reason = `Cycles: ${user.cycles}`; 50 | } else { 51 | reason = `Date: ${(msBetween(new Date(user.daily), new Date()) / 1000 * 60 * 60 * 24).toFixed(1)} days`; 52 | } 53 | 54 | deleted.push({ 55 | name: user.name, 56 | reason 57 | }); 58 | } 59 | } 60 | 61 | return deleted; 62 | } 63 | 64 | export async function save() { 65 | if (Object.keys(pdb).length == 0) return; // prevent corruption 66 | for (const id in pdb) { 67 | if (bannedUsers.includes(id)) delete pdb[id]; 68 | } 69 | 70 | return await db.collection("cycle-users").doc("users").set(pdb); 71 | } 72 | 73 | export async function update() { 74 | if (!process.env.NODE_ENV) { 75 | const col = db.collection("cycle-users").doc("users"); 76 | return await col.get().then(doc => { 77 | pdb = doc.data() as { [i: string]: CycleUser }; 78 | }); 79 | } 80 | } 81 | 82 | export async function saveBackup() { 83 | for (const id in pdb) { 84 | if (bannedUsers.includes(id)) delete pdb[id]; 85 | } 86 | await fs.writeFile(new URL("../../../database.json", import.meta.url), JSON.stringify(pdb, null, 2)); 87 | } 88 | 89 | export async function updateBackup() { 90 | pdb = JSON.parse(await fs.readFile(new URL("../../../database.json", import.meta.url), "utf-8")); 91 | } -------------------------------------------------------------------------------- /src/cmd/meta/delete-acc.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js"; 3 | import { Command, Colors, Database } from "../../global.js"; 4 | 5 | class C extends Command { 6 | names = ["delete-account"]; 7 | help = "Deletes your account data from the database (privacy policy)"; 8 | examples = ["delete-account"]; 9 | 10 | exec(msg: Discord.Message, _: string[], client: Discord.Client) { 11 | msg.channel.send({ 12 | embeds: [{ 13 | color: Colors.WARNING, 14 | title: "Confirm Account Deletion", 15 | description: "Are you sure you want to delete your account?\n**ALL** your progress and data will be deleted!" 16 | }], 17 | components: [ 18 | new ActionRowBuilder() 19 | .addComponents([ 20 | new ButtonBuilder() 21 | .setCustomId("next") 22 | .setEmoji("✔️") 23 | .setStyle(ButtonStyle.Success), 24 | new ButtonBuilder() 25 | .setCustomId("cancel") 26 | .setEmoji("✖️") 27 | .setStyle(ButtonStyle.Danger) 28 | ]) 29 | ] 30 | }).then(async mesg => { 31 | const collector = mesg.createMessageComponentCollector({ 32 | filter: (inter) => inter.user.id == msg.author.id, 33 | max: 1, 34 | time: 60000 35 | }); 36 | 37 | collector.on("collect", async choice => { 38 | if (choice.customId == "cancel") { 39 | collector.stop(); 40 | await mesg.edit({ 41 | embeds: [{ 42 | color: Colors.PRIMARY, 43 | title: "Cancelled", 44 | description: "Happy coding!" 45 | }] 46 | }); 47 | return; 48 | } 49 | 50 | const user = Database.pdb[msg.author.id]; 51 | const channel = await client.channels.fetch("899518500576579615") as Discord.TextChannel; 52 | channel.send({ 53 | embeds: [{ 54 | color: Colors.PRIMARY, 55 | title: "Deleted Account", 56 | description: `User: \`${user.name}\` 57 | ID: \`${msg.author.id}\`` 58 | }] 59 | }); 60 | 61 | delete Database.pdb[msg.author.id]; 62 | msg.channel.send({ 63 | embeds: [{ 64 | color: Colors.PRIMARY, 65 | title: "Deleted account", 66 | description: "Goodbye! Come back soon!" 67 | }] 68 | }); 69 | }); 70 | 71 | collector.on("end", collected => { 72 | if (collected.size == 0) { 73 | mesg.edit({ 74 | embeds: [{ 75 | color: Colors.WARNING, 76 | title: "Cancelled", 77 | description: "You didn't make a response in time!" 78 | }] 79 | }); 80 | } 81 | 82 | mesg.edit({ 83 | components: [] 84 | }); 85 | }); 86 | }); 87 | } 88 | } 89 | 90 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/stats/prof.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, parseMention, brackets, commanum, cleanName, progress } from "../../global.js"; 4 | import { socialMedia } from "../../util/data/social-media.js"; 5 | import { CycleUser } from "../../util/database/database.js"; 6 | 7 | 8 | class C extends Command { 9 | names = ["profile", "prof", "bal", "balance", "stats"]; 10 | help = "View yours or someone elses' profile."; 11 | examples = ["prof", "prof Coder100"]; 12 | isGame = "p" as const; 13 | 14 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 15 | if (args.length > 1) return Bot.argserror(msg, args.length, [0, 1]); 16 | 17 | let user: CycleUser; 18 | 19 | if (args.length == 0) { 20 | user = Database.getUser(msg.author.id); 21 | } else { 22 | const userArg = parseMention(args[0]); 23 | 24 | if (userArg.type == "id") { 25 | user = Database.getUser(userArg.value); 26 | if (!user) return Bot.usererr(msg, `User ${brackets(`<@${userArg.value}>`)} not found.`, "User not found!"); 27 | } else { 28 | const userArr = Database.findUser(u => u.name.toLowerCase().indexOf(userArg.value.toLowerCase()) > -1); 29 | if (userArr.length == 0) return Bot.usererr(msg, `User ${brackets(userArg.value)} not found. Check your spelling!`, "User not found!"); 30 | else if (userArr.length > 1) { 31 | return Bot.errormsg(msg, `Found ${brackets(userArr.length.toString())} users. 32 | ${userArr.slice(0, 10).map(o => `${brackets(Database.getUser(o).name)}: **${o}**`).join("\n")}`, "Multiple users found!"); 33 | } 34 | user = Database.getUser(userArr[0]); 35 | } 36 | } 37 | 38 | const sm = user.socialMedia; 39 | const prestige = Math.log(sm) + 0.5; 40 | 41 | const smBoost = []; 42 | if (sm > 0) { 43 | const nextTpc = new Big("1").times(prestige).dp(0); 44 | const nextCpp = new Big("1").times(prestige).dp(0); 45 | const nextTpm = new Big("0").plus(1).times(prestige).div(2).dp(0); 46 | 47 | smBoost.push({ 48 | name: "Social Media Boosts", 49 | value: `Social Media: ${brackets(socialMedia[user.socialMedia])} 50 | + **${nextTpc}**% TPC 51 | + **${nextCpp}**% CPP 52 | + **${nextTpm}**% TPM` 53 | }); 54 | } 55 | 56 | const xp = new Big(user.xp); 57 | const total = new Big(user.level).times(5); 58 | 59 | const percent = xp.div(total).times(10).toNumber(); 60 | 61 | msg.channel.send({ 62 | embeds: [{ 63 | color: Colors.PRIMARY, 64 | title: `User ${brackets(cleanName(user.name))}`, 65 | 66 | description: `**Cycles**: ${commanum(user.cycles)} 67 | **Text**: ${commanum(user.text)}`, 68 | fields: [{ 69 | name: "Stats", 70 | value: `**TPC**: ${commanum(user.tpc)} 71 | **CPP**: ${commanum(user.cpp)} 72 | **TPM**: ${commanum(user.tpm)}` 73 | }, { 74 | name: "Level", 75 | value: `**Level**: ${commanum(user.level)} 76 | **Progress**: ${progress(percent, 10)}` 77 | }].concat(smBoost) 78 | }] 79 | }); 80 | } 81 | } 82 | 83 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/game/code.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, brackets, plural, commanum, Database } from "../../global.js"; 4 | import { code as drops } from "../../util/data/drops.js"; 5 | import { boosts } from "../../util/data/boosts/boosts.js"; 6 | import { levelUp } from "../../util/levels.js"; 7 | import { socialMedia } from "../../util/data/social-media.js"; 8 | import { ActionType, checkQuest } from "../../util/data/quests.js"; 9 | 10 | class C extends Command { 11 | names = ["code", "c"]; 12 | help = "Work on your project!"; 13 | isGame = "y" as const; 14 | 15 | get cooldown() { 16 | return 5000; 17 | } 18 | 19 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 20 | const user = Database.getUser(msg.author.id); 21 | const userBoosts = Database.Boost.getUser(msg.author.id); 22 | const idx = user.socialMedia; 23 | 24 | const fields: Discord.APIEmbedField[] = []; 25 | const levelField = levelUp(user); 26 | if (levelField) fields.push(levelField); 27 | 28 | let tpc = new Big(user.tpc); 29 | let text = new Big(user.text); 30 | 31 | const isServer = msg.guild?.id == "788421241005408268"; // if user is in official server 32 | 33 | if (isServer) tpc = tpc.times(1.1).dp(0); 34 | for (const drop of drops) { 35 | if (drop.chance()) { 36 | fields.push(drop.award(user)); 37 | tpc = new Big(user.tpc), text = new Big(user.text); // have to update it again 38 | } 39 | } 40 | 41 | for (const index in userBoosts) { 42 | const itm = boosts[index]; 43 | if (!itm.tpc) continue; 44 | const amt = userBoosts[index].length; 45 | fields.push({ 46 | name: itm.name, 47 | value: itm.message || "" 48 | }); 49 | 50 | tpc = tpc.times(new Big(itm.tpc).plus(100).div(100)).times(amt).dp(0); 51 | } 52 | 53 | if (idx > 0) { 54 | const name = socialMedia[idx]; 55 | const prestige = Math.log(idx + 1) + 0.5; 56 | const tpc = new Big(user.tpc).times(prestige).dp(0); 57 | text = text.plus(tpc); 58 | 59 | fields.push({ 60 | name: "Social Media Boost!", 61 | value: `With a better code editor in ${name},\nyou get +${brackets(commanum(tpc.toString()))} more text!` 62 | }); 63 | } 64 | 65 | const quest = checkQuest(user, ActionType.Code); 66 | if (quest) fields.push(quest); 67 | 68 | msg.channel.send({ 69 | embeds: [{ 70 | color: Colors.PRIMARY, 71 | title: "Code Code Code!", 72 | description: `You code your heart out! 73 | You make ${brackets(commanum(tpc.toString()))} line${plural(tpc.toNumber())} of code!${isServer ? ` 74 | **10% text boost** for coding in the official discord server!` : ""}`, 75 | footer: { text: "Use &post to get some cycles!\nUse &open 'chest chest' to open chests!" }, 76 | fields 77 | }] 78 | }); 79 | 80 | user.text = text.plus(tpc).toString(); 81 | } 82 | 83 | cooldownError(msg: Discord.Message, ms: number) { 84 | Bot.errormsg(msg, `Your fingers are still tired from typing! 85 | Please wait ${brackets((ms / 1000).toString())} seconds before you can code again.`, "Cooldown!"); 86 | } 87 | } 88 | 89 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/data/drops.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { brackets, pluralb, commanum, hidden, Database } from "../../global.js"; 4 | import { items } from "./item.js"; 5 | import { ActionType, checkQuest } from "./quests.js"; 6 | 7 | const code: DropItem[] = [{ 8 | chance: () => Math.random() < 0.1, 9 | award: user => { 10 | let text = new Big(user.text); 11 | const tpc = new Big(user.tpc); 12 | const boost = Math.random() * 2; 13 | const amount = tpc.times(boost).dp(0); 14 | text = text.plus(amount); 15 | user.text = text.toString(); 16 | return { 17 | name: "Code Burst!", value: `You get an extra burst of energy! 18 | +${brackets(commanum(amount.toString()))} line${pluralb(amount)} of code! (+${Math.floor(boost * 100)}%)` 19 | }; 20 | } 21 | }, { 22 | chance: () => Math.random() < 0.05, 23 | award: user => { 24 | let cycles = new Big(user.cycles); 25 | cycles = cycles.plus(5); 26 | user.cycles = cycles.toString(); 27 | 28 | const field = checkQuest(user, ActionType.Answer); 29 | let value = ""; 30 | if (field) { 31 | value = ` 32 | **${field.name}** 33 | ${field.value}`; 34 | } 35 | 36 | return { 37 | name: "Question Answerer!", value: `You answered somebody's question! 38 | You earned ${brackets("5")} cycles!${value}` 39 | }; 40 | } 41 | }, { 42 | chance: () => Math.random() < 0.2, 43 | award: user => { 44 | let itemsFound = 0; 45 | const itemsGot: { [i: string]: number } = {}; 46 | 47 | for (let j = 0; j < 5; j++) { 48 | for (let i = 0; i < items.length; i++) { 49 | if (itemsFound > 15) break; 50 | if (Math.random() > 0.5) itemsFound++; 51 | 52 | const item = items[i]; 53 | if (Math.random() * 100 < item.dropChance) { 54 | itemsGot[i] = (itemsGot[i] || 0) + 1; 55 | user.inv[i] = new Big(user.inv[i] || 0).plus(1).toString(); 56 | } 57 | } 58 | } 59 | 60 | const itemText = Object.keys(itemsGot).map(i => `${hidden(`${items[Number(i)].name}`)}${itemsGot[i] > 1 ? ` x**${commanum(itemsGot[i].toString())}**` : ""}`); 61 | 62 | const field = checkQuest(user, ActionType.Chest); 63 | let value = ""; 64 | if (field) { 65 | value = ` 66 | **${field.name}** 67 | ${field.value}`; 68 | } 69 | 70 | return { 71 | name: "Mystery Chest!", value: `You accidentally make a ${brackets("chest")}! 72 | You open it up and find... 73 | ${itemText.length == 0 ? hidden("nothing :(") : itemText.join("\n")}${value}` 74 | }; 75 | } 76 | }]; 77 | 78 | // ---- 79 | 80 | const post: DropItem[] = [{ 81 | chance: () => Math.random() < 0.05, 82 | award: user => { 83 | let text = new Big(user.text); 84 | const tpc = new Big(user.tpc); 85 | const boost = Math.floor(Number(Math.random() * 10)); 86 | const amount = tpc.times(boost).dp(0); 87 | text = text.plus(amount); 88 | user.text = text.toString(); 89 | return { name: "User Suggestions", value: `People give you suggestions, and you write ${brackets(commanum(amount.toString()))} line${pluralb(amount)} of code!` }; 90 | } 91 | }]; 92 | 93 | export interface DropItem { 94 | chance: () => boolean; 95 | award: (_: Database.CycleUser) => Discord.APIEmbedField 96 | } 97 | 98 | export { code, post }; -------------------------------------------------------------------------------- /src/cmd/shop/shop.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, commanum, constrain, calcPrice, brackets, codestr, parseNumber } from "../../global.js"; 4 | import { items } from "../../util/data/shop.js"; 5 | import { boostShop } from "../../util/data/boosts/boosts-shop.js"; 6 | import { boosts } from "../../util/data/boosts/boosts.js"; 7 | import { ItemEnum } from "../../util/data/item.js"; 8 | 9 | // [topic] [value] 10 | const handleShop: { [i: string]: (user: Database.CycleUser) => string[] } = { 11 | text: user => items.upgrades.map((n, i) => `[ ${n.name} ][ ${commanum(calcPrice(new Big(n.cost), 1.07, user.bought.upgrades[i] || 0).toString())} Cycles ] 12 | <+${n.tpc} TPC> <${commanum((user.bought.upgrades[i] || 0).toString())} owned> 13 | > ${n.description}`), 14 | post: user => items.cpp.map((n, i) => `[ ${n.name} ][ ${commanum(calcPrice(new Big(n.cost), 1.14, user.bought.cpp[i] || 0).toString())} Cycles ] 15 | <+${n.cpp} CPP> <${commanum((user.bought.cpp[i] || 0).toString())} owned> 16 | > ${n.description}`), 17 | idle: user => items.idle.map((n, i) => `[ ${n.name} ][ ${commanum(calcPrice(new Big(n.cost), 1.21, user.bought.idle[i] || 0).toString())} Idle-Coins ] 18 | <+${n.tpm} TPM> <${commanum((user.bought.idle[i] || 0).toString())} owned> 19 | > ${n.description}`), 20 | boosts: _ => boostShop.map(n => { 21 | const b = boosts[n.ref]; 22 | return `[ ${b.name} ][ ${n.cost} Golden Cycles ] 23 | <+${b.tpc || 0}% TPC> <+ ${b.cpp || 0}% CPP> <+ ${b.tpm || 0}% TPM> 24 | > ${b.description}`; 25 | }) 26 | }; 27 | 28 | class C extends Command { 29 | names = ["shop"]; 30 | help = "View the shop!"; 31 | examples = ["shop idle", "shop idle 3"]; 32 | 33 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 34 | if (args.length == 0) { 35 | return Bot.errormsg(msg, `The valid shop names are: 36 | ${codestr(Object.keys(handleShop).join(", "), "yaml")}`, "Shop names"); 37 | } 38 | if (args.length != 1 && args.length != 2) return Bot.argserror(msg, args.length, [1, 2]); 39 | const num = parseNumber(args[1]); 40 | if (args[1] && isNaN(num)) return Bot.usererr(msg, "The page must be a number!"); 41 | if (!Object.keys(handleShop).includes(args[0])) { 42 | return Bot.errormsg(msg, `The valid shop names are: 43 | ${codestr(Object.keys(handleShop).join(", "), "yaml")}`, "Invalid Shop Name!!"); 44 | } 45 | 46 | const user = Database.getUser(msg.author.id); 47 | 48 | const page = constrain(num || 1, 1, Infinity); 49 | const data = handleShop[args[0]](user); 50 | 51 | Bot.carousel(msg, data, 5, (page, i): Discord.APIEmbed => ({ 52 | color: Colors.PRIMARY, 53 | title: "Shop!", 54 | description: `View the shop! Page ${brackets(page.toString())}. 55 | **Cycles**: ${brackets(commanum(user.cycles))} 56 | **Text**: ${brackets(commanum(user.text))} 57 | **Idle-Coins**: ${brackets(commanum(user.inv[ItemEnum.IdleCoin] || "0"))} 58 | **Golden Cycles**: ${brackets(commanum(user.inv[ItemEnum.GoldenCycle] || "0"))} 59 | ${i.length > 0 ? codestr(i.join("\n\n"), "md") : codestr(`[ NO ][ MORE ][ ITEMS ] 60 | > You've gone far enough!`, "md")}`, 61 | footer: { text: `Tip: Use &buy ${args[0]} to buy an item!` } 62 | }), page); 63 | } 64 | } 65 | 66 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/shop/craft.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, brackets, parseNumber, commanum, codestr, noun } from "../../global.js"; 4 | import { items } from "../../util/data/item.js"; 5 | import { craftItems } from "../../util/data/craft.js"; 6 | 7 | class C extends Command { 8 | names = ["craft", "create"]; 9 | help = "Create an item... from other items!"; 10 | examples = ["create 'chest chest chest' 5"]; 11 | 12 | exec(msg: Discord.Message, args: string[]) { 13 | if (args.length > 3) return Bot.argserror(msg, args.length, [0, 1, 2]); 14 | const num = parseNumber(args[0] || "0"); 15 | if (args.length == 0 || num) { 16 | const fields = craftItems.map(p => { 17 | const uses = p.requires.map(n => `[ ${items[n.type].name} ][ x${n.amt} ]`); 18 | return `${noun(items[p.creates].name)} 19 | ${codestr(uses.join("\n"), "md")}`; 20 | }); 21 | 22 | 23 | Bot.carousel(msg, fields, 3, (page, i) => { 24 | return { 25 | color: Colors.PRIMARY, 26 | title: "Crafting Items", 27 | description: `These are all the available crafting items! Page ${brackets(page.toString())}`, 28 | fields: i.length == 0 ? [{ 29 | name: "Empty Page", 30 | value: "No items here!" 31 | }] : [{ 32 | name: "Item", 33 | value: i.join("\n\n"), 34 | inline: true 35 | }], 36 | footer: { text: "Tip: Use &craft to craft !" } 37 | }; 38 | }, num || 1); 39 | } else { 40 | const itmInput = args[0]; 41 | const amt = parseNumber(args[1] || "1"); 42 | if (isNaN(amt)) return Bot.usererr(msg, "The amount must be a number!"); 43 | 44 | // find item 45 | let item = craftItems.find(o => items[o.creates].name.toLowerCase() == itmInput.toLowerCase()); 46 | if (!item) item = craftItems.find(o => items[o.creates].name.toLowerCase().indexOf(itmInput.toLowerCase()) > -1); 47 | if (!item) { 48 | return Bot.usererr(msg, `Item ${brackets(itmInput)} not found. 49 | Check your spelling!`, "Item not found!"); 50 | } 51 | 52 | // alias 53 | const itemMeta = items[item.creates]; 54 | 55 | // check availability 56 | const user = Database.getUser(msg.author.id); 57 | for (const itm of item.requires) { 58 | const num = new Big(itm.amt).times(amt); 59 | const userAmt = new Big(user.inv[itm.type] || 0); 60 | if (userAmt.lt(num)) { 61 | return Bot.errormsg(msg, `You don't have enough of ${brackets(items[itm.type].name)}! 62 | **You still need** ${brackets(commanum(num.minus(userAmt).toString()))} **more!**`); 63 | } 64 | } 65 | 66 | for (const itm of item.requires) { 67 | const num = new Big(itm.amt).times(amt); 68 | const userAmt = new Big(user.inv[itm.type] || 0); 69 | user.inv[itm.type] = userAmt.minus(num).toString(); 70 | } 71 | 72 | let outAmt = new Big(user.inv[item.creates] || 0); 73 | outAmt = outAmt.plus(amt); 74 | user.inv[item.creates] = outAmt.toString(); 75 | 76 | msg.channel.send({ 77 | embeds: [{ 78 | color: Colors.PRIMARY, 79 | title: "Success!", 80 | description: item.message, 81 | fields: [{ 82 | name: "Result", 83 | value: `+ ${brackets(commanum(amt.toString()))} ${itemMeta.name}!` 84 | }] 85 | }] 86 | }); 87 | } 88 | } 89 | } 90 | 91 | export const c = new C(); -------------------------------------------------------------------------------- /src/util/util.ts: -------------------------------------------------------------------------------- 1 | import Big from "bignumber.js"; 2 | 3 | function randomChoice(array: T[], amount = 1): T[] { 4 | const out = []; 5 | for (let i = 0; i < amount; i++) { 6 | out.push(array[Math.floor(array.length * Math.random())]); 7 | } 8 | 9 | return out; 10 | } 11 | 12 | /** 13 | * Generates a number 14 | * @param min Min 15 | * @param max Max 16 | * @returns Random number between [min, max) 17 | */ 18 | function random(min: number, max: number): number { 19 | return (max - min) * Math.random() + min; 20 | } 21 | 22 | function randomb(min: Big, max: Big): Big { 23 | return max.minus(min).times(Math.random()).plus(min); 24 | } 25 | 26 | /** Adds Commas */ 27 | function commanum(inp: string) { 28 | const val = inp.replace(/,/g, ""); 29 | const valSplit = val.split("."); 30 | 31 | while (/(\d+)(\d{3})/.test(valSplit[0].toString())) valSplit[0] = valSplit[0].toString().replace(/(\d+)(\d{3})/, "$1,$2"); 32 | 33 | return valSplit.length == 2 ? `${valSplit[0]}.${valSplit[1]}` : valSplit[0]; 34 | } 35 | 36 | /** Turns 1e+3 to 1000 */ 37 | function expandnum(val: string) { 38 | const data = val.split(/[eE]/); 39 | if (data.length == 1) return data[0]; 40 | 41 | let z = ""; 42 | const sign = Number(val) < 0 ? "-" : ""; 43 | const str = data[0].replace(".", ""); 44 | let mag = Number(data[1]) + 1; 45 | 46 | if (mag < 0) { 47 | z = `${sign}0.`; 48 | while (mag++) z += "0"; 49 | return z + str.replace(/^-/, ""); 50 | } 51 | 52 | mag -= str.length; 53 | while (mag--) z += "0"; 54 | return str + z; 55 | } 56 | 57 | function plural(amount: number, singular = "", plural = "s") { 58 | return amount == 1 ? singular : plural; 59 | } 60 | 61 | function pluralb(amount: Big, singular = "", plural = "s") { 62 | return amount.eq(1) ? singular : plural; 63 | } 64 | 65 | function parseMention(input: string): { type: "name" | "id", value: string } { 66 | if (/^<@!?(\d+)>$/.test(input)) return { type: "id", value: input.match(/^<@!?(\d+)>$/)?.[1] || "" }; 67 | if (/^\d+$/.test(input)) return { type: "id", value: input }; 68 | return { type: "name", value: input }; 69 | } 70 | 71 | function msBetween(start: Date, end: Date) { 72 | return end.getTime() - start.getTime(); 73 | } 74 | 75 | function addMs(start: Date, ms: number) { 76 | return new Date(start.getTime() + ms); 77 | } 78 | 79 | /** 80 | * How much 'n' amout costs. 81 | */ 82 | function calcCost(base: Big, inflation: number, amount: number, owned: number) { 83 | const binfl = new Big(inflation); 84 | return base.times(binfl.pow(amount).minus(1).times(binfl.pow(owned)).div(binfl.minus(1))).dp(0); 85 | } 86 | 87 | /** 88 | * How much one thing costs 89 | */ 90 | function calcPrice(base: Big, inflation: number, owned: number) { 91 | return calcCost(base, inflation, 1, owned); 92 | } 93 | 94 | /** 95 | * parses commas as well. 96 | * @param num the number 97 | * @param mention don't ignore <>@! 98 | */ 99 | function parseNumber(num = "", mention = false) { 100 | const n = num.trim().replace(mention ? /[^\d.<>@!]/g : /[^\d.]/g, ""); 101 | return n == "" ? NaN : Number(n); 102 | } 103 | 104 | function constrain(n: number, min: number, max: number) { 105 | return n < min ? min : n > max ? max : n; 106 | } 107 | 108 | function cleanName(name: string) { 109 | return name.replace(/([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]+)/g, "\\$1"); 110 | } 111 | 112 | export { randomChoice, random, randomb, commanum, expandnum, plural, pluralb, parseMention, msBetween, addMs, calcCost, calcPrice, parseNumber, constrain, cleanName }; -------------------------------------------------------------------------------- /src/util/data/item.ts: -------------------------------------------------------------------------------- 1 | // visit ./openItem.ts for implementation 2 | // the B stands for Boost 3 | export interface BItem { 4 | name: string; 5 | emoji: string; // emoji id 6 | 7 | description: string; // description 8 | dropChance: number; // out of 100 9 | currency?: boolean; 10 | // non-openable things (like idle-coins) can't be opened, and will lack implementation. 11 | } 12 | 13 | // Name the items! 14 | export enum ItemEnum { 15 | IdleCoin, 16 | CheapPhone, 17 | ExtraFinger, 18 | Coffee, 19 | ChestChest, 20 | DailyChest, 21 | ApplePhone, 22 | ChestChestChest, 23 | Glue, 24 | SuperGlue, 25 | CraftingMat, 26 | CraftingChest, 27 | KnowledgeBook, 28 | GoldenCycle, 29 | IdleCoinMaker, 30 | GoldenCycleMaker, 31 | VoteCrate, 32 | BronzeQuestChest, 33 | SilverQuestChest, 34 | GoldQuestChest 35 | } 36 | 37 | export const items: BItem[] = [{ 38 | name: "Idle-Coin", 39 | emoji: "", // todo 40 | description: "A mysterious form of currency. Use these to buy idle machines!", 41 | dropChance: 25, 42 | currency: true 43 | }, { 44 | name: "Cheap iPhone", 45 | emoji: "", 46 | description: "The camera doesn't even work!", 47 | dropChance: 8 48 | }, { 49 | name: "Extra Finger", 50 | emoji: "", 51 | description: "Gross... but hey, more efficiency!", 52 | dropChance: 15 53 | }, { 54 | name: "Coffee", 55 | emoji: "", 56 | description: "Use this to code with a boost!", 57 | dropChance: 10 58 | }, { 59 | name: "Chest Chest", 60 | emoji: "", 61 | description: "Chests may contain chests.", 62 | dropChance: 8 63 | }, { 64 | name: "Daily Chest", 65 | emoji: "", 66 | description: "Get this item through `&d`!", 67 | dropChance: 0 68 | }, { 69 | name: "Apple iPhone", 70 | emoji: "", 71 | description: "An iPhone... for idlers!", 72 | dropChance: 5 73 | }, { 74 | name: "Chest Chest Chest", 75 | emoji: "", 76 | description: "Chests contain better loot!", 77 | dropChance: 0.01 78 | }, { 79 | name: "Glue", 80 | emoji: "", 81 | description: "Glue can be used as an adhesive.", 82 | dropChance: 13 83 | }, { 84 | name: "Super Glue", 85 | emoji: "", 86 | description: "Glue... glued together!", 87 | dropChance: 2 88 | }, { 89 | name: "Crafting Materials", 90 | emoji: "", 91 | description: "One of the key things to make items.", 92 | dropChance: 20 93 | }, { 94 | name: "Crafting Chest", 95 | emoji: "", 96 | description: "Only drops crafting materials.", 97 | dropChance: 15 98 | }, { 99 | name: "Book of knowledge", 100 | emoji: "", 101 | description: "Instantly gain xp!", 102 | dropChance: 10 103 | }, { 104 | name: "Golden Cycle", 105 | emoji: "", 106 | description: "The purest form of cycles. Use this to buy boosts!", 107 | dropChance: 24.9, // >:) 108 | currency: true 109 | }, { 110 | name: "Idle-Coin Maker", 111 | emoji: "", 112 | description: "Generates 5% of your cycles into idle-coins!", 113 | dropChance: 5 114 | }, { 115 | name: "Golden Cycle Maker", 116 | emoji: "", 117 | description: "Generates 5% of your cycles into golden cycles!", 118 | dropChance: 5 119 | }, { 120 | name: "Vote Crate", 121 | emoji: "", 122 | description: "This rare crate (not chest!) gives you all sorts of bonuses!", 123 | dropChance: 0 124 | }, { 125 | name: "Bronze Quest Chest", 126 | emoji: "", 127 | description: "For completing an easy quest!", 128 | dropChance: 0 129 | }, { 130 | name: "Silver Quest Chest", 131 | emoji: "", 132 | description: "For completing a medium quest!", 133 | dropChance: 0 134 | }, { 135 | name: "Gold Quest Chest", 136 | emoji: "", 137 | description: "For completing a hard quest!", 138 | dropChance: 0 139 | }]; 140 | -------------------------------------------------------------------------------- /src/cmd/quests/newquest.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | 4 | import { CycleUser } from "../../util/database/genschema.js"; 5 | import { Command, Bot, Database, Colors, random, brackets, formatDate, commanum, codestr } from "../../global.js"; 6 | import { quests, qDiff, checkQuest } from "../../util/data/quests.js"; 7 | 8 | 9 | const hours = 1000 * 60 * 60; 10 | 11 | class C extends Command { 12 | names = ["new-quest", "newquest"]; 13 | help = "Get a new quest! Quests cost cycles to complete, but the rewards are great!"; 14 | examples = ["new-quest easy"]; 15 | 16 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 17 | if (args.length == 0) { 18 | msg.channel.send({ 19 | embeds: [{ 20 | color: Colors.PRIMARY, 21 | title: "Quests!", 22 | description: `Use \`&new-quest \` to make a new quest!\nThe difficulties are:\n${codestr(` 23 | &new-quest Easy 24 | &new-quest Medium 25 | &new-quest Hard`, "yaml")}` 26 | }] 27 | }); 28 | return; 29 | } 30 | if (args.length != 1) return Bot.argserror(msg, args.length, [1]); 31 | 32 | const user = Database.getUser(msg.author.id); 33 | 34 | checkQuest(user); 35 | 36 | if (user.quest) { 37 | return Bot.errormsg(msg, `You already have a quest! 38 | Quest is: ${brackets(quests[user.quest.name].name)}`, "Quest in progress!"); 39 | } 40 | 41 | let difficulty: 0 | 1 | 2; 42 | let deadline = new Date().getTime(); 43 | 44 | switch (args[0].toLowerCase()) { 45 | case "easy": 46 | difficulty = 0; 47 | deadline += hours * 24; 48 | break; 49 | case "medium": 50 | difficulty = 1; 51 | deadline += hours * 12; 52 | break; 53 | case "hard": 54 | difficulty = 2; 55 | deadline += hours * 6; 56 | break; 57 | default: 58 | return Bot.usererr(msg, "Valid difficulties: `hard`, `medium`, `easy`", "Invalid difficulty!"); 59 | } 60 | 61 | const cost = this.checkCycles(msg, user, difficulty); 62 | if (cost == 0) { 63 | return; 64 | } 65 | 66 | const questi = Math.floor(random(1, quests.length)); 67 | const quest = quests[questi]; 68 | 69 | user.quest = { 70 | name: questi, 71 | end: new Date(deadline).toString(), 72 | difficulty, 73 | progress: 0 74 | }; 75 | 76 | msg.channel.send({ 77 | embeds: [{ 78 | color: Colors.PRIMARY, 79 | title: `New ${qDiff[difficulty]} quest!`, 80 | description: `You got ${brackets(quest.name)}! 81 | \\- ${brackets(cost.toString())} cycles`, 82 | fields: [{ 83 | name: "Description", 84 | value: quest.description 85 | }, { 86 | name: "Deadline", 87 | value: formatDate(new Date(deadline).getTime() - new Date().getTime()), 88 | }] 89 | }] 90 | }); 91 | } 92 | 93 | private checkCycles(msg: Discord.Message, user: CycleUser, diff: 0 | 1 | 2) { 94 | const userCycles = new Big(user.cycles); 95 | const handle = (amt: number) => { 96 | if (userCycles.lt(new Big(amt))) { 97 | Bot.errormsg(msg, `Not enough cycles!! 98 | You need ${brackets(commanum(new Big(amt).minus(userCycles).toString()))} more cycles.`); 99 | return false; 100 | } 101 | 102 | user.cycles = userCycles.minus(amt).toString(); 103 | return amt; 104 | }; 105 | 106 | switch (diff) { 107 | case 0: return handle(1_000); 108 | case 1: return handle(100_000); 109 | case 2: return handle(1_000_000); 110 | } 111 | } 112 | } 113 | 114 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/shop/use-media.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js"; 3 | import Big from "bignumber.js"; 4 | import { Command, Colors, Bot, Database, brackets, commanum } from "../../global.js"; 5 | import { socialMedia } from "../../util/data/social-media.js"; 6 | 7 | class C extends Command { 8 | names = ["advance-media", "use-media", "prestige", "next-media", "nm"]; 9 | help = "Go to the next social media!"; 10 | 11 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 12 | msg.channel.send({ 13 | embeds: [{ 14 | color: Colors.WARNING, 15 | title: "Confirm Reset", 16 | description: "Are you sure you want to reset all your progress and go to the next social media?\nClick the ✅!" 17 | }], 18 | components: [ 19 | new ActionRowBuilder() 20 | .addComponents([ 21 | new ButtonBuilder() 22 | .setCustomId("next") 23 | .setEmoji("✔️") 24 | .setStyle(ButtonStyle.Success), 25 | new ButtonBuilder() 26 | .setCustomId("cancel") 27 | .setEmoji("✖️") 28 | .setStyle(ButtonStyle.Danger) 29 | ]) 30 | ] 31 | }).then(async mesg => { 32 | const collector = mesg.createMessageComponentCollector({ 33 | filter: (inter) => inter.user.id == msg.author.id, 34 | max: 1, 35 | time: 60000 36 | }); 37 | 38 | collector.on("collect", async choice => { 39 | if (choice.customId == "cancel") { 40 | collector.stop(); 41 | await mesg.edit({ 42 | embeds: [{ 43 | color: Colors.PRIMARY, 44 | title: "Cancelled", 45 | description: "You decided not to make a new account.\nMaybe next time!" 46 | }] 47 | }); 48 | return; 49 | } 50 | 51 | const user = Database.getUser(msg.author.id); 52 | const idx = user.socialMedia; 53 | const cyclesNeeded = new Big(100_000).pow(Math.ceil(Math.log(2 * (idx + 1)))); 54 | const userCycles = new Big(user.cycles); 55 | 56 | if (userCycles.lt(cyclesNeeded)) { 57 | return Bot.errormsg(msg, `Not enough cycles!! 58 | You need ${brackets(commanum(cyclesNeeded.minus(userCycles).toString()))} more cycles!`); 59 | } 60 | 61 | let sm = user.socialMedia + 1; 62 | const name = socialMedia[sm]; 63 | if (sm >= socialMedia.length) sm = socialMedia.length - 1; 64 | 65 | Object.assign(user, { 66 | cycles: "0", 67 | text: "0", 68 | xp: "0", 69 | tpc: "1", 70 | cpp: "1", 71 | tpm: "0", 72 | bought: { 73 | idle: {}, 74 | upgrades: {}, 75 | cpp: {} 76 | }, 77 | socialMedia: sm 78 | }); 79 | 80 | msg.channel.send({ 81 | embeds: [{ 82 | color: Colors.PRIMARY, 83 | title: `You moved to ${brackets(name)}!`, 84 | description: `Your cycles, tpc, cpp, and tpm have been reset, 85 | but now you get a base boost!`, 86 | footer: { 87 | text: "Use &prof to see more!" 88 | } 89 | }] 90 | }); 91 | }); 92 | 93 | collector.on("end", collected => { 94 | if (collected.size == 0) { 95 | mesg.edit({ 96 | embeds: [{ 97 | color: Colors.PRIMARY, 98 | title: "Cancelled", 99 | description: "You didn't make a response!" 100 | }] 101 | }); 102 | } 103 | 104 | mesg.edit({ 105 | components: [] 106 | }); 107 | }); 108 | }); 109 | } 110 | } 111 | 112 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/minigame/casino.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js"; 3 | import Big from "bignumber.js"; 4 | import { Command, Colors, Bot, Database, constrain, random, brackets, parseNumber } from "../../global.js"; 5 | import { commanum } from "../../util/util.js"; 6 | import { ActionType, checkQuest } from "../../util/data/quests.js"; 7 | 8 | class C extends Command { 9 | names = ["casino", "gamble"]; 10 | help = "Gamble some money!"; 11 | examples = ["casino "]; 12 | 13 | get cooldown() { 14 | return 60e3; 15 | } 16 | 17 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 18 | if (args.length != 1) return Bot.argserror(msg, args.length, [1]); 19 | const num = parseNumber(args[0]); 20 | if (isNaN(num)) return Bot.usererr(msg, "Amount must be a number!"); 21 | const amt = new Big(constrain(num, 1, Infinity)); 22 | 23 | const user = Database.getUser(msg.author.id); 24 | const cycles = new Big(user.cycles); 25 | 26 | if (amt.gt(cycles)) return Bot.usererr(msg, "You can't bet more than your worth!"); 27 | 28 | msg.channel.send({ 29 | embeds: [{ 30 | color: Colors.PRIMARY, 31 | title: "Choose a color!", 32 | description: "Choose wisely... You would win big or lose everything!" 33 | }], 34 | components: [ 35 | new ActionRowBuilder() 36 | .addComponents([ 37 | new ButtonBuilder() 38 | .setCustomId("red") 39 | .setLabel("\u200b") 40 | .setStyle(ButtonStyle.Danger) 41 | ]) 42 | .addComponents([ 43 | new ButtonBuilder() 44 | .setCustomId("green") 45 | .setLabel("\u200b") 46 | .setStyle(ButtonStyle.Success) 47 | ]) 48 | .addComponents([ 49 | new ButtonBuilder() 50 | .setCustomId("blue") 51 | .setLabel("\u200b") 52 | .setStyle(ButtonStyle.Primary) 53 | ]) 54 | ] 55 | }).then(async mesg => { 56 | const collector = mesg.createMessageComponentCollector({ 57 | filter: (inter) => inter.user.id == msg.author.id, 58 | time: 60000, 59 | max: 1, 60 | componentType: Discord.ComponentType.Button 61 | }); 62 | 63 | collector.on("collect", () => { 64 | if (random(0, 3) < 1) { 65 | const field = checkQuest(user, ActionType.Bet); 66 | 67 | mesg.edit({ 68 | embeds: [{ 69 | color: Colors.PRIMARY, 70 | title: "You win!", 71 | description: `You chose the right color! You earned ${brackets(commanum(amt.toString()))} cycles!`, 72 | fields: field ? [field] : [] 73 | }] 74 | }); 75 | 76 | user.cycles = cycles.plus(amt).toString(); 77 | } else { 78 | mesg.edit({ 79 | embeds: [{ 80 | color: Colors.ERROR, 81 | title: "You lose!", 82 | description: `You chose the wrong color. You lost ${brackets(commanum(amt.toString()))} cycles.` 83 | }] 84 | }); 85 | 86 | user.cycles = cycles.minus(amt).toString(); 87 | } 88 | }); 89 | 90 | collector.on("end", async collected => { 91 | if (collected.size == 0) { 92 | await mesg.edit({ 93 | embeds: [{ 94 | color: Colors.ERROR, 95 | title: "You lose!", 96 | description: `You didn't choose a color. You lost ${brackets(commanum(amt.toString()))} cycles.` 97 | }] 98 | }); 99 | } 100 | 101 | await mesg.edit({ 102 | components: [] 103 | }); 104 | }); 105 | }); 106 | } 107 | } 108 | 109 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/admin/eval/eval.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { ButtonBuilder, ActionRowBuilder, ButtonStyle } from "discord.js"; 3 | import Big from "bignumber.js"; 4 | import { Command, Colors, codestr, Database, commanum } from "../../../global.js"; 5 | import { ItemEnum } from "../../../util/data/item.js"; 6 | 7 | const vars = [ 8 | "client", 9 | "process", 10 | "Big", 11 | "Database", 12 | "ItemEnum" 13 | ].map(k => `\`${k}\``).join(", "); 14 | 15 | class C extends Command { 16 | names = ["eval"]; 17 | help = "Evaluates code."; 18 | examples = ["eval ```return 1+1```"]; 19 | isGame = "n" as const; 20 | 21 | isAdmin = true; 22 | 23 | exec(msg: Discord.Message, args: string[], client: Discord.Client) { 24 | const code = msg.content.replace(/&.+? (```([\s\S]*?)```|([^`]+))/g, "$2$3"); 25 | 26 | msg.channel.send({ 27 | embeds: [{ 28 | color: Colors.WARNING, 29 | title: "Confirm Execution!", 30 | description: `Executing: 31 | ${codestr(code, "js")} 32 | Vars: ${vars}`, 33 | footer: { 34 | text: "Use return to return output!" 35 | } 36 | }], 37 | components: [ 38 | new ActionRowBuilder() 39 | .addComponents([ 40 | new ButtonBuilder() 41 | .setCustomId("yes") 42 | .setEmoji("✔️") 43 | .setStyle(ButtonStyle.Success), 44 | new ButtonBuilder() 45 | .setCustomId("no") 46 | .setEmoji("✖️") 47 | .setStyle(ButtonStyle.Danger) 48 | ]) 49 | ] 50 | }).then(async mesg => { 51 | const collector = mesg.createMessageComponentCollector({ 52 | filter: (inter) => inter.user.id == msg.author.id, 53 | max: 1, 54 | time: 60000 55 | }); 56 | 57 | collector.on("collect", async choice => { 58 | if (choice.customId == "no") { 59 | collector.stop(); 60 | await mesg.edit({ 61 | embeds: [{ 62 | color: Colors.WARNING, 63 | title: "CANCELLED", 64 | description: "Code execution cancelled. All done!" 65 | }] 66 | }); 67 | return; 68 | } 69 | 70 | try { 71 | let output = new Function( 72 | "client", 73 | "process", 74 | "Big", 75 | "Database", 76 | "ItemEnum", 77 | code 78 | )(client, 79 | process, 80 | Big, 81 | Database, 82 | ItemEnum); 83 | 84 | if (output instanceof Big) { 85 | output = `new Big(${commanum(output.toString())})`; 86 | } else if (typeof output == "object") { 87 | output = JSON.stringify(output, null, 2); 88 | } else { 89 | output = JSON.stringify(output); 90 | } 91 | 92 | mesg.edit({ 93 | embeds: [{ 94 | color: Colors.PRIMARY, 95 | title: "Beep Boop Bop Boop!", 96 | description: `Output was\n${codestr(output, "yaml")}` 97 | }] 98 | }); 99 | } catch (e) { 100 | mesg.edit({ 101 | embeds: [{ 102 | color: Colors.ERROR, 103 | title: "Error!!", 104 | description: `Error was\n${codestr(String(e), "js")}` 105 | }] 106 | }); 107 | } 108 | }); 109 | 110 | collector.on("end", collected => { 111 | if (collected.size == 0) { 112 | mesg.edit({ 113 | embeds: [{ 114 | color: Colors.PRIMARY, 115 | title: "Cancelled", 116 | description: "You didn't make a response!" 117 | }] 118 | }); 119 | } 120 | 121 | mesg.edit({ 122 | components: [] 123 | }); 124 | }); 125 | }); 126 | } 127 | } 128 | 129 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/minigame/trivia.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Bot, Database, randomChoice, codestr, brackets, parseNumber, commanum } from "../../global.js"; 4 | import { trivia, users } from "../../util/data/trivia.js"; 5 | import { ActionType, checkQuest } from "../../util/data/quests.js"; 6 | 7 | const replaces = { 8 | "C++": "cpp", 9 | "C#": "csharp", 10 | "React": "js" 11 | }; 12 | 13 | class C extends Command { 14 | names = ["trivia", "fix-code", "t"]; 15 | help = "Find the bug and get some cycles!"; 16 | isGame = "y" as const; 17 | 18 | get cooldown() { 19 | return 6e4; 20 | } 21 | 22 | exec(msg: Discord.Message, _: string[], _1: Discord.Client) { 23 | const user = Database.getUser(msg.author.id); 24 | let cycles = new Big(user.cycles); 25 | const cycleEarn = Big.max(new Big(user.cpp).div(50).dp(0), 10); 26 | if (cycles.lt(cycleEarn)) return Bot.errormsg(msg, `You need at least ${brackets(commanum(cycleEarn.toString()))} cycles to play!`, "Not enough cycles!"); 27 | 28 | function addCycle() { 29 | cycles = cycles.plus(cycleEarn); 30 | user.cycles = cycles.toString(); 31 | } 32 | 33 | function minCycle() { 34 | cycles = cycles.minus(cycleEarn); 35 | user.cycles = cycles.toString(); 36 | } 37 | 38 | const question = randomChoice(trivia)[0]; 39 | 40 | // add line numbers 41 | const code = question.code.split("\n"); 42 | const length = code.length.toString().length + 1; 43 | // final output 44 | const editorCode = code.map((c, i) => `${(i + 1).toString().padEnd(length)}| ${c}`).join("\n"); 45 | msg.channel.send({ 46 | embeds: [{ 47 | color: Colors.PRIMARY, 48 | title: `${brackets(question.lang)} Trivia!`, 49 | description: `Respond with the line of the bug! 50 | 51 | ${codestr(editorCode, replaces[question.lang as keyof typeof replaces] || question.lang)}` 52 | }] 53 | }).then(() => { 54 | const filter = (m: Discord.Message) => msg.author.id == m.author.id; 55 | 56 | msg.channel.awaitMessages({ filter, time: 60000, max: 1, errors: ["time"] }) 57 | .then(collected => { 58 | const reply = collected.first(); 59 | const num = parseNumber(reply?.content); 60 | if (isNaN(num)) { 61 | // can't use `Bot` here... it throws error! 62 | msg.channel.send({ 63 | embeds: [{ 64 | title: "Not a number!", 65 | color: Colors.WARNING, 66 | description: `You didn't input a number! 67 | Use \`&trivia\` to try again!` 68 | }] 69 | }); 70 | 71 | return; 72 | } 73 | 74 | const username = randomChoice(users)[0]; 75 | if (num == question.line) { 76 | const field = checkQuest(user, ActionType.Trivia); 77 | 78 | msg.channel.send({ 79 | embeds: [{ 80 | title: "Correct!", 81 | color: Colors.PRIMARY, 82 | description: `You found the bug! 83 | 84 | ${username} thanks you! 85 | + ${brackets(commanum(cycleEarn.toString()))} cycles`, 86 | fields: field ? [field] : [] 87 | }] 88 | }); 89 | addCycle(); 90 | } else { 91 | msg.channel.send({ 92 | embeds: [{ 93 | title: "Wrong!", 94 | color: Colors.ERROR, 95 | description: `You made ${username} spend 10 hours debugging... 96 | **IN THE WRONG SPOT** 97 | 98 | \\- ${brackets(commanum(cycleEarn.toString()))} cycles` 99 | }] 100 | }); 101 | minCycle(); 102 | } 103 | }) 104 | .catch(() => { 105 | msg.reply({ 106 | embeds: [{ 107 | title: "Time's up!", 108 | color: Colors.ERROR, 109 | description: `You ran out of time! 110 | 111 | - ${brackets(commanum(cycleEarn.toString()))} cycles 112 | 113 | Use \`&trivia\` to try again!` 114 | }] 115 | }); 116 | 117 | minCycle(); 118 | }); 119 | }); 120 | } 121 | } 122 | 123 | export const c = new C(); -------------------------------------------------------------------------------- /src/cmd/game/post.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, Database, Bot, brackets, random, pluralb, commanum, parseNumber } from "../../global.js"; 4 | import { post as drops } from "../../util/data/drops.js"; 5 | import { boosts } from "../../util/data/boosts/boosts.js"; 6 | import { levelUp } from "../../util/levels.js"; 7 | import { socialMedia } from "../../util/data/social-media.js"; 8 | import { ActionType, checkQuest } from "../../util/data/quests.js"; 9 | 10 | class C extends Command { 11 | names = ["post", "p"]; 12 | help = "Post lines of code for some cycles!"; 13 | examples = ["post 10"]; 14 | isGame = "y" as const; 15 | 16 | get cooldown() { 17 | return 10000; 18 | } 19 | 20 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 21 | if (args.length > 1) return Bot.argserror(msg, args.length, [0, 1]); 22 | 23 | const user = Database.getUser(msg.author.id); 24 | const userBoosts = Database.Boost.getUser(msg.author.id); 25 | const idx = user.socialMedia; 26 | 27 | const levelField = levelUp(user); 28 | 29 | let text = new Big(user.text); 30 | let cycles = new Big(user.cycles); 31 | let cpp = new Big(user.cpp); 32 | 33 | const amt = new Big(parseNumber(args[0]) || user.text).decimalPlaces(0, Big.ROUND_CEIL); 34 | 35 | if (amt.lte(0)) return Bot.usererr(msg, `You cannot post less than ${brackets("0")} lines of code!`); 36 | if (text.lt(amt)) { 37 | return Bot.usererr(msg, `You don't have enough code! 38 | You need ${brackets(amt.minus(text).toString())} more code.`); 39 | } 40 | 41 | // refer to desmos. 42 | let upvotes = amt.plus(cpp).plus(new Big(random(-0.5, 1)).times(cpp)).abs().dp(0); 43 | 44 | const isServer = msg.guild?.id == "788421241005408268"; // refer to ./code.ts 45 | if (isServer) upvotes = upvotes.times(1.05).dp(0); 46 | 47 | const fields: Discord.APIEmbedField[] = []; 48 | for (const drop of drops) { 49 | if (drop.chance()) { 50 | fields.push(drop.award(user)); 51 | cycles = new Big(user.cycles); 52 | text = new Big(user.text); // have to update it again 53 | } 54 | } 55 | 56 | for (const index in userBoosts) { 57 | const itm = boosts[index]; 58 | if (!itm.cpp) continue; 59 | const amt = userBoosts[index].length; 60 | fields.push({ 61 | name: itm.name, 62 | value: itm.message || "" 63 | }); 64 | 65 | cpp = cpp.times(new Big(itm.cpp).plus(100).div(100)).times(amt).dp(0); 66 | } 67 | 68 | if (idx > 0) { 69 | const name = socialMedia[idx]; 70 | const prestige = Math.log(idx + 1) + 0.5; 71 | const cpp = new Big(user.cpp).times(prestige).dp(0); 72 | cycles = cycles.plus(cpp); 73 | 74 | fields.push({ 75 | name: "Social Media Boost!", 76 | value: `With a new clout in ${name}, you get +${brackets(commanum(cpp.toString()))} more text!` 77 | }); 78 | } 79 | 80 | if (levelField) fields.push(levelField); 81 | 82 | const quest = checkQuest(user, ActionType.Post); 83 | if (quest) fields.push(quest); 84 | 85 | cycles = cycles.plus(upvotes); 86 | 87 | msg.channel.send({ 88 | embeds: [{ 89 | color: Colors.PRIMARY, 90 | title: "Post your Code!", 91 | description: `You posted ${brackets(commanum(amt.toString()))} line${pluralb(amt)} of code. 92 | People view your post! 93 | + ${brackets(commanum(upvotes.toString()))} cycle${pluralb(new Big(upvotes))}! 94 | 95 | > You now have ${brackets(commanum(cycles.toString()))} cycles!${isServer ? ` 96 | **5% cycle boost** for posting in the official discord server!` : ""}`, 97 | fields, 98 | footer: { 99 | text: "Use &bal to view your balance!" 100 | } 101 | }] 102 | }); 103 | 104 | text = text.minus(amt); 105 | 106 | Database.pdb[msg.author.id].cycles = cycles.toString(); 107 | Database.pdb[msg.author.id].text = text.toString(); 108 | } 109 | 110 | cooldownError(msg: Discord.Message, ms: number) { 111 | Bot.errormsg(msg, `Don't spam people! 112 | Please wait ${brackets((ms / 1000).toFixed(1))} seconds before you can post again.`, "No Spamming!"); 113 | } 114 | } 115 | 116 | export const c = new C(); -------------------------------------------------------------------------------- /src/loader.ts: -------------------------------------------------------------------------------- 1 | import { URL } from "node:url"; 2 | import * as fs from "node:fs"; 3 | 4 | import info from "./cmd/cmd.js"; 5 | 6 | import * as Discord from "discord.js"; 7 | import { Command, brackets, codestr, noun, Colors, Bot, parseNumber } from "./global.js"; 8 | 9 | function getLastItem(path: string) { 10 | return path.substring(path.lastIndexOf("/") + 1); 11 | } 12 | 13 | async function load() { 14 | const output: { [i: string]: { cmds: Command[], desc: string } } = {}; 15 | 16 | async function loadDir(dir: URL, dirn?: URL) { 17 | const dirns = getLastItem(dirn?.toString() ?? ""); // dirnstring 18 | 19 | for (const file of fs.readdirSync(dir)) { 20 | const stat = fs.lstatSync(new URL(`${dir}/${file}`)); 21 | if (stat.isFile() && /\.js$/.test(file)) { 22 | const C: Command = (await import(new URL(`${dir}/${file}`, import.meta.url).toString())).c; 23 | 24 | if (dirns) { 25 | if (!output[dirns]) output[dirns] = { cmds: [], desc: "" }; 26 | 27 | output[dirns].cmds.push(C); 28 | } 29 | } else if (stat.isDirectory()) { 30 | await loadDir(new URL(`${dir.toString()}/${file}`, import.meta.url), dirn ?? new URL(file, import.meta.url)); 31 | } 32 | } 33 | } 34 | 35 | await loadDir(new URL("cmd", import.meta.url)); 36 | 37 | Object.keys(info).forEach(key => output[key].desc = info[key as keyof typeof info]); 38 | 39 | return output; 40 | } 41 | 42 | async function help(msg: Discord.Message, args: string[], output: { [i: string]: { cmds: Command[], desc: string } }) { 43 | if (args.length != 1) { // &help 44 | const fields: Discord.APIEmbedField[] = Object.keys(output).map((k) => { 45 | const itm = output[k]; 46 | const cmds = itm.cmds; 47 | 48 | return { 49 | name: k, 50 | value: `> ${itm.desc}\n${cmds.map(n => `**&${n.names[0]}**`).join("\n")}`, 51 | inline: true 52 | }; 53 | }); 54 | 55 | Bot.carousel(msg, fields, 2, (page, i): Discord.APIEmbed => { 56 | return { 57 | color: Colors.PRIMARY, 58 | title: "Help Categories", 59 | description: `View the help categories! Page: ${brackets(page.toString())}${codestr("&help ")} 60 | **PRO TIP**: Use \`&guide\` for a guide!`, 61 | fields: i.length == 0 ? [{ name: "End of Help!", value: "No more commands!" }] : i 62 | }; 63 | }); 64 | } else { 65 | // &help invalid 66 | let cmd: Command | null = null; 67 | 68 | for (const k in output) { 69 | const result = output[k].cmds.find(n => n.names.includes(args[0])); 70 | if (result) cmd = result; 71 | } 72 | 73 | if (!cmd) return Bot.usererr(msg, `Help for ${brackets(args[0])} was not found!`, "Command not found!"); 74 | // &help meta 75 | const fields: Discord.APIEmbedField = { 76 | name: noun(cmd.names[0]), 77 | value: `${cmd.help}\ 78 | ${cmd.examples.length == 0 79 | ? cmd.names.map(o => codestr(`&${o}`)).join("") 80 | : cmd.examples.map(o => codestr(`&${o}`)).join("")}\ 81 | ${cmd.names.length > 1 82 | ? `**Aliases**: ${cmd.names.slice(1).join(",")}\n` : ""}\ 83 | ${cmd.isAdmin ? `${brackets("ADMIN-ONLY")}\n` : ""}\ 84 | ${cmd.cooldown ? `**Cooldown**: **${cmd.cooldown}**ms` : ""}`, 85 | inline: false 86 | }; 87 | 88 | msg.channel.send({ 89 | embeds: [{ 90 | color: Colors.PRIMARY, 91 | title: `Help ${brackets(args[0])}`, 92 | description: `View the help for ${brackets(args[0])}!`, 93 | fields: [fields] 94 | }] 95 | }); 96 | } 97 | } 98 | 99 | function verifyHuman(msg: Discord.Message, args: string[], commandsUsed: { [i: string]: [number, string, number] }) { 100 | // commands used, input, answer 101 | const user = commandsUsed[msg.author.id]; 102 | 103 | if (!user || user[0] < 100) return false; 104 | 105 | if (parseNumber(args[0]) != user[2]) { 106 | msg.channel.send({ 107 | embeds: [{ 108 | color: Colors.ERROR, 109 | title: "Error!", 110 | description: `You gave the wrong answer! 111 | If you forgot, your input was ${brackets(user[1])}\n 112 | For example, if you get **1**, type in ${codestr("&verify 1")}`, 113 | footer: { 114 | text: "You cannot continue until you complete this challenge!" 115 | } 116 | }] 117 | }); 118 | 119 | return false; 120 | } 121 | 122 | msg.channel.send({ 123 | embeds: [{ 124 | color: Colors.SUCCESS, 125 | title: "Challenge Complete!", 126 | description: "You may now continue." 127 | }] 128 | }); 129 | 130 | return true; 131 | } 132 | 133 | export { load, help, verifyHuman }; -------------------------------------------------------------------------------- /src/util/data/trivia.ts: -------------------------------------------------------------------------------- 1 | export interface TriviaItem { 2 | code: string; 3 | line: number; 4 | lang: string; 5 | } 6 | 7 | export const users = ["Mr. Coder", "Bob", "Coder100", "Tom", "Mr. 100", "Six", "Snowy", "Alt", "Hithere", "John"]; 8 | 9 | export const trivia: TriviaItem[] = [{ 10 | code: `console 11 | .log(a); 12 | let a = 5;`, 13 | line: 2, 14 | lang: "JavaScript" 15 | }, { 16 | code: `#include 17 | int main() { 18 | cout << 19 | "Hello, world!" << endl; 20 | return 0; 21 | }`, 22 | line: 3, 23 | lang: "C++" 24 | }, { 25 | code: `using System; 26 | class MainClass { 27 | static 28 | void 29 | Main 30 | (string[] args) { 31 | Console.WriteLine("Hello, world!"); 32 | } 33 | }`, 34 | line: 3, 35 | lang: "C#" 36 | }, { 37 | code: `a = True 38 | print( 39 | if a == True 40 | 'a is true' 41 | else 42 | 'a is false' 43 | )`, 44 | line: 3, 45 | lang: "Python" 46 | }, { 47 | code: `var leaderboard = { 48 | Coder100: { score: 3 }, 49 | altarium: { score: 6 }, 50 | nou: { score: 8 } 51 | }; 52 | 53 | console.log(leaderboard 54 | .coder100 55 | .score 56 | );`, 57 | line: 9, 58 | lang: "JavaScript" 59 | }, { 60 | code: `a = 5 61 | b = "3" 62 | 63 | print( 64 | a 65 | + 66 | b 67 | )`, 68 | line: 5, 69 | lang: "Python" 70 | }, { 71 | code: `a = int(input("Enter a number: ") 72 | b = int(input("Enter another one: ")) 73 | 74 | print(f"{a} + {b} = {a + b}")`, 75 | line: 1, 76 | lang: "Python" 77 | }, { 78 | code: `a = 7 79 | b = 6 80 | 81 | if b == a: 82 | print("something really bad just happened!") 83 | print("is your computer broken?")`, 84 | line: 6, 85 | lang: "Python" 86 | }, { 87 | code: `#include 88 | int main() { 89 | std::string a = "hi"; 90 | switch (a) { 91 | case "hello": 92 | std::cout << "a is hello" << std::endl; 93 | break; 94 | default: 95 | std::cout << "idk" << std::endl; 96 | break; 97 | } 98 | }`, 99 | line: 4, 100 | lang: "C++" 101 | }, { 102 | code: `a = "3" 103 | b = "4" 104 | 105 | print( 106 | "Hello" 107 | " World", 108 | a 109 | b 110 | )`, 111 | line: 8, 112 | lang: "Python" 113 | }, { 114 | code: `let a; 115 | a.b = 5; 116 | console.log(a.b);`, 117 | line: 2, 118 | lang: "JavaScript" 119 | }, { 120 | code: `class MyClass { 121 | constructor(callback) { 122 | this .callback = callback; 123 | } 124 | 125 | hook(element) { 126 | element.addEventListener("click", function(e) { 127 | this.callback(e); 128 | }); 129 | } 130 | }`, 131 | line: 8, 132 | lang: "JavaScript" 133 | }, { 134 | code: `export default function element() { 135 | return ( 136 | <> 137 | < 138 | input 139 | placeholder="Enter your text: " 140 | > 141 | 142 | ); 143 | }`, 144 | line: 7, 145 | lang: "React" 146 | }, { 147 | code: `print("hello" " world!") 148 | a = 5 149 | def my_func(): 150 | a = a + 5 151 | print(a) 152 | 153 | my_func()`, 154 | line: 4, 155 | lang: "Python" 156 | }, { 157 | code: `a = 1 158 | b =+ 1 159 | print(a + " + " + b + " = " + (a + b))`, 160 | line: 3, 161 | lang: "Python" 162 | }, { 163 | code: `fn main() { 164 | let a = 5; 165 | let a = String::from("Hello, "); 166 | 167 | a += "World!"; 168 | 169 | println!("{a}"); 170 | }`, 171 | line: 5, 172 | lang: "Rust" 173 | }, { 174 | code: `let a = 5 175 | [1, 2, 3] 176 | .forEach( 177 | k => { 178 | console.log(k + a) 179 | } 180 | )`, 181 | line: 4, 182 | lang: "JavaScript" 183 | }, { 184 | code: `/* 185 | /* 186 | /* Comments? */ 187 | */ 188 | */ 189 | 190 | fn main() { 191 | let a = 5..; 192 | 193 | for k in a { 194 | println!(k); 195 | } 196 | }`, 197 | line: 11, 198 | lang: "Rust" 199 | }, { 200 | code: `let: for(let i = 0; i< 5; i++) { 201 | for(let j = -2;;) { 202 | console.log( 203 | i 204 | + 205 | + 206 | j 207 | + 208 | 5.toString() 209 | ); 210 | 211 | if (i == j) { 212 | break let; 213 | } 214 | } 215 | }`, 216 | line: 9, 217 | lang: "JavaScript" 218 | }, { 219 | code: `var let = 5.; 220 | console.log(let); 221 | let var = 5; 222 | console.log(var);`, 223 | line: 3, 224 | lang: "JavaScript" 225 | }, { 226 | code: `console.log(5.toString()); 227 | console.log(5 . toString()); 228 | console.log(5+[]);`, 229 | line: 1, 230 | lang: "JavaScript" 231 | }, { 232 | code: `let (a) = 5; 233 | (a) += 3; 234 | ((a) + 3) += 3; 235 | console.log((a));`, 236 | line: 3, 237 | lang: "JavaScript" 238 | }, { 239 | code: `let a = 10, b = null; 240 | console.log(a.toString()); 241 | console.log(5..toString()); 242 | console.log(b.toString()); 243 | console.log(5.toString());`, 244 | line: 5, 245 | lang: "JavaScript" 246 | }]; -------------------------------------------------------------------------------- /src/util/data/quests.ts: -------------------------------------------------------------------------------- 1 | import Big from "bignumber.js"; 2 | import { APIEmbedField } from "discord.js"; 3 | import { Database, brackets, addMs, random, progress } from "../../global.js"; 4 | import { ItemEnum } from "./item.js"; 5 | 6 | export const qDiff = ["easy", "medium", "hard"]; 7 | 8 | // you invest an amount into a quest 9 | // and you have x hours to complete the quest! 10 | // you then get a quest chest: bronze, silver, gold 11 | export enum QuestName { 12 | Fail, 13 | Multiple, 14 | Cycles, 15 | Betting, 16 | Coding, 17 | Answerer, 18 | ChestOpener, 19 | Trivia, 20 | } 21 | 22 | // the C stands for challenge 23 | export interface CQuest { 24 | name: string; 25 | description: string; 26 | max: number; 27 | } 28 | 29 | export const quests: CQuest[] = [{ 30 | name: "Failed Quest", 31 | description: "You failed a quest lol", 32 | max: 1 33 | }, { 34 | name: "Multitasking", 35 | description: "Get cycles and text!", 36 | max: 256 37 | }, { 38 | name: "Cycle Farming", 39 | description: "Get cycles!", 40 | max: 128 41 | }, { 42 | name: "Betting", 43 | description: "Win in Casinos!", 44 | max: 43 // 128 / 3 45 | }, { 46 | name: "Coding", 47 | description: "Write some code!", 48 | max: 128 49 | }, { 50 | name: "Helpful", 51 | description: "Answer some questions while coding!", 52 | max: 5 // 128 * 0.05 53 | }, { 54 | name: "Lucky", 55 | description: "Get some chests while coding!", 56 | max: 7 // 128 * 0.1 57 | }, { 58 | name: "Smart", 59 | description: "Participate in some trivia!", 60 | max: 10 61 | }]; 62 | 63 | export enum ActionType { 64 | Code, 65 | Post, 66 | Answer, 67 | Bet, 68 | Chest, 69 | Trivia 70 | } 71 | 72 | export function checkQuest(user: Database.CycleUser, action?: ActionType): APIEmbedField | undefined { 73 | const quest = user.quest; 74 | 75 | if (!quest) { 76 | return; 77 | } 78 | 79 | if (new Date().getTime() > new Date(quest.end).getTime()) { 80 | if (user.quest?.name == QuestName.Fail) { 81 | user.quest = null; 82 | 83 | return { 84 | name: "Quest update!", 85 | value: `Your cooldown has ended. 86 | You can start quests again!` 87 | }; 88 | } 89 | 90 | const deadline = addMs(new Date(), 1000 * 60 * 60 * 24); 91 | 92 | user.quest = { 93 | name: QuestName.Fail, 94 | end: deadline.toString(), 95 | difficulty: 0, 96 | progress: 0 97 | }; 98 | 99 | return { 100 | name: "Quest Failed!", 101 | value: `You failed your quest ${brackets(quests[quest.name].name)}! 102 | You can't get another quest for 24 hours!` 103 | }; 104 | } 105 | 106 | const start = quest.progress; 107 | switch (quest.name) { 108 | case QuestName.Fail: 109 | return; 110 | case QuestName.Multiple: 111 | if (action == ActionType.Code || action == ActionType.Post) { 112 | quest.progress++; 113 | } 114 | break; 115 | case QuestName.Cycles: 116 | if (action == ActionType.Post) { 117 | quest.progress += Math.round(random(1, 3)); 118 | } 119 | break; 120 | case QuestName.Betting: 121 | if (action == ActionType.Bet) { 122 | quest.progress++; 123 | } 124 | break; 125 | case QuestName.Coding: 126 | if (action == ActionType.Code) { 127 | quest.progress++; 128 | } 129 | break; 130 | case QuestName.Answerer: 131 | if (action == ActionType.Answer) { 132 | quest.progress++; 133 | } 134 | break; 135 | case QuestName.ChestOpener: 136 | if (action == ActionType.Chest) { 137 | quest.progress++; 138 | } 139 | break; 140 | case QuestName.Trivia: 141 | if (action == ActionType.Trivia) { 142 | quest.progress++; 143 | } 144 | break; 145 | } 146 | 147 | const amt = quest.progress; 148 | const max = quests[quest.name].max; 149 | 150 | if (quest.progress >= max) { 151 | const diffs = ["Easy", "Medium", "Hard"]; 152 | const chests = ["Bronze", "Silver", "Gold"]; 153 | const enums = [ItemEnum.BronzeQuestChest, ItemEnum.SilverQuestChest, ItemEnum.GoldQuestChest]; 154 | const diff = quest.difficulty; 155 | 156 | const name = enums[diff]; 157 | const items = new Big(user.inv[name] || "0"); 158 | user.inv[name] = items.plus(1).toString(); 159 | 160 | user.quest = null; 161 | 162 | return { 163 | name: "Quest Complete!", 164 | value: `Because you chose a ${diffs[diff]} quest, 165 | you get a ${brackets(`${chests[diff]} Quest Chest`)}!` 166 | }; 167 | } 168 | 169 | if (amt != start) { 170 | return { 171 | name: "Quest Progress!", 172 | value: `Your progress has increased! 173 | ${progress(amt / max * 10, 10)} (${amt.toLocaleString()} / ${max.toLocaleString()})` 174 | }; 175 | } 176 | } -------------------------------------------------------------------------------- /src/global.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { Bot, brackets, Colors } from "./util/format.js"; 3 | import { addMs, msBetween } from "./util/util.js"; 4 | import { genSchema, setUser, getUser, CycleUser } from "./util/database/database.js"; 5 | import admin from "./util/admin.js"; 6 | 7 | export * from "./util/format.js"; 8 | export * from "./util/util.js"; 9 | 10 | export * as Database from "./util/database/database.js"; 11 | 12 | let lockedDown = false; 13 | export function toggleLockDown() { 14 | return lockedDown = !lockedDown; 15 | } 16 | 17 | export interface UserInput { 18 | command: string; 19 | args: string[]; 20 | } 21 | 22 | export class Command { 23 | names: string[] = []; 24 | help = "*no help provided*"; 25 | examples: string[] = []; 26 | /** 27 | * its either: 28 | * 'y' (yes, auto-initiate) 29 | * 'n' (don't initiate) 30 | * 'p' (don't initiate, and error if player does not have profile) 31 | */ 32 | isGame: "y" | "n" | "p" = "p"; // if we should initiate player or not 33 | 34 | isAdmin = false; 35 | 36 | // Date, isSent 37 | private cooldownUsers: { [index: string]: [Date, boolean] } = {}; 38 | get cooldown() { 39 | return 0; 40 | } // ms 41 | 42 | async wrap(msg: Discord.Message, args: string[], client: Discord.Client) { 43 | if (lockedDown) { 44 | if (admin.includes(msg.author.id)) { 45 | if (!this.isAdmin) { 46 | msg.channel.send({ 47 | embeds: [{ 48 | color: Colors.WARNING, 49 | title: "Warning!", 50 | description: "Bot is in **LOCKDOWN MODE**.\nUse `&admin-lock-down` to unlock!" 51 | }] 52 | }); 53 | } 54 | } else { 55 | return; 56 | } 57 | } 58 | 59 | try { 60 | const guild = await client.guilds.fetch("788421241005408268"); 61 | // throws error if user is not found 62 | const isJoined = await guild.members.fetch(msg.author.id).catch(() => null); 63 | 64 | if (this.isGame == "y" && !getUser(msg.author.id)) { 65 | msg.channel.send({ 66 | embeds: [{ 67 | color: Colors.PRIMARY, 68 | title: "Welcome!", 69 | description: `Welcome to the bot, ${brackets(msg.author.username)}! 70 | **Use \`&guide\` to get a simple tutorial!**${!isJoined ? ` 71 | Join the [discord server](https://discord.gg/4vTPWdpjFz) for support and perks!` : ""}` 72 | }] 73 | }); 74 | setUser(msg.author.id, genSchema(msg.author)); 75 | } else if (this.isGame == "p" && !getUser(msg.author.id)) { 76 | return Bot.errormsg(msg, `You don't have a profile yet! 77 | > Do \`&code\` to start playing!`, "Profile not found!"); 78 | } 79 | 80 | if (this.isGame != "n" && getUser(msg.author.id).name != msg.author.username) { 81 | setUser(msg.author.id, { name: msg.author.username } as CycleUser); 82 | } 83 | 84 | if (Math.random() * 100 < 3 && !isJoined) { 85 | msg.channel.send({ 86 | embeds: [{ 87 | color: Colors.PRIMARY, 88 | title: "Reminder", 89 | description: "Remember to join the [discord server](https://discord.gg/4vTPWdpjFz) for giveaways, perks, and more!" 90 | }] 91 | }); 92 | } 93 | 94 | this.exec(msg, args, client); 95 | if (this.cooldown) this.setCooldown(msg.author); 96 | } catch (e) { 97 | if (!(e instanceof Bot.BotErr)) throw e; 98 | } 99 | } 100 | 101 | exec(_: Discord.Message, _1: string[], _2: Discord.Client): void { } 102 | 103 | getCooldown(user: Discord.User) { 104 | if (!this.cooldownUsers[user.id]) return null; 105 | 106 | if (msBetween(new Date(), this.cooldownUsers[user.id][0]) <= 0) { 107 | delete this.cooldownUsers[user.id]; 108 | return null; 109 | } 110 | 111 | return msBetween(new Date(), this.cooldownUsers[user.id][0]); 112 | } 113 | 114 | setCooldown(user: Discord.User) { 115 | if (this.getCooldown(user)) return; 116 | this.cooldownUsers[user.id] = [addMs(new Date(), this.cooldown), false]; 117 | } 118 | 119 | sentCooldown(user: Discord.User) { 120 | if (!this.cooldownUsers[user.id]) return null; 121 | 122 | return this.cooldownUsers[user.id][1]; 123 | } 124 | 125 | setSent(user: Discord.User) { 126 | if (this.cooldownUsers) this.cooldownUsers[user.id][1] = true; 127 | } 128 | 129 | /** 130 | * Send a cooldown error, and propagate errors. 131 | * @param msg Discord Message 132 | * @param ms Time left (if cool-downed) 133 | */ 134 | wrapCooldown(msg: Discord.Message, ms: number) { 135 | try { 136 | this.cooldownError(msg, ms); 137 | } catch (e) { 138 | if (!(e instanceof Bot.BotErr)) throw e; 139 | } 140 | } 141 | 142 | // to be overriden 143 | cooldownError(msg: Discord.Message, ms: number) { 144 | Bot.errormsg(msg, `You still have ${brackets((ms / 1000).toFixed(2))} seconds left!`, "Cooldown!"); 145 | } 146 | } -------------------------------------------------------------------------------- /src/util/format.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import { ButtonBuilder, ButtonStyle, ComponentType } from "discord.js"; 3 | import { constrain } from "./util.js"; 4 | 5 | enum Colors { 6 | PRIMARY = 0x1a8ff0, 7 | ERROR = 0xf0351a, 8 | SUCCESS = 0x06c012, 9 | WARNING = 0xfaa12f 10 | } 11 | 12 | // [ **smth** ] 13 | function brackets(input: string) { 14 | return `[ **${input}** ]`; 15 | } 16 | 17 | // **__noun__** 18 | function noun(input: string) { 19 | return `**__${input}__**`; 20 | } 21 | 22 | // ||[ **`code`** ]|| 23 | function hidden(input: string) { 24 | return `||[ **\`${input}\`** ]||`; 25 | } 26 | 27 | function codestr(str: string, lang = "html") { 28 | return `\`\`\`${lang}\n${str}\`\`\``; 29 | } 30 | 31 | // [...ooo] 32 | const black = "■"; 33 | const white = "□"; 34 | 35 | /** 36 | * Progress, assumes width > percent 37 | * @param percent 0-width 38 | * @param width max 39 | */ 40 | function progress(percent: number, width: number) { 41 | return `[${black.repeat(percent)}${white.repeat(width - percent)}]`; 42 | } 43 | 44 | function formatDate(ms: number) { 45 | const days = Math.floor(ms / (24 * 60 * 60 * 1000)); 46 | const hours = Math.floor(ms % (24 * 60 * 60 * 1000) / (60 * 60 * 1000)); 47 | const minutes = Math.floor(ms % (60 * 60 * 1000) / (60 * 1000)); 48 | const seconds = Math.floor(ms % (60 * 1000) / 1000); 49 | 50 | return `${(days > 0 ? `${brackets(days.toString().padStart(2, "0"))} days : ` : "") 51 | + (hours > 0 ? `${brackets(hours.toString().padStart(2, "0"))} hours : ` : "") 52 | + (minutes > 0 ? `${brackets(minutes.toString().padStart(2, "0"))} minutes : ` : "") 53 | + brackets(seconds.toString().padStart(2, "0"))} seconds`; 54 | } 55 | 56 | namespace Bot { 57 | export class BotErr extends Error { } 58 | 59 | export function argserror(msg: Discord.Message, got: number, expect: number[]) { 60 | msg.channel.send({ 61 | embeds: [{ 62 | color: Colors.ERROR, 63 | title: "Invalid Arguments!", 64 | description: `Invalid arguments!\nExpected \`${expect.join(", ")}\` arguments but got \`${got}\` argument${got == 1 ? "" : "s"}.`, 65 | fields: [{ 66 | name: "Tip", 67 | value: `If you have an argument with spaces, use quotes! 68 | ${codestr("&use 'cheap iphone'", "js")}` 69 | }, { 70 | name: "Tip", 71 | value: "Use `&help ` if you don't know how to use the command!" 72 | }] 73 | }] 74 | }); 75 | throw new BotErr(); 76 | } 77 | 78 | /** 79 | * Sends *without* problem-solving. Good for internal errors. 80 | * @param msg msg 81 | * @param error error 82 | * @param title title 83 | */ 84 | export function errormsg(msg: Discord.Message, error: string, title = "Error!!") { 85 | msg.channel.send({ 86 | embeds: [{ 87 | color: Colors.ERROR, 88 | title, 89 | description: error 90 | }] 91 | }); 92 | throw new BotErr(); 93 | } 94 | 95 | /** 96 | * Sends *with* problem-solving. Good for argument/user input errors. 97 | * @param msg msg 98 | * @param error error 99 | * @param title title 100 | */ 101 | export function usererr(msg: Discord.Message, error: string, title = "Error!!") { 102 | msg.channel.send({ 103 | embeds: [{ 104 | color: Colors.ERROR, 105 | title, 106 | description: error, 107 | fields: [{ 108 | name: "Tip", 109 | value: `If you have an argument with spaces, use quotes! 110 | ${codestr("&use 'cheap iphone'", "js")}` 111 | }, { 112 | name: "Tip", 113 | value: "Use `&help ` if you don't know how to use the command!" 114 | }] 115 | }] 116 | }); 117 | throw new BotErr(); 118 | } 119 | 120 | /** 121 | * 'carousel' 122 | * @param msg msg (for user) 123 | * @param data array of item data 124 | * @param count how much should be in each page 125 | * @param send the embed to be sent 126 | * @param curr curr page (1) 127 | */ 128 | export async function carousel(msg: Discord.Message, data: T[], count: number, send: (_: number, _1: T[]) => Discord.APIEmbed, curr = 1) { 129 | let page = curr; 130 | function getComponents() { 131 | const components = []; 132 | if (page > 1) { 133 | components.push( 134 | new ButtonBuilder() 135 | .setCustomId("prev") 136 | .setEmoji("\u2B05") // < 137 | .setStyle(ButtonStyle.Secondary) 138 | ); 139 | } 140 | components.push( 141 | new ButtonBuilder() 142 | .setCustomId("next") 143 | .setEmoji("\u27A1") // > 144 | .setStyle(ButtonStyle.Secondary) 145 | ); 146 | return new Discord.ActionRowBuilder() 147 | .addComponents(...components); 148 | } 149 | 150 | const initialMessage = await msg.reply({ 151 | embeds: [ 152 | send( 153 | page, data.slice( 154 | constrain(count * (page - 1), 0, data.length), constrain(count * page, 0, data.length) 155 | ) 156 | ) 157 | ], 158 | allowedMentions: { 159 | repliedUser: false 160 | }, 161 | components: [ 162 | getComponents() 163 | ] 164 | }); 165 | 166 | const collector = initialMessage.createMessageComponentCollector({ componentType: ComponentType.Button, time: 160000, filter: (inter) => inter.user.id === msg.author.id }); 167 | 168 | collector.on("collect", async (choice) => { 169 | if (choice.customId == "prev") page--; 170 | else if (choice.customId == "next") page++; 171 | await choice.update({ 172 | embeds: [ 173 | send( 174 | page, data.slice( 175 | constrain(count * (page - 1), 0, data.length), constrain(count * page, 0, data.length) 176 | ) 177 | ) 178 | ], 179 | components: [ 180 | getComponents() 181 | ] 182 | }); 183 | }); 184 | 185 | collector.on("end", async () => { 186 | await initialMessage.edit({ 187 | components: [] 188 | }); 189 | }); 190 | } 191 | } 192 | 193 | export { Colors, brackets, noun, hidden, codestr, progress, formatDate, Bot }; -------------------------------------------------------------------------------- /src/cmd/shop/buy.ts: -------------------------------------------------------------------------------- 1 | import * as Discord from "discord.js"; 2 | import Big from "bignumber.js"; 3 | import { Command, Colors, constrain, Database, brackets, Bot, calcCost, commanum, codestr, parseNumber } from "../../global.js"; 4 | import { items, SItem } from "../../util/data/shop.js"; 5 | import { ItemEnum } from "../../util/data/item.js"; 6 | 7 | const handleBuy: { [i: string]: (user: Database.CycleUser, item: SItem, itmIndex: number, amt: number, id: string) => string[] | string } = { 8 | text(user, item, itmIndex, amt) { 9 | const cost = calcCost(new Big(item.cost), 1.07, amt, user.bought.upgrades[itmIndex] || 0); 10 | const cycles = new Big(user.cycles), tpc = new Big(user.tpc); 11 | 12 | if (cycles.lt(cost)) { 13 | return [`You don't have enough cycles! 14 | **You have** ${brackets(commanum(cycles.toString()))} 15 | **You need** ${brackets(commanum(cost.minus(cycles).toString()))} more!`, "Not enough cycles!"]; 16 | } 17 | 18 | user.cycles = cycles.minus(cost).toString(); 19 | if (!user.bought.upgrades[itmIndex]) user.bought.upgrades[itmIndex] = 0; 20 | user.bought.upgrades[itmIndex]! += amt; 21 | user.tpc = tpc.plus(new Big(items.upgrades[itmIndex].tpc ?? 0).times(amt)).toString(); 22 | 23 | return `Successfully bought ${brackets(item.name)} 24 | You Spent: ${brackets(commanum(cost.toString()))} 25 | Your TPC: ${brackets(commanum(user.tpc))}`; 26 | }, 27 | 28 | post(user, item, itmIndex, amt) { 29 | const cost = calcCost(new Big(item.cost), 1.14, amt, user.bought.cpp[itmIndex] || 0); 30 | const cycles = new Big(user.cycles), cpp = new Big(user.cpp); 31 | 32 | if (cycles.lt(cost)) { 33 | return [`You don't have enough cycles! 34 | **You have** ${brackets(commanum(cycles.toString()))} 35 | **You need** ${brackets(commanum(cost.minus(cycles).toString()))} more!`, "Not enough cycles!"]; 36 | } 37 | 38 | user.cycles = cycles.minus(cost).toString(); 39 | if (!user.bought.cpp[itmIndex]) user.bought.cpp[itmIndex] = 0; 40 | user.bought.cpp[itmIndex]! += amt; 41 | user.cpp = cpp.plus(new Big(items.cpp[itmIndex].cpp ?? 0).times(amt)).toString(); 42 | 43 | return `Successfully bought ${brackets(item.name)} 44 | You Spent: ${brackets(commanum(cost.toString()))} 45 | Your CPP: ${brackets(commanum(user.cpp))}`; 46 | }, 47 | 48 | idle(user, item, itmIndex, amt) { 49 | const cost = calcCost(new Big(item.cost), 1.21, amt, user.bought.idle[itmIndex] || 0); 50 | 51 | const coins = new Big(user.inv[ItemEnum.IdleCoin] || 0), tpm = new Big(user.tpm); 52 | 53 | if (coins.lt(cost)) { 54 | return [`You don't have enough Idle-Coins! 55 | **You have** ${brackets(commanum(coins.toString()))} 56 | **You need** ${brackets(commanum(cost.minus(coins).toString()))} more!`, "Not enough Idle-Coins!"]; 57 | } 58 | 59 | user.inv[ItemEnum.IdleCoin] = coins.minus(cost).toString(); 60 | if (!user.bought.idle[itmIndex]) user.bought.idle[itmIndex] = 0; 61 | user.bought.idle[itmIndex]! += amt; 62 | user.tpm = tpm.plus(new Big(items.idle[itmIndex].tpm ?? 0).times(amt)).toString(); 63 | 64 | return `Successfully bought ${brackets(item.name)} 65 | You Spent: ${brackets(commanum(cost.toString()))} 66 | Your TPM: ${brackets(commanum(user.tpm))}`; 67 | }, 68 | 69 | boosts(user, item, _, amt, id) { 70 | const cost = new Big(item.cost).times(amt); 71 | const coins = new Big(user.inv[ItemEnum.GoldenCycle] || 0); 72 | const bUser = Database.Boost.getUser(id); 73 | const ref = item.ref || 0; 74 | 75 | if (coins.lt(cost)) { 76 | return [`You don't have enough Golden Cycles! 77 | **You have** ${brackets(commanum(coins.toString()))} 78 | **You need** ${brackets(commanum(cost.minus(coins).toString()))} more!`, "Not enough Golden Cycles!"]; 79 | } 80 | 81 | user.inv[ItemEnum.GoldenCycle] = coins.minus(cost).toString(); 82 | if (!bUser[ref]) bUser[ref] = []; 83 | bUser[ref].push(new Date()); 84 | 85 | return `Successfully bought ${brackets(item.name)} 86 | You Spent: ${brackets(commanum(cost.toString()))}`; 87 | } 88 | }; 89 | 90 | // because internal api is not the same as visual rip 91 | const map = { 92 | text: "upgrades", 93 | post: "cpp", 94 | idle: "idle", 95 | boosts: "boosts" 96 | }; 97 | 98 | 99 | class C extends Command { 100 | names = ["buy", "b"]; 101 | help = "Buy an item from the shop!"; 102 | examples = ["buy idle \"cookie cutter\"", "buy idle \"cookie cutter\" 50"]; 103 | 104 | exec(msg: Discord.Message, args: string[], _: Discord.Client) { 105 | if (args.length == 0 || args.length == 1) { 106 | return Bot.errormsg(msg, `The valid shop names are: 107 | ${codestr(Object.keys(handleBuy).join(", "), "yaml")}`, "Shop names"); 108 | } 109 | if (args.length != 2 && args.length != 3) return Bot.argserror(msg, args.length, [2, 3]); 110 | if (!Object.keys(handleBuy).includes(args[0])) { 111 | return Bot.errormsg(msg, `The valid shop names are: 112 | ${codestr(Object.keys(handleBuy).join(", "), "yaml")}`, "Invalid Shop Name!"); 113 | } 114 | const num = parseNumber(args[2]); 115 | if (args[2] && isNaN(num)) return Bot.usererr(msg, "The amount must be a number!"); 116 | 117 | const itm = args[1]; 118 | const amt = constrain(num || 1, 1, 50); 119 | 120 | const user = Database.getUser(msg.author.id); 121 | 122 | // todo: refactor 123 | let itmIndex: number; 124 | 125 | const catKey = map[args[0] as keyof typeof map] as keyof typeof items; // catalog key 126 | const itemCat = items[catKey]; 127 | const potential = itemCat.findIndex(o => o.name.toLowerCase() == itm.toLowerCase()); 128 | if (potential == -1) itmIndex = itemCat.findIndex(o => o.name.toLowerCase().indexOf(itm.toLowerCase()) > -1); 129 | else itmIndex = potential; 130 | 131 | const item = itemCat[itmIndex]; 132 | 133 | if (itmIndex == -1) { 134 | return Bot.usererr(msg, `Item ${brackets(itm)} not found. 135 | Check your spelling!`, "Item not found!"); 136 | } 137 | 138 | const result = handleBuy[args[0]](user, item, itmIndex, amt, msg.author.id); 139 | if (Array.isArray(result)) Bot.errormsg(msg, result[0], result[1]); 140 | else { 141 | msg.channel.send({ 142 | embeds: [{ 143 | title: "Transaction Successful!", 144 | color: Colors.PRIMARY, 145 | description: result 146 | }] 147 | }); 148 | } 149 | } 150 | } 151 | 152 | export const c = new C(); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Invite link: https://discord.com/api/oauth2/authorize?client_id=781939317450342470&permissions=272448&scope=bot 3 | */ 4 | import "dotenv/config"; 5 | 6 | import { execSync } from "node:child_process"; 7 | 8 | 9 | import * as Discord from "discord.js"; 10 | 11 | 12 | import * as g from "./global.js"; 13 | 14 | import admins from "./util/admin.js"; 15 | 16 | import { parse } from "./cmd-parser.js"; 17 | import { help, load, verifyHuman } from "./loader.js"; 18 | 19 | 20 | import "./idle.js"; 21 | import { initiate } from "./server.js"; 22 | import { ActivityType } from "discord.js"; 23 | 24 | 25 | const client = new Discord.Client({ 26 | intents: [ 27 | "Guilds", 28 | "DirectMessages", 29 | "GuildEmojisAndStickers", 30 | "GuildMessages", 31 | "GuildMessageReactions", 32 | "MessageContent" 33 | ] 34 | }); 35 | 36 | 37 | let commands: { [i: string]: { cmds: g.Command[], desc: string } }, gcmdarr: g.Command[], ready = false; 38 | 39 | 40 | // the limit is x before we have people confirm they are not self-botting. 41 | // the array is: `commands used,bot input,bot answer` 42 | const commandsUsed: { [i: string]: [number, string, number] } = {}; 43 | 44 | 45 | if (process.argv?.[2] == "-d") { 46 | console.log("[Info] Debugging mode active."); 47 | client.on("debug", x => console.log("[Log] ", x)); 48 | } 49 | 50 | console.log("[Info] Loaded index.ts."); 51 | 52 | 53 | client.on("ready", async () => { 54 | client.user?.setPresence({ activities: [{ name: "&help for help!", type: ActivityType.Playing }], status: "idle" }); 55 | console.log(`[Info] Logged in as ${client.user?.tag}!`); 56 | 57 | await (process.env.NODE_ENV ? g.Database.updateBackup() : g.Database.update()); 58 | console.log("[Info] Loaded database."); 59 | 60 | commands = await load(); 61 | gcmdarr = Object.keys(commands).reduce((prev: g.Command[], kurr): g.Command[] => prev.concat(commands[kurr].cmds), []); 62 | 63 | console.log(`[Info] Loaded ${gcmdarr.length} commands.`); 64 | ready = true; 65 | 66 | initiate(client); 67 | console.log("[Info] Initiated server."); 68 | }); 69 | 70 | client.on("messageCreate", async (msg: Discord.Message) => { 71 | if (ready) { 72 | try { 73 | if (msg.author.bot || !msg.guild) return; 74 | 75 | const cmd = parse("&", msg.content); 76 | if (!cmd) return; 77 | 78 | 79 | if (cmd.command == "help" || cmd.command == "h") help(msg, cmd.args, commands); 80 | else if (cmd.command == "verify") { 81 | if (commandsUsed[msg.author.id] && verifyHuman(msg, cmd.args, commandsUsed)) delete commandsUsed[msg.author.id]; 82 | } else { 83 | for (const cmdclss of gcmdarr) { 84 | if (cmdclss.names.includes(cmd.command)) { 85 | if (commandsUsed[msg.author.id] && commandsUsed[msg.author.id][0] >= 100) { 86 | msg.channel.send({ 87 | embeds: [{ 88 | color: g.Colors.WARNING, 89 | title: "Anti-Bot Verification", 90 | description: `Type the number for ${g.brackets(commandsUsed[msg.author.id][1])}\n 91 | For example, if you get **one**, type in ${g.codestr("&verify 1")}`, 92 | footer: { 93 | text: "You cannot continue until you complete this challenge!" 94 | } 95 | }] 96 | }); 97 | break; 98 | } 99 | 100 | if (cmdclss.cooldown && cmdclss.getCooldown(msg.author) != null) { 101 | if (!cmdclss.sentCooldown(msg.author)) { 102 | cmdclss.wrapCooldown(msg, cmdclss.getCooldown(msg.author) ?? 0); 103 | cmdclss.setSent(msg.author); 104 | } 105 | } else if (cmdclss.isAdmin) { 106 | if (admins.includes(msg.author.id)) { 107 | await cmdclss.wrap(msg, cmd.args, client); 108 | } else { 109 | try { 110 | g.Bot.errormsg(msg, "haha you don't have the perms!", "Permissions needed!"); 111 | } catch { 112 | // ignore BotErr 113 | } 114 | } 115 | } else { 116 | await cmdclss.wrap(msg, cmd.args, client); 117 | } 118 | 119 | if (!commandsUsed[msg.author.id]) { 120 | const numbers = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]; 121 | const choice = g.randomChoice(numbers)[0]; 122 | const answer = numbers.indexOf(choice); 123 | commandsUsed[msg.author.id] = [1, choice, answer]; 124 | } else commandsUsed[msg.author.id][0]++; 125 | break; 126 | } 127 | } 128 | } 129 | } catch (err: any) { 130 | console.log("[Error]", err); 131 | showError("UNHANDLED EXCEPTION", err.message, err); 132 | } 133 | } 134 | }); 135 | 136 | client.on("interactionCreate", async interaction => { 137 | if (!interaction.isChatInputCommand()) return; 138 | 139 | const { commandName } = interaction; 140 | 141 | await interaction.reply({ 142 | embeds: [{ 143 | title: commandName, 144 | color: g.Colors.PRIMARY, 145 | description: "Coming soon..." 146 | }] 147 | }); 148 | }); 149 | 150 | client.on("rateLimit", e => { 151 | if (e.timeout > 10_000) { 152 | console.log("[Bot Ratelimit], timeout:", e.timeout); 153 | showError("BOT RATELIMIT", "The bot got ratelimited, so we killed it.\nCheck the repl for more!"); 154 | 155 | // restart the shell 156 | execSync("kill 1"); 157 | } 158 | }); 159 | 160 | async function showError(title: string, text: string, obj?: any) { 161 | const channel = await client.channels.fetch("899518500576579615") as Discord.TextChannel; 162 | 163 | if (channel) { 164 | channel.send({ 165 | embeds: [{ 166 | color: g.Colors.ERROR, 167 | title: "Error!", 168 | description: `Error is type ${g.brackets(title)}`, 169 | fields: [{ 170 | name: "Error", 171 | value: `${g.codestr(text, "js")}\n${g.codestr(JSON.stringify(obj, null, 2), "js")}` 172 | }] 173 | }] 174 | }); 175 | } 176 | } 177 | 178 | 179 | setInterval(async () => { 180 | if (process.env.NODE_ENV) { 181 | await g.Database.saveBackup(); 182 | await g.Database.updateBackup(); 183 | } else { 184 | await g.Database.save(); 185 | await g.Database.update(); 186 | } 187 | }, 6e5); // 10 min 188 | 189 | 190 | client.login(process.env.TOKEN); 191 | 192 | process.on("unhandledRejection", reason => { 193 | console.log("[REJECTION ERROR]", reason); 194 | }); -------------------------------------------------------------------------------- /src/util/data/shop.ts: -------------------------------------------------------------------------------- 1 | import { boosts } from "./boosts/boosts.js"; 2 | import { boostShop } from "./boosts/boosts-shop.js"; 3 | 4 | // the S stands for Shop 5 | export interface SItem { 6 | name: string; 7 | description: string; 8 | 9 | cost: number | string; // in cycles 10 | 11 | tpc?: number | string; 12 | cpp?: number | string; 13 | tpm?: number | string; 14 | 15 | ref?: number; // ref for boosts 16 | } 17 | 18 | // upgrades are tpc, idles are tpm 19 | export const items: { upgrades: SItem[], cpp: SItem[], idle: SItem[], boosts: SItem[] } = { 20 | upgrades: [{ 21 | name: "Inspiration", 22 | description: "The idea is the start of everything!", 23 | cost: 10, 24 | tpc: 1 25 | }, { 26 | name: "Typing.com", 27 | description: "Time to learn to type!", 28 | cost: 12, 29 | tpc: 5 30 | }, { 31 | name: "NotePad", 32 | description: "A featureless editor", 33 | cost: 16, 34 | tpc: 10 35 | }, { 36 | name: "Pastebin", 37 | description: "Nowhere else to host", 38 | cost: 21, 39 | tpc: 15 40 | }, { 41 | name: "Hastebin", 42 | description: "It's pastebin but with an H!", 43 | cost: 90, 44 | tpc: 45 45 | }, { 46 | name: "NotePad++", 47 | description: "It's evolution! Still featureless...", 48 | cost: 115, 49 | tpc: 50 50 | }, { 51 | name: "SourceB.in", 52 | description: "Another bin?!?", 53 | cost: 146, 54 | tpc: 55 55 | }, { 56 | name: "Whitespace", 57 | description: "[ ] [\t]", 58 | cost: 304, 59 | tpc: 70 60 | }, { 61 | name: "Indentation Error", 62 | description: "Did you use four spaces?!?", 63 | cost: 495, 64 | tpc: 80 65 | }, { 66 | name: "Windows Powershell", 67 | description: "At least `clear` works", 68 | cost: 1315, 69 | tpc: 100 70 | }, { 71 | name: "NullPointerException", 72 | description: "Segmentation fault (core dumped)", 73 | cost: 3489, 74 | tpc: 120 75 | }, { 76 | name: "Stack Overflow", 77 | description: "To understand recursion, you must first understand recursion.", 78 | cost: 15079, 79 | tpc: 150 80 | }, { 81 | name: "Windows Powershell+", 82 | description: "The better version of Windows Powershell.", 83 | cost: 51065, 84 | tpc: 175 85 | }, { 86 | name: "Stack Overflow II", 87 | description: "That place where you ask questions", 88 | cost: 172925, 89 | tpc: 200 90 | }, { 91 | name: "Hoisting", 92 | description: "Use it before you define it!", 93 | cost: "1983009", 94 | tpc: 250 95 | }, { 96 | name: "Personal Care Robot", 97 | description: "You now don't have to spend time combing your hair all day!", 98 | cost: "9537000000000", 99 | tpc: 300 100 | }, { 101 | name: "Gamer Mouse", 102 | description: "The ultimate mouse.", 103 | cost: "2.274e7", 104 | tpc: 400 105 | }, { 106 | name: "Github account", 107 | description: "Don't lose any of your code!", 108 | cost: "3.429e10", 109 | tpc: 450 110 | }, { 111 | name: "Learn a new language", 112 | description: "Now you can share even more code!", 113 | cost: "3.9323e11", 114 | tpc: 500 115 | }, { 116 | name: "Replit Ghostwriter", 117 | description: "Writes code... for you!", 118 | cost: "5.171e13", 119 | tpc: 600 120 | }, { 121 | name: "Github Copilot", 122 | description: "Ghostwriter but more expensive!", 123 | cost: "6.8e15", 124 | tpc: 700 125 | }], 126 | 127 | cpp: [{ 128 | name: "Popular", 129 | description: "You are now slightly popular.", 130 | cost: 10, 131 | cpp: 1 132 | }, { 133 | name: "Meme", 134 | description: "Haha funnies", 135 | cost: 14, 136 | cpp: 5 137 | }, { 138 | name: "Friends", 139 | description: "bff", 140 | cost: 19, 141 | cpp: 10 142 | }, { 143 | name: "Subscribers", 144 | description: "A real subscriber!", 145 | cost: 27, 146 | cpp: 15 147 | }, { 148 | name: "Paparazzi", 149 | description: "Get in those shots!", 150 | cost: 38, 151 | cpp: 20 152 | }, { 153 | name: "Internet Stranger", 154 | description: "The best kind of fan!", 155 | cost: 76, 156 | cpp: 30 157 | }, { 158 | name: "Diehard Fan", 159 | description: "Uhh... that's too much?!", 160 | cost: 149, 161 | cpp: 40 162 | }, { 163 | name: "Yourself", 164 | description: "Nobody loves me like me", 165 | cost: 295, 166 | cpp: 50 167 | }, { 168 | name: "Best Friend", 169 | description: "When friend just isn't enough", 170 | cost: 580, 171 | cpp: 60 172 | }, { 173 | name: "Mailing List", 174 | description: "They were probably tricked into it", 175 | cost: 1140, 176 | cpp: 70 177 | }, { 178 | name: "Advertisement", 179 | description: "Is it really worth it?", 180 | cost: 1600, 181 | cpp: 75 182 | }, { 183 | name: "Tracking", 184 | description: "Personalized fans!", 185 | cost: 2242, 186 | cpp: 80 187 | }, { 188 | name: "Significant Other", 189 | description: "<3", 190 | cost: 4411, 191 | cpp: 90 192 | }, { 193 | name: "OG Fan", 194 | description: "They support you... no matter what!", 195 | cost: 8677, 196 | cpp: 100 197 | }, { 198 | name: "Bot", 199 | description: "Backup when all else fails", 200 | cost: 17069, 201 | cpp: 110 202 | }, { 203 | name: "AI Bot", 204 | description: "Acts like a fan, but is it a fan?", 205 | cost: 23940, 206 | cpp: 120 207 | }, { 208 | name: "Superiority Complex", 209 | description: "I'm the best! Probably! Maybe...", 210 | cost: 47094, 211 | cpp: 125 212 | }], 213 | 214 | idle: [{ 215 | name: "Idle Machine", 216 | description: "Your first idle!", 217 | cost: 5, 218 | tpm: 1 219 | }, { 220 | name: "Code Robot", 221 | description: "Please click all the bugs to continue.", 222 | cost: 21, 223 | tpm: 5 224 | }, { 225 | name: "StackOverflow Commitee", 226 | description: "Free code review!", 227 | cost: 41, 228 | tpm: 10 229 | }, { 230 | name: "Intern", 231 | description: "Free code!", 232 | cost: 61, 233 | tpm: 15 234 | }, { 235 | name: "Code AI", 236 | description: "One day it'll write binary", 237 | cost: 81, 238 | tpm: 20 239 | }, { 240 | name: "Debugger", 241 | description: "Isn't that just spamming prints?", 242 | cost: 101, 243 | tpm: 25 244 | }, { 245 | name: "Hacking Machine", 246 | description: "There is nothing you can't hack anymore.", 247 | cost: 121, 248 | tpm: 30 249 | }, { 250 | name: "Macros", 251 | description: "Let the code do it's job!", 252 | cost: 141, 253 | tpm: 35 254 | }, { 255 | name: "VSCode Debugger", 256 | description: "Now you debug all your code... Without prints!", 257 | cost: 161, 258 | tpm: 40 259 | }, { 260 | name: "Fiverr worker", 261 | description: "I mean you payed for it...", 262 | cost: 181, 263 | tpm: 45 264 | }, { 265 | name: "Google", 266 | description: "Google is your best friend... When it works!", 267 | cost: 201, 268 | tpm: 50 269 | }, { 270 | name: "Bing", 271 | description: "There's search engines, and then there's bing", 272 | cost: 221, 273 | tpm: 55 274 | }, { 275 | name: "Idle Machine II", 276 | description: "The OG coder remastered!", 277 | cost: 241, 278 | tpm: 60 279 | }, { 280 | name: "Discord Server", 281 | description: "Get distracted in #bots with a bot called Cycle!", 282 | cost: 261, 283 | tpm: 65 284 | }, { 285 | name: "Slack Server", 286 | description: "Slack off", 287 | cost: 281, 288 | tpm: 70 289 | }, { 290 | name: "Linter", 291 | description: "Your worst enemy! But it helps you?", 292 | cost: 301, 293 | tpm: 75 294 | }, { 295 | name: "Eslint", 296 | description: "Don't forget to spend time configuring it!", 297 | cost: 341, 298 | tpm: 85 299 | }, { 300 | name: "Webpack", 301 | description: "Now you can generate small code!", 302 | cost: 381, 303 | tpm: 95 304 | }, { 305 | name: "Jest", 306 | description: "Test-Driven Development is the best!", 307 | cost: 401, 308 | tpm: 100 309 | }, { 310 | name: "Replit Bounties", 311 | description: "What's better than coding? Having people code for you!", 312 | cost: 461, 313 | tpm: 115 314 | }], 315 | 316 | boosts: boostShop.map(n => { 317 | const ref = boosts[n.ref]; 318 | return { 319 | name: ref.name, 320 | description: ref.description, 321 | cost: n.cost, 322 | ref: n.ref 323 | }; 324 | }) 325 | }; 326 | -------------------------------------------------------------------------------- /src/util/data/open-item.ts: -------------------------------------------------------------------------------- 1 | // refer to ./item.ts for metadata. 2 | import * as Discord from "discord.js"; 3 | import { Database, random, brackets, commanum, hidden, plural } from "../../global.js"; 4 | import Big from "bignumber.js"; 5 | import { items, ItemEnum } from "./item.js"; 6 | 7 | // this way, we can utilize the inefficiency of an array search. 8 | // we will check if this object has an implementation, and if not, there won't be one! 9 | export const openItem: { [i: number]: (user: Database.CycleUser, amt: number) => Discord.APIEmbedField } = { 10 | [ItemEnum.CheapPhone]: (user, amt) => { 11 | const cycles = new Big(user.cycles); 12 | const cpp = new Big(user.cpp); 13 | 14 | const num = cpp.plus(Math.round(random(1, 5))).times(amt); 15 | const newCycles = cycles.plus(num); 16 | 17 | user.cycles = newCycles.toString(); 18 | 19 | return { 20 | name: "Text text text!", 21 | value: `You use your phone. 22 | It's cheap, so it dies quickly! 23 | + ${brackets(commanum(num.toString()))} cycles!` 24 | }; 25 | }, 26 | [ItemEnum.ExtraFinger]: (user, amt) => { 27 | const text = new Big(user.text); 28 | const tpc = new Big(user.tpc); 29 | 30 | const num = tpc.plus(Math.round(random(1, 5))).times(amt); 31 | const newText = text.plus(num); 32 | 33 | user.text = newText.toString(); 34 | 35 | return { 36 | name: "Code code code!", 37 | value: `You use your extra finger. 38 | It's not your finger, and breaks! 39 | + ${brackets(commanum(num.toString()))} text!` 40 | }; 41 | }, 42 | [ItemEnum.Coffee]: (user, amt) => { 43 | const text = new Big(user.text); 44 | const tpc = new Big(user.tpc); 45 | 46 | const num = tpc.times((10 + amt) / 10).dp(0); 47 | const newText = text.plus(num); 48 | 49 | user.text = newText.toString(); 50 | 51 | return { 52 | name: "Code code code!", 53 | value: `You drink some coffee. 54 | You feel a surge of energy! 55 | + ${brackets(commanum(num.toString()))} text!` 56 | }; 57 | }, 58 | [ItemEnum.ChestChest]: (user, amt) => { 59 | const itemsGot: { [i: string]: number } = {}; 60 | 61 | for (let j = 0; j < 2 * amt; j++) { 62 | for (let i = 0; i < items.length; i++) { 63 | if (i == ItemEnum.ChestChest || i == ItemEnum.ChestChestChest) continue; 64 | const item = items[i]; 65 | if (Math.random() * 100 < item.dropChance) { 66 | itemsGot[i] = (itemsGot[i] || 0) + 1; 67 | user.inv[i] = new Big(user.inv[i] || 0).plus(1).toString(); 68 | } 69 | } 70 | } 71 | const itemText = Object.keys(itemsGot).map(i => `${hidden(`${items[Number(i)].name}`)}${itemsGot[i] > 1 ? ` x**${commanum(itemsGot[i].toString())}**` : ""}`); 72 | 73 | return { 74 | name: "Mystery Chest!", value: `You open up the chest. 75 | You got... 76 | ${itemText.length == 0 ? hidden("nothing :(") : itemText.join("\n")}` 77 | }; 78 | }, 79 | [ItemEnum.DailyChest]: (user, amt) => { 80 | let itemsFound = 0; 81 | const itemsGot: { [i: string]: number } = {}; 82 | let cycles = new Big(user.cycles); 83 | const cpp = new Big(user.cpp); 84 | const cyclesGot = cpp.times(2).times(amt); 85 | cycles = cycles.plus(cyclesGot); 86 | 87 | user.cycles = cycles.toString(); 88 | 89 | for (let j = 0; j < 10 * amt; j++) { 90 | for (let i = 0; i < items.length; i++) { 91 | if (itemsFound > 25) break; 92 | if (Math.random() > 0.5) itemsFound++; 93 | 94 | const item = items[i]; 95 | if (Math.random() * 100 < item.dropChance) { 96 | itemsGot[i] = (itemsGot[i] || 0) + 1; 97 | user.inv[i] = new Big(user.inv[i] || 0).plus(1).toString(); 98 | } 99 | } 100 | } 101 | 102 | const itemText = Object.keys(itemsGot).map(i => `${hidden(`${items[Number(i)].name}`)}${itemsGot[i] > 1 ? ` x**${commanum(itemsGot[i].toString())}**` : ""}`); 103 | 104 | return { 105 | name: "Mystery Chest!", value: `You open up the chest. 106 | You got... 107 | ${itemText.length == 0 ? hidden("nothing :(") : itemText.join("\n")} 108 | You also got ${brackets(commanum(cyclesGot.toString()))} cycles!` 109 | }; 110 | }, 111 | [ItemEnum.ApplePhone]: (user, amt) => { 112 | const cycles = new Big(user.cycles); 113 | const text = new Big(user.text); 114 | const cpp = new Big(user.cpp); 115 | const tpc = new Big(user.tpc); 116 | 117 | const num = cpp.plus(Math.round(random(1, 5))).times(amt); 118 | const num2 = tpc.plus(Math.round(random(1, 5))).times(amt); 119 | const newCycles = cycles.plus(num); 120 | const newText = text.plus(num2); 121 | 122 | user.cycles = newCycles.toString(); 123 | user.text = newText.toString(); 124 | 125 | return { 126 | name: "Text text text!", 127 | value: `You use your *expensive* phone. 128 | It's apple, so it dies quickly! 129 | + ${brackets(commanum(num.toString()))} cycles! 130 | + ${brackets(commanum(num2.toString()))} text!` 131 | }; 132 | }, 133 | [ItemEnum.ChestChestChest]: (user, amt) => { 134 | const itemsGot: { [i: string]: number } = {}; 135 | 136 | for (let j = 0; j < 25 * amt; j++) { 137 | for (let i = 0; i < items.length; i++) { 138 | if (i == ItemEnum.ChestChest || i == ItemEnum.ChestChestChest) continue; 139 | const item = items[i]; 140 | if (Math.random() * 150 < item.dropChance) { 141 | itemsGot[i] = (itemsGot[i] || 0) + 1; 142 | user.inv[i] = new Big(user.inv[i] || 0).plus(1).toString(); 143 | } 144 | } 145 | } 146 | 147 | user.inv[ItemEnum.ChestChest] = new Big(user.inv[ItemEnum.ChestChest] || 0).plus(amt).toString(); 148 | 149 | const itemText = Object.keys(itemsGot).map(i => `${hidden(`${items[Number(i)].name}`)}${itemsGot[i] > 1 ? ` x**${commanum(itemsGot[i].toString())}**` : ""}`); 150 | 151 | return { 152 | name: "Mystery Chest Chest!", value: `You open up the chest chest. 153 | You got... 154 | ${itemText.length == 0 ? hidden("nothing :(") : itemText.join("\n")} 155 | Also you got ${brackets(commanum(amt.toString()))} **chest chest${plural(amt)}**!` 156 | }; 157 | }, 158 | [ItemEnum.CraftingChest]: (user, amt) => { 159 | const itemsGot: { [i: string]: number } = {}; 160 | const craftingItems = [ItemEnum.Glue, ItemEnum.SuperGlue, ItemEnum.CraftingMat]; 161 | 162 | for (let j = 0; j < 20 * amt; j++) { 163 | for (const i of craftingItems) { 164 | const item = items[i]; 165 | if (Math.random() * 150 < item.dropChance) { 166 | itemsGot[i] = (itemsGot[i] || 0) + 1; 167 | user.inv[i] = new Big(user.inv[i] || 0).plus(1).toString(); 168 | } 169 | } 170 | } 171 | 172 | const itemText = Object.keys(itemsGot).map(i => `${hidden(`${items[Number(i)].name}`)}${itemsGot[i] > 1 ? ` x**${commanum(itemsGot[i].toString())}**` : ""}`); 173 | 174 | return { 175 | name: "Mystery Chest!", value: `You open up the crafting chest. 176 | You got... 177 | ${itemText.length == 0 ? hidden("nothing :(") : itemText.join("\n")}` 178 | }; 179 | }, 180 | [ItemEnum.KnowledgeBook]: (user, amt) => { 181 | let xp = new Big(user.xp); 182 | const level = new Big(user.level); 183 | const xpAmt = level.plus(12 * amt); 184 | xp = xp.plus(amt); 185 | user.xp = xp.toString(); 186 | 187 | return { 188 | name: "Book of knowledge!", value: `You read the book... 189 | You instantly gain SMORT! 190 | + ${brackets(commanum(xpAmt.toString()))} XP! 191 | 192 | **Note: ** Use \`&c\` to level up!` 193 | }; 194 | }, 195 | [ItemEnum.IdleCoinMaker]: (user, amt) => { 196 | // every idle coin is worth 20 197 | let cycles = new Big(user.cycles); 198 | const cycleUsed = cycles.times(0.05 * amt).dp(0); 199 | 200 | let idleCoins = new Big(user.inv[ItemEnum.IdleCoin] || 0); 201 | const idleCoinAmt = cycleUsed.div(2).times(Math.random() * 0.9 + 0.1).plus(1).dp(0); 202 | 203 | if (cycles.lt(cycleUsed)) { 204 | return { 205 | name: "Boom!", 206 | value: `With a crash, the idle-coin maker breaks. 207 | You didn't have enough cycles! 208 | You needed ${brackets(commanum(cycleUsed.toString()))} cycles!` 209 | }; 210 | } 211 | 212 | cycles = cycles.minus(cycleUsed); 213 | idleCoins = idleCoins.plus(idleCoinAmt); 214 | 215 | user.cycles = cycles.toString(); 216 | user.inv[ItemEnum.IdleCoin] = idleCoins.toString(); 217 | 218 | return { 219 | name: "Idle Coin Maker!", 220 | value: `You use the machine, and you got... 221 | +${brackets(commanum(idleCoinAmt.toString()))} Idle-Coins! 222 | -${brackets(commanum(cycleUsed.toString()))} Cycles! 223 | 224 | You now have ${brackets(commanum(idleCoins.toString()))} idle-coins! 225 | You now have ${brackets(commanum(user.cycles.toString()))} cycles!` 226 | }; 227 | }, 228 | [ItemEnum.GoldenCycleMaker]: (user, amt) => { 229 | let cycles = new Big(user.cycles); 230 | const cycleUsed = cycles.times(0.05 * amt).dp(0); 231 | 232 | let goldenCycles = new Big(user.inv[ItemEnum.GoldenCycle] || 0); 233 | const goldenCycleAmt = cycleUsed.div(2).times(Math.random() * 0.9 + 0.1).plus(1).dp(0); 234 | 235 | if (cycles.lt(cycleUsed)) { 236 | return { 237 | name: "Boom!", 238 | value: `With a crash, the golden cycle maker breaks. 239 | You didn't have enough cycles! 240 | You needed ${brackets(commanum(cycleUsed.toString()))} cycles!` 241 | }; 242 | } 243 | 244 | cycles = cycles.minus(cycleUsed); 245 | goldenCycles = goldenCycles.plus(goldenCycleAmt); 246 | 247 | user.cycles = cycles.toString(); 248 | user.inv[ItemEnum.GoldenCycle] = goldenCycles.toString(); 249 | 250 | return { 251 | name: "Golden Cycle Maker!", 252 | value: `You use the machine, and you got... 253 | +${brackets(commanum(goldenCycleAmt.toString()))} Golden Cycles! 254 | -${brackets(commanum(cycleUsed.toString()))} Cycles! 255 | 256 | You now have ${brackets(commanum(goldenCycles.toString()))} golden cycles! 257 | You now have ${brackets(commanum(user.cycles.toString()))} cycles!` 258 | }; 259 | }, 260 | [ItemEnum.VoteCrate]: (user, amt) => { 261 | const text = new Big(user.text); 262 | const cycles = new Big(user.cycles); 263 | const tpc = new Big(user.tpc); 264 | const cpp = new Big(user.cpp); 265 | 266 | const txt = tpc.plus(20).times(amt).dp(0); 267 | const cycl = cpp.plus(20).times(amt).dp(0); 268 | 269 | const newText = text.plus(txt); 270 | const newCycles = cycles.plus(cycl); 271 | 272 | user.text = newText.toString(); 273 | user.cycles = newCycles.toString(); 274 | 275 | return { 276 | name: "Vote Crate!", 277 | value: `You open the vote crate. 278 | + ${brackets(commanum(txt.toString()))} text! 279 | + ${brackets(commanum(cycl.toString()))} cycles! 280 | 281 | Thanks for voting!` 282 | }; 283 | }, 284 | [ItemEnum.BronzeQuestChest]: (user, amt) => { 285 | // 1% tpm * cpp -> cycle 286 | // 10 idle coins 287 | // 1 chest chest 288 | 289 | const cyclesEarned = new Big(user.tpm).times(0.01 * amt).times(new Big(user.cpp)).plus(500 * amt).dp(0, Big.ROUND_FLOOR); 290 | const icsEarned = 10 * amt; 291 | const cchestsEarned = amt; 292 | 293 | const nCycles = new Big(user.cycles).plus(cyclesEarned).toString(); 294 | const ics = new Big(user.inv[ItemEnum.IdleCoin] || 0).plus(icsEarned).toString(); 295 | const cchests = new Big(user.inv[ItemEnum.ChestChest] || 0).plus(cchestsEarned).toString(); 296 | 297 | user.cycles = nCycles; 298 | user.inv[ItemEnum.IdleCoin] = ics; 299 | user.inv[ItemEnum.ChestChest] = cchests; 300 | 301 | return { 302 | name: "Bronze Quest Chest!", 303 | value: `You open up a ${brackets("Bronze quest chest")}! 304 | You got: 305 | + ${brackets(cyclesEarned.toString())} Cycles! 306 | + ${brackets(icsEarned.toString())} Idle-Coins! 307 | + ${brackets(cchestsEarned.toString())} Chest Chests!` 308 | }; 309 | }, 310 | [ItemEnum.SilverQuestChest]: (user, amt) => { 311 | // 5% tpm * cpp -> cycle 312 | // 20 idle coins 313 | // 10 chest chest 314 | 315 | const cyclesEarned = new Big(user.tpm).times(0.05 * amt).times(new Big(user.cpp)).plus(5e4 * amt).dp(0, Big.ROUND_FLOOR); 316 | const icsEarned = 20 * amt; 317 | const cchestsEarned = 10 * amt; 318 | 319 | const nCycles = new Big(user.cycles).plus(cyclesEarned).toString(); 320 | const ics = new Big(user.inv[ItemEnum.IdleCoin] || 0).plus(icsEarned).toString(); 321 | const cchests = new Big(user.inv[ItemEnum.ChestChest] || 0).plus(cchestsEarned).toString(); 322 | 323 | user.cycles = nCycles; 324 | user.inv[ItemEnum.IdleCoin] = ics; 325 | user.inv[ItemEnum.ChestChest] = cchests; 326 | 327 | return { 328 | name: "Silver Quest Chest!", 329 | value: `You open up a ${brackets("Silver quest chest")}! 330 | You got: 331 | + ${brackets(cyclesEarned.toString())} Cycles! 332 | + ${brackets(icsEarned.toString())} Idle-Coins! 333 | + ${brackets(cchestsEarned.toString())} Chest Chests!` 334 | }; 335 | }, 336 | [ItemEnum.GoldQuestChest]: (user, amt) => { 337 | // 10% tpm * cpp -> cycle 338 | // 30 idle coins 339 | // 15 chest chest 340 | // 1 chest chest chest 341 | 342 | const cyclesEarned = new Big(user.tpm).times(0.1 * amt).times(new Big(user.cpp)).plus(5e5 * amt).dp(0, Big.ROUND_FLOOR); 343 | const icsEarned = 30 * amt; 344 | const cchestsEarned = 15 * amt; 345 | const ccchestsEarned = amt; 346 | 347 | const nCycles = new Big(user.cycles).plus(cyclesEarned).toString(); 348 | const ics = new Big(user.inv[ItemEnum.IdleCoin] || 0).plus(icsEarned).toString(); 349 | const cchests = new Big(user.inv[ItemEnum.ChestChest] || 0).plus(cchestsEarned).toString(); 350 | const ccchests = new Big(user.inv[ItemEnum.ChestChestChest] || 0).plus(ccchestsEarned).toString(); 351 | 352 | user.cycles = nCycles; 353 | user.inv[ItemEnum.IdleCoin] = ics; 354 | user.inv[ItemEnum.ChestChest] = cchests; 355 | user.inv[ItemEnum.ChestChestChest] = ccchests; 356 | 357 | return { 358 | name: "Gold Quest Chest!", 359 | value: `You open up a ${brackets("Gold quest chest")}! 360 | You got: 361 | + ${brackets(cyclesEarned.toString())} Cycles! 362 | + ${brackets(icsEarned.toString())} Idle-Coins! 363 | + ${brackets(cchestsEarned.toString())} Chest Chests! 364 | + ${brackets(ccchestsEarned.toString())} Chest Chest Chests!` 365 | }; 366 | } 367 | }; --------------------------------------------------------------------------------