import {Grid, Point, Rect, Size} from "./engine/datatypes.ts"; import {ALL_STATS, Resource, Stat} from "./datatypes.ts"; import {DrawPile} from "./drawpile.ts"; import {D} from "./engine/public.ts"; import {sprDrips, sprLadder, sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; import {BG_INSET, FG_TEXT} from "./colors.ts"; import {getPlayerProgress} from "./playerprogress.ts"; import {generate} from "./mapgen.ts"; export type MapCellContent = {type: "statPickup", stat: Stat} | {type: "resourcePickup", resource: Resource} | {type: "stairs"} | {type: "empty"} | {type: "block"} export type MapCell = { content: MapCellContent, isValidSpawn: boolean, revealed: boolean, nextMoveAccessible: boolean, } export type LoadedMap = { cells: Grid, player: Point } export class HuntMode { map: LoadedMap drawpile: DrawPile frame: number depth: number constructor() { this.map = null!; // initialized in replaceMap this.drawpile = new DrawPile(); this.frame = 0; this.depth = 1; this.replaceMap(); } replaceMap(deeper?: boolean) { this.map = generate(); this.#updateVisibilityAndPossibleMoves(); if (deeper) { this.depth += 1; } } getDepth() { return this.depth; } // == update logic == #updateVisibilityAndPossibleMoves() { for (let x = 0; x < this.map.cells.size.w; x++) { for (let y = 0; y < this.map.cells.size.h; y++) { let position = new Point(x, y); let data = this.map.cells.get(position); data.nextMoveAccessible = false; if ( Math.abs(x - this.map.player.x) <= 1 && Math.abs(y - this.map.player.y) <= 1 ) { data.revealed = true; if (!this.map.player.equals(position)) { data.nextMoveAccessible = true; } } } } } #collectResources() { let present = this.map.cells.get(this.map.player); if (present.content.type == "stairs") { getPlayerProgress().addBlood(1000); this.replaceMap(true); } if (present.content.type == "statPickup") { let stat = present.content.stat; let amount = 1; present.content = {type: "empty"}; getPlayerProgress().add(stat, amount); } if (present.content.type == "resourcePickup") { let resource = present.content.resource; switch(resource) { case "EXP": getPlayerProgress().addExperience(25); break; default: throw `not sure how to add ${resource}` } } present.content = {type: "empty"}; } #computeCostToMoveTo(mapPosition: Point): number | null { let present = this.map.cells.get(mapPosition); if (present.content.type == "statPickup" || present.content.type == "resourcePickup") { return 100; } if (present.content.type == "stairs") { return 0; } if (present.content.type == "empty") { return 10; } return null; } movePlayerTo(newPosition: Point) { this.map.player = newPosition; this.#updateVisibilityAndPossibleMoves(); this.#collectResources(); } // draw update() { this.frame += 1; this.drawpile.clear(); let globalOffset = new Point(this.map.player.x * MAP_CELL_ONSCREEN_SIZE.w, this.map.player.y * MAP_CELL_ONSCREEN_SIZE.h).offset( new Point(-192, -192) ) let map = this.map.cells; for (let y = 0; y < map.size.h; y += 1) { for (let x = 0; x < map.size.w; x += 1) { let cellOffset = new Point(x * MAP_CELL_ONSCREEN_SIZE.w, y * MAP_CELL_ONSCREEN_SIZE.h).offset(globalOffset.negate()); let cellData = this.map.cells.get(new Point(x, y)) let belowIsBlock = true; if (y < map.size.h - 1) { let below = this.map.cells.get(new Point(x, y + 1)); belowIsBlock = !below.revealed || below.content.type == "block"; } this.#drawMapCell(cellOffset, new Point(x, y), cellData, belowIsBlock); } } this.#drawPlayer(globalOffset); this.drawpile.executeOnClick(); } draw() { this.drawpile.draw() } #drawMapCell( cellOffset: Point, mapPosition: Point, cellData: MapCell, belowIsBlock: boolean ) { const OFFSET_FLOOR = -256; const OFFSET_AIR = 0; const depth = mapPosition.y; const onFloor = OFFSET_FLOOR + depth; const inAir = OFFSET_AIR + depth; if (!cellData.revealed) { return; } let cellTopLeft = cellOffset.offset(new Size(-MAP_CELL_ONSCREEN_SIZE.w / 2, -MAP_CELL_ONSCREEN_SIZE.h / 2)); let cellSize = MAP_CELL_ONSCREEN_SIZE; if (cellData.content.type == "block") { if (!belowIsBlock) { this.drawpile.add(inAir, () => { D.drawSprite(sprDrips, cellOffset.offset(new Point(0, -cellSize.h / 2)), 1, {xScale: 3, yScale: 3}) }) } return; } // draw inset zone let cost = this.#computeCostToMoveTo(mapPosition); this.drawpile.addClickable(onFloor, (hover: boolean) => { D.fillRect(cellTopLeft, cellSize, hover ? FG_TEXT : BG_INSET) if (cellData.content.type == "stairs") { // draw ladder if applicable D.drawSprite(sprLadder, cellTopLeft, 0, {xScale: 3, yScale: 3}); } }, new Rect(cellTopLeft, cellSize), cellData.nextMoveAccessible && cost != null && cost <= getPlayerProgress().getBlood(), () => { if (cost != null) { getPlayerProgress().spendBlood(cost); this.movePlayerTo(mapPosition) } } ); if (belowIsBlock) { // draw the underhang this.drawpile.add(onFloor, () => { D.drawSprite(sprDrips, cellOffset.offset(new Point(0, cellSize.h / 2)), 0, {xScale: 3, yScale: 3}) }) } if (cellData.content.type == "statPickup") { let content = cellData.content; let extraXOffset = 0; // Math.cos(this.frame / 80 + mapPosition.x + mapPosition.y) * 1; let extraYOffset = Math.sin(this.frame / 50 + mapPosition.x * 2+ mapPosition.y * 0.75) * 6 - 18; this.drawpile.add(inAir, () => { D.drawSprite( sprStatPickup, cellOffset.offset(new Point(extraXOffset, extraYOffset)), ALL_STATS.indexOf(content.stat), { xScale: 3, yScale: 3, } ) }); } if (cellData.content.type == "resourcePickup" && cellData.content.resource == "EXP") { this.drawpile.add(inAir, () => { D.drawSprite( sprResourcePickup, cellOffset.offset(new Point(0, -16 * 3)), 0, { xScale: 3, yScale: 3, } ); }); } } #drawPlayer(globalOffset: Point) { let cellOffset = new Point( this.map.player.x * MAP_CELL_ONSCREEN_SIZE.w, this.map.player.y * MAP_CELL_ONSCREEN_SIZE.h ).offset(globalOffset.negate()) this.drawpile.add(this.map.player.y, () => { D.drawSprite( sprRaccoonWalking, cellOffset.offset(new Point(0, 22)), 0, { xScale: 3, yScale: 3 } ) }); } } const MAP_CELL_ONSCREEN_SIZE: Size = new Size(96, 48) let active = new HuntMode(); export function getHuntMode() { return active; }