diff --git a/src/engine/datatypes.ts b/src/engine/datatypes.ts index 479e768..341065f 100644 --- a/src/engine/datatypes.ts +++ b/src/engine/datatypes.ts @@ -84,6 +84,13 @@ export class Point { return new Point(this.x * other.w, this.y * other.h); } + unscale(other: Point | Size) { + if (other instanceof Point) { + return new Point(this.x / other.x, this.y / other.y); + } + return new Point(this.x / other.w, this.y / other.h); + } + subtract(top: Point): Size { return new Size(this.x - top.x, this.y - top.y); } @@ -91,6 +98,13 @@ export class Point { manhattan(other: Point) { return Math.abs(this.x - other.x) + Math.abs(this.y - other.y); } + + snap(x: number, y: number) { + return new Point( + lerp(x, Math.floor(this.x), Math.ceil(this.x)), + lerp(y, Math.floor(this.y), Math.ceil(this.y)) + ); + } } export class Size { @@ -118,12 +132,16 @@ export class Size { export class Rect { readonly top: Point; readonly size: Size; - constructor(top: Point, size: Size) { this.top = top; this.size = size; } + toString(): string { + return `Rect(${this.top},${this.size})`; + } + + offset(offset: Point) { return new Rect(this.top.offset(offset), this.size); } @@ -137,6 +155,28 @@ export class Rect { ); } + overlappedCells(size: Size) { + let x0 = this.top.x; + let y0 = this.top.y; + let x1 = x0 + this.size.w; + let y1 = y0 + this.size.w; + + let cx0 = Math.floor(x0 / size.w); + let cy0 = Math.floor(y0 / size.h); + let cx1 = Math.ceil(x1 / size.w); + let cy1 = Math.ceil(y1 / size.h); + + let cells = []; + for (let cy = cy0; cy < cy1; cy++) { + for (let cx = cx0; cx < cx1; cx++) { + let px0 = cx * size.w; + let py0 = cy * size.h; + cells.push(new Rect(new Point(px0, py0), size)); + } + } + return cells; + } + overlaps(other: Rect) { let ax0 = this.top.x; let ay0 = this.top.y; @@ -254,3 +294,13 @@ export enum AlignY { Middle = 1, Bottom = 2, } + +export function lerp(amt: number, lo: number, hi: number) { + if (amt <= 0) { + return lo; + } + if (amt >= 1) { + return hi; + } + return lo + (hi - lo) * amt; +}; diff --git a/src/gridart.ts b/src/gridart.ts index 2d33bae..7249895 100644 --- a/src/gridart.ts +++ b/src/gridart.ts @@ -8,7 +8,7 @@ export const CENTER = new Point(192, 192); export const MOULDING_SZ = new Size(1, 1); export class GridArt { - #at: Point; + #atPixel: Point; #floorCenter: Point; #ceilingCenter: Point; @@ -17,27 +17,29 @@ export class GridArt { #floorBr: Point; #ceilingBr: Point; - constructor(at: Point) { - this.#at = at; - this.#floorCenter = at.scale(FLOOR_CELL_SIZE).offset(CENTER); + constructor(atPixel: Point) { + this.#atPixel = atPixel; + let at = atPixel.unscale(FLOOR_CELL_SIZE); + this.#floorCenter = atPixel.offset(CENTER); this.#ceilingCenter = at.scale(CEILING_CELL_SIZE).offset(CENTER); - this.#floorTl = at - .offset(new Point(-0.5, -0.5)) - .scale(FLOOR_CELL_SIZE) + this.#floorTl = atPixel.offset(new Point(-FLOOR_CELL_SIZE.w / 2, -FLOOR_CELL_SIZE.h / 2)) .offset(CENTER); this.#ceilingTl = at .offset(new Point(-0.5, -0.5)) .scale(CEILING_CELL_SIZE) - .offset(CENTER); - this.#floorBr = at - .offset(new Point(0.5, 0.5)) - .scale(FLOOR_CELL_SIZE) + .offset(CENTER) + .snap(0, 0) + this.#floorBr = atPixel.offset(new Point(FLOOR_CELL_SIZE.w / 2, FLOOR_CELL_SIZE.h / 2)) .offset(CENTER); this.#ceilingBr = at .offset(new Point(0.5, 0.5)) .scale(CEILING_CELL_SIZE) - .offset(CENTER); + .offset(CENTER) + .snap(0, 0) + + // console.log(`floorTl: ${this.#floorTl}`) + // console.log(`floorBr: ${this.#floorBr}`) } get floorRect(): Rect { @@ -83,84 +85,134 @@ export class GridArt { } drawWallTop(color: Color) { - if (this.#at.y > 0) { + if (this.#atPixel.y > FLOOR_CELL_SIZE.h / 2) { return; } this.#drawWallTop(color); } drawWallLeft(color: Color) { - if (this.#at.x > 0) { + if (this.#atPixel.x > FLOOR_CELL_SIZE.w / 2) { return; } this.#drawWallLeft(color); } drawWallBottom(color: Color) { - if (this.#at.y < 0) { + if (this.#atPixel.y < -FLOOR_CELL_SIZE.h / 2) { return; } - new GridArt(this.#at.offset(new Point(0, 1))).#drawWallTop(color); + new GridArt(this.#atPixel.offset(new Point(0, FLOOR_CELL_SIZE.h))).#drawWallTop(color); } drawWallRight(color: Color) { - if (this.#at.x < 0) { + if (this.#atPixel.x < -FLOOR_CELL_SIZE.w / 2) { return; } - new GridArt(this.#at.offset(new Point(1, 0))).#drawWallLeft(color); + new GridArt(this.#atPixel.offset(new Point(FLOOR_CELL_SIZE.w, 0))).#drawWallLeft(color); } drawMouldingTop(color: Color) { - let lhs = this.#ceilingTl.offset(new Point(0, -MOULDING_SZ.h)); - D.fillRect(lhs, new Size(CEILING_CELL_SIZE.w, MOULDING_SZ.h), color); + let x0 = this.#ceilingTl.x; + let y0 = this.#ceilingTl.y - MOULDING_SZ.h; + let x1 = this.#ceilingBr.x; + let y1 = this.#ceilingTl.y; + + D.fillRect( + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color + ); } drawMouldingTopLeft(color: Color) { + let x0 = this.#ceilingTl.x - MOULDING_SZ.w; + let y0 = this.#ceilingTl.y - MOULDING_SZ.h; + let x1 = this.#ceilingTl.x; + let y1 = this.#ceilingTl.y; + D.fillRect( - this.#ceilingTl.offset(new Point(-MOULDING_SZ.w, -MOULDING_SZ.h)), - MOULDING_SZ, - color, + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color ); } drawMouldingLeft(color: Color) { - let lhs = this.#ceilingTl.offset(new Point(-MOULDING_SZ.w, 0)); - D.fillRect(lhs, new Size(MOULDING_SZ.w, CEILING_CELL_SIZE.h), color); + let x0 = this.#ceilingTl.x - MOULDING_SZ.w; + let y0 = this.#ceilingTl.y; + let x1 = this.#ceilingTl.x; + let y1 = this.#ceilingBr.y; + + D.fillRect( + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color + ); } drawMouldingTopRight(color: Color) { + let x0 = this.#ceilingBr.x; + let y0 = this.#ceilingTl.y - MOULDING_SZ.h; + let x1 = this.#ceilingBr.x + MOULDING_SZ.w; + let y1 = this.#ceilingTl.y; + D.fillRect( - this.#ceilingTl.offset(new Point(CEILING_CELL_SIZE.w, -MOULDING_SZ.h)), - MOULDING_SZ, - color, + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color ); } drawMouldingBottom(color: Color) { - let lhs = this.#ceilingTl.offset(new Point(0, CEILING_CELL_SIZE.h)); - D.fillRect(lhs, new Size(CEILING_CELL_SIZE.w, MOULDING_SZ.h), color); + let x0 = this.#ceilingTl.x; + let y0 = this.#ceilingBr.y; + let x1 = this.#ceilingBr.x; + let y1 = this.#ceilingBr.y + MOULDING_SZ.h; + + D.fillRect( + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color + ); } drawMouldingBottomLeft(color: Color) { + let x0 = this.#ceilingTl.x - MOULDING_SZ.w; + let y0 = this.#ceilingBr.y; + let x1 = this.#ceilingTl.x; + let y1 = this.#ceilingBr.y + MOULDING_SZ.h; + D.fillRect( - this.#ceilingTl.offset(new Point(-MOULDING_SZ.w, CEILING_CELL_SIZE.h)), - MOULDING_SZ, + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), color, ); } drawMouldingRight(color: Color) { - let lhs = this.#ceilingTl.offset(new Point(CEILING_CELL_SIZE.w, 0)); - D.fillRect(lhs, new Size(MOULDING_SZ.w, CEILING_CELL_SIZE.h), color); + let x0 = this.#ceilingBr.x; + let y0 = this.#ceilingTl.y; + let x1 = this.#ceilingBr.x + MOULDING_SZ.w; + let y1 = this.#ceilingBr.y; + + D.fillRect( + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color + ); } drawMouldingBottomRight(color: Color) { + let x0 = this.#ceilingBr.x; + let y0 = this.#ceilingBr.y; + let x1 = this.#ceilingBr.x + MOULDING_SZ.w; + let y1 = this.#ceilingBr.y + MOULDING_SZ.h; + D.fillRect( - this.#ceilingTl.offset( - new Point(CEILING_CELL_SIZE.w, CEILING_CELL_SIZE.h), - ), - MOULDING_SZ, - color, + new Point(x0, y0), + new Size(x1 - x0, y1 - y0), + color ); } diff --git a/src/huntmode.ts b/src/huntmode.ts index 90916d0..da5bce0 100644 --- a/src/huntmode.ts +++ b/src/huntmode.ts @@ -1,7 +1,7 @@ -import { Point, Size } from "./engine/datatypes.ts"; -import { DrawPile } from "./drawpile.ts"; -import { D } from "./engine/public.ts"; -import { sprThrallLore } from "./sprites.ts"; +import {Point, Rect, Size} from "./engine/datatypes.ts"; +import {DrawPile} from "./drawpile.ts"; +import {D, I} from "./engine/public.ts"; +import {sprThrallLore} from "./sprites.ts"; import { BG_INSET, BG_WALL_OR_UNREVEALED, @@ -11,16 +11,16 @@ import { FG_TEXT_ENDORSED, FG_TOO_EXPENSIVE, } from "./colors.ts"; -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 { getCheckModal } from "./checkmodal.ts"; -import { withCamera } from "./layout.ts"; +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 {withCamera} from "./layout.ts"; export class HuntMode { map: LoadedNewMap; - player: Point; + floatingPlayer: Point; + velocity: Point; faceLeft: boolean; drawpile: DrawPile; @@ -29,7 +29,8 @@ export class HuntMode { constructor(depth: number, map: LoadedNewMap) { this.map = map; - this.player = map.entrance; + this.floatingPlayer = map.entrance.offset(new Point(0.5, 0.5)); + this.velocity = new Point(0, 0); this.faceLeft = false; this.drawpile = new DrawPile(); @@ -39,13 +40,27 @@ export class HuntMode { // getCheckModal().show(standardVaultTemplates[5].checks[1], null) } + get gridifiedPlayer(): Point { + return new Point( + Math.floor(this.floatingPlayer.x), + Math.floor(this.floatingPlayer.y), + ); + } + + get pixelPlayer(): Point { + return new Point( + Math.floor(this.floatingPlayer.x * FLOOR_CELL_SIZE.w - FLOOR_CELL_SIZE.w / 2), + Math.floor(this.floatingPlayer.y * FLOOR_CELL_SIZE.h - FLOOR_CELL_SIZE.h / 2), + ) + } + getDepth() { return this.depth; } // == update logic == #collectResources() { - let cell = this.map.get(this.player); + let cell = this.map.get(this.gridifiedPlayer); let pickup = cell.pickup; if (pickup != null) { @@ -61,11 +76,11 @@ export class HuntMode { } let dist = Math.max( - Math.abs(mapPosition.x - this.player.x), - Math.abs(mapPosition.y - this.player.y), + Math.abs(mapPosition.x - this.gridifiedPlayer.x), + Math.abs(mapPosition.y - this.gridifiedPlayer.y), ); - if (dist != 1) { + if (dist > 1) { return null; } @@ -76,6 +91,7 @@ export class HuntMode { return pickup.computeCostToClick(); } + /* movePlayerTo(newPosition: Point) { let oldX = this.player.x; let newX = newPosition.x; @@ -88,9 +104,10 @@ export class HuntMode { } this.#collectResources(); } + */ getZoneLabel(): string | null { - return this.map.get(this.player).zoneLabel; + return this.map.get(this.gridifiedPlayer).zoneLabel; } // draw @@ -109,18 +126,20 @@ export class HuntMode { this.frame += 1; this.drawpile.clear(); - let globalOffset = new Point( - this.player.x * FLOOR_CELL_SIZE.w, - this.player.y * FLOOR_CELL_SIZE.h, - ).offset(new Point(-192, -192)); + let globalOffset = this.pixelPlayer.offset( + new Point(-192, -192) + ); + this.#updatePlayer(); 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) { - let offsetInCells = new Point(x - this.player.x, y - this.player.y); - this.#drawMapCell(offsetInCells, new Point(x, y)); + let offsetInPixels = new Point(x, y).scale(FLOOR_CELL_SIZE).offset( + this.pixelPlayer.negate() + ); + this.#drawMapCell(offsetInPixels, new Point(x, y)); } } this.#drawPlayer(globalOffset); @@ -130,6 +149,60 @@ export class HuntMode { this.drawpile.executeOnClick(); } + #updatePlayer() { + let dx = this.velocity.x; + let dy = this.velocity.y; + + let amt = 0.005; + + if (I.isKeyDown("w")) { dy -= amt; } + if (I.isKeyDown("s")) { dy += amt; } + if (I.isKeyDown("a")) { dx -= amt; } + if (I.isKeyDown("d")) { dx += amt; } + + dx *= 0.9; + dy *= 0.9; + + this.velocity = new Point(dx, dy); + let nSteps = 40; + let szX = 0.5; + let szY = 0.5; + + for (let i = 0; i < nSteps; i++) { + let oldXy = this.floatingPlayer; + let newXy = oldXy.offset(new Point(this.velocity.x / nSteps, 0)); + + let bbox = new Rect( + newXy.offset(new Point(-szX / 2, -szY / 2)), + new Size(szX, szY) + ) + + for (let cell of bbox.overlappedCells(new Size(1, 1)).values()) { + if (this.#blocksMovement(cell.top)) { + this.velocity = new Point(0, this.velocity.y); + newXy = oldXy; + } + } + + oldXy = newXy; + newXy = oldXy.offset(new Point(0, this.velocity.y / nSteps)); + + bbox = new Rect( + newXy.offset(new Point(-szX / 2, -szY / 2)), + new Size(szX, szY) + ) + + for (let cell of bbox.overlappedCells(new Size(1, 1)).values()) { + if (this.#blocksMovement(cell.top)) { + this.velocity = new Point(this.velocity.x, 0); + newXy = oldXy; + } + } + + this.floatingPlayer = newXy; + } + } + #updateFov() { for (let y = 0; y < this.map.size.h; y += 1) { for (let x = 0; x < this.map.size.w; x += 1) { @@ -137,15 +210,15 @@ export class HuntMode { } } - this.map.get(new Point(this.player.x, this.player.y)).revealed = true; + this.map.get(this.gridifiedPlayer).revealed = true; shadowcast( - [this.player.x, this.player.y], + [this.gridifiedPlayer.x, this.gridifiedPlayer.y], ([x, y]: [number, number]): boolean => { let cell = this.map.get(new Point(x, y)); let pickup = cell.pickup; return ( cell.architecture == Architecture.Wall || - (pickup != null && pickup.isObstructive()) + (pickup != null && pickup.obstructsVision()) ); }, ([x, y]: [number, number]) => { @@ -167,8 +240,8 @@ export class HuntMode { } #inVisibilityRange(x: number, y: number): boolean { - let dx = x - this.player.x; - let dy = y - this.player.y; + let dx = x - this.gridifiedPlayer.x; + let dy = y - this.gridifiedPlayer.y; return dx * dx + dy * dy < 13; } @@ -176,14 +249,14 @@ export class HuntMode { this.drawpile.draw(); } - #drawMapCell(offsetInCells: Point, mapPosition: Point) { + #drawMapCell(offsetInPixels: Point, mapPosition: Point) { const OFFSET_UNDER_FLOOR = -512 + mapPosition.y; const OFFSET_FLOOR = -256 + mapPosition.y; const OFFSET_AIR = 0 + mapPosition.y; const OFFSET_TOP = 256 + mapPosition.y; const OFFSET_TOP_OF_TOP = 512 + mapPosition.y; - const gridArt = new GridArt(offsetInCells); + const gridArt = new GridArt(offsetInPixels); let cellData = this.map.get(mapPosition); @@ -240,8 +313,10 @@ export class HuntMode { } getPlayerProgress().spendBlood(cost); - this.movePlayerTo(mapPosition); - getCheckModal().show(null, null); + + // TODO: This isn't a thing anymore + // this.movePlayerTo(mapPosition); + // getCheckModal().show(null, null); }, onSqueeze: () => { // the cost _gates_ squeezes @@ -320,10 +395,7 @@ export class HuntMode { } #drawPlayer(globalOffset: Point) { - let cellOffset = new Point( - this.player.x * FLOOR_CELL_SIZE.w, - this.player.y * FLOOR_CELL_SIZE.h, - ).offset(globalOffset.negate()); + let cellOffset = this.pixelPlayer.offset(globalOffset.negate()); this.drawpile.add(0, () => { D.drawSprite(sprThrallLore, cellOffset, 1, { xScale: this.faceLeft ? -2 : 2, @@ -379,6 +451,20 @@ export class HuntMode { ); }); } + + #blocksMovement(xy: Point) { + let cell = this.map.get(xy); + if (cell.architecture == Architecture.Wall) { + return true; + } + + if (cell.pickup?.blocksMovement()) { + return true; + } + + // TODO: Other cases + return false; + } } let active: HuntMode | null = null; diff --git a/src/pickups.ts b/src/pickups.ts index 84feafb..6345084 100644 --- a/src/pickups.ts +++ b/src/pickups.ts @@ -44,7 +44,11 @@ export class LockPickup { return true; } - isObstructive() { + blocksMovement() { + return true; + } + + obstructsVision() { return true; } @@ -90,7 +94,11 @@ export class BreakableBlockPickup { return true; } - isObstructive() { + blocksMovement() { + return true; + } + + obstructsVision() { return true; } @@ -175,7 +183,11 @@ export class LadderPickup { return true; } - isObstructive() { + blocksMovement() { + return true; + } + + obstructsVision() { return false; } @@ -217,7 +229,11 @@ export class ThrallPickup { return true; } - isObstructive() { + blocksMovement() { + return true; + } + + obstructsVision() { return false; } @@ -263,7 +279,11 @@ export class ThrallPosterPickup { return true; } - isObstructive() { + blocksMovement() { + return true; + } + + obstructsVision() { return false; } @@ -310,7 +330,11 @@ export class ThrallRecruitedPickup { return !this.bitten; } - isObstructive() { + blocksMovement() { + return true; + } + + obstructsVision() { return false; } @@ -425,7 +449,11 @@ export class ThrallCollectionPlatePickup { return !this.rewarded; } - isObstructive() { + blocksMovement() { + return false; + } + + obstructsVision() { return false; } @@ -530,7 +558,10 @@ export class ThrallItemPickup { return true; } - isObstructive() { + blocksMovement() { + return true; + } + obstructsVision() { return false; }