fledgling/src/pickups.ts
2025-02-22 21:08:03 -08:00

747 lines
15 KiB
TypeScript

import { getThralls, ItemStage, LifeStage, 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 {
sprCollectibles,
sprCollectiblesSilhouettes,
sprLadder,
sprLock,
} from "./sprites.ts";
import { GridArt } from "./gridart.ts";
import { getCheckModal } from "./checkmodal.ts";
import { Point, Size } from "./engine/datatypes.ts";
import { choose } from "./utils.ts";
import { FG_BOLD, FG_TEXT, SWATCH_EXP, SWATCH_STAT } from "./colors.ts";
import { Block3D } from "./world3d.ts";
import { DrawPile } from "./drawpile.ts";
import { Floater } from "./floater.ts";
export type Pickup =
| LockPickup
| BreakableBlockPickup
| LadderPickup
| ThrallPickup
| ThrallPosterPickup
| ThrallRecruitedPickup;
export class LockPickup {
check: CheckData;
constructor(check: CheckData) {
this.check = check;
}
computeCostToClick() {
return 0;
}
advertisesBadge() {
return false;
}
advertisesClickable() {
return true;
}
blocksMovement() {
return true;
}
obstructsVision() {
return true;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(0, () => {
for (let z = 0; z < 5; z += 0.25) {
D.drawSprite(sprLock, gridArt.project(z), 0, {
xScale: 2.0,
yScale: 2.0,
});
}
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(cell: CellView): boolean {
getCheckModal().show(this.check, () => (cell.pickup = null));
return true;
}
onSqueeze() {}
}
export type BreakableBlockPickupCallbacks =
| StatPickupCallbacks
| ExperiencePickupCallbacks;
const RECOVERY_PER_TICK: number = 0.1;
export class BreakableBlockPickup {
callbacks: BreakableBlockPickupCallbacks;
breakProgress: number;
constructor(callbacks: BreakableBlockPickupCallbacks) {
this.callbacks = callbacks;
this.breakProgress = 0.0;
}
computeCostToClick() {
return this.callbacks.cost;
}
advertisesBadge() {
return false;
}
advertisesClickable() {
return true;
}
blocksMovement() {
return true;
}
obstructsVision() {
return true;
}
get #adjustedProgress(): number {
return Math.pow(this.breakProgress, 2.15);
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(384, () => {
let progress = this.#adjustedProgress;
let extraMult = 1.0;
let angleRange = 0;
if (progress != 0) {
extraMult = 1.2;
angleRange = 10;
}
this.callbacks.draw(gridArt, {
xScale: 2 * (1.0 - progress * 0.7) * extraMult,
yScale: 2 * (1.0 - progress * 0.7) * extraMult,
angle: (2 * progress - 1) * angleRange,
});
});
}
getBlock(): Block3D | null {
return this.callbacks.getBlock(this.breakProgress);
}
update(cellData: CellView) {
if (this.breakProgress >= 1.0) {
getPlayerProgress().spendBlood(this.callbacks.cost);
cellData.pickup = null;
let n = choose([1, 1, 1, 1, 1, 2, 3]);
for (let i = 0; i < n; i++) {
let floater = new Floater(
cellData.xy.offset(new Point(0.5, 0.5)),
50,
this.callbacks,
);
let speed = 0.015;
let direction = Math.random() * Math.PI * 2;
floater.velocity = new Point(
Math.cos(direction) * speed,
Math.sin(direction) * speed * 0.1,
);
floater.velZ = 0.8;
getHuntMode().spawnFloater(floater);
}
}
this.breakProgress = Math.max(0.0, this.breakProgress - RECOVERY_PER_TICK);
}
onClick(): boolean {
return true;
}
onSqueeze(_cellData: CellView) {
this.breakProgress = Math.min(
this.breakProgress + 0.02 + RECOVERY_PER_TICK,
1.0,
);
}
}
export class StatPickupCallbacks {
#stat: Stat;
constructor(stat: Stat) {
this.#stat = stat;
}
get cost(): number {
return 100;
}
obtain() {
getPlayerProgress().add(this.#stat, 1);
getPlayerProgress().purloinItem();
}
getBlock(progress: number) {
return new Block3D(
progress > 0.6 ? FG_BOLD : SWATCH_STAT[this.#stat][1],
progress > 0.6 ? FG_TEXT : SWATCH_STAT[this.#stat][0],
progress > 0.6 ? FG_BOLD : SWATCH_STAT[this.#stat][1],
);
}
draw(
gridArt: GridArt,
options: { xScale?: number; yScale?: number; angle?: number },
) {
let statIndex = ALL_STATS.indexOf(this.#stat);
if (statIndex == -1) {
return;
}
let at = gridArt.project(100);
D.drawSprite(sprCollectiblesSilhouettes, at, statIndex, options);
}
drawParticle(at: Point, isShadow: boolean, rotation: number) {
let statIndex = ALL_STATS.indexOf(this.#stat);
if (statIndex == -1) {
return;
}
D.drawSprite(
isShadow ? sprCollectiblesSilhouettes : sprCollectibles,
at,
statIndex,
{ xScale: 2 * Math.cos(rotation), yScale: 2 },
);
}
}
export class ExperiencePickupCallbacks {
constructor() {}
get cost(): number {
return 100;
}
obtain() {
getPlayerProgress().addExperience(250);
getPlayerProgress().purloinItem();
}
getBlock(progress: number) {
return new Block3D(
progress > 0.6 ? FG_BOLD : SWATCH_EXP[1],
progress > 0.6 ? FG_TEXT : SWATCH_EXP[0],
progress > 0.6 ? FG_BOLD : SWATCH_EXP[1],
);
}
draw(
gridArt: GridArt,
options: { xScale?: number; yScale?: number; angle?: number },
) {
let at = gridArt.project(100);
D.drawSprite(sprCollectiblesSilhouettes, at, 4, options);
}
drawParticle(at: Point, isShadow: boolean, rotation: number) {
D.drawSprite(
isShadow ? sprCollectiblesSilhouettes : sprCollectibles,
at,
4,
{ xScale: 2 * Math.cos(rotation), yScale: 2 },
);
}
}
export class LadderPickup {
computeCostToClick() {
return 0;
}
advertisesBadge() {
return false;
}
advertisesClickable() {
return true;
}
blocksMovement() {
return true;
}
obstructsVision() {
return false;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(-128, () => {
D.drawSprite(sprLadder, gridArt.project(0.0), 0, {
xScale: 2.0,
yScale: 2.0,
});
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(): boolean {
getPlayerProgress().addBlood(1000);
initHuntMode(new HuntMode(getHuntMode().depth + 1, generateMap()));
return false;
}
onSqueeze() {}
}
export class ThrallPickup {
thrall: Thrall;
constructor(thrall: Thrall) {
this.thrall = thrall;
}
computeCostToClick() {
return 0;
}
advertisesBadge() {
return false;
}
advertisesClickable() {
return true;
}
blocksMovement() {
return true;
}
obstructsVision() {
return false;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(0, () => {
let data = getThralls().get(this.thrall);
D.drawSprite(data.sprite, gridArt.project(0.0), 0, {
xScale: 2.0,
yScale: 2.0,
});
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(cell: CellView): boolean {
let data = getThralls().get(this.thrall);
getCheckModal().show(data.initialCheck, () => {
getPlayerProgress().unlockThrall(this.thrall);
cell.pickup = null;
});
return true;
}
onSqueeze() {}
}
export class ThrallPosterPickup {
thrall: Thrall;
constructor(thrall: Thrall) {
this.thrall = thrall;
}
computeCostToClick() {
return 0;
}
advertisesBadge() {
return false;
}
advertisesClickable() {
return true;
}
blocksMovement() {
return true;
}
obstructsVision() {
return false;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(0, () => {
let data = getThralls().get(this.thrall);
D.drawSprite(data.sprite, gridArt.project(0.0), 2, {
xScale: 2.0,
yScale: 2.0,
});
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(cell: CellView): boolean {
let data = getThralls().get(this.thrall);
getCheckModal().show(data.posterCheck, () => (cell.pickup = null));
return true;
}
onSqueeze() {}
}
export class ThrallRecruitedPickup {
thrall: Thrall;
spokenTo: boolean;
bitten: boolean;
constructor(thrall: Thrall) {
this.thrall = thrall;
this.spokenTo = false;
this.bitten = false;
}
computeCostToClick() {
return 0;
}
advertisesBadge() {
return !this.spokenTo;
}
advertisesClickable() {
return !this.bitten;
}
blocksMovement() {
return true;
}
obstructsVision() {
return false;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(0, () => {
let data = getThralls().get(this.thrall);
let lifeStage = getPlayerProgress().getThrallLifeStage(this.thrall);
let ix = 0;
let rot = 0;
if (lifeStage == LifeStage.Vampirized) {
ix = 1;
}
if (lifeStage == LifeStage.Dead) {
ix = 1;
rot = 270;
}
D.drawSprite(data.sprite, gridArt.project(0.0), ix, {
xScale: 2.0,
yScale: 2.0,
angle: rot,
});
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(_cell: CellView): boolean {
this.spokenTo = true;
if (this.bitten) {
return true;
}
let data = getThralls().get(this.thrall);
let lifeStage = getPlayerProgress().getThrallLifeStage(this.thrall);
let text = data.lifeStageText[lifeStage];
getCheckModal().show(
{
label: `${text.prebite}`,
options: [
{
isChoice: true,
countsAsSuccess: true,
unlockable: "Bite!",
success: text.postbite,
},
{
isChoice: true,
countsAsSuccess: false,
unlockable: "Refrain",
success: "Maybe next time.",
},
],
},
() => {
this.bitten = true;
getPlayerProgress().addBlood(
lifeStage == LifeStage.Fresh
? 1000
: lifeStage == LifeStage.Average
? 500
: lifeStage == LifeStage.Poor
? 300
: lifeStage == LifeStage.Vampirized
? 1500 // lethal bite
: // lifeStage == LifeStage.Dead ?
100,
);
getPlayerProgress().damageThrall(this.thrall, choose([0.9]));
},
);
return true;
}
onSqueeze() {}
}
export class ThrallCollectionPlatePickup {
thrall: Thrall;
rewarded: boolean;
constructor(thrall: Thrall) {
this.thrall = thrall;
this.rewarded = false;
}
computeCostToClick() {
return 0;
}
advertisesBadge() {
let lifeStage = getPlayerProgress().getThrallLifeStage(this.thrall);
let itemStage = getPlayerProgress().getThrallItemStage(this.thrall);
if (itemStage == ItemStage.Obtained) {
// the player should deliver it! make sure they see a badge informing them of that
return true;
}
if (
itemStage == ItemStage.Delivered &&
lifeStage != LifeStage.Dead &&
!this.rewarded
) {
// the player should collect it! make sure they see a badge informing them of that
return true;
}
// OK, the only interaction is to get a hint
return false;
}
advertisesClickable() {
return !this.rewarded;
}
blocksMovement() {
return false;
}
obstructsVision() {
return false;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(0, () => {
let itemStage = getPlayerProgress().getThrallItemStage(this.thrall);
let data = getThralls().get(this.thrall);
if (itemStage != ItemStage.Delivered) {
D.drawRect(
gridArt.project(0).offset(new Point(-18, -18)),
new Size(36, 36),
FG_TEXT,
);
} else {
D.drawSprite(data.sprite, gridArt.project(2), 3, {
xScale: 2.0,
yScale: 2.0,
});
}
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(cell: CellView): boolean {
let lifeStage = getPlayerProgress().getThrallLifeStage(this.thrall);
let itemStage = getPlayerProgress().getThrallItemStage(this.thrall);
let data = getThralls().get(this.thrall);
// if (itemStage == ItemStage.Untouched) { itemStage = ItemStage.Obtained; }
if (itemStage == ItemStage.Untouched) {
if (lifeStage == LifeStage.Dead) {
getCheckModal().show(
{
label: "There's no point in delivering this now.",
options: [],
},
null,
);
} else {
getCheckModal().show(
{
label: data.itemHint,
options: [],
},
null,
);
}
} else if (itemStage == ItemStage.Obtained) {
getPlayerProgress().deliverThrallItem(this.thrall);
if (lifeStage != LifeStage.Dead) {
getCheckModal().show(
{
label: data.deliveryMessage,
options: [],
},
null,
);
}
} else {
if (lifeStage == LifeStage.Dead) {
// nothing happens
} else if (this.rewarded) {
// nothing happens
} else {
this.rewarded = true;
getCheckModal().show(
{
label: data.rewardMessage,
options: [],
},
null,
);
data.rewardCallback((what) => this.spawn(cell.xy, what));
}
}
// getCheckModal().show(this.check, () => (cell.pickup = null));
return true;
}
spawn(xy: Point, what: Stat | "EXP") {
let callbacks = null;
if (what == "EXP") {
callbacks = new ExperiencePickupCallbacks();
} else {
callbacks = new StatPickupCallbacks(what);
}
if (callbacks == null) { return; }
let floater = new Floater(
xy.offset(new Point(0.5, 0.5)),
50,
callbacks,
);
let speed = 0.015;
let direction = Math.random() * Math.PI * 2;
floater.velocity = new Point(
Math.cos(direction) * speed,
Math.sin(direction) * speed * 0.1,
);
floater.velZ = 0.8;
getHuntMode().spawnFloater(floater);
}
onSqueeze() {}
}
export class ThrallItemPickup {
thrall: Thrall;
constructor(thrall: Thrall) {
this.thrall = thrall;
}
computeCostToClick() {
return 0;
}
advertisesBadge() {
return false;
}
advertisesClickable() {
return true;
}
blocksMovement() {
return true;
}
obstructsVision() {
return false;
}
draw(drawpile: DrawPile, gridArt: GridArt) {
drawpile.add(0, () => {
let data = getThralls().get(this.thrall);
D.drawSprite(data.sprite, gridArt.project(2), 3, {
xScale: 2.0,
yScale: 2.0,
});
});
}
getBlock(): Block3D | null {
return null;
}
update() {}
onClick(cell: CellView): boolean {
let data = getThralls().get(this.thrall);
cell.pickup = null;
getCheckModal().show(
{
label: data.itemPickupMessage,
options: [],
},
null,
);
getPlayerProgress().obtainThrallItem(this.thrall);
return true;
}
onSqueeze() {}
}