diff --git a/src/art/pickups/resources.png b/src/art/pickups/resources.png index 6db5d47..bc989aa 100644 Binary files a/src/art/pickups/resources.png and b/src/art/pickups/resources.png differ diff --git a/src/button.ts b/src/button.ts index 57f2659..675d1bd 100644 --- a/src/button.ts +++ b/src/button.ts @@ -57,6 +57,6 @@ export function addButton( }, new Rect(topLeftPadded, sizePadded), enabled, - cbClick, + {onClick: cbClick}, ); } diff --git a/src/drawpile.ts b/src/drawpile.ts index ac87ec8..5bfe804 100644 --- a/src/drawpile.ts +++ b/src/drawpile.ts @@ -1,8 +1,13 @@ import { D, I } from "./engine/public.ts"; import { Rect } from "./engine/datatypes.ts"; +export type Handlers = { + onClick?: () => void, + onSqueeze?: () => void, +} + export class DrawPile { - #draws: { depth: number; op: () => void; onClick?: () => void }[]; + #draws: { depth: number; op: () => void; handlers?: Handlers, }[]; #hoveredIndex: number | null; constructor() { @@ -24,7 +29,10 @@ export class DrawPile { op: (hover: boolean) => void, rect: Rect, enabled: boolean, - onClick: () => void, + handlers: { + onClick?: () => void, + onSqueeze?: () => void, + } ) { let position = I.mousePosition?.offset(D.camera); let hovered = false; @@ -37,14 +45,24 @@ export class DrawPile { if (hovered) { this.#hoveredIndex = this.#draws.length; } - this.#draws.push({ depth, op: () => op(hovered), onClick: onClick }); + this.#draws.push({ depth, op: () => op(hovered), handlers }); } executeOnClick() { if (I.isMouseClicked("leftMouse")) { let hi = this.#hoveredIndex; if (hi != null) { - let cb = this.#draws[hi]?.onClick; + let cb = this.#draws[hi]?.handlers?.onClick; + if (cb != null) { + cb(); + } + } + } + + if (I.isMouseDown("leftMouse")) { + let hi = this.#hoveredIndex; + if (hi != null) { + let cb = this.#draws[hi]?.handlers?.onSqueeze; if (cb != null) { cb(); } diff --git a/src/endgamemodal.ts b/src/endgamemodal.ts index 3f958d9..e98df86 100644 --- a/src/endgamemodal.ts +++ b/src/endgamemodal.ts @@ -341,11 +341,13 @@ export class EndgameModal { generalRect, enabled, - () => { - if (this.#selectedSuccessor == ix) { - this.#selectedSuccessor = null; - } else { - this.#selectedSuccessor = ix; + { + onClick: () => { + if (this.#selectedSuccessor == ix) { + this.#selectedSuccessor = null; + } else { + this.#selectedSuccessor = ix; + } } }, ); @@ -399,14 +401,16 @@ export class EndgameModal { }, generalRect, enabled, + { - () => { - if (this.#selectedWish == ix) { - this.#selectedWish = null; - } else { - this.#selectedWish = ix; - } - }, + onClick: () => { + if (this.#selectedWish == ix) { + this.#selectedWish = null; + } else { + this.#selectedWish = ix; + } + }, + } ); } diff --git a/src/engine/internal/drawing.ts b/src/engine/internal/drawing.ts index dc64a17..2b2b014 100644 --- a/src/engine/internal/drawing.ts +++ b/src/engine/internal/drawing.ts @@ -91,7 +91,7 @@ class Drawing { sprite: Sprite, position: Point, ix?: number, - options?: { xScale?: number; yScale: number; angle?: number }, + options?: { xScale?: number; yScale?: number; angle?: number }, ) { position = this.camera.negate().offset(position); diff --git a/src/huntmode.ts b/src/huntmode.ts index 76834ae..90916d0 100644 --- a/src/huntmode.ts +++ b/src/huntmode.ts @@ -115,6 +115,7 @@ export class HuntMode { ).offset(new Point(-192, -192)); this.#updateFov(); + this.#updatePickups(); for (let y = 0; y < this.map.size.h; y += 1) { for (let x = 0; x < this.map.size.w; x += 1) { @@ -156,6 +157,15 @@ export class HuntMode { ); } + #updatePickups() { + for (let y = 0; y < this.map.size.h; y++) { + for (let x = 0; x < this.map.size.w; x++) { + let cell = this.map.get(new Point(x, y)); + cell.pickup?.update(cell); + } + } + } + #inVisibilityRange(x: number, y: number): boolean { let dx = x - this.player.x; let dy = y - this.player.y; @@ -220,18 +230,29 @@ export class HuntMode { }, gridArt.floorRect, true, - () => { - if (cost == null || tooExpensive) { - return; - } - if (pickup?.onClick(cellData)) { - return; - } + { + onClick: () => { + if (cost == null || tooExpensive) { + return; + } + if (pickup?.onClick(cellData)) { + return; + } - getPlayerProgress().spendBlood(cost); - this.movePlayerTo(mapPosition); - getCheckModal().show(null, null); - }, + getPlayerProgress().spendBlood(cost); + this.movePlayerTo(mapPosition); + getCheckModal().show(null, null); + }, + onSqueeze: () => { + // the cost _gates_ squeezes + // but onSqueeze must pay the cost manually if a cost + // is to be paid + if (cost == null || tooExpensive) { + return; + } + pickup?.onSqueeze(cellData); + } + } ); if (pickup != null) { diff --git a/src/mapgen.ts b/src/mapgen.ts index 55f5068..f85f814 100644 --- a/src/mapgen.ts +++ b/src/mapgen.ts @@ -4,10 +4,9 @@ import { choose, shuffle } from "./utils.ts"; import { standardVaultTemplates, VaultTemplate } from "./vaulttemplate.ts"; import { ALL_STATS } from "./datatypes.ts"; import { - ExperiencePickup, + BreakableBlockPickup, ExperiencePickupCallbacks, LadderPickup, - LockPickup, - StatPickup, + LockPickup, StatPickupCallbacks, ThrallItemPickup, ThrallPickup, } from "./pickups.ts"; @@ -279,21 +278,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 = new StatPickup(stat); + knife.map.get(xy).pickup = new BreakableBlockPickup(new StatPickupCallbacks(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 = new StatPickup(vaultTemplate.stats.primary); + knife.map.get(xy).pickup = new BreakableBlockPickup(new StatPickupCallbacks(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 = new StatPickup(vaultTemplate.stats.primary); + knife.map.get(xy).pickup = new BreakableBlockPickup(new StatPickupCallbacks(vaultTemplate.stats.primary)); } } @@ -337,7 +336,7 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { let cell = knife.map.get(goodie); if (a.contains(goodie)) { - cell.pickup = new ExperiencePickup(); + cell.pickup = new BreakableBlockPickup(new ExperiencePickupCallbacks()); let thrall = vaultTemplate.thrall(); if (!getPlayerProgress().isThrallUnlocked(thrall)) { cell.pickup = new ThrallPickup(thrall); @@ -345,16 +344,16 @@ function carveVault(knife: Knife, room: Rect, vaultTemplate: VaultTemplate) { } if (b.contains(goodie)) { - cell.pickup = new ExperiencePickup(); + cell.pickup = new BreakableBlockPickup(new ExperiencePickupCallbacks()); } if (c.contains(goodie)) { - cell.pickup = new ExperiencePickup(); + cell.pickup = new BreakableBlockPickup(new ExperiencePickupCallbacks()); // TODO: Fill this room with the common item for this room } if (d.contains(goodie)) { - cell.pickup = new ExperiencePickup(); + cell.pickup = new BreakableBlockPickup(new ExperiencePickupCallbacks()); // replace with a fancy item if nothing is eligible let thrallItem = vaultTemplate.thrallItem(); @@ -395,10 +394,10 @@ function carveRoom(knife: Knife, room: Rect, label?: string) { new Point(room.size.w - dx - 1, room.size.h - dy - 1), ); let stat = choose(ALL_STATS); - 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); + knife.map.get(xy0).pickup = new BreakableBlockPickup(new StatPickupCallbacks(stat)); + knife.map.get(xy1).pickup = new BreakableBlockPickup(new StatPickupCallbacks(stat)); + knife.map.get(xy2).pickup = new BreakableBlockPickup(new StatPickupCallbacks(stat)); + knife.map.get(xy3).pickup = new BreakableBlockPickup(new StatPickupCallbacks(stat)); } } } diff --git a/src/pickups.ts b/src/pickups.ts index 19b4408..84feafb 100644 --- a/src/pickups.ts +++ b/src/pickups.ts @@ -19,8 +19,7 @@ import { FG_TEXT } from "./colors.ts"; export type Pickup = | LockPickup - | StatPickup - | ExperiencePickup + | BreakableBlockPickup | LadderPickup | ThrallPickup | ThrallPosterPickup @@ -59,17 +58,24 @@ export class LockPickup { } } + update() { } + onClick(cell: CellView): boolean { getCheckModal().show(this.check, () => (cell.pickup = null)); return true; } + + onSqueeze() { } } -export class StatPickup { - stat: Stat; +const RECOVERY_PER_TICK: number = 0.10; +export class BreakableBlockPickup { + callbacks: StatPickupCallbacks | ExperiencePickupCallbacks; + breakProgress: number; - constructor(stat: Stat) { - this.stat = stat; + constructor(callbacks: StatPickupCallbacks | ExperiencePickupCallbacks) { + this.callbacks = callbacks; + this.breakProgress = 0.0; } computeCostToClick() { @@ -90,58 +96,69 @@ export class StatPickup { drawFloor() {} drawInAir(gridArt: GridArt) { - let statIndex = ALL_STATS.indexOf(this.stat); + let progress = Math.pow(this.breakProgress, 2.15); + let extraMult = 1.0; + let angleRange = 0; + if (progress != 0) { + extraMult = 1.2; + angleRange = 10; + } + + this.callbacks.draw(gridArt.project(5), { + xScale: 2 * (1.0 - progress * 0.7) * extraMult, + yScale: 2 * (1.0 - progress * 0.7) * extraMult, + angle: (2 * progress - 1) * angleRange, + }); + } + + update(cellData: CellView) { + if (this.breakProgress >= 1.0) { + cellData.pickup = null; + this.callbacks.obtain(); + } + + 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; } + + obtain() { + getPlayerProgress().add(this.#stat, 1); + getPlayerProgress().purloinItem(); + } + + draw(at: Point, options: {xScale?: number, yScale?: number, angle?: number}) { + 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; + D.drawSprite(sprStatPickup, at, statIndex, options); } } -export class ExperiencePickup { - computeCostToClick() { - return 100; - } +export class ExperiencePickupCallbacks { + constructor() { } - advertisesBadge() { - return false; - } - - advertisesClickable() { - return true; - } - - 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 { + obtain() { getPlayerProgress().addExperience(250); getPlayerProgress().purloinItem(); - return false; + } + + draw(at: Point, options: {xScale?: number, yScale?: number, angle?: number}) { + D.drawSprite(sprResourcePickup, at, 0, options); } } @@ -170,11 +187,15 @@ export class LadderPickup { } drawInAir() {} + update() { } + onClick(): boolean { getPlayerProgress().addBlood(1000); initHuntMode(new HuntMode(getHuntMode().depth + 1, generateMap())); return false; } + + onSqueeze() { } } export class ThrallPickup { @@ -209,6 +230,8 @@ export class ThrallPickup { }); } + update() { } + onClick(cell: CellView): boolean { let data = getThralls().get(this.thrall); getCheckModal().show(data.initialCheck, () => { @@ -217,6 +240,8 @@ export class ThrallPickup { }); return true; } + + onSqueeze() { } } export class ThrallPosterPickup { @@ -251,11 +276,15 @@ export class ThrallPosterPickup { }); } + update() { } + onClick(cell: CellView): boolean { let data = getThralls().get(this.thrall); getCheckModal().show(data.posterCheck, () => (cell.pickup = null)); return true; } + + onSqueeze() { } } export class ThrallRecruitedPickup { @@ -306,6 +335,8 @@ export class ThrallRecruitedPickup { }); } + update() { } + onClick(_cell: CellView): boolean { this.spokenTo = true; if (this.bitten) { @@ -352,6 +383,8 @@ export class ThrallRecruitedPickup { ); return true; } + + onSqueeze() { } } export class ThrallCollectionPlatePickup { @@ -415,6 +448,8 @@ export class ThrallCollectionPlatePickup { } } + update() { } + onClick(_cell: CellView): boolean { let lifeStage = getPlayerProgress().getThrallLifeStage(this.thrall); let itemStage = getPlayerProgress().getThrallItemStage(this.thrall); @@ -472,6 +507,8 @@ export class ThrallCollectionPlatePickup { // getCheckModal().show(this.check, () => (cell.pickup = null)); return true; } + + onSqueeze() { } } export class ThrallItemPickup { @@ -507,6 +544,8 @@ export class ThrallItemPickup { }); } + update() { } + onClick(cell: CellView): boolean { let data = getThralls().get(this.thrall); @@ -521,4 +560,6 @@ export class ThrallItemPickup { getPlayerProgress().obtainThrallItem(this.thrall); return true; } + + onSqueeze() { } } diff --git a/src/skillsmodal.ts b/src/skillsmodal.ts index dc6e627..a8cb43c 100644 --- a/src/skillsmodal.ts +++ b/src/skillsmodal.ts @@ -99,9 +99,11 @@ export class SkillsModal { }, skillRect, enabled, - () => { - this.#skillSelection = skill; - }, + { + onClick: () => { + this.#skillSelection = skill; + }, + } ); y += 16; }); diff --git a/src/sprites.ts b/src/sprites.ts index 5b916c2..54b897d 100644 --- a/src/sprites.ts +++ b/src/sprites.ts @@ -1,6 +1,5 @@ import { Sprite } from "./engine/internal/sprite.ts"; -import imgRaccoon from "./art/characters/raccoon.png"; import imgResourcePickup from "./art/pickups/resources.png"; import imgStatPickup from "./art/pickups/stats.png"; import imgLadder from "./art/pickups/ladder.png"; @@ -14,13 +13,6 @@ import imgThrallParty from "./art/thralls/thrall_party.png"; import imgThrallStare from "./art/thralls/thrall_stare.png"; import imgThrallStealth from "./art/thralls/thrall_stealth.png"; -export let sprRaccoon = new Sprite( - imgRaccoon, - new Size(64, 64), - new Point(32, 32), - new Size(1, 1), - 1, -); export let sprResourcePickup = new Sprite( imgResourcePickup, new Size(32, 32),