import { withCamera } from "./layout.ts"; import { D } from "./engine/public.ts"; import { BG_INSET, FG_BOLD, FG_TEXT } from "./colors.ts"; import { AlignX, AlignY, Point, Rect, Size } from "./engine/datatypes.ts"; import { DrawPile } from "./drawpile.ts"; import { addButton } from "./button.ts"; import { ALL_STATS, Ending } from "./datatypes.ts"; import { getStateManager } from "./statemanager.ts"; import { getWishes } from "./wishes.ts"; const WIDTH = 384; const HEIGHT = 384; export class EndgameModal { #drawpile: DrawPile; #page: number; #selectedSuccessor: number | null; #selectedWish: number | null; #ending: Ending | null; constructor() { this.#drawpile = new DrawPile(); this.#page = 0; this.#selectedSuccessor = null; this.#selectedWish = null; this.#ending = null; // this.show(getScorer().pickEnding()); } get isShown(): boolean { return this.#ending != null; } show(ending: Ending) { this.#page = 0; this.#selectedSuccessor = null; this.#selectedWish = null; this.#ending = ending; } update() { withCamera("FullscreenPopover", () => this.#update()); } draw() { withCamera("FullscreenPopover", () => this.#draw()); } get #canProgenerate(): boolean { return this.#selectedSuccessor != null; } #progenerate() { let successor = this.#ending!.successorOptions[this.#selectedSuccessor!]; let wish = 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; let rank = this.#ending?.personal?.rank ?? "No Rank"; let domicile = this.#ending?.personal?.domicile ?? "No Domicile"; let itemsPurloined = analytics?.itemsPurloined ?? 0; let vampiricSkills = analytics?.vampiricSkills ?? 0; let mortalServants = analytics?.mortalServants ?? 0; this.#drawpile.add(0, () => { D.drawText( "It is time to announce the sentence of fate.", new Point(0, 0), FG_TEXT, ); D.drawText( "You are no longer a fledgling. Your new rank:", new Point(0, 32), FG_TEXT, ); D.drawText(rank, new Point(WIDTH / 2, 64), FG_BOLD, { alignX: AlignX.Center, }); D.drawText( "You have achieved a DOMICILE STATUS of:", new Point(0, 96), FG_TEXT, ); D.drawText(domicile, new Point(WIDTH / 2, 128), FG_BOLD, { alignX: AlignX.Center, }); let whereLabel = mortalServants >= 25 ? "where you live with many friends." : mortalServants >= 1 ? "where you live with a couple of friends." : "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"; let vampiricSkillsText = vampiricSkills == 1 ? "vampiric skill" : "vampiric skills"; let mortalServantsText = mortalServants == 1 ? "mortal servant" : "mortal servants"; let itemsPurloinedSpcr = itemsPurloined == 1 ? " " : " "; let vampiricSkillsSpcr = vampiricSkills == 1 ? " " : " "; let mortalServantsSpcr = mortalServants == 1 ? " " : " "; D.drawText( `${itemsPurloined} ${itemsPurloinedText}\n${vampiricSkills} ${vampiricSkillsText}\n${mortalServants} ${mortalServantsText}`, new Point(WIDTH / 2, 224), FG_TEXT, { alignX: AlignX.Center }, ); D.drawText( `${itemsPurloined} ${itemsPurloinedSpcr}\n${vampiricSkills} ${vampiricSkillsSpcr}\n${mortalServants} ${mortalServantsSpcr}`, new Point(WIDTH / 2, 224), FG_BOLD, { alignX: AlignX.Center }, ); let msg = "That's pretty dreadful."; if (mortalServants >= 10) { msg = "That's more than zero."; } if (mortalServants >= 30) { msg = "That feels like a lot!"; } D.drawText(msg, new Point(0, 288), FG_TEXT); 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, this.#ending?.personal?.successorVerb ?? "Do Unknown Things", new Rect(new Point(0, HEIGHT - 32), new Size(WIDTH, 32)), true, () => { this.#page += 1; }, ); } else if (this.#page == 1) { this.#drawpile.add(0, () => { D.drawText("Choose your successor:", new Point(0, 0), FG_TEXT); }); 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:${optionalNote}`, new Point(0, 224), FG_TEXT, ); }); this.#addWish(1, new Point(0, 240)); this.#addWish(0, new Point(128, 240)); this.#addWish(2, new Point(256, 240)); addButton( this.#drawpile, "Back", new Rect(new Point(0, HEIGHT - 32), new Size(WIDTH / 3, 32)), true, () => { this.#page -= 1; }, ); addButton( this.#drawpile, this.#ending?.personal.progenerateVerb ?? "Unknown Action", new Rect( new Point(WIDTH / 3, HEIGHT - 32), new Size(WIDTH - WIDTH / 3, 32), ), this.#canProgenerate, () => { this.#progenerate(); }, ); } 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) { let candidates = this.#ending?.successorOptions; if (candidates == null) { return; } let candidate = candidates[ix]; if (candidate == null) { return; } let w = WIDTH; let h = 64; let enabled = true; let generalRect = new Rect(at, new Size(w, h)); let selected = this.#selectedSuccessor == ix; this.#drawpile.addClickable( 0, (hover) => { let [bg, fg, fgBold] = [BG_INSET, FG_TEXT, FG_BOLD]; if (hover || selected) { [bg, fg, fgBold] = [FG_BOLD, BG_INSET, BG_INSET]; } D.fillRect(at.offset(new Point(0, 4)), new Size(w, h - 8), bg); D.drawRect(at.offset(new Point(0, 4)), new Size(w, h - 8), fg); D.drawText( candidate.name + ", " + candidate.title, at.offset(new Point(4, 8)), fg, ); D.drawText(candidate.name, at.offset(new Point(4, 8)), fgBold); let xys = [ new Point(4, 24), new Point(4, 40), new Point(116, 24), new Point(116, 40), ]; let i = 0; for (let s of ALL_STATS.values()) { let statValue = candidate.stats[s]; let talentValue = candidate.talents[s]; D.drawText(s, at.offset(xys[i]), fg); D.drawText( `${statValue}`, at.offset(xys[i].offset(new Point(32, 0))), fgBold, ); 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; } if (candidate.note != null) { D.drawText(candidate.note, at.offset(new Point(224, 24)), fg, { forceWidth: w - 224, }); } }, generalRect, enabled, { onClick: () => { if (this.#selectedSuccessor == ix) { this.#selectedSuccessor = null; } else { this.#selectedSuccessor = ix; } } }, ); } #addWish(ix: number, at: Point) { let wishOptions = this.#ending?.wishOptions; if (wishOptions == null) { return; } let wishOption = wishOptions[ix]; if (wishOption == null) { return; } let selected = this.#selectedWish == ix; let w = 128; let h = 88; let generalRect = new Rect(at, new Size(w, h)); let enabled = true; let wishData = getWishes().get(wishOption); this.#drawpile.addClickable( 0, (hover) => { let [bg, fg, fgBold] = [BG_INSET, FG_TEXT, FG_BOLD]; if (hover || selected) { [bg, fg, fgBold] = [FG_BOLD, BG_INSET, BG_INSET]; } D.fillRect(at.offset(new Point(2, 4)), new Size(w - 4, h - 8), bg); D.drawRect(at.offset(new Point(2, 4)), new Size(w - 4, h - 8), fg); D.drawText( wishData.profile.name, at.offset(new Point(w / 2, h / 2)), fgBold, { forceWidth: w - 4, 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, { onClick: () => { if (this.#selectedWish == ix) { this.#selectedWish = null; } else { this.#selectedWish = ix; } }, } ); } #draw() { this.#drawpile.draw(); } get blocksHud(): boolean { return true; } } let active = new EndgameModal(); export function getEndgameModal() { return active; }