diff --git a/assets/json/cards-against-humanity.json b/assets/json/cards-against-humanity.json index 2c795b87..8566ae53 100644 --- a/assets/json/cards-against-humanity.json +++ b/assets/json/cards-against-humanity.json @@ -17,7 +17,7 @@ "pick": 1 }, { - "text": "This is the way the world ends \/ This is the way the world ends \/ Not with a bang but with _.", + "text": "This is the way the world ends / This is the way the world ends / Not with a bang but with _.", "pick": 1 }, { @@ -301,7 +301,7 @@ "pick": 3 }, { - "text": "What's the next superhero\/sidekick duo?", + "text": "What's the next superhero/sidekick duo?", "pick": 2 }, { @@ -465,7 +465,7 @@ "pick": 1 }, { - "text": "My mom freaked out when she looked at my browser history and found _.com\/_.", + "text": "My mom freaked out when she looked at my browser history and found _.com/_.", "pick": 2 }, { @@ -533,7 +533,7 @@ "pick": 2 }, { - "text": "Tonight on 20\/20: What you don't know about _ could kill you.", + "text": "Tonight on 20/20: What you don't know about _ could kill you.", "pick": 1 }, { @@ -1258,7 +1258,7 @@ }, { "pick": 2, - "text": "In line with our predictions, we find a robust correlation between _ and _." + "text": "In line with our predictions, we find a robust correlation between _ and _ (p>.05)." }, { "pick": 1, @@ -1477,7 +1477,7 @@ "pick": 1 }, { - "text": "What the hell?! They added a 6\/6 with flying, trample, and _.", + "text": "What the hell?! They added a 6/6 with flying, trample, and _.", "pick": 1 }, { @@ -1656,9 +1656,617 @@ "text": "What's the Canadian government using to inspire rural students to succeed?", "pick": 1 }, + { + "text": "Real men watch _.", + "pick": 1 + }, + { + "text": "So wait, _ was actually _? Wow, I didn't see that one coming!", + "pick": 2 + }, + { + "text": "Whenever I'm splashed with cold water, I turn into _.", + "pick": 1 + }, + { + "text": "No matter how you look at it, ultimately _ is responsible for _.", + "pick": 2 + }, + { + "text": "Vegeta, what does the scouter say?", + "pick": 1 + }, + { + "text": "Who the hell do you think I am?!", + "pick": 1 + }, + { + "text": "On the next episode of Dragon Ball Z, Goku has a fierce battle with _.", + "pick": 1 + }, + { + "text": "Truly and without question, _ is the manliest of all men.", + "pick": 1 + }, + { + "text": "WANTED: $50,000,000,000 reward for the apprehension of_.", + "pick": 1 + }, + { + "text": "People die when they are _.", + "pick": 1 + }, + { + "text": "Okay, I'll admit it. I would totally go gay for _.", + "pick": 1 + }, + { + "text": "_. Goddammit, Japan.", + "pick": 1 + }, + { + "text": "Digimon! Digivolve to: _-mon!", + "pick": 1 + }, + { + "text": "I have never in my life laughed harder than the first time I watched _.", + "pick": 1 + }, + { + "text": "There are guilty pleasures. And then there's _.", + "pick": 1 + }, + { + "text": "When it comes to Japanese cuisine, there's simply nothing better than _.", + "pick": 1 + }, + { + "text": "Just announced: The brand new anime adaptation of _, starring _ as the voice of _.", + "pick": 3 + }, + { + "text": "I'M-A FIRIN' MAH _!", + "pick": 1 + }, + { + "text": "Of my entire collection, my most prized possession is _.", + "pick": 1 + }, + { + "text": "Mom, I swear! Despite its name, _ is NOT a porno!", + "pick": 1 + }, + { + "text": "The government of Japan recently passed a law that effectively forbids all forms of _.", + "pick": 1 + }, + { + "text": "When it comes to hentai, nothing gets me hotter than _.", + "pick": 1 + }, + { + "text": "Lupin the III's latest caper involves him trying to steal _.", + "pick": 1 + }, + { + "text": "_. YOU SHOULD BE WATCHING.", + "pick": 1 + }, + { + "text": "_ vs. _. BEST. FIGHT. EVER.", + "pick": 2 + }, + { + "text": "Fresh from Japan: The new smash hit single by _ titled _.", + "pick": 2 + }, + { + "text": "Congratulations, _.", + "pick": 1 + }, + { + "text": "Hayao Miyazaki's latest family film is about a young boy befriending _.", + "pick": 1 + }, + { + "text": "One thing you almost never see in anime is _.", + "pick": 1 + }, + { + "text": "By far the best panel at any anime convention is the one for _.", + "pick": 1 + }, + { + "text": "S-Shut up!! I-It's not like I'm _ or anything.", + "pick": 1 + }, + { + "text": "What is moé?", + "pick": 1 + }, + { + "text": "The English dub of _ sucks worse than _.", + "pick": 2 + }, + { + "text": "_. BELIEVE IT!", + "pick": 1 + }, + { + "text": "Make a contract with me, and become _!", + "pick": 1 + }, + { + "text": "You guys are so wrong. Obviously, _ is best waifu.", + "pick": 1 + }, + { + "text": "In the latest chapter of Toriko, our hero hunts down, kills, and eats a creature made entirely of _.", + "pick": 1 + }, + { + "text": "On the next episode of Dragon Ball Z, _ is forced to do the fusion dance with _.", + "pick": 2 + }, + { + "text": "You are already _.", + "pick": 1 + }, + { + "text": "THIS _ HAS BEEN PASSED DOWN THE ARMSTRONG FAMILY LINE FOR GENERATIONS!!!", + "pick": 1 + }, + { + "text": "My favorite episode of _ is the one with _.", + "pick": 2 + }, + { + "text": "Make a yaoi shipping.", + "pick": 2 + }, + { + "text": "This doujinshi I just bought has _ and _ getting it on, hardcore.", + "pick": 2 + }, + { + "text": "Make a love triangle.", + "pick": 3 + }, + { + "text": "Dr. Black Jack, please hurry! The patient is suffering from a terminal case of _!", + "pick": 1 + }, + { + "text": "So just who is this Henry Goto fellow, anyway?", + "pick": 1 + }, + { + "text": "When Henry Goto is alone and thinks that no one's looking, he secretly enjoys _.", + "pick": 1 + }, + { + "text": "He might just save the universe, if he only had some _!", + "pick": 1 + }, + { + "text": "This _ of mine glows with an awesome power! Its _ tells me to defeat you!", + "pick": 2 + }, + { + "text": "Cards Against Anime: It's more fun than _!", + "pick": 1 + }, + { + "text": "On the mean streets of Tokyo, everyone knows that _ is the leader of the _ Gang.", + "pick": 2 + }, + { + "text": "When I found all 7 Dragon Balls, Shenron granted me my wish for _.", + "pick": 1 + }, + { + "text": "The best part of my _ costume is _.", + "pick": 2 + }, + { + "text": "My _ is the _ that will pierce the heavens!! *same white card used for both blanks*", + "pick": 1 + }, + { + "text": "After years of searching, the crew of the Thousand Sunny finally found out that the One Piece is actually _.", + "pick": 1 + }, + { + "text": "The World Line was changed when I sent a D-mail to myself about _.", + "pick": 1 + }, + { + "text": "In this episode of Master Keaton, Keaton builds _ out of _ and _.", + "pick": 3 + }, + { + "text": "So, what have you learned from all of this?", + "pick": 1 + }, + { + "text": "Someday when I have kids, I want to share with them the joys of _.", + "pick": 1 + }, + { + "text": "Who placed first in the most recent Shonen Jump popularity poll?", + "pick": 1 + }, + { + "text": "No matter how many times I see it, _ always brings a tear to my eye.", + "pick": 1 + }, + { + "text": "Coming to Neon Alley: _, completely UNCUT & UNCENSORED.", + "pick": 1 + }, + { + "text": "This year, I'm totally gonna cosplay as _.", + "pick": 1 + }, + { + "text": "My favorite hentai is the one where _ is held down and violated by _.", + "pick": 2 + }, + { + "text": "Cooking is so fun! Cooking is so fun! Now it's time to take a break and see what we have done! _. YAY! IT'S READY!!", + "pick": 1 + }, + { + "text": "The most annoying kind of anime fans are _.", + "pick": 1 + }, + { + "text": "_. So kawaii!! <3 <3", + "pick": 1 + }, + { + "text": "Animation studio _ is perhaps best known for _.", + "pick": 2 + }, + { + "text": "This is our final battle. Mark my words, I will defeat you, _!", + "pick": 1 + }, + { + "text": "After a long, arduous battle, _ finally met their end by _.", + "pick": 2 + }, + { + "text": "The best English dub I've ever heard is the one for _.", + "pick": 1 + }, + { + "text": "You used _. It's super effective!", + "pick": 1 + }, + { + "text": "_. HE DEDD.", + "pick": 1 + }, + { + "text": "I know of opinions and all that, but I just don't understand how anyone could actually enjoy _.", + "pick": 1 + }, + { + "text": "You see, I'm simply _.", + "pick": 1 + }, + { + "text": "She'll thaw out if you try _.", + "pick": 1 + }, + { + "text": "Yoko Kanno's latest musical score features a song sung entirely by _.", + "pick": 1 + }, + { + "text": "This year, I totally lucked out and found _ in the dealer's room.", + "pick": 1 + }, + { + "text": "If I was a magical girl, my cute mascot sidekick would be _.", + "pick": 1 + }, + { + "text": "How did I avoid your attack? Simple. By _.", + "pick": 1 + }, + { + "text": "In the future of 199X, the barrier between our world and the demon world is broken, and thousands of monsters invade our realm to feed upon _.", + "pick": 1 + }, + { + "text": "From the creators of Tiger & Bunny: _ & _!!", + "pick": 2 + }, + { + "text": "In truth, the EVA units are actually powered by the souls of _.", + "pick": 1 + }, + { + "text": "Dreaming! Don't give it up _! Dreaming! Don't give it up _! Dreaming! Don't give it up _!", + "pick": 3 + }, + { + "text": "Yo-Ho-Ho! He took a bite of _.", + "pick": 1 + }, + { + "text": "The new Gurren Lagann blurays from Aniplex will literally cost you _.", + "pick": 1 + }, + { + "text": "The most overused anime cliche is _.", + "pick": 1 + }, + { + "text": "The inspiration behind the latest hit show is _.", + "pick": 1 + }, + { + "text": "While writing Dragon Ball, Akira Toriyama would occasionally take a break from working to enjoy _.", + "pick": 1 + }, + { + "text": "The show was great, until _ showed up.", + "pick": 1 + }, + { + "text": "Nothing ruins a good anime faster than _.", + "pick": 1 + }, + { + "text": "I want to be the very best, like no one ever was! _ is my real test, _ is my cause!", + "pick": 2 + }, + { + "text": "Who are you callin' _ so short he can't see over his own _?!?!", + "pick": 2 + }, + { + "text": "If you ask me, there need to be more shows about _.", + "pick": 1 + }, + { + "text": "_. That is the kind of man I was.", + "pick": 1 + }, + { + "text": "I'm sorry! I'm sorry! I didn't mean to accidentally walk in on you while you were _!", + "pick": 1 + }, + { + "text": "In the latest episode of Case Closed, Conan deduces that it was _ who killed _ because of _.", + "pick": 3 + }, + { + "text": "Welcome home, Master! Is there anything your servant girl can bring you today?", + "pick": 1 + }, + { + "text": "In the latest chapter of Golgo 13, he kills his target with _.", + "pick": 1 + }, + { + "text": "Karaoke night! I'm totally gonna sing my favorite song, _.", + "pick": 1 + }, + { + "text": "Take this! My love, my anger, and all of my _!", + "pick": 1 + }, + { + "text": "_. Only on Toonami", + "pick": 1 + }, + { + "text": "From the twisted mind of Nabeshin: An anime about _, _, and _.", + "pick": 3 + }, + { + "text": "Behold the name of my Zanpakuto, _!", + "pick": 1 + }, + { + "text": "Now! Face my ultimate attack!", + "pick": 1 + }, + { + "text": "Sasuke has _ implants.", + "pick": 1 + }, + { + "text": "To save the world, you must collect all 7 _.", + "pick": 1 + }, + { + "text": "The new manga from _ is about a highschool girl discovering _.", + "pick": 2 + }, + { + "text": "I am in despair! _ has left me in despair!", + "pick": 1 + }, + { + "text": "Mamoru Oshii's latest film is a slow-paced, two hour-long cerebral piece about the horrors of _.", + "pick": 1 + }, + { + "text": "What do I love most about anime?", + "pick": 1 + }, + { + "text": "The rarest Pokémon in my collection is _.", + "pick": 1 + }, + { + "text": "What do I hate most about anime?", + "pick": 1 + }, + { + "text": "Watch it! Or I'll take your _.", + "pick": 1 + }, + { + "text": "This morning, hundreds of Japanese otaku lined up outside their favorite store to buy the limited collector's edition of _.", + "pick": 1 + }, + { + "text": "Every now and then, I like to participate in the time-honored Japanese tradition of _.", + "pick": 1 + }, + { + "text": "Chicks. Dig. _. Nice.", + "pick": 1 + }, + { + "text": "New from Studio GAINAX: _ the Animation.", + "pick": 1 + }, + { + "text": "Using my power of Geass, I command you to do... THIS!", + "pick": 1 + }, + { + "text": "Don't worry, he's okay! He survived thanks to _.", + "pick": 1 + }, + { + "text": "The next big Tokusatsu show: \"Super Sentai _ Ranger!\"", + "pick": 1 + }, + { + "text": "In the name of the moon, I will punish _!", + "pick": 1 + }, + { + "text": "Ladies and gentlemen, I give you _... COVERED IN BEES!!!", + "pick": 1 + }, + { + "text": "The Chocolate Underground stopped the Good For You Party by capturing their _ and exposing their leader as _.", + "pick": 2 + }, + { + "text": "Who cares about the printing press, did that medieval peasant girl just invent _?!", + "pick": 1 + }, + { + "text": "The court finds the defendant, _, guilty of _, and sentences them to a lifetime of _.", + "pick": 3 + }, + { + "text": "What does Alucard have nightmares about?", + "pick": 1 + }, + { + "text": "I've always wanted to become a voice actor, so I could play the role of _.", + "pick": 1 + }, + { + "text": "It's no secret. Deep down, everybody wants to fuck _.", + "pick": 1 + }, + { + "text": "Behold! My trap card, _!", + "pick": 1 + }, + { + "text": "As part of a recent promotion, Japanese KFCs are now dressing their Colonel Sanders statues up as _.", + "pick": 1 + }, + { + "text": "Fighting _ by moonlight! Winning _ by daylight! Never running from a real fight! She is the one named _!", + "pick": 3 + }, + { + "text": "Don't stand behind him, if you value your _.", + "pick": 1 + }, + { + "text": "What the hell is \"Juvijuvibro\"?!", + "pick": 1 + }, + { + "text": "If the anime industry is dying, what will be the final nail in it's coffin?", + "pick": 1 + }, + { + "text": "It has been said... That there are entire forests of _, made from the sweetest _.", + "pick": 2 + }, + { + "text": "IT'S _ TIME!", + "pick": 1 + }, + { + "text": "My love for you is like _. BERSERKER!", + "pick": 1 + }, + { + "text": "They are the prey, and we are the _.", + "pick": 1 + }, + { + "text": "No matter how I look at it, it's your fault I'm not _!", + "pick": 1 + }, + { + "text": "My Little Sister Can't Be _!", + "pick": 1 + }, + { + "text": "By far, the most mind-bogglingly awesome thing I've ever seen in anime is _.", + "pick": 1 + }, + { + "text": "After eating a Devil Fruit, I now have the power of _.", + "pick": 1 + }, + { + "text": "That's not a squid! It's _!", + "pick": 1 + }, + { + "text": "The moé debate was surprisingly civil until someone mentioned _.", + "pick": 1 + }, + { + "text": "Anime has taught me that classic literature can always be improved by adding _.", + "pick": 1 + }, + { + "text": "And from Kyoto Animation, a show about cute girls doing _.", + "pick": 1 + }, + { + "text": "\"_.\" \"What the hell, man?!\" \"_.\" \"Oh, okay.\"", + "pick": 2 + }, + { + "text": "Madoka Kyouno's nickname for Muginami's older brother is _.", + "pick": 1 + }, + { + "text": "What do otaku smell like?", + "pick": 1 + }, + { + "text": "Attention, duelists: My hair is _.", + "pick": 1 + }, { "text": "_ loves to eat pant.", "pick": 1 + }, + { + "text": "What will finally make hydrabolt release Discord.js v12?", + "pick": 1 } ], "whiteCards": [ @@ -2765,7 +3373,7 @@ "Some of that good dick.", "Some real spicy shrimps.", "Starting a shitty podcast.", - "Straight blazin' 24\/7.", + "Straight blazin' 24/7.", "Sucking each other's penises for hours on end.", "Sudden and unwanted slam poetry.", "Swearing praise upon the Sultan's hideous daughters.", @@ -2812,7 +3420,7 @@ "Watching you die.", "Water.", "When the big truck goes \"Toot! Toot!\"", - "Who really did 9\/11.", + "Who really did 9/11.", "Whomsoever let the dogs out.", "Whooping your ass at Mario Kart.", "Working so hard to have muscles and then having them.", @@ -3216,7 +3824,533 @@ "A Molson muscle.", "The Royal Canadian Mounted Police.", "An icy handjob from an Edmonton hooker.", - "eat pant.", + "A fuck-mothering vampire.", + "Tentacle rape.", + "FUNimation.", + "Narutards.", + "Dead catgirls.", + "Tengen Toppa Gurren Lagann.", + "Mewtwo.", + "Cowboy Bebop.", + "Fullmetal Alchemist.", + "Futanari.", + "Vash the Stampede.", + "Sarah Fuckin' Palin.", + "Naruto.", + "Idiots who don't seem to realize that Avatar: The Last Airbender isn't really an anime.", + "A fat middle-aged man in a Sailor Moon costume.", + "Yu-Gi-Oh! The Abridged Series.", + "OVER 9000!!", + "Aya Hirano being gang-banged by her entire band.", + "\". . . .\"", + "A mindfuck.", + "A Hello Kitty! vibrator.", + "The Death Note.", + "Being eaten by a titan.", + "The Hare Hare Yukai dance.", + "Getting your penis cut in half.", + "Cousin marriage.", + "4Kids.", + "Watching FLCL while tripping on acid.", + "Sticking your finger up her ass.", + "Standing outside the gates of the White House completely naked with a revolver in your hand.", + "Catholic priests who drink, smoke, and carry guns.", + "Your virgin soul.", + "Engrish.", + "A dead meme.", + "Twincest.", + "Drills for hands.", + "Asian cock.", + "Holy dildos.", + "Lolis.", + "Hot-blooded shonen protagonists.", + "Crispin Freeman.", + "Steve Motherfuckin' Blum.", + "Norio Wakamoto.", + "Eating an entire box of Pocky in a single bite.", + "Gen Fukunaga counting his money.", + "Hatsune Miku.", + "Strangling hardcore otaku nerds with razor wire.", + "A big-breasted 14-year-old wearing a bikini and sucking on a popsicle.", + "Henry Goto being savagely raped by a bear.", + "Yet another goddamn Goku vs. Superman argument.", + "FANSERVICE!!!", + "Weeaboos.", + "Aniplex of America.", + "Kyubey.", + "Wearing panties on the head.", + "Sir Integra Fairbrook Wingates Hellsing.", + "CARD GAMES ON MOTORCYCLES.", + "Brina Palencia as an angsty teenage boy, Monica Rial as his bratty little sister, and Shelley Calene-Black as their hot mom.", + "Boku no Pico.", + "Nice boat.", + "A Bleach hentai doujin where Rukia rapes Ichigo.", + "The One Piece rap.", + "CENTURY SOOOOUUUP!!!!!", + "Stupid fucking Kululu.", + "Thinking Misty from Pokémon is... kinda sexy.", + "The Gripper.", + "Reverse harems.", + "Garzey's Wing.", + "Kenshiro.", + "Puella Magi Madoka Magica.", + "Taking a potato chip... and EATING IT.", + "Unreasonably long transformation sequences.", + "Ass dance!! Ass dance!!", + "Rice balls.", + "Monkey D. Luffy.", + "JesuOtaku's ginormous lips.", + "Princess Tutu.", + "Fujiko's boobs.", + "Shinji being a whiny little bitch.", + "Third Impact.", + "Edward Wong Hau Pepelu Tivruski IV.", + "That sound effect in every hentai when the guy ejaculates.", + "Red-headed tsunderes.", + "Wendee Lee.", + "Prof's legs.", + "\"Bang.\"", + "Dio Brando.", + "\"WE UNDERSTAND ANIME FAN WANTS!!\"", + "DAN GREEN.", + "Man-Faye.", + "Soldier A.", + "Goku.", + "Vic Micderpaderp.", + "Hokuto! Hyakurestu-ken!", + "Christopher R. Sabat.", + "PASTAAAAA!!!!", + "MAWNING LESCUE!!!", + "$300 anime bluray boxsets.", + "Johnny Yong Bosch.", + "Little Kuriboh.", + "Bukkake.", + "A Captain Harlock body pillow.", + "Onii-chan.", + "Bulma's panties.", + "Mami getting her head bitten off.", + "Satoshi Kon.", + "A Japanese schoolgirl covered head-to-toe in semen.", + "Good ol' fashioned Japanese sexism.", + "Kigurumi.", + "Ikki Tousen.", + "Apocalypse Zero.", + "The inevitable beach episode.", + "The thousands upon thousands of women that Golgo 13 has slept with.", + "Kira worshippers.", + "Old man dragon dick.", + "Uguu...", + "Whatever the hell the Utena movie was about.", + "Creamy Mami.", + "The ungodly abomination that is Robotech.", + "Carl Macek.", + "Panty & Stocking with Garterbelt.", + "Punching a man so hard his clothes fly off.", + "Banana sushi.", + "Oro?", + "A puppy being beaten to death with a flower pot.", + "Gantz. Just.... Gantz.", + "Snapping the nipple off of a prostitute's breast and eating it.", + "Black cosplayers.", + "Fake Nendoroids.", + "Eating ramen noodles for a month because you HAD to have that out of print Macross boxset.", + "Showing Serial Experiments Lain to toddlers.", + "Mamoru Miyano.", + "TETSUOOOOOO!!!", + "Wibble.", + "That stupid opening song from Steel Angel Kurumi getting stuck in your head.", + "Bad K-on! fanart.", + "Nerdy kids in Speedos.", + "My Cresta.", + "Groping strangers on a train.", + "Fuckin' Bronies.", + "An overly defensive fanbase.", + "Recap episodes.", + "Muscle-bound shirtless MEN.", + "Cartoon buttholes.", + "Sucking Kyle Hebert's dick for hamburgers.", + "Gratuitous panty shots.", + "Sailor Moon and the 7 Ballz.", + "El Psy Congroo.", + "Moe Moe Kyun!", + "Home For Infinite Losers (HFIL).", + "THE MAN-POLE OF DOOOOOOOOOM!!!", + "Ladd Russo.", + "Studio Ghibli.", + "Masa! <3", + "Giant robots.", + "Osamu Tezuka.", + "A \"read the manga\" style ending.", + "Tits on your hand.", + "That little fat kid from Accel World.", + "Casca's hairy unshaven vag.", + "Girls with glasses.", + "Eating the wrong end of a chocolate cornet.", + "Walpurgisnacht.", + "Bludgeoning Haruhi Suzumiya to death with a tire iron.", + "Banging 1,000 dudes.", + "Pure grade-A opium.", + "Maid cafes.", + "A drunken Japanese businessman.", + "Dr. Who fans showing up at anime cons despite not being invited.", + "Guro.", + "Nekomimi.", + "Plot armor.", + "School swimsuits.", + "The inkvasion.", + "Acidic breast milk.", + "Underpants. Underpants. Underpants. Underpants. Under-", + "Shooting out nearly an entire liter of cum.", + "Host clubs.", + "Shining Finger!", + "Crystal Boy's creepy smile.", + "Gackt.", + "Used panty vending machines.", + "Butt sniffing.", + "Kamen Rider.", + "The Major's hips and bust.", + "Captain Bravo.", + "Kirino's ass.", + "Fishcake.", + "Go Nagai sideburns.", + "CHIIIIIIIIN.", + "Getting the bad ending of a visual novel.", + "Vegeta's sweet goatee.", + "Sub-only releases.", + "Getting drunk on sake.", + "Flying Vortex of Fear.", + "Tachikoma-kun.", + "Naoki Urasawa.", + "Stupid sexy Johan.", + "Bear punching, tiger chopping, shark suplexing, & helicopter bodyslamming.", + "Juvijuvibro.", + "Jacuzzi Splot.", + "The Tsukihime anime.", + "Giant naked Rei.", + "The entire cast of School Days.", + "An arm and a leg.", + "The power of friendship.", + "Being the uke.", + "You dumbass!", + "Super Milk-chan.", + "Dancin' on the Planet Dance.", + "Calling out the name of your attack.", + "Multi-episode fights.", + "Making someone's head explode.", + "Michelle Ru- er, I mean... \"Sophie Roberts.\"", + "Masturbating over Asuka's comatose body.", + "Let's Fighting Love!", + "Fat, sweaty otaku.", + "Ganguro girls.", + "Girls with guns.", + "The goddamn Maho.", + "Darrel Guilbeau trying to act.", + "Highschool of the Dead.", + "M.D. Geist.", + "Blue Water Studios.", + "Hot female bass players.", + "Magical girls.", + "Black?Star", + "Goku, Luffy, Toriko, and Lina Inverse in an eating contest.", + "You cactus bastard!", + "KING!!! KING!!! KING GAINER!!! *does the Monkey*", + "Sexy schoolteacher types.", + "Underwater Ray Romano.", + "Lesbian subtext.", + "Tig ol' bitties.", + "Inspector Zenigata.", + "The GARtender.", + "Pure fighting spirit.", + "Mad Bull 34.", + "Freddie riding to school on a wild black stallion.", + "Whatever turns you on, big guy.", + "I'LL ANSWER THE PHONE FROM NOW ON, LILY!!", + "KITTEH. :3", + "Manly tears of manliness.", + "Zetman.", + "Giant mutant cockroaches.", + "Bible Black.", + "Rape fantasies.", + "Keith David's voice.", + "Scott McNeil.", + "The eternal pervert, Eric Vale.", + "The Irresponsible Captain Tylor.", + "Birdy the Mighty.", + "Prying SpacemanHardy's Master Keaton boxset from his cold, dead hands.", + "R-R-R-R-R-REDLINE!!!", + "Hunting down every single copy of Ninja Resurrection and setting them on fire.", + "Satan worshipping Christians.", + "Being beaten to a bloody pulp by a middle school student.", + "One HELL of a butler.", + "Losing 20 gallons of blood... and surviving.", + "Badass 15-year-olds.", + "The Shikon Jewel.", + "A large paper fan.", + "Having blackmail sex with your teacher.", + "Anime News Network.", + "A Claymore swimsuit issue.", + "Revy Two-Hands.", + "Sneaking a peek at the girls' open bath.", + "\"INUYASHA!!\" \"KAGOME!!\"", + "Gangnam Style.", + "MUNGLE!! *shakes fist*", + "A samurai terminator.", + "Physics.", + "The Animatrix.", + "A talking motorcycle.", + "Rie Kugimiya.", + "Production I.G.", + "GONZO.", + "Franky's awesome Speedo dance.", + "Soul traveling.", + "Sticking a chopstick in your pee-hole.", + "A copy of Trigun signed by Micah Solusod.", + "Really shitty CGI effects.", + "The hot buttered sex voice of Patrick Seitz.", + "Heavily-tattooed yakuza henchmen.", + "The life-sized Gundam statue.", + "Forcing someone to watch every episode of Dragon Ball GT.", + "ZA WARUDO.", + "Governor Ishihara.", + "Shrine maidens.", + "Taking a shit in the shrine's donation box.", + "Farting... tadpoles?", + "Madhouse.", + "ARMS.", + "Mr. Tadakichi.", + "Showing episodes of Toriko to starving children.", + "Gilgamesh.", + "That one guy who always dresses up as the Red Ranger.", + "Broken-ass Aizen.", + "Romi Paku.", + "The Garden of Sinners.", + "1,000 years of pain.", + "Hetalia porn.", + "A Maka Chop.", + "Rally Vincent firing a gun in her underwear.", + "Raccoon testicles.", + "Johannes Krauser II.", + "Rule 63'd Death the Kid.", + "Beautiful bishonen boys.", + "Awesome Prussia.", + "Eating a banana all sexy-like.", + "A shitload of yen.", + "Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora! Ora!", + "Getting in a fistfight with an earthquake.", + "WcDonald's.", + "Black★Star", + "Octopus balls.", + "My badass numchucks.", + "Trying to get your die-cast Gundam model through airport security.", + "Strapping hand grenades to your pubes.", + "Toilet worship.", + "A brand new, mint condition copy of JoJo's Bizarre Adventure vol. 4, still in shrinkwrap.", + "Grave of the Fireflies.", + "Farting on your cat.", + "Combat afros.", + "Ice cold water (and it's only a dollar).", + "Me wearing a penguin suit.", + "My Johnny!", + "Petite Princess Yucie.", + "Kotetsu T. Kaburagi, aka. \"The D.I.L.F.\"", + "An argument lasting over an hour about what moé really is.", + "Saber Starblast.", + "A succubus living inside your testes.", + "Raping Tokyo Tower.", + "Fucking a nun.", + "#DesuDes4Life.", + "Ghosts that come out of your asscrack.", + "Being accidentally turned into a girl by aliens.", + "Giving a girl an orgasm using only your shoulderpads.", + "Swallowing an entire carton of cigarettes before barfing them back up.", + "Puppets made from the skin of children.", + "My hot zombie girlfriend..", + "Eating someone else's drool.", + "The alpha bitch.", + "Undead body-swapping space vampire teens.", + "Suplexing your teacher.", + "A leopard print fundoshi.", + "Breast envy.", + "The entire last episode of Blood-C.", + "Origami sex toys.", + "ALL OF THE HOMO!", + "Japanese rope bondage.", + "Japanese Spider-Man.", + "A bass guitar straight to the face.", + "Sonny Strait's manly parts.", + "Guts.", + "Finger nigiri.", + "Dragon Balls.", + "Putting all the condiments on your steak. ALL OF THEM.", + "Rapping samurai.", + "A dolphin in a mech suit.", + "Naga's extremely annoying laugh.", + "Strikeman and his \"Balls of Justice\".", + "Flying Nimbus.", + "Running during the credits.", + "A busty, blonde, blue-eyed, dumb-as-rocks American.", + "Naughty geishas.", + "Griffith's mysterious disappearing penis.", + "Pubic hair needle attack.", + "Ninjas!", + "Getting your fingernails ripped out.", + "Sexy jutsu.", + "Love Machine.", + "Shinichiro Watanabe single-handedly curing cancer, ending world hunger, and bringing peace to the Middle East.", + "Henry Goto ending up homeless on the streets and sucking dick for coke.", + "Two gallons of elephant shit being dropped on Henry Goto's desk.", + "Henry Goto fapping to a photograph of himself.", + "Henry Goto being eaten by a group of backwoods hillbilly cannibals.", + "Henry Goto bleeding profusely from his groin after having his penis bitten off by a 15-year-old Vietnamese prostitute.", + "A 30-year-old man who's obsessed with K-on!", + "Henry Goto falling down the stairs, receiving a massive head injury, and believing he's really Sailor Moon.", + "A Henry Goto joke that no one will laugh at other than SpacemanHardy.", + "Cutting off a finger to restore your honor.", + "Robots with tits.", + "Red bean paste.", + "Gender-swapped Oda Nobunaga.", + "Henry Goto's massive peyote & wine cooler addiction.", + "Henry Goto, John Sirabella, and Stu Levy in a three man fight to the death.", + "Henry Goto.", + "Henry Goto having an accident in his pants during the live Aniplex of America panel.", + "Getting kicked in the nuts by a mermaid.", + "I AM AWESOME!!", + "LAZAR!", + "Urd, Kana, and Misato in a drinking contest.", + "Cute stuff.", + "An artbox that feels like human skin.", + "Polygamy jokes in a kid's show.", + "Dangling Pokéballs.", + "Having a giant drill for a dick.", + "A robot having an orgasm.", + "Villagulio.", + "Angry naked people.", + "Going Super Saiyan while taking a massive dump.", + "My daikon.", + "Literally spanking a monkey.", + "Going Super Saiyan.", + "Going Super Saiyan during orgasm.", + "Having sex with a dragon.", + "Manga Jesus.", + "Manly pink sparkles.", + "7 ft. tall red-headed Alexander the Great.", + "Training a dinosaur to ride a ball.", + "Samba-dancing dinosaurs.", + "An armored truck full of shit.", + "A Togepi omelet.", + "A laser horse.", + "Girls with guns AND glasses.", + "Teenaged miniskirt-wearing space pirates.", + "Gas station sushi.", + "Jerry Jewell's serial killer face.", + "A FUCKING DRAGONITE, MOTHERFUCKER!!", + "A school bus orgy.", + "Super Aryan Hitler.", + "4,000 tacos, and one Diet Coke.", + "An actual, honest-to-God black guy.", + "Black and white samurai movies.", + "Sick with the cancer.", + "Transvestite police officers.", + "A promotional crossover with Pizza Hut.", + "Star-shaped nipples.", + "Con funk.", + "Pissing yourself.", + "Hot anime moms.", + "Vocaloid death metal.", + "GAO! GAI! GAR!!!", + "Yaoi paddles.", + "The Dark Lord Shawne Kleckner.", + "Mr. Satan.", + "Bad Steven Foster dubs.", + "A potato committing seppuku.", + "A giant robot German suplex.", + "Hentai voice acting.", + "A vampire ninja.", + "Hello Kitty! pregnancy doujins.", + "Waving it around all willy-nilly.", + "A Godzilla attack.", + "Eating KFC on Christmas day.", + "Astro Boy.", + "Jacking off into a bottle of formaldehyde and calling it our firstborn.", + "Traps.", + "Korean Jesus.", + "An unending, unquenchable thirst for orange Fanta.", + "Breaking the fourth wall to kill the mangaka.", + "Valvrape the Dominator.", + "Piles of dead children.", + "An 8 million yen debt to a club of rich pretty boys.", + "Mamoru Oshii's dog love.", + "A diet consisting almost entirely of potatoes.", + "Rock-hard, glistening abs.", + "Totoro.", + "Wild Tiger's Hundred Power.", + "Fucking postcards as a cheap-ass pack-in gift.", + "Hideaki Anno's poor, tortured therapist.", + "Naming yourself after the method of your suicide.", + "The Chupacabra.", + "Blowing a child's head off with a rocket launcher.", + "Erotic incestuous toothbrushing.", + "Pokémon tears.", + "Pokésexuality.", + "Chopstick-based martial arts.", + "All the gayness in GetBackers.", + "Gen \"The Uro-Butcher\".", + "Mikuru Beam!", + "Tons and tons of close-up underaged schoolgirl ass-shots.", + "Starfish Hitler.", + "An ancient vampire who looks like she's 10.", + "Literally ripping your own heart out.", + "Japanese-style elf ears.", + "Flamboyantly gay William Shakespeare.", + "Tripping, falling, and landing with your face in a girl's breasts.", + "Banging your adopted daughter.", + "A 10-year old with boobs twice the size of her head.", + "A bunny girl having a lightsaber duel with Darth Vader.", + "A third-grader seducing her 23-year-old teacher.", + "SHAFT being SHAFT.", + "One a them bamboo things that goes \"doonk\".", + "Shotas.", + "Moé schoolgirl Hitler.", + "Romance of the Three Kingdoms, but everyone is gender-swapped.", + "A washpan falling onto someone's head from out of nowhere.", + "Franken Fran.", + "Hentai artists who don't change their pen name when they go legit.", + "Getting sucked into a fantasy world.", + "Loli in a box.", + "A Masamune Shirou artbook.", + "Clothing-dissolving slime.", + "Involuntary crossdressing.", + "Completely losing your shit over Endless Eight.", + "Violently beating your friends to death with a baseball bat.", + "Fujoshi.", + "Matrix boobs.", + "A chainsaw-wielding male magical girl zombie.", + "Inoue Kikoku, 17-years old.", + "Chest-hair afros.", + "Cowboy Andy.", + "A cyborg assassin dressed as a magical girl fighting a talking lion and a floating psychic electric jellyfish.", + "J-pop idols.", + "The War on Pants.", + "An ending where everyone dies.", + "Garbage collectors... IN SPACE!!", + "Magical friendship lasers.", + "High-stakes mahjong.", + "Succubus-on-futanari action.", + "Alice in Sexland.", + "A couple that spends over 30 manga volumes trying to get to first base.", + "Dick Saucer.", + "Millionaire Beaver.", + "A sweaty shirtless man holding a large, writhing fish against his chest.", + "A naughty nurse outfit.", + "Morphin'.", + "Zelgadis' flame-proof bikini briefs.", + "A Dragon Slave.", + "A smashed-in face.", + "Epic old bald dudes.", + "Fuckingham Palace.", + "Dying over and over again.", + "The Puchuu.", + "Rainbow Dash.", "", "", "", @@ -3229,7 +4363,9 @@ "", "dragonfire535.", "Xiao.", - "Xiao's public source code.", - "Discord." + "Discord.", + "Discord.js v12.", + "hydrabolt.", + "Nomsy." ] } diff --git a/commands/games/apples-to-apples.js b/commands/games/apples-to-apples.js index 071fa426..b8093f73 100644 --- a/commands/games/apples-to-apples.js +++ b/commands/games/apples-to-apples.js @@ -3,6 +3,7 @@ const { Collection, escapeMarkdown } = require('discord.js'); const { stripIndents } = require('common-tags'); const { shuffle, awaitPlayers } = require('../../util/Util'); const { greenCards, redCards } = require('../../assets/json/apples-to-apples'); +const { SUCCESS_EMOJI_ID, FAILURE_EMOJI_ID } = process.env; module.exports = class ApplesToApplesCommand extends Command { constructor(client) { @@ -20,6 +21,13 @@ module.exports = class ApplesToApplesCommand extends Command { type: 'integer', min: 1, max: 20 + }, + { + key: 'noMidJoin', + label: 'disable mid-game join', + prompt: 'Do you want to disable mid-game joining?', + type: 'boolean', + default: false } ] }); @@ -27,22 +35,34 @@ module.exports = class ApplesToApplesCommand extends Command { this.playing = new Set(); } - async run(msg, { maxPts }) { + async run(msg, { maxPts, noMidJoin }) { // eslint-disable-line complexity if (this.playing.has(msg.channel.id)) return msg.reply('Only one game may be occurring per channel.'); this.playing.add(msg.channel.id); + let joinLeaveCollector = null; + let pointViewCollector = null; try { await msg.say('You will need at least 2 more players, at maximum 10. To join, type `join game`.'); - const awaitedPlayers = await awaitPlayers(msg, 10, 3, { dmCheck: true }); + const awaitedPlayers = await awaitPlayers(msg, 10, 3); if (!awaitedPlayers) { this.playing.delete(msg.channel.id); return msg.say('Game could not be started...'); } - const players = await this.generatePlayers(awaitedPlayers); - const czars = Array.from(players.values()); + const players = new Collection(); + for (const user of awaitedPlayers) this.generatePlayer(user, players); + const czars = players.map(player => player.id); let winner = null; + if (!noMidJoin) joinLeaveCollector = this.createJoinLeaveCollector(msg.channel, players, czars); + pointViewCollector = this.createPointViewCollector(msg.channel, players); while (!winner) { - const czar = czars[0]; - czars.push(czar); + for (const player of players) { + if (player.strikes >= 3) this.kickPlayer(player, players, czars); + } + if (players.size < 3) { + await msg.say('Oh... It looks like everyone left...'); + break; + } + const czar = players.get(czars[0]); + czars.push(czar.id); czars.shift(); const green = greenCards[Math.floor(Math.random() * greenCards.length)]; await msg.say(stripIndents` @@ -52,56 +72,10 @@ module.exports = class ApplesToApplesCommand extends Command { Sending DMs... `); const chosenCards = []; - const turns = players.map(async player => { - if (player.hand.size < 11) { - const valid = redCards.filter(card => !player.hand.has(card)); - player.hand.add(valid[Math.floor(Math.random() * valid.length)]); - } - if (player.user.id === czar.user.id) return; - if (!player.hand.size) { - await player.user.send('You don\'t have enough cards!'); - return; - } - const hand = Array.from(player.hand); - await player.user.send(stripIndents` - __**Your hand is**__: - ${hand.map((card, i) => `**${i + 1}.** ${card}`).join('\n')} - - **Green Card**: ${escapeMarkdown(green)} - **Card Czar**: ${czar.user.username} - Pick **1** card! - `); - let chosen = null; - const filter = res => { - const existing = hand[Number.parseInt(res.content, 10) - 1]; - if (!existing) return false; - chosen = existing; - return true; - }; - const choice = await player.user.dmChannel.awaitMessages(filter, { - max: 1, - time: 120000 - }); - if (!choice.size) { - await player.user.send('Skipping your turn...'); - return; - } - if (chosen === '') { - const handled = await this.handleBlank(player); - chosen = handled; - } else { - player.hand.delete(chosen); - } - chosenCards.push({ - id: player.id, - card: chosen - }); - await player.user.send(`Nice! Return to ${msg.channel} to await the results!`); - }); - await Promise.all(turns); + await Promise.all(players.map(player => this.playerTurn(player, czar, green, msg.channel, chosenCards))); if (!chosenCards.length) { await msg.say('Hmm... No one even tried.'); - break; + continue; } const cards = shuffle(chosenCards); await msg.say(stripIndents` @@ -112,6 +86,7 @@ module.exports = class ApplesToApplesCommand extends Command { `); const filter = res => { if (res.author.id !== czar.user.id) return false; + if (!/^[0-9]+$/g.test(res.content)) return false; if (!cards[Number.parseInt(res.content, 10) - 1]) return false; return true; }; @@ -120,50 +95,169 @@ module.exports = class ApplesToApplesCommand extends Command { time: 120000 }); if (!chosen.size) { - await msg.say('Hmm... No one wins.'); + await msg.say('Hmm... No one wins. Dealing back cards...'); + for (const pick of cards) players.get(pick.id).hand.add(pick.card); + players.get(czar.id).strikes++; continue; } const player = players.get(cards[Number.parseInt(chosen.first().content, 10) - 1].id); + if (!player) { + await msg.say('Oh no, I think that player left! No points will be awarded...'); + continue; + } ++player.points; - if (player.points >= maxPts) winner = player.user; - else await msg.say(`Nice one, ${player.user}! You now have **${player.points}** points!`); + if (player.points >= maxPts) { + winner = player.user; + } else { + const addS = player.points > 1 ? 's' : ''; + await msg.say(`Nice, ${player.user}! You now have **${player.points}** point${addS}!`); + } } + if (joinLeaveCollector) joinLeaveCollector.stop(); + pointViewCollector.stop(); this.playing.delete(msg.channel.id); if (!winner) return msg.say('See you next time!'); return msg.say(`And the winner is... ${winner}! Great job!`); } catch (err) { this.playing.delete(msg.channel.id); + if (joinLeaveCollector) joinLeaveCollector.stop(); + if (pointViewCollector) pointViewCollector.stop(); return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } - async generatePlayers(list) { - const players = new Collection(); - for (const user of list) { - const cards = new Set(); - for (let i = 0; i < 5; i++) { - const valid = redCards.filter(card => !cards.has(card)); - cards.add(valid[Math.floor(Math.random() * valid.length)]); - } - players.set(user.id, { - id: user.id, - user, - points: 0, - hand: cards - }); - await user.send('Hi! Waiting for your turn to start...'); + generatePlayer(user, players) { + const cards = new Set(); + for (let i = 0; i < 10; i++) { + const valid = redCards.filter(card => !cards.has(card)); + cards.add(valid[Math.floor(Math.random() * valid.length)]); } + players.set(user.id, { + id: user.id, + user, + points: 0, + hand: cards, + strikes: 0 + }); return players; } + async playerTurn(player, czar, green, channel, chosenCards) { + if (player.user.id === czar.user.id) return; + if (player.hand.size < 10) { + const valid = redCards.filter(card => !player.hand.has(card)); + player.hand.add(valid[Math.floor(Math.random() * valid.length)]); + } + try { + if (player.hand.size < 1) { + await player.user.send('You don\'t have enough cards!'); + return; + } + const hand = Array.from(player.hand); + await player.user.send(stripIndents` + __**Your hand is**__: _(Type \`swap\` to exchange a point for a new hand.)_ + ${hand.map((card, i) => `**${i + 1}.** ${card}`).join('\n')} + + **Green Card**: ${escapeMarkdown(green)} + **Card Czar**: ${czar.user.username} + Pick **1** card! + `); + let chosen = null; + const filter = res => { + if (res.content.toLowerCase() === 'swap' && player.points > 0) return true; + const existing = hand[Number.parseInt(res.content, 10) - 1]; + if (!existing) return false; + chosen = existing; + return true; + }; + const choices = await player.user.dmChannel.awaitMessages(filter, { + max: 1, + time: 60000 + }); + if (choices.first().content.toLowerCase() === 'swap') { + player.points--; + await player.user.send('Swapping cards...'); + for (const card of player.hand) player.hand.delete(card); + for (let i = 0; i < 10; i++) { + const valid = redCards.filter(card => !player.hand.has(card)); + player.hand.add(valid[Math.floor(Math.random() * valid.length)]); + } + return this.playerTurn(player, czar, green, channel, chosenCards); // eslint-disable-line consistent-return + } + if (choices.size < 1) { + chosen = hand[Math.floor(Math.random() * hand.length)]; + player.strikes++; + } + if (chosen === '') { + if (choices.size < 1) { + const handled = await this.handleBlank(player); + chosen = handled; + } else { + chosen = 'A randomly chosen blank card.'; + } + } + player.hand.delete(chosen); + chosenCards.push({ + id: player.id, + card: chosen + }); + await player.user.send(`Nice! Return to ${channel} to await the results!`); + } catch (err) { + return; // eslint-disable-line no-useless-return + } + } + async handleBlank(player) { await player.user.send('What do you want the blank card to say? Must be 100 or less characters.'); const blank = await player.user.dmChannel.awaitMessages(res => res.content.length <= 100, { max: 1, - time: 120000 + time: 60000 }); player.hand.delete(''); if (!blank.size) return `A blank card ${player.user.tag} forgot to fill out.`; return blank.first().content; } + + createJoinLeaveCollector(channel, players, czars) { + const filter = res => { + if (res.author.bot) return false; + if (players.has(res.author.id) && res.content.toLowerCase() !== 'leave game') return false; + if (czars[0] === res.author.id || players.size >= 10) { + res.react(FAILURE_EMOJI_ID || '❌').catch(() => null); + return false; + } + if (!['join game', 'leave game'].includes(res.content.toLowerCase())) return false; + res.react(SUCCESS_EMOJI_ID || '✅').catch(() => null); + return true; + }; + const collector = channel.createMessageCollector(filter); + collector.on('collect', msg => { + if (msg.content.toLowerCase() === 'join game') { + this.generatePlayer(msg.author, players); + czars.push(msg.author.id); + } else if (msg.content.toLowerCase() === 'leave game') { + this.kickPlayer(msg.author, players, czars); + } + }); + return collector; + } + + createPointViewCollector(channel, players) { + const collector = channel.createMessageCollector(res => { + if (res.author.bot) return false; + if (!players.has(res.author.id)) return false; + if (res.content.toLowerCase() !== 'view points') return false; + return true; + }); + collector.on('collect', msg => { + const player = players.get(msg.author.id); + msg.reply(`You have **${player.points}** point${player.points > 1 ? 's' : ''}.`).catch(() => null); + }); + return collector; + } + + kickPlayer(player, players, czars) { + players.delete(player.id); + czars.splice(czars.indexOf(player.id), 1); + } }; diff --git a/commands/games/cards-against-humanity.js b/commands/games/cards-against-humanity.js index 2dd488b5..25ba339a 100644 --- a/commands/games/cards-against-humanity.js +++ b/commands/games/cards-against-humanity.js @@ -3,6 +3,7 @@ const { Collection, escapeMarkdown } = require('discord.js'); const { stripIndents } = require('common-tags'); const { shuffle, awaitPlayers } = require('../../util/Util'); const { blackCards, whiteCards } = require('../../assets/json/cards-against-humanity'); +const { SUCCESS_EMOJI_ID, FAILURE_EMOJI_ID } = process.env; module.exports = class CardsAgainstHumanityCommand extends Command { constructor(client) { @@ -21,6 +22,13 @@ module.exports = class CardsAgainstHumanityCommand extends Command { type: 'integer', min: 1, max: 20 + }, + { + key: 'noMidJoin', + label: 'disable mid-game join', + prompt: 'Do you want to disable mid-game joining?', + type: 'boolean', + default: false } ] }); @@ -28,22 +36,34 @@ module.exports = class CardsAgainstHumanityCommand extends Command { this.playing = new Set(); } - async run(msg, { maxPts }) { + async run(msg, { maxPts, noMidJoin }) { // eslint-disable-line complexity if (this.playing.has(msg.channel.id)) return msg.reply('Only one game may be occurring per channel.'); this.playing.add(msg.channel.id); + let joinLeaveCollector = null; + let pointViewCollector = null; try { await msg.say('You will need at least 2 more players, at maximum 10. To join, type `join game`.'); - const awaitedPlayers = await awaitPlayers(msg, 10, 3, { dmCheck: true }); + const awaitedPlayers = await awaitPlayers(msg, 10, 3); if (!awaitedPlayers) { this.playing.delete(msg.channel.id); return msg.say('Game could not be started...'); } - const players = this.generatePlayers(awaitedPlayers); - const czars = Array.from(players.values()); + const players = new Collection(); + for (const user of awaitedPlayers) this.generatePlayer(user, players); + const czars = players.map(player => player.id); let winner = null; + if (!noMidJoin) joinLeaveCollector = this.createJoinLeaveCollector(msg.channel, players, czars); + pointViewCollector = this.createPointViewCollector(msg.channel, players); while (!winner) { - const czar = czars[0]; - czars.push(czar); + for (const player of players) { + if (player.strikes >= 3) this.kickPlayer(player, players, czars); + } + if (players.size < 3) { + await msg.say('Oh... It looks like everyone left...'); + break; + } + const czar = players.get(czars[0]); + czars.push(czar.id); czars.shift(); const black = blackCards[Math.floor(Math.random() * blackCards.length)]; await msg.say(stripIndents` @@ -53,56 +73,10 @@ module.exports = class CardsAgainstHumanityCommand extends Command { Sending DMs... `); const chosenCards = []; - const turns = players.map(async player => { - if (player.hand.size < 11) { - const valid = whiteCards.filter(card => !player.hand.has(card)); - player.hand.add(valid[Math.floor(Math.random() * valid.length)]); - } - if (player.user.id === czar.user.id) return; - if (player.hand.size < black.pick) { - await player.user.send('You don\'t have enough cards!'); - return; - } - const hand = Array.from(player.hand); - await player.user.send(stripIndents` - __**Your hand is**__: - ${hand.map((card, i) => `**${i + 1}.** ${card}`).join('\n')} - - **Black Card**: ${escapeMarkdown(black.text)} - **Card Czar**: ${czar.user.username} - Pick **${black.pick}** card${black.pick > 1 ? 's' : ''}! - `); - const chosen = []; - const filter = res => { - const existing = hand[Number.parseInt(res.content, 10) - 1]; - if (!existing) return false; - if (chosen.includes(existing)) return false; - chosen.push(existing); - return true; - }; - const choices = await player.user.dmChannel.awaitMessages(filter, { - max: black.pick, - time: 120000 - }); - if (!choices.size || choices.size < black.pick) { - await player.user.send('Skipping your turn...'); - return; - } - if (chosen.includes('')) { - const handled = await this.handleBlank(player); - chosen[chosen.indexOf('')] = handled; - } - for (const card of chosen) player.hand.delete(card); - chosenCards.push({ - id: player.id, - cards: chosen - }); - await player.user.send(`Nice! Return to ${msg.channel} to await the results!`); - }); - await Promise.all(turns); + await Promise.all(players.map(player => this.playerTurn(player, czar, black, msg.channel, chosenCards))); if (!chosenCards.length) { await msg.say('Hmm... No one even tried.'); - break; + continue; } const cards = shuffle(chosenCards); await msg.say(stripIndents` @@ -113,6 +87,7 @@ module.exports = class CardsAgainstHumanityCommand extends Command { `); const filter = res => { if (res.author.id !== czar.user.id) return false; + if (!/^[0-9]+$/g.test(res.content)) return false; if (!cards[Number.parseInt(res.content, 10) - 1]) return false; return true; }; @@ -121,49 +96,172 @@ module.exports = class CardsAgainstHumanityCommand extends Command { time: 120000 }); if (!chosen.size) { - await msg.say('Hmm... No one wins.'); + await msg.say('Hmm... No one wins. Dealing back cards...'); + for (const pick of cards) { + for (const card of pick.cards) players.get(pick.id).hand.add(card); + } + players.get(czar.id).strikes++; continue; } const player = players.get(cards[Number.parseInt(chosen.first().content, 10) - 1].id); + if (!player) { + await msg.say('Oh no, I think that player left! No points will be awarded...'); + continue; + } ++player.points; - if (player.points >= maxPts) winner = player.user; - else await msg.say(`Nice one, ${player.user}! You now have **${player.points}** points!`); + if (player.points >= maxPts) { + winner = player.user; + } else { + const addS = player.points > 1 ? 's' : ''; + await msg.say(`Nice, ${player.user}! You now have **${player.points}** point${addS}!`); + } } + if (joinLeaveCollector) joinLeaveCollector.stop(); + pointViewCollector.stop(); this.playing.delete(msg.channel.id); if (!winner) return msg.say('See you next time!'); return msg.say(`And the winner is... ${winner}! Great job!`); } catch (err) { this.playing.delete(msg.channel.id); + if (joinLeaveCollector) joinLeaveCollector.stop(); + if (pointViewCollector) pointViewCollector.stop(); return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } - generatePlayers(list) { - const players = new Collection(); - for (const user of list) { - const cards = new Set(); - for (let i = 0; i < 5; i++) { - const valid = whiteCards.filter(card => !cards.has(card)); - cards.add(valid[Math.floor(Math.random() * valid.length)]); - } - players.set(user.id, { - id: user.id, - user, - points: 0, - hand: cards - }); + generatePlayer(user, players) { + const cards = new Set(); + for (let i = 0; i < 10; i++) { + const valid = whiteCards.filter(card => !cards.has(card)); + cards.add(valid[Math.floor(Math.random() * valid.length)]); } + players.set(user.id, { + id: user.id, + user, + points: 0, + hand: cards, + strikes: 0 + }); return players; } + async playerTurn(player, czar, black, channel, chosenCards) { + if (player.user.id === czar.user.id) return; + if (player.hand.size < 10) { + const valid = whiteCards.filter(card => !player.hand.has(card)); + player.hand.add(valid[Math.floor(Math.random() * valid.length)]); + } + try { + if (player.hand.size < black.pick) { + await player.user.send('You don\'t have enough cards!'); + return; + } + const hand = Array.from(player.hand); + await player.user.send(stripIndents` + __**Your hand is**__: _(Type \`swap\` to exchange a point for a new hand.)_ + ${hand.map((card, i) => `**${i + 1}.** ${card}`).join('\n')} + + **Black Card**: ${escapeMarkdown(black.text)} + **Card Czar**: ${czar.user.username} + Pick **${black.pick}** card${black.pick > 1 ? 's' : ''}! + `); + const chosen = []; + const filter = res => { + if (res.content.toLowerCase() === 'swap' && player.points > 0) return true; + const existing = hand[Number.parseInt(res.content, 10) - 1]; + if (!existing) return false; + if (chosen.includes(existing)) return false; + chosen.push(existing); + return true; + }; + const choices = await player.user.dmChannel.awaitMessages(filter, { + max: black.pick, + time: 60000 + }); + if (choices.first().content.toLowerCase() === 'swap') { + player.points--; + await player.user.send('Swapping cards...'); + for (const card of player.hand) player.hand.delete(card); + for (let i = 0; i < 10; i++) { + const valid = whiteCards.filter(card => !player.hand.has(card)); + player.hand.add(valid[Math.floor(Math.random() * valid.length)]); + } + return this.playerTurn(player, czar, black, channel, chosenCards); // eslint-disable-line consistent-return + } + if (choices.size < black.pick) { + for (let i = 0; i < black.pick; i++) chosen.push(hand[Math.floor(Math.random() * hand.length)]); + player.strikes++; + } + if (chosen.includes('')) { + if (choices.size < black.pick) { + const handled = await this.handleBlank(player); + chosen[chosen.indexOf('')] = handled; + } else { + chosen[chosen.indexOf('')] = 'A randomly chosen blank card.'; + } + } + for (const card of chosen) player.hand.delete(card); + chosenCards.push({ + id: player.id, + cards: chosen + }); + await player.user.send(`Nice! Return to ${channel} to await the results!`); + } catch (err) { + return; // eslint-disable-line no-useless-return + } + } + async handleBlank(player) { await player.user.send('What do you want the blank card to say? Must be 100 or less characters.'); const blank = await player.user.dmChannel.awaitMessages(res => res.content.length <= 100, { max: 1, - time: 120000 + time: 60000 }); player.hand.delete(''); if (!blank.size) return `A blank card ${player.user.tag} forgot to fill out.`; return blank.first().content; } + + createJoinLeaveCollector(channel, players, czars) { + const filter = res => { + if (res.author.bot) return false; + if (players.has(res.author.id) && res.content.toLowerCase() !== 'leave game') return false; + if (czars[0] === res.author.id || players.size >= 10) { + res.react(FAILURE_EMOJI_ID || '❌').catch(() => null); + return false; + } + if (!['join game', 'leave game'].includes(res.content.toLowerCase())) return false; + res.react(SUCCESS_EMOJI_ID || '✅').catch(() => null); + return true; + }; + const collector = channel.createMessageCollector(filter); + collector.on('collect', msg => { + if (msg.content.toLowerCase() === 'join game') { + this.generatePlayer(msg.author, players); + czars.push(msg.author.id); + } else if (msg.content.toLowerCase() === 'leave game') { + this.kickPlayer(msg.author, players, czars); + } + }); + return collector; + } + + createPointViewCollector(channel, players) { + const collector = channel.createMessageCollector(res => { + if (res.author.bot) return false; + if (!players.has(res.author.id)) return false; + if (res.content.toLowerCase() !== 'view points') return false; + return true; + }); + collector.on('collect', msg => { + const player = players.get(msg.author.id); + msg.reply(`You have **${player.points}** point${player.points > 1 ? 's' : ''}.`).catch(() => null); + }); + return collector; + } + + kickPlayer(player, players, czars) { + players.delete(player.id); + czars.splice(czars.indexOf(player.id), 1); + } }; diff --git a/package.json b/package.json index 8d367b85..029fe6ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "85.12.4", + "version": "85.12.5", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {