fledgling/src/endgamemodal.ts

430 lines
12 KiB
TypeScript

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;
}