Create an initial set of checks
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								src/art/pickups/lock.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/art/pickups/lock.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 173 B | 
							
								
								
									
										101
									
								
								src/checkmodal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/checkmodal.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| import {DrawPile} from "./drawpile.ts"; | ||||
| import {CheckData, CheckDataOption} from "./newmap.ts"; | ||||
| import {getPartLocation, withCamera} from "./layout.ts"; | ||||
| import {Point, Rect, Size} from "./engine/datatypes.ts"; | ||||
| import {D} from "./engine/public.ts"; | ||||
| import {BG_INSET, FG_BOLD} from "./colors.ts"; | ||||
| import {addButton} from "./button.ts"; | ||||
| import {getSkills} from "./skills.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
|  | ||||
| export class CheckModal { | ||||
|   #drawpile: DrawPile; | ||||
|   #activeCheck: CheckData | null; | ||||
|   #callback: (() => void) | null; | ||||
|  | ||||
|   constructor() { | ||||
|     this.#drawpile = new DrawPile(); | ||||
|     this.#activeCheck = null; | ||||
|     this.#callback = null; | ||||
|   } | ||||
|  | ||||
|   get isShown() { | ||||
|     return this.#activeCheck != null | ||||
|   } | ||||
|  | ||||
|   get #size(): Size { | ||||
|     return getPartLocation("BottomModal").size | ||||
|   } | ||||
|  | ||||
|   update() { | ||||
|     withCamera("BottomModal", () => this.#update()) | ||||
|     this.#drawpile.executeOnClick() | ||||
|   } | ||||
|  | ||||
|   draw() { | ||||
|     withCamera("BottomModal", () => this.#draw()) | ||||
|   } | ||||
|  | ||||
|   show(checkData: CheckData | null, callback: (() => void) | null) { | ||||
|     this.#activeCheck = checkData; | ||||
|     this.#callback = callback; | ||||
|   } | ||||
|  | ||||
|   #update() { | ||||
|     this.#drawpile.clear(); | ||||
|  | ||||
|     let check = this.#activeCheck | ||||
|     if (!check) { return; } | ||||
|  | ||||
|     let size = this.#size; | ||||
|     this.#drawpile.add(0, () => { | ||||
|       D.fillRect(new Point(-4, -4), size.add(new Size(8, 8)), BG_INSET) | ||||
|     }) | ||||
|  | ||||
|     let labelText = check.label; | ||||
|     this.#drawpile.add(0, () => { | ||||
|       D.drawText(labelText, new Point(0, 0), FG_BOLD, { | ||||
|         forceWidth: size.w | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     let options = check.options; | ||||
|  | ||||
|     let addOptionButton = (option: CheckDataOption, rect: Rect) => { | ||||
|       let skill = option.skill(); | ||||
|       let skillName = getSkills().get(skill).profile.name; | ||||
|       let hasSkill = getPlayerProgress().hasLearned(skill); | ||||
|       hasSkill ||= true; | ||||
|       let optionLabel: string | ||||
|       if (hasSkill) { | ||||
|         optionLabel = `[${skillName}] ${option.unlockable}`; | ||||
|       } else { | ||||
|         optionLabel = `[Needs ${skillName}] ${option.locked}`; | ||||
|       } | ||||
|       addButton(this.#drawpile, optionLabel, rect, hasSkill, () => { | ||||
|         let cb = this.#callback; | ||||
|         if (cb) { cb(); } | ||||
|         this.show(null, null); | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     if (options.length == 1) { | ||||
|       addOptionButton(options[0], new Rect(new Point(0, size.h - 64), new Size(size.w, 64))); | ||||
|     } | ||||
|     else if (options.length == 2) { | ||||
|       addOptionButton(options[0], new Rect(new Point(0, size.h - 64), new Size(size.w, 32))); | ||||
|       addOptionButton(options[1], new Rect(new Point(0, size.h - 32), new Size(size.w, 32))); | ||||
|     } else { | ||||
|       throw new Error(`unexpected number of options ${options.length}`) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   #draw() { | ||||
|     this.#drawpile.draw(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| let active: CheckModal = new CheckModal(); | ||||
| export function getCheckModal() { | ||||
|   return active; | ||||
| } | ||||
| @@ -31,7 +31,7 @@ export class Sprite { | ||||
|     angle = angle == undefined ? 0.0 : angle; | ||||
|  | ||||
|     // ctx.translate(Math.floor(x), Math.floor(y)); | ||||
|     ctx.translate(position.x, position.y); | ||||
|     ctx.translate(Math.floor(position.x), Math.floor(position.y)); | ||||
|     ctx.rotate(angle * Math.PI / 180); | ||||
|     ctx.scale(xScale, yScale); | ||||
|     ctx.translate(-this.origin.x, -this.origin.y); | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import {getSleepModal, SleepModal} from "./sleepmodal.ts"; | ||||
| import {getVNModal, VNModal} from "./vnmodal.ts"; | ||||
| import {Gameplay, getGameplay} from "./gameplay.ts"; | ||||
| import {getEndgameModal} from "./endgamemodal.ts"; | ||||
| import {CheckModal, getCheckModal} from "./checkmodal.ts"; | ||||
|  | ||||
| class MenuCamera { | ||||
|   // measured in whole screens | ||||
| @@ -34,7 +35,7 @@ export class Game implements IGame { | ||||
|   camera: MenuCamera; | ||||
|   page: Page; | ||||
|   #mainThing: Gameplay | VNModal | null; | ||||
|   #bottomThing: SkillsModal | SleepModal | Hotbar | null; | ||||
|   #bottomThing: CheckModal | SkillsModal | SleepModal | Hotbar | null; | ||||
|  | ||||
|   constructor() { | ||||
|     this.camera = new MenuCamera({ | ||||
| @@ -104,6 +105,12 @@ export class Game implements IGame { | ||||
|     // meaning that events after this in updateGameplay should not affect | ||||
|     // its value | ||||
|  | ||||
|     let checkModal = getCheckModal(); | ||||
|     if (checkModal.isShown) { | ||||
|       this.#bottomThing = checkModal; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let skillsModal = getSkillsModal(); | ||||
|     if (skillsModal.isShown) { | ||||
|       this.#bottomThing = skillsModal; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import {Point} from "./engine/datatypes.ts"; | ||||
| import {ALL_STATS, Stat} from "./datatypes.ts"; | ||||
| import {DrawPile} from "./drawpile.ts"; | ||||
| import {D} from "./engine/public.ts"; | ||||
| import {sprLadder, sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; | ||||
| import {sprLadder, sprLock, sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; | ||||
| import { | ||||
|   BG_INSET, | ||||
|   BG_WALL_OR_UNREVEALED, | ||||
| @@ -15,6 +15,8 @@ import {Architecture, LoadedNewMap} from "./newmap.ts"; | ||||
| import {FLOOR_CELL_SIZE, GridArt} from "./gridart.ts"; | ||||
| import {shadowcast} from "./shadowcast.ts"; | ||||
| import {generateMap} from "./mapgen.ts"; | ||||
| import {getCheckModal} from "./checkmodal.ts"; | ||||
| import {standardVaultTemplates} from "./vaulttemplate.ts"; | ||||
|  | ||||
|  | ||||
| export class HuntMode { | ||||
| @@ -32,6 +34,8 @@ export class HuntMode { | ||||
|     this.drawpile = new DrawPile(); | ||||
|     this.frame = 0; | ||||
|     this.depth = depth; | ||||
|  | ||||
|     getCheckModal().show(standardVaultTemplates[0].checks[0], null) | ||||
|   } | ||||
|  | ||||
|   getDepth() { | ||||
| @@ -176,6 +180,8 @@ export class HuntMode { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let check = cellData.check; | ||||
|  | ||||
|     // draw inset zone | ||||
|     let cost = this.#computeCostToMoveTo(mapPosition); | ||||
|     this.drawpile.addClickable( | ||||
| @@ -201,13 +207,32 @@ export class HuntMode { | ||||
|       gridArt.floorRect, | ||||
|       cost != null && cost <= getPlayerProgress().getBlood(), | ||||
|       () => { | ||||
|         if (check != null) { | ||||
|           getCheckModal().show(check, () => cellData.check = null); | ||||
|           return; | ||||
|         } | ||||
|         if (cost != null) { | ||||
|           getPlayerProgress().spendBlood(cost); | ||||
|           this.movePlayerTo(mapPosition) | ||||
|           getCheckModal().show(null, null); | ||||
|         } | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     if (check != null) { | ||||
|       this.drawpile.add( | ||||
|         OFFSET_AIR, | ||||
|         () => { | ||||
|           for (let z = 0; z < 5; z += 0.25) { | ||||
|             D.drawSprite(sprLock, gridArt.project(z), 0, { | ||||
|               xScale: 2.0, | ||||
|               yScale: 2.0, | ||||
|             }) | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|     } | ||||
|  | ||||
|     const isRevealedBlock = (dx: number, dy: number) => { | ||||
|       let other = this.map.get(mapPosition.offset(new Point(dx, dy))); | ||||
|       return other.revealed && other.architecture == Architecture.Wall; | ||||
|   | ||||
| @@ -13,7 +13,7 @@ const NUM_VAULT_TRIES = 90; | ||||
| const NUM_ROOM_TRIES = 90; | ||||
| const NUM_STAIRCASE_TRIES = 90; | ||||
| const NUM_STAIRCASES_DESIRED = 3 | ||||
| const NUM_ROOMS_DESIRED = 4; | ||||
| const NUM_ROOMS_DESIRED = 0; // 4; | ||||
|  | ||||
| const EXTRA_CONNECTOR_CHANCE = 0.15; | ||||
| const WINDING_PERCENT = 0; | ||||
| @@ -272,10 +272,18 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { | ||||
|  | ||||
|     if (mergeRects(b, c).contains(connector)) { | ||||
|       // TODO: Put check 1 here | ||||
|       let check = vaultTemplate.checks[0]; | ||||
|       if (check != null) { | ||||
|         knife.map.setCheck(connector, check); | ||||
|       } | ||||
|       knife.carve(connector) | ||||
|     } | ||||
|     if (mergeRects(c, d).contains(connector)) { | ||||
|       // TODO: Put check 2 here | ||||
|       let check = vaultTemplate.checks[1]; | ||||
|       if (check != null) { | ||||
|         knife.map.setCheck(connector, check) | ||||
|       } | ||||
|       knife.carve(connector) | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -26,10 +26,10 @@ export type CheckData = { | ||||
|   options: CheckDataOption[], | ||||
| } | ||||
| export type CheckDataOption = { | ||||
|   skills: () => Skill[], | ||||
|   skill: () => Skill, | ||||
|   locked: string, | ||||
|   unlockable: string, | ||||
|   unlockScene: VNScene, | ||||
|   // unlockScene: VNScene, | ||||
| } | ||||
|  | ||||
| export enum Architecture { Wall, Floor } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import imgRaccoonWalking from "./art/characters/raccoon_walking.png"; | ||||
| import imgResourcePickup from "./art/pickups/resources.png"; | ||||
| import imgStatPickup from "./art/pickups/stats.png"; | ||||
| import imgLadder from "./art/pickups/ladder.png"; | ||||
| import imgLock from "./art/pickups/lock.png"; | ||||
| import imgDrips from "./art/tilesets/drips.png"; | ||||
| import {Point, Size} from "./engine/datatypes.ts"; | ||||
|  | ||||
| @@ -49,3 +50,8 @@ export let sprLadder = new Sprite( | ||||
|   imgLadder, new Size(16, 16), new Point(8, 8), | ||||
|   new Size(1, 1), 1 | ||||
| ); | ||||
|  | ||||
| export let sprLock = new Sprite( | ||||
|   imgLock, new Size(16, 16), new Point(8, 8), | ||||
|   new Size(1, 1), 1 | ||||
| ); | ||||
|   | ||||
| @@ -1,32 +1,190 @@ | ||||
| import {Stat} from "./datatypes.ts"; | ||||
| import { | ||||
|   bat0, | ||||
|   bat1, | ||||
|   bat2, | ||||
|   charm0, charm1, | ||||
|   charm2, | ||||
|   lore0, | ||||
|   lore1, | ||||
|   lore2, | ||||
|   party0, | ||||
|   party1, | ||||
|   party2, | ||||
|   stare0, | ||||
|   stare1, | ||||
|   stare2, | ||||
|   stealth0, | ||||
|   stealth1, | ||||
|   stealth2 | ||||
| } from "./skills.ts"; | ||||
| import {CheckData} from "./newmap.ts"; | ||||
|  | ||||
|  | ||||
|  | ||||
| export type VaultTemplate = { | ||||
|   stats: {primary: Stat, secondary: Stat}, | ||||
|   checks: [CheckData, CheckData] | ||||
| } | ||||
|  | ||||
| export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // blood bank | ||||
|     stats: {primary: "AGI", secondary: "INT"}, | ||||
|     // zoo | ||||
|     stats: {primary: "AGI", secondary: "PSI"}, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "You're blocked from further access by a sturdy-looking brick wall. Playful bats swoop close to the alligators behind the bars.", | ||||
|         options: [{ | ||||
|           skill: () => lore1, | ||||
|           locked: "Looks sturdy.", | ||||
|           unlockable: "Find a weakness.", | ||||
|         }, { | ||||
|           skill: () => stare0, | ||||
|           locked: "Admire the bats.", | ||||
|           unlockable: "Get chiropteran help.", | ||||
|         }], | ||||
|       }, | ||||
|       { | ||||
|         label: "There's no person-sized route to the backroom -- only a tiny bat-sized opening.", | ||||
|         options: [{ | ||||
|           skill: () => bat2, | ||||
|           locked: "So small!", | ||||
|           unlockable: "Crawl in.", | ||||
|         }], | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     // club, | ||||
|     stats: {primary: "CHA", secondary: "PSI"}, | ||||
|     // blood bank | ||||
|     stats: {primary: "AGI", secondary: "INT"}, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "The nice lady at the counter says you can't have any blood without a doctor's note.", | ||||
|         options: [ | ||||
|           { | ||||
|             skill: () => stare1, | ||||
|             locked: "Stare at the blood", | ||||
|             unlockable: "Hypnotize her.", | ||||
|           }, | ||||
|           { | ||||
|             skill: () => lore0, | ||||
|             locked: "Pace awkwardly.", | ||||
|             unlockable: "Explain vampires.", | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         label: "There's a security camera watching the blood.", | ||||
|         options: [{ | ||||
|           skill: () => stealth2, | ||||
|           locked: "Better not.", | ||||
|           unlockable: "Sneak past." | ||||
|         }], | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     // coffee shop | ||||
|     stats: {primary: "PSI", secondary: "CHA"}, | ||||
|   }, | ||||
|   { | ||||
|     // library | ||||
|     stats: {primary: "INT", secondary: "CHA"}, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "You don't actually drink coffee, so you probably wouldn't fit in inside.", | ||||
|         options: [{ | ||||
|           skill: () => stealth1, | ||||
|           locked: "Cringe at the thought.", | ||||
|           unlockable: "Sip zealously.", | ||||
|         }, { | ||||
|           skill: () => bat0, | ||||
|           locked: "Throat feels dry.", | ||||
|           unlockable: "Fracture teacup.", | ||||
|         }], | ||||
|       }, | ||||
|       { | ||||
|         label: "There's a little studio back here for getting photos -- you weren't thinking about getting your photo taken, were you?", | ||||
|         options: [{ | ||||
|           skill: () => charm2, | ||||
|           locked: "Not photogenic enough.", | ||||
|           unlockable: "Be dazzling.", | ||||
|         }], | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     // optometrist | ||||
|     stats: {primary: "PSI", secondary: "PSI"}, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "The glasses person doesn't have time for you unless you have a prescription that needs filling.", | ||||
|         options: [{ | ||||
|           skill: () => charm1, | ||||
|           locked: "That's too bad.", | ||||
|           unlockable: "Insist you want one.", | ||||
|         }, { | ||||
|           skill: () => party0, | ||||
|           locked: "Squint at him.", | ||||
|           unlockable: "Drink a whole bottle of glasses cleaner.", | ||||
|         }], | ||||
|       }, | ||||
|       { | ||||
|         label: "The intimidating, massive Eyeball Machine is not going to dispense a prescription for a vampire. It is far too smart for you.", | ||||
|         options: [{ | ||||
|           skill: () => stare2, | ||||
|           locked: "Indeed.", | ||||
|           unlockable: "A worthy opponent." | ||||
|         }], | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     // zoo | ||||
|     stats: {primary: "AGI", secondary: "PSI"} | ||||
|   } | ||||
|     // club, | ||||
|     stats: {primary: "CHA", secondary: "PSI"}, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "You're not here to party, are you? Vampires are total nerds! Everyone's going to laugh at you and say you're totally uncool.", | ||||
|         options: [{ | ||||
|           skill: () => bat1, | ||||
|           locked: "So awkward!", | ||||
|           unlockable: "Demonstrate a new dance.", | ||||
|         }, { | ||||
|           skill: () => stealth0, | ||||
|           locked: "Cry for help.", | ||||
|           unlockable: "Say nothing.", | ||||
|         }], | ||||
|       }, | ||||
|       { | ||||
|         label: "This illegal poker game consists of individuals as different from ordinary people as you are. This guy is dropping _zero_ tells.", | ||||
|         options: [{ | ||||
|           skill: () => party2, | ||||
|           locked: "Lose money.", | ||||
|           unlockable: "Make him leave.", | ||||
|         }], | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
|   { | ||||
|     // library | ||||
|     stats: {primary: "INT", secondary: "CHA"}, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "Special Collections. This guy is not just a librarian -- he's a vampire, too -- which he makes no effort to hide.", | ||||
|         options: [{ | ||||
|           skill: () => party1, | ||||
|           locked: "Quietly do nothing.", | ||||
|           unlockable: "Be super loud.", | ||||
|         }, { | ||||
|           skill: () => charm0, | ||||
|           locked: "Gawk at him.", | ||||
|           unlockable: "Say he's cool.", | ||||
|         }], | ||||
|       }, | ||||
|       { | ||||
|         label: "The librarian took a big risk letting you in back here. He's obviously deciding whether or not he's made a mistake.", | ||||
|         options: [{ | ||||
|           skill: () => lore2, | ||||
|           locked: "Look at the books.", | ||||
|           unlockable: "Prove you read something." | ||||
|         }], | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
| ] | ||||
		Reference in New Issue
	
	Block a user