import { CheckData } from "./newmap.ts"; import { bat0, bat1, charm0, charm1, lore0, lore1, party0, party1, stare0, stare1, stealth0, stealth1, } from "./skills.ts"; import { sprThrallBat, sprThrallCharm, sprThrallLore, sprThrallParty, sprThrallStare, sprThrallStealth, } from "./sprites.ts"; import { Sprite } from "./engine/internal/sprite.ts"; import { Stat } from "./datatypes.ts"; export type Thrall = { id: number; }; class ThrallsTable { #thralls: ThrallData[]; constructor() { this.#thralls = []; } add(data: ThrallData) { let id = this.#thralls.length; this.#thralls.push(data); return { id }; } get(thrall: Thrall): ThrallData { return this.#thralls[thrall.id]; } getAll(): Thrall[] { let thralls = []; for (let id = 0; id < this.#thralls.length; id++) { thralls.push({ id }); } return thralls; } } export type ThrallData = { label: string; sprite: Sprite; hitboxSize: number; posterCheck: CheckData; initialCheck: CheckData; itemHint: string; itemPickupMessage: string; deliveryMessage: string; rewardMessage: string; rewardCallback: (spawn: (what: Stat | "EXP") => void) => void; lifeStageText: Record; }; export enum LifeStage { Fresh = "fresh", Average = "average", Poor = "poor", Vampirized = "vampirized", Dead = "dead", } export enum ItemStage { Untouched = "untouched", Obtained = "obtained", Delivered = "delivered", } export type LifeStageText = { prebite: string; postbite: string; }; let table = new ThrallsTable(); export function getThralls() { return table; } // Thralls are labeled by which zone's item they like // Their initial check is, generally, the initial check of the // thrall n-2 or thrall n+1 (ex: Party's initial check is Stealth // or Lore) // // I then made some swaps: // - Garrett gets Flatter instead of Respect Elders // - Monica gets Respect Elders instead of Flatter export let thrallParty = table.add({ label: "Garrett", sprite: sprThrallParty, hitboxSize: 0.7, posterCheck: { label: "This room would be perfect for someone with an ostensibly managed gambling addiction.", options: [], }, initialCheck: { label: "That's Garrett. He plays poker, but he goes to the zoo to cool down after he's lost a lot of chips. His ice cream cone has melted.", options: [ { skill: () => stealth1, // Disguise locked: '"What\'s up?"', failure: "\"If you're not a large pile of money, don't talk to me.\"\n\nHe sobs into his ice cream.", unlockable: "*look like a large pile of money*", success: "He scoops you eagerly into his wallet.", }, { skill: () => charm0, // Flatter locked: "Ask him how much he's winning", failure: 'He rolls his eyes at you. "A billion."', unlockable: "Tell him he's cute", success: "He looks at you like no one has ever told him that before, and blushes. You hold his wing and feel his pulse rise.", }, ], }, itemHint: '"Ah. I lost my wedding ring in a poker game.\n\nNot _my_ wedding ring, I won it from a lady."', itemPickupMessage: "This antique wedding ring looks like it was worth at least fifty big blinds.", deliveryMessage: '"Oh, that? Yeah, I won it." And then lost it, apparently.\n\nHe\'s elated. He will never leave.', rewardMessage: "Garrett showers you with INT!", rewardCallback: (spawn) => { for (let i = 0; i < 30; i++) { spawn("INT"); } }, lifeStageText: { fresh: { prebite: "Garrett flips a poker chip and mutters to himself.", postbite: "You plunge your fangs into his feathered neck and feed.", }, average: { prebite: "Garrett looks a little less fresh than last time. He's resigned to the fate of being bitten.", postbite: "You puncture him in almost the same place as before and take a moderate amount of blood from his veins.", }, poor: { prebite: "Garrett, limp in bed, doesn't look like he's doing so well. He's pale and he's breathing heavily.", postbite: '"Please..." you hear him moan as you force him into the state of ecstasy that brings compliance.', }, vampirized: { prebite: "Garrett looks about as cold and pale as you. Another bite may kill him.", postbite: "The final bite is always the most satisfying. You feel little emotion as you hold the body of a dead crow in your arms.", }, dead: { prebite: "This bird is dead, on account of the fact that you killed him with your teeth.", postbite: "The blood in his veins hasn't coagulated yet. There's still more. Still more...", }, }, }); export let thrallLore = table.add({ label: "Lupin", sprite: sprThrallLore, hitboxSize: 0.65, posterCheck: { label: "This room would be perfect for someone with a love of nature and screaming.", options: [], }, initialCheck: { label: "That's Lupin. He's a Wolf Scout, but hardcore about it. I'm not sure he knows he's a raccoon.", options: [ { skill: () => stare1, // Hypnotize locked: '"I\'m a wolf too."', failure: '"AROO?" He shakes his head no.', unlockable: 'Zonk him. "I\'m a wolf now."', success: "He blinks a few times under your gaze -- then touches your muzzle -- then his own -- then arfs submissively.", }, { skill: () => bat0, // Screech locked: "Try to yowl", failure: "He sniffs at your collar. You don't _seem_ like a Wolf Scout.", unlockable: '"Wolf Scouts AWOO!"', success: "Taken aback at how well you know the cheer, he freezes -- then joins you with a similar howl.", }, ], }, itemHint: "He sniffs his heels like a wolf.\nMaybe shattering his illusion would be good for him. Maybe it would be really bad...", itemPickupMessage: "You can't see yourself in this antique silver mirror. On the other hand, they say silver is effective against wolves.", deliveryMessage: "Lupin looks at his own reflection -- with interest, confusion, dismissal, and then deep satisfaction. He loves it. He will never leave.", rewardMessage: "Lupin showers you with AGI!", rewardCallback: (spawn) => { for (let i = 0; i < 30; i++) { spawn("AGI"); } }, lifeStageText: { fresh: { prebite: "Lupin awoos quietly to himself.", postbite: "You bite the raccoon and drink his blood.", }, average: { prebite: "The color in Lupin's cheeks is beginning to fade. He's becoming accustomed to your bite.", postbite: "He'll let you do anything to him if you make him feel good, so you make him feel good. Fresh blood...", }, poor: { prebite: "Lupin is barely conscious. There's drool at the edges of his mouth and his eyes are glassy.", postbite: "This is no concern to you. You're hungry. You need this.", }, vampirized: { prebite: "Lupin's fangs have erupted partially from his jaw. You've taken enough. More will kill him.", postbite: "His life is less valuable to you than his warm, delicious blood. You need sustenance.", }, dead: { prebite: "This dead raccoon used to be full of blood. Now he's empty. Isn't that a shame?", postbite: "You root around in his neck. His decaying muscle is soft.", }, }, }); export let thrallBat = table.add({ label: "Monica", sprite: sprThrallBat, hitboxSize: 0.5, posterCheck: { label: "This room would be perfect for some kind of television chef.", options: [], }, initialCheck: { label: "That's Monica, the evil judge from MasterCook. A bit older than you, and you don't age anymore. She's enjoying a kiwi flan -- it looks good.", options: [ { skill: () => party1, // Rave locked: "Act crazy to get her attention", failure: '"I -- you -- you know we\'re not being filmed, right?"', unlockable: "Flash your eyes like a TV camera", success: "The clash of light and color causes some kind of flashback.\nYou convince her that she's on set. She agrees to follow you backstage.", }, { skill: () => lore0, // Respect Elders locked: '"That looks good."', failure: "\"It's not.\" She eats it with such relish it's hard to tell.", unlockable: "Not as good as they used to be", success: '"Certainly." She seems pleased that you understand.\n\n"Teach me to make a real one?"', }, ], }, itemHint: "\"I'm from New Zealand, actually.\n\nThere's this regional dish -- I haven't had it in years...\"", itemPickupMessage: "This particular instance of gator food resembles an infamous Aotearoan entree: colonial goose.", deliveryMessage: 'Monica salivates. "This is... this is... simply exquisite!"\n\nShe is happy. She will never leave.', rewardMessage: "Monica showers you with CHA and INT!", rewardCallback: (spawn) => { for (let i = 0; i < 15; i++) { spawn("CHA"); } for (let i = 0; i < 15; i++) { spawn("INT"); } }, lifeStageText: { fresh: { prebite: "Monica nibbles a pastry.", postbite: "You dig your teeth into the koala's mortal flesh.", }, average: { prebite: "Monica doesn't look as fresh and vibrant as you recall from her TV show.", postbite: "A little bite seems to improve her mood, even though she twitches involuntarily as if you're hurting her.", }, poor: { prebite: "Monica weakly raises a hand as if to stop you from approaching for a bite.", postbite: "You press yourself to her body and embrace her. Her fingers curl around you and she lets you drink your fill.", }, vampirized: { prebite: "Monica shows no interest in food. She's lethargic, apathetic. A bite would kill her, but you're thirsty.", postbite: "Her last words are too quiet to make out, but you're not interested in them. Nothing matters except blood.", }, dead: { prebite: "This used to be Monica. Now it's just her corpse.", postbite: "She's very delicate, even as a corpse.", }, }, }); export let thrallCharm = table.add({ label: "Renfield", sprite: sprThrallCharm, hitboxSize: 0.85, posterCheck: { label: "This room would be perfect for someone who likes vampires even more than you enjoy being a vampire.", options: [], }, initialCheck: { label: "Doesn't this guy seem a little creepy? His nametag says Renfield. Not sure you should trust him...", options: [ { skill: () => lore1, // Brick by Brick locked: `Ask for vampire facts`, failure: `He eagerly describes the cape, the coffin, the crypt, the castle -- a whole lot of stuff you don't necessarily don't have.`, unlockable: `Talk about your manor`, success: "You describe your manor for a while without inviting him. He salivates -- swallowing hard before he manages, in response to your comments, to beg for a peek.", }, { skill: () => stealth0, // Be Quiet locked: "Get your fangs in his face", failure: '"Wow. You\'re -- wow! Wow! Wow!"\n\n"And what\'s more --" you say, giving him far too much of a desirable thing.', unlockable: "Say absolutely nothing", success: "His mind overflows with fantasy, and when you let a glint of fang peek through, he claps his arms affectionately around your supercold torso.", }, ], }, itemHint: '"I wish I had some way to look at you... when you\'re not here."', itemPickupMessage: "Your photo is going to be in a lot of places if it gets out, but you've got the original.", deliveryMessage: "Renfield inhales sharply and widens his stance, trying to hide his physical reaction to your face. He is elated and will never leave.", rewardMessage: "Renfield showers you with PSI!", rewardCallback: (spawn) => { for (let i = 0; i < 24; i++) { spawn("PSI"); } }, lifeStageText: { fresh: { prebite: "Renfield exposes the underside of his jaw.", postbite: "You press your face flat to his armorlike scales and part them with your teeth.", }, average: { prebite: "Renfield seems relieved to be free of all that extra blood.", postbite: "You taste a little bit of fear as you press yourself to him. Is he less devoted than you thought?", }, poor: { prebite: "Renfield presses his face to the window. He won't resist you and won't look at you. He does not want your bite.", postbite: "Does it matter that he doesn't want your bite? You're hungry. He should have known you would do this.", }, vampirized: { prebite: "Renfield is repulsed by the vampiric features that his body has begun to display. Another bite would kill him.", postbite: "Better to free him if he's going to behave like this anyways.", }, dead: { prebite: "Here lies a crocodile who really, really liked vampires.", postbite: "At least in death he can't backslide on his promise to feed you.", }, }, }); export let thrallStealth = table.add({ label: "Narthyss", sprite: sprThrallStealth, hitboxSize: 0.85, posterCheck: { label: "This room would be perfect for someone who can breathe fire.", options: [], }, initialCheck: { label: "Narthyss (dragon, heiress) actually owns the club, so she probably wouldn't talk to you... Would she?", options: [ { skill: () => bat1, // Flap locked: "Drink cherry soda with your fangs", failure: '"Wow! That\'s incredibly cool," she says. Her eyes scan the other patrons. "Can I get you anything?"', unlockable: "Hang upside-down and offer a drink", success: '"You\'re ADORABLE!" Her attention is yours alone and she offers to follow you home.', }, { skill: () => stare0, // Dazzle locked: "Show her some sparks", failure: '"Neat trick!" she says. She blinds you with a glowstick, then heads back into the crowd.', unlockable: "Trap her in a tunnel of light", success: "She blushes bright peach, unable to perceive any exit until you approach -- close enough to feel her warm, warm breath. She plants a kiss on your lips.", }, ], }, itemHint: '"Do you know what a kobold is? They\'re like me but much smaller."', itemPickupMessage: "The freezer is empty except for this frozen kobold, who mutters something about collecting blood for its master.", deliveryMessage: "\"That? That's not mine.\" But she wants it. Now it's hers. She will never leave.", rewardMessage: "Narthyss showers you with CHA and AGI!", rewardCallback: (spawn) => { for (let i = 0; i < 15; i++) { spawn("CHA"); } for (let i = 0; i < 15; i++) { spawn("AGI"); } }, lifeStageText: { fresh: { prebite: "Narthyss is producing a new track on her gamer PC.", postbite: "You push her mouse and keyboard aside and focus her attention on your eyes.", }, average: { prebite: "Narthyss has no desire to be interrupted, but you're thirsty.", postbite: "You dazzle her with your eyes and nip her neck with erotic enthusiasm.", }, poor: { prebite: "Narthyss knows better than to resist you -- but you sense that you've taken more than she wants.", postbite: "Her response to your approach is automatic. No matter what she tells you, you show fang -- she shows neck.", }, vampirized: { prebite: "Narthyss' fire has gone out. She's a creature of venom and blood now. Another bite would kill her.", postbite: "Now she is a creature of nothing at all.", }, dead: { prebite: "Narthyss used to be a dragon. Now she's dead.", postbite: "Dragons decay slowly. There's still some warmth in there if you bury your fangs deep enough.", }, }, }); export let thrallStare = table.add({ label: "Ridley", sprite: sprThrallStare, hitboxSize: 0.85, posterCheck: { label: "This room would be perfect for a soulless robot.", options: [], }, initialCheck: { label: "Ridley is the library's catalogue system. It can give you an incorrect answer to any question. (It has a couple gears loose.)", options: [ { skill: () => charm1, // Befriend locked: "\"How many Rs in 'strawberry'?\"", failure: "It generates an image of a sad fruit shrugging in a muddy plantation.", unlockable: '"Do you want to come home with me?"', success: "It generates an image of a happy robot riding the shoulders of a tiny little bat.", }, { skill: () => party0, // Chug locked: "Check its fluid levels", failure: "It submits to your examination, revealing ordinary quantities of robot blood, robot lubricant, Ener-G, and headlight fluid.", unlockable: "Drink its battery", success: "The sulfuric acid doesn't burn you. This somehow makes Ridley want to hang out with you.\n\n(Ridley is weird.)", }, ], }, itemHint: "The slight syncopation of its beeping reminds you that it's missing a gear.", itemPickupMessage: "This glinting gear would be perfect for a malfunctioning robot.", deliveryMessage: "Ridley admires the gear but -- to your surprise -- refuses to jam it into its brain.\n\nThe pup is elated and will never leave.", rewardMessage: "Ridley showers you with EXP!", rewardCallback: (spawn) => { for (let i = 0; i < 6; i++) { spawn("EXP"); } }, lifeStageText: { fresh: { prebite: "Ridley is solving math problems.", postbite: "You delicately sip electronic blood from the robot's neck.", }, average: { prebite: "Ridley's display brightens at your presence. It looks damaged.", postbite: "Damaged or not -- the robot has blood and you need it badly.", }, poor: { prebite: "The symbols on Ridley's screen have less and less rational connection. It's begging to be fed upon.", postbite: "The quality of the robot's blood decreases with every bite, but the taste is still pleasurable.", }, vampirized: { prebite: "With no concern for its survival, the now-fanged robot begs you for one more bite. This would kill it.", postbite: "Nothing is stronger than your need for blood -- and its desperation has put you in quite a state...", }, dead: { prebite: "Ridley was a robot and now Ridley is a dead robot.", postbite: "Tastes zappy.", }, }, }); export let photogenicThralls = [ thrallParty, thrallParty, thrallParty, thrallLore, thrallLore, thrallLore, thrallCharm, thrallCharm, thrallCharm, thrallStealth, thrallStealth, thrallStealth, thrallBat, ];