Map refactor -- thralls are clickable
This commit is contained in:
		
							
								
								
									
										107
									
								
								src/huntmode.ts
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								src/huntmode.ts
									
									
									
									
									
								
							| @@ -1,8 +1,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, sprLock, sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; | ||||
| import {sprRaccoonWalking} from "./sprites.ts"; | ||||
| import { | ||||
|   BG_INSET, | ||||
|   BG_WALL_OR_UNREVEALED, | ||||
| @@ -14,9 +13,7 @@ import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| 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 { | ||||
| @@ -47,31 +44,10 @@ export class HuntMode { | ||||
|     let cell = this.map.get(this.player); | ||||
|  | ||||
|     let pickup = cell.pickup; | ||||
|     if (pickup != null) { | ||||
|       switch (pickup) { | ||||
|         case "AGI": | ||||
|         case "INT": | ||||
|         case "CHA": | ||||
|         case "PSI": | ||||
|           getPlayerProgress().add(pickup, 1); | ||||
|           getPlayerProgress().purloinItem(); | ||||
|           break; | ||||
|         case "EXP": | ||||
|           getPlayerProgress().addExperience(250); | ||||
|           getPlayerProgress().purloinItem(); | ||||
|           break; | ||||
|         case "Ladder": | ||||
|           getPlayerProgress().addBlood(1000); | ||||
|           initHuntMode(new HuntMode(this.depth + 1, generateMap())); | ||||
|           break; | ||||
|         default: | ||||
|           throw `not sure how to handle ${pickup}` | ||||
|       } | ||||
|       cell.pickup = null; | ||||
|     } | ||||
|     if (pickup != null) { cell.pickup = null; } | ||||
|   } | ||||
|  | ||||
|   #computeCostToMoveTo(mapPosition: Point): number | null { | ||||
|   #computeCostToClick(mapPosition: Point): number | null { | ||||
|     let present = this.map.get(mapPosition); | ||||
|  | ||||
|     if (present.architecture != Architecture.Floor) { | ||||
| @@ -86,9 +62,8 @@ export class HuntMode { | ||||
|     if (dist != 1) { return null; } | ||||
|  | ||||
|     let pickup = present.pickup; | ||||
|     if (pickup == "Ladder") { return 0; } | ||||
|     if (pickup == null) { return 10; } | ||||
|     return 100;  // any other pickup (EXP, stats, etc) | ||||
|     return pickup.computeCostToClick() | ||||
|   } | ||||
|  | ||||
|   movePlayerTo(newPosition: Point) { | ||||
| @@ -131,7 +106,8 @@ export class HuntMode { | ||||
|       [this.player.x, this.player.y], | ||||
|       ([x, y]: [number, number]): boolean => { | ||||
|         let cell = this.map.get(new Point(x, y)); | ||||
|         return cell.architecture == Architecture.Wall || ALL_STATS.indexOf(cell.pickup as Stat) != -1; | ||||
|         let pickup = cell.pickup; | ||||
|         return cell.architecture == Architecture.Wall || (pickup != null && pickup.isObstructive()); | ||||
|       }, | ||||
|       ([x, y]: [number, number]) => { | ||||
|         let dx = x - this.player.x; | ||||
| @@ -180,37 +156,23 @@ export class HuntMode { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let check = cellData.check; | ||||
|     let pickup = cellData.pickup; | ||||
|  | ||||
|     // draw inset zone | ||||
|     let cost = this.#computeCostToMoveTo(mapPosition); | ||||
|     let cost = this.#computeCostToClick(mapPosition); | ||||
|     this.drawpile.addClickable( | ||||
|       OFFSET_FLOOR, | ||||
|       (hover: boolean) => { | ||||
|         gridArt.drawFloor(hover ? FG_TEXT : BG_INSET) | ||||
|  | ||||
|         if (cellData.pickup == "Ladder") { | ||||
|           D.drawSprite(sprLadder, gridArt.project(0.0), 0, { | ||||
|             xScale: 2.0, | ||||
|             yScale: 2.0, | ||||
|           }) | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|         // TODO: Stairs | ||||
|         if (cellData.content.type == "stairs") { | ||||
|           // draw ladder if applicable | ||||
|           D.drawSprite(sprLadder, cellTopLeft, 0, {xScale: 3, yScale: 3}); | ||||
|         } | ||||
|          */ | ||||
|         pickup?.drawFloor(gridArt); | ||||
|       }, | ||||
|       gridArt.floorRect, | ||||
|       cost != null && cost <= getPlayerProgress().getBlood(), | ||||
|       () => { | ||||
|         if (check != null) { | ||||
|           getCheckModal().show(check, () => cellData.check = null); | ||||
|         if (pickup?.onClick(cellData)) { | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|         if (cost != null) { | ||||
|           getPlayerProgress().spendBlood(cost); | ||||
|           this.movePlayerTo(mapPosition) | ||||
| @@ -219,18 +181,8 @@ export class HuntMode { | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     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, | ||||
|             }) | ||||
|           } | ||||
|         } | ||||
|       ) | ||||
|     if (pickup != null) { | ||||
|       this.drawpile.add(OFFSET_AIR, () => { pickup.drawInAir(gridArt); }); | ||||
|     } | ||||
|  | ||||
|     const isRevealedBlock = (dx: number, dy: number) => { | ||||
| @@ -266,39 +218,6 @@ export class HuntMode { | ||||
|       this.drawpile.add(OFFSET_AIR, () => { gridArt.drawWallRight(FG_BOLD); }) | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingRight(FG_MOULDING); }) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     let pickup = cellData.pickup; | ||||
|     if (pickup != null) { | ||||
|       let statIndex = ALL_STATS.indexOf(pickup as Stat); | ||||
|       if (statIndex != -1) { | ||||
|         this.drawpile.add(OFFSET_AIR, () => { | ||||
|           D.drawSprite( | ||||
|             sprStatPickup, | ||||
|             gridArt.project(5), | ||||
|             statIndex, | ||||
|             { | ||||
|               xScale: 2, | ||||
|               yScale: 2, | ||||
|             } | ||||
|           ) | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|       if (pickup == "EXP") { | ||||
|         this.drawpile.add(OFFSET_AIR, () => { | ||||
|           D.drawSprite( | ||||
|             sprResourcePickup, | ||||
|             gridArt.project(0.0).offset(new Point(0, -16)), | ||||
|             0, | ||||
|             { | ||||
|               xScale: 2, | ||||
|               yScale: 2, | ||||
|             } | ||||
|           ); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   #drawPlayer(globalOffset: Point) { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import {Grid, Point, Rect, Size} from "./engine/datatypes.ts"; | ||||
| import {choose, shuffle} from "./utils.ts"; | ||||
| import {standardVaultTemplates, VaultTemplate} from "./vaulttemplate.ts"; | ||||
| import {ALL_STATS} from "./datatypes.ts"; | ||||
| import {ExperiencePickup, LadderPickup, LockPickup, StatPickup, ThrallPickup} from "./pickups.ts"; | ||||
|  | ||||
| const WIDTH = 19; | ||||
| const HEIGHT = 19; | ||||
| @@ -242,21 +243,21 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { | ||||
|       if (!(a.contains(xy) || b.contains(xy))) { | ||||
|         stat = vaultTemplate.stats.secondary; | ||||
|       } | ||||
|       knife.map.get(xy).pickup = stat; | ||||
|       knife.map.get(xy).pickup = new StatPickup(stat); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   for (let dy = 0; dy < c.size.h; dy++) { | ||||
|     for (let dx = 0; dx < c.size.w; dx++) { | ||||
|       let xy = c.top.offset(new Point(dx, dy)); | ||||
|       knife.map.get(xy).pickup = vaultTemplate.stats.primary; | ||||
|       knife.map.get(xy).pickup = new StatPickup(vaultTemplate.stats.primary); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   for (let dy = 0; dy < d.size.h; dy++) { | ||||
|     for (let dx = 0; dx < d.size.w; dx++) { | ||||
|       let xy = d.top.offset(new Point(dx, dy)); | ||||
|       knife.map.get(xy).pickup = vaultTemplate.stats.primary; | ||||
|       knife.map.get(xy).pickup = new StatPickup(vaultTemplate.stats.primary); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -274,7 +275,7 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { | ||||
|       // TODO: Put check 1 here | ||||
|       let check = vaultTemplate.checks[0]; | ||||
|       if (check != null) { | ||||
|         knife.map.setCheck(connector, check); | ||||
|         knife.map.get(connector).pickup = new LockPickup(check); | ||||
|       } | ||||
|       knife.carve(connector) | ||||
|     } | ||||
| @@ -282,7 +283,7 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { | ||||
|       // TODO: Put check 2 here | ||||
|       let check = vaultTemplate.checks[1]; | ||||
|       if (check != null) { | ||||
|         knife.map.setCheck(connector, check) | ||||
|         knife.map.get(connector).pickup = new LockPickup(check); | ||||
|       } | ||||
|       knife.carve(connector) | ||||
|     } | ||||
| @@ -297,17 +298,20 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { | ||||
|   ] | ||||
|   for (let offset of goodies.values()) { | ||||
|     let goodie = room.top.offset(offset); | ||||
|     let cell = knife.map.get(goodie); | ||||
|  | ||||
|     if (a.contains(goodie)) { | ||||
|       // TODO: Place the zone's NPC here | ||||
|       let thrall = vaultTemplate.thrall(); | ||||
|       cell.pickup = new ThrallPickup(thrall); | ||||
|     } | ||||
|  | ||||
|     if (b.contains(goodie)) { | ||||
|       knife.map.setPickup(goodie, "EXP"); | ||||
|       cell.pickup = new ExperiencePickup(); | ||||
|     } | ||||
|  | ||||
|     if (c.contains(goodie)) { | ||||
|       knife.map.setPickup(goodie, "EXP"); | ||||
|       cell.pickup = new ExperiencePickup(); | ||||
|       // TODO: Fill this room with the common item for this room | ||||
|     } | ||||
|  | ||||
| @@ -329,7 +333,7 @@ function carveStaircase(knife: Knife, room: Rect, ix: number) { | ||||
|     knife.map.entrance = center; | ||||
|     knife.map.get(center).pickup = null; | ||||
|   } else { | ||||
|     knife.map.get(center).pickup = "Ladder"; | ||||
|     knife.map.get(center).pickup = new LadderPickup(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -348,10 +352,10 @@ function carveRoom(knife: Knife, room: Rect) { | ||||
|       let xy2 = room.top.offset(new Point(dx, room.size.h - dy - 1)); | ||||
|       let xy3 = room.top.offset(new Point(room.size.w - dx - 1, room.size.h - dy - 1)); | ||||
|       let stat = choose(ALL_STATS); | ||||
|       knife.map.get(xy0).pickup = stat; | ||||
|       knife.map.get(xy1).pickup = stat; | ||||
|       knife.map.get(xy2).pickup = stat; | ||||
|       knife.map.get(xy3).pickup = stat; | ||||
|       knife.map.get(xy0).pickup = new StatPickup(stat); | ||||
|       knife.map.get(xy1).pickup = new StatPickup(stat); | ||||
|       knife.map.get(xy2).pickup = new StatPickup(stat); | ||||
|       knife.map.get(xy3).pickup = new StatPickup(stat); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										104
									
								
								src/newmap.ts
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								src/newmap.ts
									
									
									
									
									
								
							| @@ -1,25 +1,9 @@ | ||||
| import {Resource, Skill, Stat} from "./datatypes.ts"; | ||||
| import {Grid, Point, Size} from "./engine/datatypes.ts"; | ||||
| import {choose} from "./utils.ts"; | ||||
| import {Pickup} from "./pickups.ts"; | ||||
| import {Skill} from "./datatypes.ts"; | ||||
|  | ||||
| export enum Architecture { Wall, Floor } | ||||
|  | ||||
| export type Province = "a" | "b" | "c"; | ||||
| export type Check = "1" | "2"; | ||||
| export type Progress = Stat | Resource | ||||
| export type Pickup = Progress | "Ladder"; // TODO: Items | ||||
| export type NewMapInput = { | ||||
|   id: string, | ||||
|   data: { | ||||
|     architecture: string, | ||||
|     provinces: string, | ||||
|   } | ||||
|   pickups: { | ||||
|     "*"?: string, | ||||
|     stat: {primary: Stat, secondary: Stat}, | ||||
|     "!"?: string, | ||||
|   }, | ||||
|   provinces: Record<Province, string>, | ||||
|   checks: Record<Check, CheckData>, | ||||
| } | ||||
| export type CheckData = { | ||||
|   label: string, | ||||
|   options: CheckDataOption[], | ||||
| @@ -32,8 +16,6 @@ export type CheckDataOption = { | ||||
|   success: string, | ||||
| } | ||||
|  | ||||
| export enum Architecture { Wall, Floor } | ||||
|  | ||||
| export class LoadedNewMap { | ||||
|   #id: string | ||||
|   #size: Size | ||||
| @@ -41,7 +23,6 @@ export class LoadedNewMap { | ||||
|   #architecture: Grid<Architecture> | ||||
|   #pickups: Grid<Pickup | null> | ||||
|   #provinces: Grid<string | null> | ||||
|   #checks: Grid<CheckData | null> | ||||
|   #revealed: Grid<boolean> | ||||
|  | ||||
|   constructor(id: string, size: Size) { | ||||
| @@ -51,7 +32,6 @@ export class LoadedNewMap { | ||||
|     this.#architecture = new Grid<Architecture>(size, () => Architecture.Wall); | ||||
|     this.#pickups = new Grid<Pickup | null>(size, () => null); | ||||
|     this.#provinces = new Grid<string | null>(size, () => null); | ||||
|     this.#checks = new Grid<CheckData | null>(size, () => null); | ||||
|     this.#revealed = new Grid<boolean>(size, () => false); | ||||
|   } | ||||
|  | ||||
| @@ -98,14 +78,6 @@ export class LoadedNewMap { | ||||
|     return this.#provinces.get(point); | ||||
|   } | ||||
|  | ||||
|   setCheck(point: Point, value: CheckData | null) { | ||||
|     this.#checks.set(point, value); | ||||
|   } | ||||
|  | ||||
|   getCheck(point: Point): CheckData | null { | ||||
|     return this.#checks.get(point); | ||||
|   } | ||||
|  | ||||
|   setRevealed(point: Point, value: boolean) { | ||||
|     this.#revealed.set(point, value) | ||||
|   } | ||||
| @@ -133,9 +105,6 @@ export class CellView { | ||||
|   set province(value: string | null) { this.#map.setProvince(this.#point, value) } | ||||
|   get province(): string | null { return this.#map.getProvince(this.#point) } | ||||
|  | ||||
|   set check(value: CheckData | null) { this.#map.setCheck(this.#point, value) } | ||||
|   get check(): CheckData | null { return this.#map.getCheck(this.#point) } | ||||
|  | ||||
|   set revealed(value: boolean) { this.#map.setRevealed(this.#point, value) } | ||||
|   get revealed(): boolean { return this.#map.getRevealed(this.#point) } | ||||
|  | ||||
| @@ -143,69 +112,6 @@ export class CellView { | ||||
|     this.architecture = cell.architecture; | ||||
|     this.pickup = cell.pickup; | ||||
|     this.province = cell.province; | ||||
|     this.check = cell.check; | ||||
|     this.revealed = cell.revealed; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export type NewMap = () => LoadedNewMap; | ||||
|  | ||||
| export function compileNewMap(input: NewMapInput): NewMap { | ||||
|   let {architecture: architectureInput, provinces: provincesInput} = input.data; | ||||
|   let architecture = Grid.createGridFromMultilineString(architectureInput); | ||||
|   let provinces = Grid.createGridFromMultilineString(provincesInput); | ||||
|  | ||||
|   let size = architecture.size; | ||||
|   if (!size.equals(provinces.size)) { | ||||
|     throw `${input.id}: malformed, wrong province size (${provinces.size})`; | ||||
|   } | ||||
|  | ||||
|   return () => { | ||||
|     let map = new LoadedNewMap(input.id, size); | ||||
|  | ||||
|     for (let y = 0; y < size.h; y++) { | ||||
|       for (let x = 0; x < size.w; x++) { | ||||
|         let xy = new Point(x, y); | ||||
|         let cell = map.get(xy); | ||||
|  | ||||
|         // set up the wall | ||||
|         let arch = architecture.get(xy); | ||||
|         cell.architecture = Architecture.Floor; | ||||
|         if (arch == "#") { | ||||
|           cell.architecture = Architecture.Wall; | ||||
|         } else if (arch == "@") { | ||||
|           map.entrance = xy; | ||||
|         } else if (arch == " " || arch == "-") { | ||||
|  | ||||
|         } | ||||
|         // player resources: pickups | ||||
|         else if (arch == ".") { | ||||
|           let stat = choose([ | ||||
|             input.pickups.stat.primary, | ||||
|             input.pickups.stat.primary, | ||||
|             input.pickups.stat.secondary | ||||
|           ]) | ||||
|           cell.pickup = choose<Progress>([stat, stat, stat, "EXP"]); | ||||
|         } else if (arch == "*") { | ||||
|           // TODO: Common item | ||||
|         } else if (arch == "!") { | ||||
|           // TODO: Artifact | ||||
|         } | ||||
|         // stat checks | ||||
|         else if (input.checks.hasOwnProperty(arch)) { | ||||
|           cell.check = input.checks[arch as Check]; | ||||
|         } else { | ||||
|           throw `${input.id}: unrecognized architecture cell: ${arch}` | ||||
|         } | ||||
|  | ||||
|         // set province | ||||
|         let provinceId = provinces.get(xy); | ||||
|         if (input.provinces.hasOwnProperty(provinceId)) { | ||||
|           cell.province = input.provinces[provinceId as Province]; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return map; | ||||
|   } | ||||
| } | ||||
| } | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #  #   #  # | ||||
| #  #   2  # | ||||
| -  #   #  # | ||||
| #  #   #  # | ||||
| #  #####  # | ||||
| #      1  # | ||||
| #     ##### | ||||
| #         # | ||||
| #####     # | ||||
| ########### | ||||
| @@ -1,27 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
|  | ||||
| const mapBloodBank = compileNewMap({ | ||||
|   id: "mapBloodBank", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "AGI", secondary: "INT"}, | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Blood Bank", | ||||
|     b: "Special Reserve", | ||||
|     c: "Freezer", | ||||
|   }, | ||||
|   checks: { | ||||
|     1: null!, | ||||
|     2: null!, | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapBloodBank; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #aa#ccc#bb# | ||||
| #aa#ccc bb# | ||||
| -aa#ccc#bb# | ||||
| #aa#ccc#bb# | ||||
| #aa#####bb# | ||||
| #aaaaaa bb# | ||||
| #aaaaa##### | ||||
| #aaaaaaaaa# | ||||
| #####aaaaa# | ||||
| ########### | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #         # | ||||
| #         # | ||||
| ########  # | ||||
| #      1  # | ||||
| #  #####  - | ||||
| #  #   #  # | ||||
| #  #   #  # | ||||
| #  2   #  # | ||||
| #  #   #  # | ||||
| ########### | ||||
| @@ -1,27 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
|  | ||||
| const mapClub = compileNewMap({ | ||||
|   id: "mapClub", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "CHA", secondary: "PSI"}, | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Club", | ||||
|     b: "Poker Game", | ||||
|     c: "Trophy Collection", | ||||
|   }, | ||||
|   checks: { | ||||
|     1: null!, | ||||
|     2: null!, | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapClub; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #aaaaaaaaa# | ||||
| #aaaaaaaaa# | ||||
| ########aa# | ||||
| #bbbbbb aa# | ||||
| #bb#####aa- | ||||
| #bb#ccc#aa# | ||||
| #bb#ccc#aa# | ||||
| #bb ccc#aa# | ||||
| #bb#ccc#aa# | ||||
| ########### | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #   #     # | ||||
| #   2     # | ||||
| #   #     # | ||||
| #   #####1# | ||||
| #    #    # | ||||
| #    #    # | ||||
| ######    # | ||||
| #         # | ||||
| #         # | ||||
| ###### #### | ||||
| @@ -1,27 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
|  | ||||
| const mapCoffeeShop = compileNewMap({ | ||||
|   id: "mapCoffeeShop", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "PSI", secondary: "CHA"}, | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Coffee Shop", | ||||
|     b: "Studio", | ||||
|     c: "Personal Photo Room", | ||||
|   }, | ||||
|   checks: { | ||||
|     1: null!, | ||||
|     2: null!, | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapCoffeeShop; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #ccc#bbbbb# | ||||
| #ccc bbbbb# | ||||
| #ccc#bbbbb# | ||||
| #ccc##### # | ||||
| #cccc#aaaa# | ||||
| #cccc#aaaa# | ||||
| ######aaaa# | ||||
| #aaaaaaaaa# | ||||
| #aaaaaaaaa# | ||||
| ###### #### | ||||
| @@ -1,63 +0,0 @@ | ||||
| import metamap from "./metamap.txt?raw"; | ||||
| import {Architecture, LoadedNewMap, NewMap, Progress} from "../../newmap.ts"; | ||||
| import {Grid, Point} from "../../engine/datatypes.ts"; | ||||
| import mapZoo from "../zoo/map.ts"; | ||||
| import mapOptometrist from "../optometrist/map.ts"; | ||||
| import mapBloodBank from "../bloodBank/map.ts"; | ||||
| import mapCoffeeShop from "../coffeeShop/map.ts"; | ||||
| import mapClub from "../club/map.ts"; | ||||
| import mapManor from "../manor/map.ts"; | ||||
| import mapLibrary from "../library/map.ts"; | ||||
| import {choose} from "../../utils.ts"; | ||||
| import {ALL_STATS} from "../../datatypes.ts"; | ||||
|  | ||||
| const mapHub: NewMap = () => { | ||||
|   let metamapLayer = Grid.createGridFromMultilineString(metamap); | ||||
|  | ||||
|   // NOTE: We could deduce this from the file -- | ||||
|   // BUT, for now, let's just use the maps directly | ||||
|   let blits = [ | ||||
|     {at: new Point(2, 0), map: mapOptometrist()}, | ||||
|     {at: new Point(0, 12), map: mapZoo()}, | ||||
|     {at: new Point(13, 9), map: mapBloodBank()}, | ||||
|     {at: new Point(24, 9), map: mapCoffeeShop()}, | ||||
|     {at: new Point(0, 22), map: mapClub()}, | ||||
|     {at: new Point(13, 22), map: mapManor(), useEntrance: true}, | ||||
|     {at: new Point(26, 22), map: mapLibrary()}, | ||||
|   ]; | ||||
|  | ||||
|   let metamapContent = new LoadedNewMap("hub", metamapLayer.size); | ||||
|  | ||||
|   for (let y = 0; y < metamapLayer.size.h; y++) { | ||||
|     for (let x = 0; x < metamapLayer.size.w; x++) { | ||||
|       let src = new Point(x, y); | ||||
|       let cell = metamapContent.get(src); | ||||
|       if (metamapLayer.get(src) == "#") { | ||||
|         cell.architecture = Architecture.Wall; | ||||
|       } else if (metamapLayer.get(src) == " ") { | ||||
|         cell.architecture = Architecture.Floor; | ||||
|         let stat = choose(ALL_STATS); | ||||
|         cell.pickup = choose<Progress>([stat, stat, stat, "EXP"]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   for (let {at, map, useEntrance} of blits.values()) { | ||||
|     for (let srcY = 0; srcY < map.size.h; srcY++) { | ||||
|       for (let srcX = 0; srcX < map.size.w; srcX++) { | ||||
|         let src = new Point(srcX, srcY); | ||||
|         let dst = at.offset(new Point(srcX, srcY)); | ||||
|         metamapContent.get(dst).copyFrom(map.get(src)) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (useEntrance ?? false) { | ||||
|       console.log("beep"); | ||||
|       metamapContent.entrance = at.offset(map.entrance); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return metamapContent; | ||||
| } | ||||
|  | ||||
| export default mapHub; | ||||
| @@ -1,33 +0,0 @@ | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##11111111111######################## | ||||
| ##111111111113333333333344444444444## | ||||
| ##111111111113333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222  3333333333344444444444## | ||||
| 22222222222                    ###### | ||||
| 22222222222                    ###### | ||||
| 55555555555  66666666666  77777777777 | ||||
| 55555555555  66666666666  77777777777 | ||||
| 55555555555  66666666666  77777777777 | ||||
| 55555555555  66666666666  77777777777 | ||||
| 55555555555  66666666666  77777777777 | ||||
| 55555555555  66666666666##77777777777 | ||||
| 55555555555##66666666666##77777777777 | ||||
| 55555555555##66666666666##77777777777 | ||||
| 55555555555##66666666666##77777777777 | ||||
| 55555555555##66666666666##77777777777 | ||||
| 55555555555##66666666666##77777777777 | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 912 B | 
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #  #      # | ||||
| #  #      # | ||||
| -  #      # | ||||
| # ######2## | ||||
| #  1      # | ||||
| #  #      # | ||||
| #  #      # | ||||
| #  #      # | ||||
| #  #      # | ||||
| ########### | ||||
| @@ -1,27 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
|  | ||||
| const mapLibrary = compileNewMap({ | ||||
|   id: "mapLibrary", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "INT", secondary: "CHA"}, | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Library", | ||||
|     b: "Computer Room", | ||||
|     c: "Special Collection", | ||||
|   }, | ||||
|   checks: { | ||||
|     1: null!, | ||||
|     2: null!, | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapLibrary; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #aa#cccccc# | ||||
| #aa#cccccc# | ||||
| -aa#cccccc# | ||||
| #a###### ## | ||||
| #aa bbbbbb# | ||||
| #aa#bbbbbb# | ||||
| #aa#bbbbbb# | ||||
| #aa#bbbbbb# | ||||
| #aa#bbbbbb# | ||||
| ########### | ||||
| @@ -1,11 +0,0 @@ | ||||
| ####   #### | ||||
| #  #   #  # | ||||
| #  ## ##  # | ||||
| #   # #   # | ||||
| #         # | ||||
| ##### ##### | ||||
| #   # #   # | ||||
| #         # | ||||
| #    @    # | ||||
| #         # | ||||
| ########### | ||||
| @@ -1,27 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
|  | ||||
| const mapManor = compileNewMap({ | ||||
|   id: "mapManor", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "AGI", secondary: "PSI"},  // doesn't matter | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Bedroom", | ||||
|     b: "Thrall Bedroom", | ||||
|     c: "Thrall Bedroom", | ||||
|   }, | ||||
|   checks: { | ||||
|     1: null!, | ||||
|     2: null!, | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapManor; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ####   #### | ||||
| #bb#   #cc# | ||||
| #bb## ##cc# | ||||
| #bbb# #ccc# | ||||
| #bbb   ccc# | ||||
| ##### ##### | ||||
| #aaa# #aaa# | ||||
| #aaaaaaaaa# | ||||
| #aaaaaaaaa# | ||||
| #aaaaaaaaa# | ||||
| ########### | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #         # | ||||
| #         # | ||||
| #         # | ||||
| #   ###   # | ||||
| ##2## ##### | ||||
| #     #   # | ||||
| #     #   # | ||||
| #  ##1# # # | ||||
| #  #    # # | ||||
| ######### # | ||||
| @@ -1,27 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
|  | ||||
| const mapOptometrist = compileNewMap({ | ||||
|   id: "mapOptometrist", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "PSI", secondary: "PSI"}, | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Optometrist's Office", | ||||
|     b: "Glasses Fitting", | ||||
|     c: "Eyeball Machine", | ||||
|   }, | ||||
|   checks: { | ||||
|     1: null!, | ||||
|     2: null!, | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapOptometrist; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #ccccccccc# | ||||
| #ccccccccc# | ||||
| #ccccccccc# | ||||
| #ccc###ccc# | ||||
| ## ## ##### | ||||
| #bbbbb#aaa# | ||||
| #bbbbb#aaa# | ||||
| #bb## #a#a# | ||||
| #bb#aaaa#a# | ||||
| ######### # | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #    # ** # | ||||
| # !  # .. # | ||||
| #    # .. # | ||||
| ##2### ## - | ||||
| #   #* .. - | ||||
| # . ## ## - | ||||
| # .. # .. # | ||||
| # .. 1 .. # | ||||
| # .. # ** # | ||||
| ########### | ||||
| @@ -1,83 +0,0 @@ | ||||
| import architecture from "./architecture.txt?raw"; | ||||
| import provinces from "./provinces.txt?raw"; | ||||
| import {bat2, lore1, stare0} from "../../skills.ts"; | ||||
| import {compileNewMap} from "../../newmap.ts"; | ||||
| import {compile} from "../../vnscene.ts"; | ||||
|  | ||||
| const mapZoo = compileNewMap({ | ||||
|   id: "mapZoo", | ||||
|   data: { | ||||
|     architecture: architecture, | ||||
|     provinces: provinces, | ||||
|   }, | ||||
|   pickups: { | ||||
|     // "*": itemGecko, | ||||
|     stat: {primary: "AGI", secondary: "PSI"}, | ||||
|     // "!": {"item": "colonialGoose"}, | ||||
|   }, | ||||
|   provinces: { | ||||
|     a: "Zoo", | ||||
|     b: "Gator Pen", | ||||
|     c: "Food Storage" | ||||
|   }, | ||||
|   checks: { | ||||
|     1: { | ||||
|       label: | ||||
|         "The gator pen appears to be locked. " + | ||||
|         "Some bats behind the barred gate are amusing " + | ||||
|         "themselves by swooping and darting just out of the alligators' reach.", | ||||
|       options: [ | ||||
|         { | ||||
|           skills: () => [lore1], | ||||
|           locked: "That wall sure does look impenetrable.", | ||||
|           unlockable: "I see a failure in the construction.", | ||||
|           unlockScene: compile([ | ||||
|             "I dig my clawed fingers into a crack between the bricks and feel the concrete give way.", | ||||
|             "This structure, built by mortals, is impermanent. Soon none of it will exist.", | ||||
|             "I rip another clump of brittle earth from the crack, no longer invisible.", | ||||
|             "And another. When the gap's wide enough to crawl through, I climb in." | ||||
|           ]) | ||||
|         }, | ||||
|         { | ||||
|           skills: () => [stare0], | ||||
|           locked: "The bats are happy by themselves.", | ||||
|           unlockable: "These bats could be enjoying themselves so much more.", | ||||
|           unlockScene: compile([ | ||||
|             "I hold my face to the bars. One bat looks at me.", | ||||
|             "\"Here, little bat\" -- I think before I say it. No, relate to it as an equal.", | ||||
|             "What does it really want?", | ||||
|             "What does any mortal want? It wants to feel a whole lot better --", | ||||
|             "So stare --", | ||||
|             "...", | ||||
|             "... Now it breaks. Like any mortal, but with a loud, high-pitched squeal.", | ||||
|             "Still dripping, it flaps unsurely to the door and -- with only a little coaxing -- it opens the lock." | ||||
|           ]) | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     2: { | ||||
|       label: | ||||
|         "The unattended food storage cabinet is secure. " + | ||||
|         "The shiny surface, which I lack a reflection in, repels direct attack. " + | ||||
|         "Through the keyhole I can see something delicious.", | ||||
|       options: [ | ||||
|         { | ||||
|           skills: () => [bat2], | ||||
|           locked: "If only were smaller...", | ||||
|           unlockable: "I am small enough to crawl through.", | ||||
|           unlockScene: compile([ | ||||
|             "There's a bat inside me. I guess it's like that with every vampire.", | ||||
|             "I can fight, if I try, to keep it inside. But that's not how we want it.", | ||||
|             "And in the second before I let it out -- as the soft fur behind my ears begins to sprout -- I feel a sense of relief.", | ||||
|             "A sense of gratitude.", | ||||
|             "I can't resist it now. It's urgent. I shed the rest of my mass.", | ||||
|             "Now I'm as sleek and narrow as a dart --", | ||||
|             "I'm inside." | ||||
|           ]) | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   } | ||||
| }) | ||||
|  | ||||
| export default mapZoo; | ||||
| @@ -1,11 +0,0 @@ | ||||
| ########### | ||||
| #cccc#aaaa# | ||||
| #cccc#aaaa# | ||||
| #cccc#aaaa# | ||||
| ## ###a##a- | ||||
| #bbb#aaaaa- | ||||
| #bbb##a##a- | ||||
| #bbbb#aaaa# | ||||
| #bbbb aaaa# | ||||
| #bbbb#aaaa# | ||||
| ########### | ||||
							
								
								
									
										150
									
								
								src/pickups.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/pickups.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| import {getThralls, Thrall} from "./thralls.ts"; | ||||
| import {CellView, CheckData} from "./newmap.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| import {getHuntMode, HuntMode, initHuntMode} from "./huntmode.ts"; | ||||
| import {generateMap} from "./mapgen.ts"; | ||||
| import {ALL_STATS, Stat} from "./datatypes.ts"; | ||||
| import {D} from "./engine/public.ts"; | ||||
| import {sprLadder, sprLock, sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; | ||||
| import {GridArt} from "./gridart.ts"; | ||||
| import {getCheckModal} from "./checkmodal.ts"; | ||||
| import {Point} from "./engine/datatypes.ts"; | ||||
|  | ||||
| export type Pickup | ||||
|   = LockPickup | ||||
|   | StatPickup | ||||
|   | ExperiencePickup | ||||
|   | LadderPickup | ||||
|   | ThrallPickup | ||||
|  | ||||
| export class LockPickup { | ||||
|   check: CheckData; | ||||
|  | ||||
|   constructor(check: CheckData) { | ||||
|     this.check = check; | ||||
|   } | ||||
|  | ||||
|   computeCostToClick() { return 0; } | ||||
|  | ||||
|   isObstructive() { return true; } | ||||
|  | ||||
|   drawFloor() { } | ||||
|   drawInAir(gridArt: GridArt) { | ||||
|     for (let z = 0; z < 5; z += 0.25) { | ||||
|       D.drawSprite(sprLock, gridArt.project(z), 0, { | ||||
|         xScale: 2.0, | ||||
|         yScale: 2.0, | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   onClick(cell: CellView): boolean { | ||||
|     getCheckModal().show(this.check, () => cell.pickup = null); | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class StatPickup { | ||||
|   stat: Stat; | ||||
|  | ||||
|   constructor(stat: Stat) { | ||||
|     this.stat = stat; | ||||
|   } | ||||
|  | ||||
|   computeCostToClick() { return 100; } | ||||
|  | ||||
|   isObstructive() { return true; } | ||||
|  | ||||
|   drawFloor() { } | ||||
|   drawInAir(gridArt: GridArt) { | ||||
|     let statIndex = ALL_STATS.indexOf(this.stat); | ||||
|     if (statIndex == -1) { return; } | ||||
|  | ||||
|     D.drawSprite( | ||||
|       sprStatPickup, | ||||
|       gridArt.project(5), | ||||
|       statIndex, | ||||
|       { | ||||
|         xScale: 2, | ||||
|         yScale: 2, | ||||
|       } | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   onClick(): boolean { | ||||
|     getPlayerProgress().add(this.stat, 1); | ||||
|     getPlayerProgress().purloinItem(); | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class ExperiencePickup { | ||||
|   computeCostToClick() { return 100; } | ||||
|  | ||||
|   isObstructive() { return true; } | ||||
|  | ||||
|   drawFloor() { } | ||||
|   drawInAir(gridArt: GridArt) { | ||||
|     D.drawSprite( | ||||
|       sprResourcePickup, | ||||
|       gridArt.project(0.0).offset(new Point(0, -16)), | ||||
|       0, | ||||
|       { | ||||
|         xScale: 2, | ||||
|         yScale: 2, | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   onClick(): boolean { | ||||
|     getPlayerProgress().addExperience(250); | ||||
|     getPlayerProgress().purloinItem(); | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class LadderPickup { | ||||
|   computeCostToClick() { return 0; } | ||||
|  | ||||
|   isObstructive() { return false; } | ||||
|  | ||||
|   drawFloor(gridArt: GridArt) { | ||||
|     D.drawSprite(sprLadder, gridArt.project(0.0), 0, { | ||||
|       xScale: 2.0, | ||||
|       yScale: 2.0, | ||||
|     }) | ||||
|   } | ||||
|   drawInAir() { } | ||||
|  | ||||
|   onClick(): boolean { | ||||
|     getPlayerProgress().addBlood(1000); | ||||
|     initHuntMode(new HuntMode(getHuntMode().depth + 1, generateMap())); | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class ThrallPickup { | ||||
|   thrall: Thrall; | ||||
|  | ||||
|   constructor(thrall: Thrall) { | ||||
|     this.thrall = thrall; | ||||
|   } | ||||
|  | ||||
|   computeCostToClick() { return 0; } | ||||
|  | ||||
|   isObstructive() { return false; } | ||||
|  | ||||
|   drawFloor() { } | ||||
|   drawInAir(gridArt: GridArt) { | ||||
|     D.drawSprite(sprRaccoonWalking, gridArt.project(0.0), 0, { | ||||
|       xScale: 2.0, | ||||
|       yScale: 2.0, | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   onClick(cell: CellView): boolean { | ||||
|     let data = getThralls().get(this.thrall); | ||||
|     getCheckModal().show(data.initialCheck, () => cell.pickup = null); | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
| @@ -42,6 +42,10 @@ export type ThrallData = { | ||||
|  | ||||
| let table = new ThrallsTable(); | ||||
|  | ||||
| export function getThralls() { | ||||
|   return table; | ||||
| } | ||||
|  | ||||
| // Thralls are labeled by which zone's item they like | ||||
| // Their initial check is, generally, the initial check of the | ||||
| // thrall n-2 or thrall n+1 (ex: Party's initial check is Stealth | ||||
|   | ||||
| @@ -19,11 +19,13 @@ import { | ||||
|   stealth2 | ||||
| } from "./skills.ts"; | ||||
| import {CheckData} from "./newmap.ts"; | ||||
| import {Thrall, thrallBat, thrallCharm, thrallLore, thrallParty, thrallStare, thrallStealth} from "./thralls.ts"; | ||||
|  | ||||
|  | ||||
|  | ||||
| export type VaultTemplate = { | ||||
|   stats: {primary: Stat, secondary: Stat}, | ||||
|   thrall: () => Thrall, | ||||
|   checks: [CheckData, CheckData] | ||||
| } | ||||
|  | ||||
| @@ -31,6 +33,7 @@ export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // zoo | ||||
|     stats: {primary: "AGI", secondary: "PSI"}, | ||||
|     thrall: () => thrallParty, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "You're blocked from further access by a sturdy-looking brick wall. Playful bats swoop close to the alligators behind the bars.", | ||||
| @@ -63,6 +66,7 @@ export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // blood bank | ||||
|     stats: {primary: "AGI", secondary: "INT"}, | ||||
|     thrall: () => thrallLore, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "The nice old lady at the counter says you can't have any blood without a doctor's note.", | ||||
| @@ -98,6 +102,7 @@ export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // coffee shop | ||||
|     stats: {primary: "PSI", secondary: "CHA"}, | ||||
|     thrall: () => thrallBat, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "You don't actually drink coffee, so you probably wouldn't fit in inside.", | ||||
| @@ -130,6 +135,7 @@ export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // optometrist | ||||
|     stats: {primary: "PSI", secondary: "PSI"}, | ||||
|     thrall: () => thrallCharm, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "The glasses person doesn't have time for you unless you have a prescription that needs filling.", | ||||
| @@ -162,6 +168,7 @@ export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // club, | ||||
|     stats: {primary: "CHA", secondary: "PSI"}, | ||||
|     thrall: () => thrallStealth, | ||||
|     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.", | ||||
| @@ -194,6 +201,7 @@ export const standardVaultTemplates: VaultTemplate[] = [ | ||||
|   { | ||||
|     // library | ||||
|     stats: {primary: "INT", secondary: "CHA"}, | ||||
|     thrall: () => thrallStare, | ||||
|     checks: [ | ||||
|       { | ||||
|         label: "Special Collections. This guy is not just a librarian -- he's a vampire, too -- which he makes no effort to hide.", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user