diff --git a/src/datatypes.ts b/src/datatypes.ts index 8576024..c3071ad 100644 --- a/src/datatypes.ts +++ b/src/datatypes.ts @@ -14,6 +14,7 @@ export type SkillGoverning = { note: string, scoring: SkillScoring, mortalServantValue: number, + flipped: boolean, }; export type SkillProfile = { name: string, @@ -21,6 +22,7 @@ export type SkillProfile = { } export type SkillData = { + isDegrading?: boolean; governing: SkillGoverning, profile: SkillProfile, prereqs: Skill[] @@ -35,11 +37,25 @@ export type Skill = { } export type WishData = { - profile: {name: string}, + profile: { + name: string, + note: string, + domicile: string, + reignSentence: string; + failureName: string, + failureDomicile: string, + failureReignSentence: string, + failureSuccessorVerb: string; + }, + isRandomlyAvailable: boolean, + isCompulsory: boolean; bannedSkills: () => Skill[], discouragedSkills: () => Skill[], encouragedSkills: () => Skill[], requiredSkills: () => Skill[] + prologue: VNScene, + onVictory: VNScene, + onFailure: VNScene, } export type Wish = { id: number @@ -61,6 +77,9 @@ export type Ending = { export type EndingPersonal = { rank: string, domicile: string, + reignSentence: string, + successorVerb: string, + progenerateVerb: string, } export type EndingAnalytics = { @@ -74,6 +93,9 @@ export type SuccessorOption = { title: string, note: string | null, // ex "already a vampire" stats: Record, - talents: Record + talents: Record, + skills: Skill[], + inPenance: boolean; + isCompulsory: boolean; } diff --git a/src/endgamemodal.ts b/src/endgamemodal.ts index 25cfa86..04e0eb8 100644 --- a/src/endgamemodal.ts +++ b/src/endgamemodal.ts @@ -50,19 +50,23 @@ export class EndgameModal { } get #canProgenerate(): boolean { - return this.#selectedSuccessor != null && this.#selectedWish != null; + return this.#selectedSuccessor != null; } #progenerate() { let successor = this.#ending!.successorOptions[this.#selectedSuccessor!]; let wish = - this.#ending!.wishOptions[this.#selectedWish!]; + this.#selectedWish != null + ? this.#ending!.wishOptions[this.#selectedWish!] + : null; this.#ending = null; getStateManager().startGame(successor, wish); } #update() { + this.#fixCompulsory(); + this.#drawpile.clear(); if (this.#page == 0) { let analytics = this.#ending?.analytics; @@ -81,7 +85,7 @@ export class EndgameModal { let whereLabel = mortalServants >= 25 ? "where you live with many friends." : mortalServants >= 1 ? "where you live with a couple of friends." : - "where you live completely alone."; + "where you live without friends."; D.drawText(whereLabel, new Point(0, 160), FG_TEXT) D.drawText("You have achieved:", new Point(0, 192), FG_TEXT) let itemsPurloinedText = itemsPurloined == 1 ? "item purloined" : "items purloined"; @@ -107,11 +111,12 @@ export class EndgameModal { msg = "That feels like a lot!" } D.drawText(msg, new Point(0, 288), FG_TEXT) - D.drawText("Your reign continues unimpeded from the shadows. It is now time to", new Point(0, 320), FG_TEXT, {forceWidth: WIDTH}) + let reignSentence = this.#ending?.personal?.reignSentence ?? "Your reign is in an unknown state."; + D.drawText(`${reignSentence} It is now time to`, new Point(0, 320), FG_TEXT, {forceWidth: WIDTH}) }) addButton( this.#drawpile, - "Appoint a Successor", + this.#ending?.personal?.successorVerb ?? "Do Unknown Things", new Rect( new Point(0, HEIGHT - 32), new Size(WIDTH, 32) ), @@ -126,17 +131,21 @@ export class EndgameModal { D.drawText("Choose your successor:", new Point(0, 0), FG_TEXT); }) - this.#addCandidate(0, new Point(0, 32)) - this.#addCandidate(1, new Point(0, 96)) - this.#addCandidate(2, new Point(0, 160)) + this.#addCandidate(0, new Point(0, 16)) + this.#addCandidate(1, new Point(0, 80)) + this.#addCandidate(2, new Point(0, 144)) + let optionalNote = " (optional, punishes failure)"; + if (this.#hasCompulsoryWish) { + optionalNote = ""; + } this.#drawpile.add(0, () => { - D.drawText("Plan their destiny:", new Point(0, 240), FG_TEXT); + D.drawText(`Plan their destiny:${optionalNote}`, new Point(0, 224), FG_TEXT); }) - this.#addWish(0, new Point(0, 272)) - this.#addWish(1, new Point(128, 272)) - this.#addWish(2, new Point(256, 272)) + this.#addWish(1, new Point(0, 240)) + this.#addWish(0, new Point(128, 240)) + this.#addWish(2, new Point(256, 240)) addButton( this.#drawpile, @@ -151,7 +160,7 @@ export class EndgameModal { ) addButton( this.#drawpile, - "Progenerate", + this.#ending?.personal.progenerateVerb ?? "Unknown Action", new Rect( new Point(WIDTH/3, HEIGHT - 32), new Size(WIDTH - WIDTH / 3, 32) ), @@ -163,6 +172,61 @@ export class EndgameModal { } this.#drawpile.executeOnClick(); + this.#fixCompulsory(); + } + + #fixCompulsory() { + // allow player to select freely between compulsory options + { + let candidates = this.#ending?.successorOptions ?? []; + let selectedSuccessor = this.#selectedSuccessor; + let compulsorySelected = false; + if (selectedSuccessor) { + compulsorySelected = candidates[selectedSuccessor]?.isCompulsory; + } + + if (!compulsorySelected) { + for (let c = 0; c < candidates.length; c++) { + if (candidates[c]?.isCompulsory) { + this.#selectedSuccessor = c; + break; + } + } + } + } + + { + let wishes = this.#ending?.wishOptions ?? []; + let selectedWish = this.#selectedWish; + let compulsorySelected = false; + if (selectedWish) { + let wish = wishes[selectedWish]; + if (wish) { + let data = getWishes().get(wish); + compulsorySelected = data.isCompulsory; + } + } + + if (!compulsorySelected) { + for (let w = 0; w < wishes.length; w++) { + if (getWishes().get(wishes[w]).isCompulsory) { + this.#selectedWish = w; + break; + } + } + } + } + } + + get #hasCompulsoryWish(): boolean { + let wishes = this.#ending?.wishOptions ?? []; + + for (let w = 0; w < wishes.length; w++) { + if (getWishes().get(wishes[w]).isCompulsory) { + return true; + } + } + return false; } #addCandidate(ix: number, at: Point) { @@ -214,6 +278,9 @@ export class EndgameModal { if (talentValue > 0) { D.drawText(`(+${talentValue})`, at.offset(xys[i].offset(new Point(56, 0))), fg) } + if (talentValue < 0) { + D.drawText(`(${talentValue})`, at.offset(xys[i].offset(new Point(56, 0))), fg) + } i += 1; } @@ -225,7 +292,11 @@ export class EndgameModal { enabled, () => { - this.#selectedSuccessor = ix; + if (this.#selectedSuccessor == ix) { + this.#selectedSuccessor = null + } else { + this.#selectedSuccessor = ix; + } }); } @@ -235,9 +306,12 @@ export class EndgameModal { return; } let wishOption = wishOptions[ix]; + if (wishOption == null) { + return; + } let selected = this.#selectedWish == ix; let w = 128; - let h = 72; + let h = 88; let generalRect = new Rect(at, new Size(w, h)); let enabled = true; @@ -262,12 +336,19 @@ export class EndgameModal { alignX: AlignX.Center, alignY: AlignY.Middle, }); + D.drawText(wishData.profile.note, at.offset(new Point(w / 2, h)), FG_TEXT, { + alignX: AlignX.Center + }); }, generalRect, enabled, () => { - this.#selectedWish = ix; + if (this.#selectedWish == ix) { + this.#selectedWish = null; + } else { + this.#selectedWish = ix; + } } ); } diff --git a/src/hud.ts b/src/hud.ts index 4098ab6..e8900d2 100644 --- a/src/hud.ts +++ b/src/hud.ts @@ -25,9 +25,12 @@ export class Hud { D.drawText(`${s}`, new Point(0, y), FG_BOLD) D.drawText(`${prog.getStat(s)}`, new Point(32, y), FG_TEXT) let talent = prog.getTalent(s); - if (talent) { + if (talent > 0) { D.drawText(`(+${talent})`, new Point(56, y), FG_TEXT) } + if (talent < 0) { + D.drawText(`(${talent})`, new Point(56, y), FG_TEXT) + } y += 16; } D.drawText("EXP", new Point(0, 144), FG_BOLD); diff --git a/src/main.ts b/src/main.ts index 2a11c3f..0d24d14 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,6 @@ import {hostGame} from "./engine/internal/host.ts"; import {game} from "./game.ts"; import {getStateManager} from "./statemanager.ts"; -import {batFreak} from "./wishes.ts"; getStateManager().startGame({ name: "Pyrex", @@ -9,5 +8,8 @@ getStateManager().startGame({ note: null, stats: {AGI: 10, INT: 10, CHA: 10, PSI: 10}, talents: {AGI: 0, INT: 0, CHA: 0, PSI: 0}, -}, batFreak); + skills: [], + isCompulsory: false, + inPenance: false, +}, null); hostGame(game); \ No newline at end of file diff --git a/src/playerprogress.ts b/src/playerprogress.ts index ee10782..7d952e1 100644 --- a/src/playerprogress.ts +++ b/src/playerprogress.ts @@ -5,6 +5,7 @@ export class PlayerProgress { #name: string #stats: Record #talents: Record + #isInPenance: boolean; #wish: Wish | null; #exp: number; #blood: number @@ -16,6 +17,7 @@ export class PlayerProgress { this.#name = asSuccessor.name; this.#stats = {...asSuccessor.stats}; this.#talents = {...asSuccessor.talents}; + this.#isInPenance = asSuccessor.inPenance; this.#wish = withWish; this.#exp = 0; this.#blood = 0; @@ -28,7 +30,7 @@ export class PlayerProgress { applyEndOfTurn() { for (let stat of ALL_STATS.values()) { - this.#stats[stat] += this.#talents[stat]; + this.add(stat, this.#talents[stat]); } } @@ -36,11 +38,15 @@ export class PlayerProgress { return this.#name; } + get isInPenance(): boolean { + return this.#isInPenance; + } + refill() { this.#blood = 2000; let learnableSkills = []; // TODO: Also include costing info - for (let skill of getSkills().getAllAvailableSkills().values()) { + for (let skill of getSkills().getAvailableSkills(this.#isInPenance).values()) { if (this.#canBeAvailable(skill)) { learnableSkills.push(skill); } @@ -99,10 +105,8 @@ export class PlayerProgress { if (amount != Math.floor(amount)) { throw `stat increment must be integer: ${amount}` } - if (amount <= 0) { - throw `stat increment must be >0: ${amount}` - } this.#stats[stat] += amount; + this.#stats[stat] = Math.min(Math.max(this.#stats[stat], -99), 999); } addExperience(amt: number) { @@ -169,7 +173,9 @@ export class PlayerProgress { } return learnedSkills; } -} + + getStats() { return {...this.#stats} } + getTalents() { return {...this.#talents} } } let active: PlayerProgress | null = null; diff --git a/src/scorer.ts b/src/scorer.ts index 6134359..0006cba 100644 --- a/src/scorer.ts +++ b/src/scorer.ts @@ -3,7 +3,7 @@ import {getPlayerProgress} from "./playerprogress.ts"; import {getSkills} from "./skills.ts"; import {Ending, SCORING_CATEGORIES, ScoringCategory} from "./datatypes.ts"; import {sceneBat, sceneCharm, sceneLore, sceneParty, sceneStare, sceneStealth} from "./endings.ts"; -import {generateWishes} from "./wishes.ts"; +import {generateWishes, getWishes, isWishCompleted} from "./wishes.ts"; import {generateSuccessors} from "./successors.ts"; class Scorer { @@ -26,7 +26,7 @@ class Scorer { vampiricSkills += 1; } - mortalServants = Math.floor(mortalServants); + mortalServants = Math.max(Math.floor(mortalServants), 0); // NOTE: This approach isn't efficient but it's easy to understand // and it allows me to arbitrate ties however I want @@ -49,40 +49,68 @@ class Scorer { let scene: VNScene; let rank: string; let domicile: string; + let reignSentence: string; + let penance: boolean = false; + let successorVerb: string = "Appoint a Successor"; + // Let the player + let wish = getPlayerProgress().getWish(); + if (wish != null) { + let data = getWishes().get(wish); + if (isWishCompleted(wish)) { + scene = data.onVictory + rank = data.profile.name; + domicile = data.profile.domicile; + reignSentence = data.profile.reignSentence; + } else { + scene = data.onFailure; + rank = data.profile.failureName; + domicile = data.profile.failureDomicile; + reignSentence = data.profile.failureReignSentence; + penance = true; + successorVerb = data.profile.failureSuccessorVerb; + } + + } // TODO: Award different ranks depending on second-to-top skill // TODO: Award different domiciles based on overall score // TODO: Force the rank to match the wish if one existed - if (isMax("stare", 3)) { + else if (isMax("stare", 3)) { scene = sceneStare; rank = "Hypno-Chiropteran"; domicile = "Village of Brainwashed Mortals"; + reignSentence = "You rule with a fair but unflinching gaze."; } else if (isMax("lore", 3)) { scene = sceneLore; rank = "Loremaster"; domicile = "Vineyard"; + reignSentence = "You're well on the path to ultimate knowledge."; } else if (isMax("charm", 2)) { scene = sceneCharm; rank = "Seducer"; domicile = "Guest House"; + reignSentence = "You get to sink your fangs into anyone you want."; } else if (isMax("party", 1)) { scene = sceneParty; rank = "Party Animal"; domicile = "Nightclub"; + reignSentence = "Everyone thinks you're too cool to disobey."; } else if (isMax("stealth", 0)) { scene = sceneStealth; rank = "Invisible"; domicile = "Townhouse"; + reignSentence = "People don't see you but they do most of what you want."; } // if (isMax("bat")) { else { scene = sceneBat; rank = "Bat"; domicile = "Cave"; + reignSentence = "Your skreeking verdicts are irresistible to your subjects."; } // TODO: Analytics tracker @@ -91,12 +119,14 @@ class Scorer { vampiricSkills, mortalServants, } - let successorOptions = generateSuccessors(0); // TODO: generate nImprovements from mortalServants and the player's bsae improvements - let wishOptions = generateWishes(); + let successorOptions = generateSuccessors(0, penance); // TODO: generate nImprovements from mortalServants and the player's bsae improvements + let wishOptions = generateWishes(penance); + + let progenerateVerb = penance ? "Repent" : "Progenerate"; return { scene, - personal: {rank, domicile}, + personal: {rank, domicile, reignSentence, successorVerb, progenerateVerb}, analytics, successorOptions, wishOptions, diff --git a/src/skills.ts b/src/skills.ts index 738eb99..512c798 100644 --- a/src/skills.ts +++ b/src/skills.ts @@ -19,9 +19,11 @@ class SkillsTable { return this.#skills[skill.id] } - getAllAvailableSkills(): Skill[] { + getAvailableSkills(includeDegrading: boolean): Skill[] { let skills = []; for (let i = 0; i < this.#skills.length; i++) { + let isDegrading = this.#skills[i].isDegrading ?? false; + if (isDegrading && !includeDegrading) { continue; } skills.push({id: i}); } return skills; @@ -35,6 +37,10 @@ class SkillsTable { governingStatValue += getPlayerProgress().getStat(stat) / data.governing.stats.length; } + if (data.governing.flipped) { + governingStatValue = - governingStatValue + 10; + } + let mult = getCostMultiplier(getPlayerProgress().getWish(), skill); let [underTarget, target] = [data.governing.underTarget, data.governing.target]; underTarget = mult * underTarget; @@ -62,14 +68,14 @@ function geomInterpolate( return lowOut * Math.pow(highOut / lowOut, proportion) } -type Difficulty = 0 | 1 | 2 | 3 +type Difficulty = 0 | 1 | 1.25 | 2 | 3 type GoverningTemplate = { stats: Stat[], note: string scoring: SkillScoring, } -type Track = "bat" | "stealth" | "charm" | "stare" | "party" | "lore" +type Track = "bat" | "stealth" | "charm" | "stare" | "party" | "lore" | "penance" let templates: Record = { bat: { stats: ["AGI", "AGI", "PSI"], @@ -101,9 +107,14 @@ let templates: Record = { note: "Cheaper with INT and CHA.", scoring: {lore: 1}, }, + penance: { + stats: ["AGI", "INT", "CHA", "PSI"], + note: "Lower your stats for this.", + scoring: {}, + } } -function governing(track: Track, difficulty: Difficulty): SkillGoverning { +function governing(track: Track, difficulty: Difficulty, flipped?: boolean): SkillGoverning { let template = templates[track]; let underTarget: number let target: number @@ -112,9 +123,15 @@ function governing(track: Track, difficulty: Difficulty): SkillGoverning { switch(difficulty) { case 0: underTarget = 5; target = 15; cost = 50; mortalServantValue = 1; break; case 1: underTarget = 15; target = 40; cost = 100; mortalServantValue = 2; break; + case 1.25: underTarget = 17; target = 42; cost = 100; mortalServantValue = 2; break; case 2: underTarget = 30; target = 70; cost = 125; mortalServantValue = 3; break; case 3: underTarget = 50; target = 100; cost = 150; mortalServantValue = 10; break; } + + if (flipped) { + mortalServantValue = -mortalServantValue; + } + return { stats: template.stats, underTarget: underTarget, @@ -122,13 +139,15 @@ function governing(track: Track, difficulty: Difficulty): SkillGoverning { cost: cost, note: template.note, scoring: template.scoring, - mortalServantValue: mortalServantValue + mortalServantValue: mortalServantValue, + flipped: flipped ?? false, } } let table = new SkillsTable(); export let bat0 = table.add({ + isDegrading: false, governing: governing("bat", 0), profile: { name: "Screech", @@ -323,6 +342,37 @@ export let lore3 = table.add({ prereqs: [lore2] }); +export let sorry0 = table.add({ + isDegrading: true, + governing: governing("penance", 0, true), + profile: { + name: "I'm Sorry", + description: "You really hurt your Master, you know? Shame on you." + }, + prereqs: [], +}) + +export let sorry1 = table.add({ + isDegrading: true, + governing: governing("penance", 1, true), + profile: { + name: "I'm So Sorry", + description: "You should have known better! You should have done what you were told." + }, + prereqs: [], +}) + +export let sorry2 = table.add({ + isDegrading: true, + // difficulty 2 is genuinely brutal + governing: governing("penance", 1.25, true), + profile: { + name: "Forgive Me", + description: "Nothing you say will ever be enough to make up for your indiscretion.", + }, + prereqs: [], +}) + export function getSkills(): SkillsTable { return table; } \ No newline at end of file diff --git a/src/successors.ts b/src/successors.ts index b0ba582..c6ccdde 100644 --- a/src/successors.ts +++ b/src/successors.ts @@ -1,8 +1,13 @@ -import {ALL_STATS, Stat, SuccessorOption} from "./datatypes.ts"; +import {ALL_STATS, Skill, Stat, SuccessorOption} from "./datatypes.ts"; import {generateName, generateTitle} from "./namegen.ts"; import {choose} from "./utils.ts"; +import {getPlayerProgress} from "./playerprogress.ts"; + +export function generateSuccessors(nImprovements: number, penance: boolean): SuccessorOption[] { + if (penance) { + return [generateSuccessorFromPlayer()]; + } -export function generateSuccessors(nImprovements: number): SuccessorOption[] { let options = []; while (options.length < 3) { let option = generateSuccessor(nImprovements); @@ -23,6 +28,25 @@ function isEligible(existing: SuccessorOption[], added: SuccessorOption) { return true; } +export function generateSuccessorFromPlayer(): SuccessorOption { + let progress = getPlayerProgress(); + let successor = { + name: progress.name, + title: "Penitent", + note: "Failed at Master's bidding", + stats: {...progress.getStats()}, + talents: {...progress.getTalents()}, + skills: [...progress.getLearnedSkills()], + inPenance: true, + isCompulsory: true, + } + + for (let stat of ALL_STATS.values()) { + successor.talents[stat] = -8; + } + return successor; +} + export function generateSuccessor(nImprovements: number): SuccessorOption { let name = generateName(); let title = generateTitle(); @@ -50,5 +74,8 @@ export function generateSuccessor(nImprovements: number): SuccessorOption { improvement(); } - return {name, title, note, stats, talents}; + let skills: Skill[] = []; + let inPenance = false; + let isCompulsory = false; + return {name, title, note, stats, talents, skills, inPenance, isCompulsory}; } \ No newline at end of file diff --git a/src/wishes.ts b/src/wishes.ts index 1dcb009..85ca084 100644 --- a/src/wishes.ts +++ b/src/wishes.ts @@ -6,15 +6,17 @@ import { charm0, charm1, charm2, - charm3, + charm3, getSkills, lore0, lore1, lore2, party0, - party1, party2, party3, stare0, stare1, stare2, stare3, + party1, party2, party3, sorry0, sorry1, sorry2, stare0, stare1, stare2, stare3, stealth0, stealth1, stealth2, stealth3 } from "./skills.ts"; +import {compile, VNSceneBasisPart} from "./vnscene.ts"; +import {getPlayerProgress} from "./playerprogress.ts"; class WishesTable { #wishes: WishData[] @@ -33,10 +35,12 @@ class WishesTable { return this.#wishes[wish.id]; } - getAllPossibleWishes(): Wish[] { + getAllRandomWishes(): Wish[] { let wishes: Wish[] = []; for (let i = 0; i < this.#wishes.length; i++) { - wishes.push({id: i}); + if (this.#wishes[i].isRandomlyAvailable) { + wishes.push({id: i}); + } } return wishes; } @@ -47,32 +51,174 @@ export function getWishes(): WishesTable { return table; } +const whisper: VNSceneBasisPart = { + type: "message", + text: "...", + sfx: "whisper.mp3" +} + export const celebritySocialite = table.add({ - profile: {name: "Celebrity Socialite"}, + profile: { + name: "Celebrity Socialite", + note: "+Charm -Lore", + domicile: "Party Mansion", + reignSentence: "A lot of people know who you are and like you.", + failureName: "Z-List Bloodstarver", + failureDomicile: "Obscure Soap Ad", + failureReignSentence: "Nobody really knows who you are.", + failureSuccessorVerb: "Apologize For Your Failure", + }, + isRandomlyAvailable: true, + isCompulsory: false, bannedSkills: () => [lore0], discouragedSkills: () => [stealth0, stealth1, stealth2, stealth3], encouragedSkills: () => [party0, party1, party2, party3], requiredSkills: () => [charm0, charm1, charm2], + prologue: compile([ + whisper, + "Master?", + whisper, + "I see.", + "You. I -- should I buy a guitar or something?", + whisper, + "My looks and my party skills...", + ]), + onFailure: compile([ + whisper, + "You're displeased...", + whisper, + "I'm not popular enough?", + "I see.", + ]), + onVictory: compile([ + whisper, + "I did as you commanded.", + "You're pleased?", + "... I'm free.", + ]) }); export const nightswornAlchemist = table.add({ - profile: {name: "Nightsworn Alchemist"}, + profile: { + name: "Nightsworn Alchemist", + note: "+Lore -Party", + domicile: "Alchemical Lab", + reignSentence: "You understand the fundamental connection between wine and blood.", + failureName: "Failure of Science", + failureDomicile: "Remedial College", + failureReignSentence: "You don't understand much of anything.", + failureSuccessorVerb: "Apologize For Your Failure", + }, + isRandomlyAvailable: true, + isCompulsory: false, bannedSkills: () => [party0], discouragedSkills: () => [charm0, charm1, charm2, charm3], encouragedSkills: () => [stare0, stare1, stare2, stare3], - requiredSkills: () => [lore0, lore1, lore2] + requiredSkills: () => [lore0, lore1, lore2], + prologue: compile([ + whisper, + "Master?", + whisper, + "I see.", + "You. I -- should dedicate my life to the vampiric sciences.", + whisper, + "My looks and my party skills...", + ]), + onFailure: compile([ + whisper, + "You're displeased...", + whisper, + "I should have learned more lore.", + ]), + onVictory: compile([ + whisper, + "I did as you commanded.", + "You're pleased?", + "... I'm free.", + ]) }); export const batFreak = table.add({ - profile: {name: "Bat Freak"}, + profile: { + name: "Bat Freak", + note: "++Bat -All", + domicile: "Master's Chiropteriary", + reignSentence: "You're an idol among bats.", + failureName: "Practically Mortal", + failureDomicile: "Right Side Up", + failureReignSentence: "Bats can tell you don't skreek correctly.", + failureSuccessorVerb: "Apologize -- SKREEK!", + }, + isRandomlyAvailable: true, + isCompulsory: false, bannedSkills: () => [charm0, stare0, party0, lore0], discouragedSkills: () => [], encouragedSkills: () => [stealth0, stealth1, stealth2, stealth3], - requiredSkills: () => [bat0, bat1, bat2, bat3] + requiredSkills: () => [bat0, bat1, bat2, bat3], + prologue: compile([ + whisper, + "Master?", + whisper, + "I see.", + "You -- SKKREEK -- want me to become a -- SKKREEK --", + ]), + onFailure: compile([ + whisper, + "You're displeased...", + whisper, + "I -- SKREEEEK -- should have spent more time becoming a bat...", + ]), + onVictory: compile([ + whisper, + "SKRSKRSKRSK.", + "I'm FREEEEEEEEEE --", + ]) }); -export function generateWishes(): Wish[] { - let possibleWishes = table.getAllPossibleWishes(); +export const repent = table.add({ + profile: { + name: "Not Even Fit To Be Bat Food", + note: "--All", + domicile: "Master's Home", + reignSentence: "You are almost, but not quite loved.", + failureName: "Can't Even Repent Correctly", + failureDomicile: "Homeless", + failureReignSentence: "You are unloved and disrespected.", + failureSuccessorVerb: "Apologize Again", + }, + isRandomlyAvailable: false, + isCompulsory: true, + bannedSkills: () => getSkills().getAvailableSkills(false), + discouragedSkills: () => [], + encouragedSkills: () => [], + requiredSkills: () => [sorry0, sorry1, sorry2], + prologue: compile([ + whisper, + "I'm sorry.", + "Please...", + whisper, + "I must repent." + ]), + onFailure: compile([ + whisper, + "I can't --", + "I must --", + whisper, + "Master -- please, no, I --" + ]), + onVictory: compile([ + whisper, + "Yes, I see.", + "I'm free...?" + ]) +}); + +export function generateWishes(penance: boolean): Wish[] { + if (penance) { + return [repent]; + } + + let possibleWishes = table.getAllRandomWishes(); shuffle(possibleWishes); let selectedWishes: Wish[] = []; @@ -103,4 +249,18 @@ export function getCostMultiplier(wish: Wish | null, skill: Skill): number { } return 1.0; +} + +export function isWishCompleted(wish: Wish): boolean { + let player = getPlayerProgress(); + + let wishData = getWishes().get(wish); + + for (let subj of wishData.requiredSkills()) { + if (!player.hasLearned(subj)) { + return false; + } + } + + return true; } \ No newline at end of file