Map refactor -- thralls are clickable

This commit is contained in:
Pyrex 2025-02-17 14:17:50 -08:00
parent 5785a27565
commit 3a6590a942
30 changed files with 196 additions and 700 deletions

View File

@ -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) {

View File

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

View File

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

View File

@ -1,11 +0,0 @@
###########
# # # #
# # 2 #
- # # #
# # # #
# ##### #
# 1 #
# #####
# #
##### #
###########

View File

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

View File

@ -1,11 +0,0 @@
###########
#aa#ccc#bb#
#aa#ccc bb#
-aa#ccc#bb#
#aa#ccc#bb#
#aa#####bb#
#aaaaaa bb#
#aaaaa#####
#aaaaaaaaa#
#####aaaaa#
###########

View File

@ -1,11 +0,0 @@
###########
# #
# #
######## #
# 1 #
# ##### -
# # # #
# # # #
# 2 # #
# # # #
###########

View File

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

View File

@ -1,11 +0,0 @@
###########
#aaaaaaaaa#
#aaaaaaaaa#
########aa#
#bbbbbb aa#
#bb#####aa-
#bb#ccc#aa#
#bb#ccc#aa#
#bb ccc#aa#
#bb#ccc#aa#
###########

View File

@ -1,11 +0,0 @@
###########
# # #
# 2 #
# # #
# #####1#
# # #
# # #
###### #
# #
# #
###### ####

View File

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

View File

@ -1,11 +0,0 @@
###########
#ccc#bbbbb#
#ccc bbbbb#
#ccc#bbbbb#
#ccc##### #
#cccc#aaaa#
#cccc#aaaa#
######aaaa#
#aaaaaaaaa#
#aaaaaaaaa#
###### ####

View File

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

View File

@ -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

View File

@ -1,11 +0,0 @@
###########
# # #
# # #
- # #
# ######2##
# 1 #
# # #
# # #
# # #
# # #
###########

View File

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

View File

@ -1,11 +0,0 @@
###########
#aa#cccccc#
#aa#cccccc#
-aa#cccccc#
#a###### ##
#aa bbbbbb#
#aa#bbbbbb#
#aa#bbbbbb#
#aa#bbbbbb#
#aa#bbbbbb#
###########

View File

@ -1,11 +0,0 @@
#### ####
# # # #
# ## ## #
# # # #
# #
##### #####
# # # #
# #
# @ #
# #
###########

View File

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

View File

@ -1,11 +0,0 @@
#### ####
#bb# #cc#
#bb## ##cc#
#bbb# #ccc#
#bbb ccc#
##### #####
#aaa# #aaa#
#aaaaaaaaa#
#aaaaaaaaa#
#aaaaaaaaa#
###########

View File

@ -1,11 +0,0 @@
###########
# #
# #
# #
# ### #
##2## #####
# # #
# # #
# ##1# # #
# # # #
######### #

View File

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

View File

@ -1,11 +0,0 @@
###########
#ccccccccc#
#ccccccccc#
#ccccccccc#
#ccc###ccc#
## ## #####
#bbbbb#aaa#
#bbbbb#aaa#
#bb## #a#a#
#bb#aaaa#a#
######### #

View File

@ -1,11 +0,0 @@
###########
# # ** #
# ! # .. #
# # .. #
##2### ## -
# #* .. -
# . ## ## -
# .. # .. #
# .. 1 .. #
# .. # ** #
###########

View File

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

View File

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

View File

@ -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

View File

@ -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.",