Ending selector, VN sequences
This commit is contained in:
		| @@ -6,7 +6,12 @@ export type Resource = "EXP"; | ||||
| export const ALL_RESOURCES: Array<Resource> = ["EXP"] | ||||
|  | ||||
| export type SkillGoverning = { | ||||
|   stats: Stat[], underTarget: number, target: number, cost: number, note: string | ||||
|   stats: Stat[], | ||||
|   underTarget: number, | ||||
|   target: number, | ||||
|   cost: number, | ||||
|   note: string, | ||||
|   scoring: SkillScoring, | ||||
| }; | ||||
| export type SkillProfile = { | ||||
|   name: string, | ||||
| @@ -19,6 +24,10 @@ export type SkillData = { | ||||
|   prereqs: Skill[] | ||||
| } | ||||
|  | ||||
| export type ScoringCategory = "bat" | "stealth" | "charm" | "stare" | "party" | "lore"; | ||||
| export const SCORING_CATEGORIES: ScoringCategory[] = ["bat", "stealth", "charm", "stare", "party", "lore"]; | ||||
| export type SkillScoring = {[P in ScoringCategory]?: number}; | ||||
|  | ||||
| export type Skill = { | ||||
|   id: number | ||||
| } | ||||
|   | ||||
							
								
								
									
										129
									
								
								src/endings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/endings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| import {compile, VNScene, VNSceneBasisPart} from "./vnscene.ts"; | ||||
|  | ||||
| const squeak: VNSceneBasisPart = { | ||||
|   type: "message", | ||||
|   text: "...", | ||||
|   sfx: "squeak.mp3" | ||||
| } | ||||
|  | ||||
| export const sceneBat: VNScene = compile([ | ||||
|   squeak, | ||||
|   "I didn't.", | ||||
|   "I tried to, but --", | ||||
|   squeak, | ||||
|   "Well, you know what they say.", | ||||
|   "But I didn't disappear, right?", | ||||
|   "I'm still here.", | ||||
|   squeak, | ||||
|   "That's actually _why_ I stopped.", | ||||
|   "Talking is --", | ||||
|   squeak, | ||||
|   "Well. You know.", | ||||
|   squeak, | ||||
| ]); | ||||
|  | ||||
| const doorbell: VNSceneBasisPart = { | ||||
|   type: "message", | ||||
|   text: "...", | ||||
|   sfx: "doorbell.mp3" | ||||
| } | ||||
|  | ||||
| export const sceneStealth: VNScene = compile([ | ||||
|   doorbell, | ||||
|   "Yeah, you can let yourself in.", | ||||
|   doorbell, | ||||
|   "I'll have it moved.", | ||||
|   "Just -- don't call Susan, OK?", | ||||
|   doorbell, | ||||
|   "Believe me, I'm good for the money.", | ||||
|   "I'm doing... a lot better than it looks like.", | ||||
|   doorbell, | ||||
|   "The fangs? They're not real.", | ||||
|   "I'm just like you.", | ||||
|   doorbell, | ||||
| ]); | ||||
|  | ||||
| const phoneBeep: VNSceneBasisPart = { | ||||
|   type: "message", | ||||
|   text: "...", | ||||
|   sfx: "phonebeep.mp3" | ||||
| } | ||||
|  | ||||
| export const sceneCharm: VNScene = compile([ | ||||
|   phoneBeep, | ||||
|   "How do I sound?", | ||||
|   phoneBeep, | ||||
|   "*chuckle*", | ||||
|   "Sorry. I didn't plan ahead of time.", | ||||
|   "We're not on the air?", | ||||
|   phoneBeep, | ||||
|   "Well, I want a song.", | ||||
|   "Can you put me through?", | ||||
|   phoneBeep, | ||||
|   "I really want it.", | ||||
|   "It's for my boyfriend. First boyfriend, sorry.", | ||||
|   phoneBeep, | ||||
|   "*chuckle*", | ||||
|   "Yeah. I guess I do.", | ||||
|   "Is that bad?", | ||||
|   phoneBeep, | ||||
| ]); | ||||
|  | ||||
| const sleepyBreath: VNSceneBasisPart = { | ||||
|   type: "message", | ||||
|   text: "...", | ||||
|   sfx: "sleepyBreath.mp3" | ||||
| } | ||||
|  | ||||
| export const sceneStare: VNScene = compile([ | ||||
|   sleepyBreath, | ||||
|   "Don't wake up.", | ||||
|   "You're home.", | ||||
|   "A lot of things have been happening pretty fast.", | ||||
|   sleepyBreath, | ||||
|   "You have a lot of friends. You stick together.", | ||||
|   "You didn't have that when you were younger.", | ||||
|   sleepyBreath, | ||||
|   "It's not bad when things change.", | ||||
|   "And all that other stuff. It was a long time ago.", | ||||
|   "I couldn't really stop myself...", | ||||
|   sleepyBreath, | ||||
| ]); | ||||
|  | ||||
| const party: VNSceneBasisPart = { | ||||
|   type: "message", | ||||
|   text: "...", | ||||
|   sfx: "party.mp3" | ||||
| }; | ||||
|  | ||||
| export const sceneParty: VNScene = compile([ | ||||
|   party, // party noises | ||||
|   "IT'S LOUD?", | ||||
|   party, // party noises | ||||
|   "I KNOW.", | ||||
|   party, // party noises | ||||
|   "LISTEN, I --", | ||||
|   party, // party noises | ||||
|   "IF I DON'T SEE YOU AGAIN, I --", | ||||
|   party, // party noises | ||||
| ]); | ||||
|  | ||||
| const ghost: VNSceneBasisPart = { | ||||
|   type: "message", | ||||
|   text: "...", | ||||
|   sfx: "ghost.mp3" | ||||
| }; | ||||
|  | ||||
| export const sceneLore: VNScene = compile([ | ||||
|   ghost, | ||||
|   "Oh yeah, I canceled those.", | ||||
|   ghost, | ||||
|   "I'll tell you the answers later.", | ||||
|   "I guess if I said it now that would be --", | ||||
|   "It'd be like cheating, right?", | ||||
|   ghost, | ||||
|   "Don't say that! I visit you, don't I?", | ||||
|   ghost, | ||||
|   "Yeah. They remember.", | ||||
|   ghost, | ||||
| ]); | ||||
| @@ -111,6 +111,16 @@ class Input { | ||||
|   isKeyReleased(key: string) : boolean { | ||||
|     return !this.#keyDown[key] && this.#previousKeyDown[key]; | ||||
|   } | ||||
|  | ||||
|   isAnythingPressed(): boolean { | ||||
|     for (let k of Object.keys(this.#keyDown)) { | ||||
|       if (this.#keyDown[k] && !this.#previousKeyDown[k]) { return true } | ||||
|     } | ||||
|     for (let k of Object.keys(this.#mouseDown)) { | ||||
|       if (this.#mouseDown[k] && !this.#previousMouseDown[k]) { return true } | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| let active = new Input(); | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import {getHud} from "./hud.ts"; | ||||
| import {getHotbar, Hotbar} from "./hotbar.ts"; | ||||
| import {getSkillsModal, SkillsModal} from "./skillsmodal.ts"; | ||||
| import {getSleepModal, SleepModal} from "./sleepmodal.ts"; | ||||
| import {getEndgameModal} from "./vnmodal.ts"; | ||||
|  | ||||
| class MenuCamera { | ||||
|   // measured in whole screens | ||||
| @@ -88,6 +89,7 @@ export class Game implements IGame { | ||||
|     }); | ||||
|     withCamera("HUD", () => { getHud().update() }) | ||||
|     this.#bottomThing?.update(); | ||||
|     getEndgameModal().update(); | ||||
|   } | ||||
|  | ||||
|   drawGameplay() { | ||||
| @@ -96,6 +98,7 @@ export class Game implements IGame { | ||||
|     }); | ||||
|     withCamera("HUD", () => { getHud().draw() }) | ||||
|     this.#bottomThing?.draw() | ||||
|     getEndgameModal().draw(); | ||||
|   } | ||||
|  | ||||
|   #chooseBottomThing() { | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/hud.ts
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/hud.ts
									
									
									
									
									
								
							| @@ -4,10 +4,11 @@ import {FG_BOLD, FG_TEXT} from "./colors.ts"; | ||||
| import {ALL_STATS} from "./datatypes.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| import {getHuntMode} from "./huntmode.ts"; | ||||
| import {getStateManager} from "./statemanager.ts"; | ||||
|  | ||||
| export class Hud { | ||||
|   get size(): Size { | ||||
|     return new Size(96, 160) | ||||
|     return new Size(96, 176) | ||||
|   } | ||||
|  | ||||
|   update() { } | ||||
| @@ -16,18 +17,19 @@ export class Hud { | ||||
|     // D.fillRect(new Point(-4, -4), this.size.add(new Size(8, 8)), BG_INSET) | ||||
|     D.drawText("Pyrex", new Point(0, 0), FG_BOLD) | ||||
|     D.drawText(`Level ${getHuntMode().getDepth()}`, new Point(0, 16), FG_TEXT) | ||||
|     D.drawText(`Turn ${getStateManager().getTurn()}/${getStateManager().getMaxTurns()}`, new Point(0, 32), FG_TEXT) | ||||
|  | ||||
|     let y = 48; | ||||
|     let y = 64; | ||||
|     let prog = getPlayerProgress(); | ||||
|     for (let s of ALL_STATS.values()) { | ||||
|       D.drawText(`${s}`, new Point(0, y), FG_BOLD) | ||||
|       D.drawText(`${prog.getStat(s)}`, new Point(32, y), FG_TEXT) | ||||
|       y += 16; | ||||
|     } | ||||
|     D.drawText("EXP", new Point(0, 128), FG_BOLD); | ||||
|     D.drawText(`${prog.getExperience()}`, new Point(32, 128), FG_TEXT); | ||||
|     D.drawText("BLD", new Point(0, 144), FG_BOLD); | ||||
|     D.drawText(`${prog.getBlood()}cc`, new Point(32, 144), FG_TEXT); | ||||
|     D.drawText("EXP", new Point(0, 144), FG_BOLD); | ||||
|     D.drawText(`${prog.getExperience()}`, new Point(32, 144), FG_TEXT); | ||||
|     D.drawText("BLD", new Point(0, 160), FG_BOLD); | ||||
|     D.drawText(`${prog.getBlood()}cc`, new Point(32, 160), FG_TEXT); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -52,10 +52,12 @@ export function withCamera(part: UIPart, cb: () => void) { | ||||
|  | ||||
| // specific | ||||
| export type Page = "Gameplay" | "Thralls"; | ||||
| export type UIPart = "BottomModal" | "Hotbar" | "HUD" | "Gameplay" | "Thralls"; | ||||
| export type UIPart = "BottomModal" | "FullscreenPopover" | "Hotbar" | "HUD" | "Gameplay" | "Thralls"; | ||||
|  | ||||
| export function getPartPage(part: UIPart): Page { | ||||
| export function getPartPage(part: UIPart): Page | null { | ||||
|   switch (part) { | ||||
|     case "FullscreenPopover": | ||||
|       return null | ||||
|     case "BottomModal": | ||||
|     case "Hotbar": | ||||
|     case "HUD": | ||||
| @@ -83,9 +85,15 @@ export function getPageLocation(page: Page): Point { | ||||
| export function getPartLocation(part: UIPart): Rect { | ||||
|   // TODO: in pixels, not screens | ||||
|   let {w: screenW, h: screenH} = D.size; | ||||
|   let pageOffset = getPageLocation(getPartPage(part)); | ||||
|   let page = getPartPage(part); | ||||
|   let pageOffset = page ? getPageLocation(page) : null; | ||||
|   let layoutRect = internalGetPartLayoutRect(part); | ||||
|  | ||||
|   if (pageOffset == null) { | ||||
|     // follow camera | ||||
|     return layoutRect.offset(D.camera); | ||||
|   } | ||||
|  | ||||
|   return layoutRect.offset(new Point( | ||||
|     pageOffset.x * screenW, | ||||
|     pageOffset.y * screenH | ||||
| @@ -100,6 +108,8 @@ export function internalGetPartLayoutRect(part: UIPart) { | ||||
|         alignX: AlignX.Center, | ||||
|         alignY: AlignY.Bottom, | ||||
|       }); | ||||
|     case "FullscreenPopover": | ||||
|       return getLayoutRect(new Size(384, 384)); | ||||
|     case "Gameplay": | ||||
|     case "Thralls": | ||||
|       return getLayoutRect(new Size(384, 384)); | ||||
|   | ||||
| @@ -132,6 +132,14 @@ export class PlayerProgress { | ||||
|     }); | ||||
|     return skillsAvailable.slice(0, 6) | ||||
|   } | ||||
|  | ||||
|   getLearnedSkills() { | ||||
|     let learnedSkills = [] | ||||
|     for (let s of this.#skillsLearned.values()) { | ||||
|       learnedSkills.push({id: s}) | ||||
|     } | ||||
|     return learnedSkills; | ||||
|   } | ||||
| } | ||||
|  | ||||
| let active: PlayerProgress = new PlayerProgress(); | ||||
|   | ||||
							
								
								
									
										69
									
								
								src/scorer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/scorer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import {VNScene} from "./vnscene.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| import {getSkills} from "./skills.ts"; | ||||
| import {SCORING_CATEGORIES, ScoringCategory} from "./datatypes.ts"; | ||||
| import {sceneBat, sceneCharm, sceneLore, sceneParty, sceneStare, sceneStealth} from "./endings.ts"; | ||||
|  | ||||
| class Scorer { | ||||
|   constructor() { } | ||||
|  | ||||
|  | ||||
|   pickEnding(): Ending { | ||||
|     let learnedSkills = getPlayerProgress().getLearnedSkills(); | ||||
|     let scores: Record<string, number> = {}; | ||||
|  | ||||
|     for (const skill of learnedSkills.values()) { | ||||
|       let data = getSkills().get(skill); | ||||
|       for (let [category, number] of Object.entries(data.governing.scoring)) { | ||||
|         scores[category] = (scores[category] ?? 0) + number; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // NOTE: This approach isn't efficient but it's easy to understand | ||||
|     // and it allows me to arbitrate ties however I want | ||||
|     const isMax = (cat: ScoringCategory, min: number) => { | ||||
|       let score = scores[cat] ?? 0; | ||||
|       scores[cat] = 0; // each category, once checked, can't disqualify any other category | ||||
|  | ||||
|       if (score < min) { | ||||
|         return false; | ||||
|       } | ||||
|       for (let cat of SCORING_CATEGORIES.values()) { | ||||
|         if (scores[cat] > score) { | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     if (isMax("stare", 3)) { | ||||
|       return {scene: sceneStare} | ||||
|     } | ||||
|     if (isMax("lore", 3)) { | ||||
|       return {scene: sceneLore}; | ||||
|     } | ||||
|     if (isMax("charm", 2)) { | ||||
|       return {scene: sceneCharm} | ||||
|     } | ||||
|     if (isMax("party", 1)) { | ||||
|       return {scene: sceneParty}; | ||||
|     } | ||||
|     if (isMax("stealth", 0)) { | ||||
|       return {scene: sceneStealth} | ||||
|     } | ||||
|     // if (isMax("bat")) { | ||||
|     { | ||||
|       return {scene: sceneBat}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| type Ending = { | ||||
|   scene: VNScene | ||||
| } | ||||
|  | ||||
| let active = new Scorer(); | ||||
| export function getScorer(): Scorer { | ||||
|   return active; | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import {Skill, SkillData, SkillGoverning, Stat} from "./datatypes.ts"; | ||||
| import {Skill, SkillData, SkillGoverning, SkillScoring, Stat} from "./datatypes.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
|  | ||||
| class SkillsTable { | ||||
| @@ -60,16 +60,41 @@ type Difficulty = 0 | 1 | 2 | 3 | ||||
| type GoverningTemplate = { | ||||
|   stats: Stat[], | ||||
|   note: string | ||||
|   scoring: SkillScoring, | ||||
| } | ||||
|  | ||||
| type Track = "bat" | "stealth" | "charm" | "stare" | "party" | "lore" | ||||
| let templates: Record<Track, GoverningTemplate> = { | ||||
|   bat: { stats: ["AGI", "AGI", "PSI"], note: "Cheaper with AGI and PSI." }, | ||||
|   stealth: { stats: ["AGI", "AGI", "INT"], note: "Cheaper with AGI and INT." }, | ||||
|   charm: { stats: ["CHA", "PSI", "PSI"], note: "Cheaper with CHA and PSI." }, | ||||
|   stare: { stats: ["PSI", "PSI"], note: "Cheaper with PSI." }, | ||||
|   party: { stats: ["CHA", "CHA", "PSI"], note: "Cheaper with CHA and PSI." }, | ||||
|   lore: { stats: ["INT", "INT", "CHA"], note: "Cheaper with INT and CHA." }, | ||||
|   bat: { | ||||
|     stats: ["AGI", "AGI", "PSI"], | ||||
|     note: "Cheaper with AGI and PSI.", | ||||
|     scoring: {bat: 1}, | ||||
|   }, | ||||
|   stealth: { | ||||
|     stats: ["AGI", "AGI", "INT"], | ||||
|     note: "Cheaper with AGI and INT.", | ||||
|     scoring: {stealth: 1}, | ||||
|   }, | ||||
|   charm: { | ||||
|     stats: ["CHA", "PSI", "PSI"], | ||||
|     note: "Cheaper with CHA and PSI.", | ||||
|     scoring: {charm: 1}, | ||||
|   }, | ||||
|   stare: { | ||||
|     stats: ["PSI", "PSI"], | ||||
|     note: "Cheaper with PSI.", | ||||
|     scoring: {stare: 1}, | ||||
|   }, | ||||
|   party: { | ||||
|     stats: ["CHA", "CHA", "PSI"], | ||||
|     note: "Cheaper with CHA and PSI.", | ||||
|     scoring: {party: 1}, | ||||
|   }, | ||||
|   lore: { | ||||
|     stats: ["INT", "INT", "CHA"], | ||||
|     note: "Cheaper with INT and CHA.", | ||||
|     scoring: {lore: 1}, | ||||
|   }, | ||||
| } | ||||
|  | ||||
| function governing(track: Track, difficulty: Difficulty): SkillGoverning { | ||||
| @@ -89,6 +114,7 @@ function governing(track: Track, difficulty: Difficulty): SkillGoverning { | ||||
|     target: target, | ||||
|     cost: cost, | ||||
|     note: template.note, | ||||
|     scoring: template.scoring, | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,8 +5,7 @@ import {addButton} from "./button.ts"; | ||||
| import {D} from "./engine/public.ts"; | ||||
| import {BG_INSET} from "./colors.ts"; | ||||
| import {getSkillsModal} from "./skillsmodal.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| import {getHuntMode} from "./huntmode.ts"; | ||||
| import {getStateManager} from "./statemanager.ts"; | ||||
|  | ||||
| export class SleepModal { | ||||
|   #drawpile: DrawPile; | ||||
| @@ -62,9 +61,7 @@ export class SleepModal { | ||||
|     let remainingWidth = size.w - 160; | ||||
|     let nextRect = new Rect(new Point(160, 96), new Size(remainingWidth, 32)); | ||||
|     addButton(this.#drawpile, "Sleep (Next Day)", nextRect, true, () => { | ||||
|       getPlayerProgress().refill(); | ||||
|       getHuntMode().replaceMap(); | ||||
|       getSleepModal().setShown(false); | ||||
|       getStateManager().advance(); | ||||
|     }); | ||||
|  | ||||
|     this.#drawpile.executeOnClick(); | ||||
|   | ||||
							
								
								
									
										42
									
								
								src/statemanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/statemanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| import {getHuntMode} from "./huntmode.ts"; | ||||
| import {getSleepModal} from "./sleepmodal.ts"; | ||||
| import {getEndgameModal} from "./vnmodal.ts"; | ||||
| import {getScorer} from "./scorer.ts"; | ||||
|  | ||||
| const N_TURNS: number = 9; | ||||
|  | ||||
| export class StateManager { | ||||
|   #turn: number; | ||||
|  | ||||
|   constructor() { | ||||
|     this.#turn = 1; | ||||
|   } | ||||
|  | ||||
|   getTurn(): number { | ||||
|     return this.#turn | ||||
|   } | ||||
|  | ||||
|   advance() { | ||||
|     this.#turn += 1; | ||||
|  | ||||
|     if (this.#turn <= N_TURNS) { | ||||
|       getPlayerProgress().refill(); | ||||
|       getHuntMode().replaceMap(); | ||||
|       getSleepModal().setShown(false); | ||||
|     } else { | ||||
|       // TODO: Play a specific scene | ||||
|       let ending = getScorer().pickEnding(); | ||||
|       getEndgameModal().play(ending.scene); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getMaxTurns() { | ||||
|     return N_TURNS | ||||
|   } | ||||
| } | ||||
|  | ||||
| let active: StateManager = new StateManager(); | ||||
| export function getStateManager(): StateManager { | ||||
|   return active | ||||
| } | ||||
							
								
								
									
										118
									
								
								src/vnmodal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/vnmodal.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import {D, I} from "./engine/public.ts"; | ||||
| import {AlignX, AlignY, Color, Point} from "./engine/datatypes.ts"; | ||||
| import {BG_OUTER, FG_BOLD} from "./colors.ts"; | ||||
| import {withCamera} from "./layout.ts"; | ||||
| import {VNScene, VNSceneMessage, VNScenePart} from "./vnscene.ts"; | ||||
|  | ||||
| const WIDTH = 384; | ||||
| const HEIGHT = 384; | ||||
|  | ||||
| class VNModal { | ||||
|   #scene: VNScene | null; | ||||
|   #nextIndex = 0; | ||||
|   #cathexis: SceneCathexis | null; | ||||
|  | ||||
|   constructor() { | ||||
|     this.#scene = null; | ||||
|     this.#nextIndex = 0; | ||||
|     this.#cathexis = null; | ||||
|   } | ||||
|  | ||||
|   get isShown(): boolean { | ||||
|     return this.#scene != null; | ||||
|   } | ||||
|  | ||||
|   play(scene: VNScene) { | ||||
|     this.#scene = scene | ||||
|     this.#nextIndex = 0; | ||||
|     this.#cathexis = null; | ||||
|   } | ||||
|  | ||||
|   #fixCathexis() { | ||||
|     if (this.#cathexis?.isDone()) { | ||||
|       this.#cathexis = null; | ||||
|     } | ||||
|     if (this.#scene == null) { | ||||
|       return; | ||||
|     } | ||||
|     if (this.#cathexis == null) { | ||||
|       let ix = this.#nextIndex | ||||
|       if (ix < this.#scene?.length) { | ||||
|         this.#cathexis = createCathexis(this.#scene[ix]) | ||||
|         this.#nextIndex += 1; | ||||
|       } else { | ||||
|         this.#scene = null; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   update() { | ||||
|     this.#fixCathexis() | ||||
|     if (!this.isShown) { return } | ||||
|  | ||||
|     withCamera("FullscreenPopover", () => this.#update()) | ||||
|   } | ||||
|  | ||||
|   draw() { | ||||
|     if (!this.isShown) { return } | ||||
|  | ||||
|     D.fillRect(new Point(0, 0), D.size, new Color(BG_OUTER.r, BG_OUTER.g, BG_OUTER.b, 255)); | ||||
|     withCamera("FullscreenPopover", () => this.#draw()) | ||||
|   } | ||||
|  | ||||
|   #update() { | ||||
|     this.#cathexis?.update(); | ||||
|   } | ||||
|  | ||||
|   #draw() { | ||||
|     this.#cathexis?.draw(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| interface SceneCathexis { | ||||
|   isDone(): boolean; | ||||
|   update(): void; | ||||
|   draw(): void; | ||||
| } | ||||
|  | ||||
| function createCathexis(part: VNScenePart): SceneCathexis { | ||||
|   switch (part.type) { | ||||
|     case "message": | ||||
|       return new SceneMessageCathexis(part) | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
| class SceneMessageCathexis { | ||||
|   #message: VNSceneMessage; | ||||
|   #done: boolean; | ||||
|  | ||||
|   constructor (message: VNSceneMessage) { | ||||
|     this.#message = message; | ||||
|     this.#done = false; | ||||
|   } | ||||
|  | ||||
|   isDone() { | ||||
|     return this.#done; | ||||
|   } | ||||
|  | ||||
|   update() { | ||||
|     // TODO: SFX | ||||
|     if (I.isAnythingPressed()) { | ||||
|       this.#done = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   draw() { | ||||
|     D.drawText(this.#message.text, new Point(WIDTH/2, HEIGHT/2), FG_BOLD, { | ||||
|       alignX: AlignX.Center, | ||||
|       alignY: AlignY.Middle, | ||||
|       forceWidth: WIDTH | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| let active: VNModal = new VNModal(); | ||||
| export function getEndgameModal() { | ||||
|   return active; | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/vnscene.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/vnscene.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| export type VNSceneMessage = { | ||||
|   type: "message", | ||||
|   text: string, | ||||
|   sfx?: string, | ||||
| } | ||||
|  | ||||
| export type VNSceneBasisPart = string | VNSceneMessage; | ||||
| export type VNSceneBasis = VNSceneBasisPart[]; | ||||
| export type VNScenePart = VNSceneMessage; | ||||
| export type VNScene = VNScenePart[]; | ||||
|  | ||||
| export function compile(basis: VNSceneBasis): VNScene { | ||||
|   let out: VNScene = []; | ||||
|   for (let item of basis.values()) { | ||||
|     if (typeof item == 'string') { | ||||
|       out.push({ | ||||
|         type: "message", | ||||
|         text: item, | ||||
|       }) | ||||
|     } else { | ||||
|       out.push(item); | ||||
|     } | ||||
|   } | ||||
|   return out; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user