Replace FOV algorithm and add faux-3D
This commit is contained in:
		| @@ -1,6 +1,9 @@ | ||||
| import {Color} from "./engine/datatypes.ts"; | ||||
|  | ||||
| export const BG_OUTER = Color.parseHexCode("#143464"); | ||||
| export const BG_WALL_OR_UNREVEALED = Color.parseHexCode("#143464"); | ||||
| export const BG_INSET = Color.parseHexCode("#242234"); | ||||
| export const FG_TEXT = Color.parseHexCode("#c0c0c0") | ||||
| export const FG_BOLD = Color.parseHexCode("#ffffff") | ||||
| export const BG_CEILING = Color.parseHexCode("#143464"); | ||||
| export const FG_MOULDING = FG_TEXT; | ||||
|   | ||||
| @@ -70,6 +70,17 @@ export class Point { | ||||
|   equals(other: Point): boolean { | ||||
|     return this.x == other.x && this.y == other.y; | ||||
|   } | ||||
|  | ||||
|   scale(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); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class Size { | ||||
|   | ||||
							
								
								
									
										132
									
								
								src/gridart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/gridart.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| import {Color, Point, Size} from "./engine/datatypes.ts"; | ||||
| import {D} from "./engine/public.ts"; | ||||
|  | ||||
| export const FLOOR_CELL_SIZE: Size = new Size(48, 48) | ||||
| export const CEILING_CELL_SIZE: Size = new Size(52, 52) | ||||
| export const CENTER = new Point(192, 192); | ||||
| export const MOULDING_SZ = new Size(1, 1); | ||||
|  | ||||
| export class GridArt { | ||||
|   #at: Point; | ||||
|  | ||||
|   #floorCenter: Point; | ||||
|   #ceilingCenter: Point; | ||||
|   #floorTl: Point; | ||||
|   #ceilingTl: Point; | ||||
|   #floorBr: Point; | ||||
|   #ceilingBr: Point; | ||||
|  | ||||
|   constructor(at: Point) { | ||||
|     this.#at = at; | ||||
|     this.#floorCenter = at.scale(FLOOR_CELL_SIZE).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).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); | ||||
|     this.#ceilingBr = at.offset(new Point(0.5, 0.5)).scale(CEILING_CELL_SIZE).offset(CENTER); | ||||
|   } | ||||
|  | ||||
|   drawFloor(color: Color) { | ||||
|     D.fillRect(this.#floorTl, this.#floorBr.subtract(this.#floorTl), color); | ||||
|   } | ||||
|  | ||||
|   #drawWallTop(color: Color) { | ||||
|     let diff = Math.abs(this.#ceilingTl.y - this.#floorTl.y); | ||||
|     let sign = Math.sign(this.#ceilingTl.y - this.#floorTl.y); | ||||
|     // console.log(`diff, sign: ${diff}, ${sign}`) | ||||
|     for (let dy = 0; dy <= diff; dy += 0.25) {  // 0.25: fudge factor because we get two different lines | ||||
|       let progress = dy / diff; | ||||
|       let x0 = Math.floor(lerp(progress, this.#floorTl.x, this.#ceilingTl.x)); | ||||
|       let x1 = Math.ceil(lerp(progress, this.#floorBr.x, this.#ceilingBr.x)); | ||||
|       let y = this.#floorTl.y + sign * dy; | ||||
|  | ||||
|       if (dy == 0 || dy == diff) { | ||||
|         // console.log(x0, x1, y); | ||||
|       } | ||||
|       D.fillRect(new Point(x0, y - 1), new Size(x1 - x0, 1), color); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   #drawWallLeft(color: Color) { | ||||
|     let diff = Math.abs(this.#ceilingTl.x - this.#floorTl.x); | ||||
|     let sign = Math.sign(this.#ceilingTl.x - this.#floorTl.x); | ||||
|     // console.log(`diff, sign: ${diff}, ${sign}`) | ||||
|     for (let dx = 0; dx <= diff; dx += 0.25) { // fudge factor because we get two different lines | ||||
|       let progress = dx / diff; | ||||
|       let y0 = Math.floor(lerp(progress, this.#floorTl.y, this.#ceilingTl.y)); | ||||
|       let y1 = Math.ceil(lerp(progress, this.#floorBr.y, this.#ceilingBr.y)); | ||||
|  | ||||
|       let x = this.#floorTl.x + sign * dx; | ||||
|  | ||||
|       D.fillRect(new Point(x, y0), new Size(1, y1-y0), color); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   drawWallTop(color: Color) { | ||||
|     if (this.#at.y > 0) { return; } | ||||
|     this.#drawWallTop(color); | ||||
|   } | ||||
|  | ||||
|   drawWallLeft(color: Color) { | ||||
|     if (this.#at.x > 0) { return; } | ||||
|     this.#drawWallLeft(color); | ||||
|   } | ||||
|  | ||||
|   drawWallBottom(color: Color) { | ||||
|     if (this.#at.y < 0) { return; } | ||||
|     new GridArt(this.#at.offset(new Point(0, 1))).#drawWallTop(color); | ||||
|   } | ||||
|  | ||||
|   drawWallRight(color: Color) { | ||||
|     if (this.#at.x < 0) { return; } | ||||
|     new GridArt(this.#at.offset(new Point(1, 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) | ||||
|   } | ||||
|  | ||||
|   drawMouldingTopLeft(color: Color) { | ||||
|     D.fillRect(this.#ceilingTl.offset(new Point(-MOULDING_SZ.w, -MOULDING_SZ.h)), MOULDING_SZ, 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) | ||||
|   } | ||||
|  | ||||
|   drawMouldingTopRight(color: Color) { | ||||
|     D.fillRect(this.#ceilingTl.offset(new Point(CEILING_CELL_SIZE.w, -MOULDING_SZ.h)), MOULDING_SZ, 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) | ||||
|   } | ||||
|  | ||||
|   drawMouldingBottomLeft(color: Color) { | ||||
|     D.fillRect(this.#ceilingTl.offset(new Point(-MOULDING_SZ.w, CEILING_CELL_SIZE.h)), MOULDING_SZ, 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) | ||||
|   } | ||||
|  | ||||
|   drawMouldingBottomRight(color: Color) { | ||||
|     D.fillRect(this.#ceilingTl.offset(new Point(CEILING_CELL_SIZE.w, CEILING_CELL_SIZE.h)), MOULDING_SZ, color); | ||||
|   } | ||||
|  | ||||
|   drawCeiling(color: Color) { | ||||
|     D.fillRect(this.#ceilingTl, this.#ceilingBr.subtract(this.#ceilingTl), color); | ||||
|   } | ||||
| } | ||||
|  | ||||
| let lerp = (amt: number, x: number, y: number) => { | ||||
|   if (amt <= 0) { return x; } | ||||
|   if (amt >= 1) { return y; } | ||||
|   return x + (y - x) * amt; | ||||
| } | ||||
|  | ||||
							
								
								
									
										191
									
								
								src/huntmode.ts
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								src/huntmode.ts
									
									
									
									
									
								
							| @@ -2,10 +2,18 @@ import {Point, Rect, Size} from "./engine/datatypes.ts"; | ||||
| import {ALL_STATS, Stat} from "./datatypes.ts"; | ||||
| import {DrawPile} from "./drawpile.ts"; | ||||
| import {D} from "./engine/public.ts"; | ||||
| import {sprDrips, sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; | ||||
| import {BG_INSET, FG_TEXT} from "./colors.ts"; | ||||
| import {sprRaccoonWalking, sprResourcePickup, sprStatPickup} from "./sprites.ts"; | ||||
| import { | ||||
|   BG_INSET, | ||||
|   BG_WALL_OR_UNREVEALED, | ||||
|   FG_BOLD, | ||||
|   FG_MOULDING, | ||||
|   FG_TEXT | ||||
| } from "./colors.ts"; | ||||
| import {getPlayerProgress} from "./playerprogress.ts"; | ||||
| import {Architecture, CellView, LoadedNewMap} from "./newmap.ts"; | ||||
| import {Architecture, LoadedNewMap} from "./newmap.ts"; | ||||
| import {FLOOR_CELL_SIZE, GridArt} from "./gridart.ts"; | ||||
| import {shadowcast} from "./shadowcast.ts"; | ||||
|  | ||||
|  | ||||
| export class HuntMode { | ||||
| @@ -23,7 +31,6 @@ export class HuntMode { | ||||
|     this.drawpile = new DrawPile(); | ||||
|     this.frame = 0; | ||||
|     this.depth = depth; | ||||
|     this.#updateVisibilityAndPossibleMoves(); | ||||
|   } | ||||
|  | ||||
|   getDepth() { | ||||
| @@ -31,25 +38,6 @@ export class HuntMode { | ||||
|   } | ||||
|  | ||||
|   // == update logic == | ||||
|   #updateVisibilityAndPossibleMoves() { | ||||
|     let revealAt = (depth: number, xStart: number, yStart: number) => { | ||||
|       let cell = this.map.get(new Point(xStart, yStart)); | ||||
|       cell.revealed = true; | ||||
|       if (depth <= 0 || cell.architecture == Architecture.Wall) { | ||||
|         return; | ||||
|       } | ||||
|       for (let dx = -1; dx <= 1; dx++) { | ||||
|         for (let dy = -1; dy <= 1; dy++) { | ||||
|           let position = new Point(xStart + dx, yStart + dy); | ||||
|           revealAt(depth - 1, position.x, position.y); | ||||
|         } | ||||
|  | ||||
|       } | ||||
|     } | ||||
|     // NOTE: Depth 1 to reveal slightly less | ||||
|     revealAt(2, this.player.x, this.player.y); | ||||
|   } | ||||
|  | ||||
|   #collectResources() { | ||||
|     let cell = this.map.get(this.player); | ||||
|  | ||||
| @@ -102,7 +90,6 @@ export class HuntMode { | ||||
|  | ||||
|   movePlayerTo(newPosition: Point) { | ||||
|     this.player = newPosition; | ||||
|     this.#updateVisibilityAndPossibleMoves(); | ||||
|     this.#collectResources(); | ||||
|   } | ||||
|  | ||||
| @@ -112,21 +99,16 @@ export class HuntMode { | ||||
|     this.drawpile.clear(); | ||||
|  | ||||
|     let globalOffset = | ||||
|       new Point(this.player.x * MAP_CELL_ONSCREEN_SIZE.w, this.player.y * MAP_CELL_ONSCREEN_SIZE.h).offset( | ||||
|       new Point(this.player.x * FLOOR_CELL_SIZE.w, this.player.y * FLOOR_CELL_SIZE.h).offset( | ||||
|         new Point(-192, -192) | ||||
|       ) | ||||
|  | ||||
|     this.#updateFov(); | ||||
|  | ||||
|     for (let y = 0; y < this.map.size.h; y += 1) { | ||||
|       for (let x = 0; x < this.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 cell = this.map.get(new Point(x, y)) | ||||
|         let belowIsBlock = true; | ||||
|         if (y < this.map.size.h - 1) { | ||||
|           let below = this.map.get(new Point(x, y + 1)); | ||||
|           belowIsBlock = !below.revealed || below.architecture == Architecture.Wall; | ||||
|         } | ||||
|  | ||||
|         this.#drawMapCell(cellOffset, new Point(x, y), cell, belowIsBlock); | ||||
|         let offsetInCells = new Point(x - this.player.x, y - this.player.y); | ||||
|         this.#drawMapCell(offsetInCells, new Point(x, y)); | ||||
|       } | ||||
|     } | ||||
|     this.#drawPlayer(globalOffset); | ||||
| @@ -134,43 +116,79 @@ export class HuntMode { | ||||
|     this.drawpile.executeOnClick(); | ||||
|   } | ||||
|  | ||||
|   #updateFov() { | ||||
|     for (let y = 0; y < this.map.size.h; y += 1) { | ||||
|       for (let x = 0; x < this.map.size.w; x += 1) { | ||||
|         this.map.get(new Point(x, y)).revealed = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     this.map.get(new Point(this.player.x, this.player.y)).revealed = true; | ||||
|     shadowcast( | ||||
|       [this.player.x, this.player.y], | ||||
|       ([x, y]: [number, number]): boolean => { | ||||
|         return this.map.get(new Point(x, y)).architecture == Architecture.Wall; | ||||
|       }, | ||||
|       ([x, y]: [number, number]) => { | ||||
|         let dx = x - this.player.x; | ||||
|         let dy = y - this.player.y; | ||||
|         if ((dx * dx + dy * dy) >= 13) { return; } | ||||
|         this.map.get(new Point(x, y)).revealed = true; | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   draw() { | ||||
|     this.drawpile.draw() | ||||
|   } | ||||
|  | ||||
|   #drawMapCell( | ||||
|     cellOffset: Point, | ||||
|     offsetInCells: Point, | ||||
|     mapPosition: Point, | ||||
|     cellData: CellView, | ||||
|     belowIsBlock: boolean | ||||
|   ) { | ||||
|     const OFFSET_UNDER_FLOOR = -512; | ||||
|     const OFFSET_FLOOR = -256; | ||||
|     const OFFSET_AIR = 0; | ||||
|     const depth = mapPosition.y; | ||||
|     const onFloor = OFFSET_FLOOR + depth; | ||||
|     const inAir = OFFSET_AIR + depth; | ||||
|     const OFFSET_TOP = 256; | ||||
|     const OFFSET_TOP_OF_TOP = 512; | ||||
|     const cellSizeFloor = new Size(48, 48); | ||||
|     const cellSizeCeiling = new Size(52, 52); | ||||
|  | ||||
|     const floorZone = offsetInCells.scale(cellSizeFloor).offset(new Point(192, 192)); | ||||
|  | ||||
|     const gridArt = new GridArt(offsetInCells); | ||||
|  | ||||
|     let cellData = this.map.get(mapPosition) | ||||
|  | ||||
|     let cellTopLeft = floorZone.offset(new Size(-cellSizeFloor.w / 2, -cellSizeCeiling.h / 2)); | ||||
|     let cellSize = FLOOR_CELL_SIZE; | ||||
|  | ||||
|     this.drawpile.add( | ||||
|       OFFSET_UNDER_FLOOR, | ||||
|       () => { | ||||
|         gridArt.drawCeiling(BG_WALL_OR_UNREVEALED); | ||||
|       } | ||||
|     ); | ||||
|     if (cellData.architecture == Architecture.Wall || !cellData.revealed) { | ||||
|       this.drawpile.add( | ||||
|         OFFSET_TOP, | ||||
|         () => { | ||||
|           gridArt.drawCeiling(BG_WALL_OR_UNREVEALED); | ||||
|         } | ||||
|       ); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     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.architecture == Architecture.Wall) { | ||||
|       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, | ||||
|     this.drawpile.addClickable( | ||||
|       OFFSET_FLOOR, | ||||
|       (hover: boolean) => { | ||||
|         D.fillRect(cellTopLeft, cellSize, hover ? FG_TEXT : BG_INSET) | ||||
|         gridArt.drawFloor(hover ? FG_TEXT : BG_INSET) | ||||
|  | ||||
|         /* | ||||
|         // TODO: Stairs | ||||
| @@ -190,42 +208,69 @@ export class HuntMode { | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     const isRevealedBlock = (dx: number, dy: number) => { | ||||
|       let other = this.map.get(mapPosition.offset(new Point(dx, dy))); | ||||
|       return other.revealed && other.architecture == Architecture.Wall; | ||||
|  | ||||
|     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 (isRevealedBlock(0, -1) && isRevealedBlock(-1, 0)) { | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingTopLeft(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(0, -1)) { | ||||
|       this.drawpile.add(OFFSET_AIR, () => { gridArt.drawWallTop(FG_TEXT); }) | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingTop(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(0, -1) && isRevealedBlock(1, 0)) { | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingTopRight(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(-1, 0)) { | ||||
|       this.drawpile.add(OFFSET_AIR, () => { gridArt.drawWallLeft(FG_TEXT); }) | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingLeft(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(0, 1) && isRevealedBlock(-1, 0)) { | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingBottomLeft(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(0, 1)) { | ||||
|       this.drawpile.add(OFFSET_AIR, () => { gridArt.drawWallBottom(FG_BOLD); }) | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingBottom(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(0, 1) && isRevealedBlock(1, 0)) { | ||||
|       this.drawpile.add(OFFSET_TOP_OF_TOP, () => { gridArt.drawMouldingBottomRight(FG_MOULDING); }) | ||||
|     } | ||||
|     if (isRevealedBlock(1, 0)) { | ||||
|       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) { | ||||
|         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, () => { | ||||
|         let extraYOffset = Math.sin(this.frame / 50 + mapPosition.x * 2 + mapPosition.y * 0.75) * 6; | ||||
|         this.drawpile.add(OFFSET_AIR, () => { | ||||
|           D.drawSprite( | ||||
|             sprStatPickup, | ||||
|             cellOffset.offset(new Point(extraXOffset, extraYOffset)), | ||||
|             floorZone.offset(new Point(extraXOffset, extraYOffset)), | ||||
|             statIndex, | ||||
|             { | ||||
|               xScale: 3, | ||||
|               yScale: 3, | ||||
|               xScale: 2, | ||||
|               yScale: 2, | ||||
|             } | ||||
|           ) | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|       if (pickup == "EXP") { | ||||
|         this.drawpile.add(inAir, () => { | ||||
|         this.drawpile.add(OFFSET_AIR, () => { | ||||
|           D.drawSprite( | ||||
|             sprResourcePickup, | ||||
|             cellOffset.offset(new Point(0, -16 * 3)), | ||||
|             floorZone.offset(new Point(0, -16)), | ||||
|             0, | ||||
|             { | ||||
|               xScale: 3, | ||||
|               yScale: 3, | ||||
|               xScale: 2, | ||||
|               yScale: 2, | ||||
|             } | ||||
|           ); | ||||
|         }); | ||||
| @@ -235,24 +280,22 @@ export class HuntMode { | ||||
|  | ||||
|   #drawPlayer(globalOffset: Point) { | ||||
|     let cellOffset = new Point( | ||||
|       this.player.x * MAP_CELL_ONSCREEN_SIZE.w, | ||||
|       this.player.y * MAP_CELL_ONSCREEN_SIZE.h | ||||
|       this.player.x * FLOOR_CELL_SIZE.w, | ||||
|       this.player.y * FLOOR_CELL_SIZE.h | ||||
|     ).offset(globalOffset.negate()) | ||||
|     this.drawpile.add(this.player.y, () => { | ||||
|       D.drawSprite( | ||||
|         sprRaccoonWalking, | ||||
|         cellOffset.offset(new Point(0, 22)), | ||||
|         0, { | ||||
|           xScale: 3, | ||||
|           yScale: 3 | ||||
|           xScale: 2, | ||||
|           yScale: 2 | ||||
|         } | ||||
|       ) | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const MAP_CELL_ONSCREEN_SIZE: Size = new Size(96, 48) | ||||
|  | ||||
| let active: HuntMode | null = null; | ||||
| export function initHuntMode(huntMode: HuntMode) { | ||||
|   active = huntMode; | ||||
|   | ||||
| @@ -24,10 +24,10 @@ | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										125
									
								
								src/shadowcast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/shadowcast.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| // Here begins Pyrex's Standard Shadowcasting Implementation | ||||
| // (I copy this to lots of projects!) | ||||
| export var shadowcast = function ( | ||||
|   [ox, oy]: [number, number], | ||||
|   isBlocking: (xy: [number, number]) => boolean, | ||||
|   markVisible: (xy: [number, number]) => void | ||||
| ) { | ||||
|   for (var i = 0; i < 4; i++) { | ||||
|     var quadrant = new Quadrant(i, [ox, oy]); | ||||
|     var reveal = function (xy: [number, number]) { | ||||
|       markVisible(quadrant.transform(xy)); | ||||
|     } | ||||
|     var isWall = function (xy: [number, number] | undefined) { | ||||
|       if (xy == undefined) { return false; } | ||||
|       return isBlocking(quadrant.transform(xy)); | ||||
|     } | ||||
|     var isFloor = function (xy: [number, number] | undefined) { | ||||
|       if (xy == undefined) { return false; } | ||||
|       return !isBlocking(quadrant.transform(xy)); | ||||
|     } | ||||
|     var scan = function (row: Row) { | ||||
|       var prevXy: [number, number] | undefined | ||||
|       row.forEachTile((xy) => { | ||||
|         if (isWall(xy) || isSymmetric(row, xy)) { | ||||
|           reveal(xy); | ||||
|         } | ||||
|         if (isWall(prevXy) && isFloor(xy)) { | ||||
|           row.startSlope = slope(xy); | ||||
|         } | ||||
|         if (isFloor(prevXy) && isWall(xy)) { | ||||
|           var nextRow = row.next(); | ||||
|           nextRow.endSlope = slope(xy); | ||||
|           scan(nextRow); | ||||
|         } | ||||
|         prevXy = xy; | ||||
|       }) | ||||
|       if (isFloor(prevXy)) { | ||||
|         scan(row.next()); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     var firstRow = new Row(1, new Fraction(-1, 1), new Fraction(1, 1)); | ||||
|     scan(firstRow); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class Quadrant { | ||||
|   cardinal: number; | ||||
|   ox: number; | ||||
|   oy: number; | ||||
|  | ||||
|   constructor(cardinal: number, [ox, oy]: [number, number]) { | ||||
|     this.cardinal = cardinal; | ||||
|     this.ox = ox; | ||||
|     this.oy = oy; | ||||
|   } | ||||
|  | ||||
|   transform([row, col]: [number, number]): [number, number] { | ||||
|     switch (this.cardinal) { | ||||
|       case 0: return [this.ox + col, this.oy - row]; | ||||
|       case 2: return [this.ox + col, this.oy + row]; | ||||
|       case 1: return [this.ox + row, this.oy + col]; | ||||
|       case 3: return [this.ox - row, this.oy + col]; | ||||
|       default: throw new Error("invalid cardinal") | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| class Row { | ||||
|   depth: number; | ||||
|   startSlope: Fraction; | ||||
|   endSlope: Fraction; | ||||
|  | ||||
|   constructor(depth: number, startSlope: Fraction, endSlope: Fraction) { | ||||
|     this.depth = depth; | ||||
|     this.startSlope = startSlope; | ||||
|     this.endSlope = endSlope; | ||||
|   } | ||||
|  | ||||
|   forEachTile(cb: (xy: [number, number]) => void) { | ||||
|     var minCol = roundTiesUp(this.startSlope.scale(this.depth)); | ||||
|     var maxCol = roundTiesDown(this.endSlope.scale(this.depth)); | ||||
|     for (var col = minCol; col <= maxCol; col++) { | ||||
|       cb([this.depth, col]) | ||||
|     } | ||||
|   } | ||||
|   next(): Row { | ||||
|     return new Row(this.depth + 1, this.startSlope, this.endSlope); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class Fraction { | ||||
|   numerator: number; | ||||
|   denominator: number; | ||||
|  | ||||
|   constructor(numerator: number, denominator: number) { | ||||
|     this.numerator = numerator; | ||||
|     this.denominator = denominator; | ||||
|   } | ||||
|  | ||||
|   scale(n: number): Fraction { | ||||
|     return new Fraction(this.numerator * n, this.denominator); | ||||
|   } | ||||
|  | ||||
|   toDouble(): number { | ||||
|     return this.numerator / this.denominator; | ||||
|   } | ||||
| } | ||||
|  | ||||
| var slope = function ([rowDepth, col]: [number, number]): Fraction { | ||||
|   return new Fraction(2 * col - 1, 2 * rowDepth); | ||||
| } | ||||
|  | ||||
| var isSymmetric = function (row: Row, [_, col]: [number, number]) { | ||||
|   return col >= row.startSlope.scale(row.depth).toDouble() && | ||||
|     col <= (row.endSlope.scale(row.depth)).toDouble(); | ||||
| } | ||||
|  | ||||
| var roundTiesUp = function (n: Fraction) { | ||||
|   return Math.floor(n.toDouble() + 0.5); | ||||
| } | ||||
|  | ||||
| var roundTiesDown = function (n: Fraction) { | ||||
|   return Math.ceil(n.toDouble() - 0.5); | ||||
| } | ||||
| @@ -5,7 +5,6 @@ import {getVNModal} from "./vnmodal.ts"; | ||||
| import {getScorer} from "./scorer.ts"; | ||||
| import {getEndgameModal} from "./endgamemodal.ts"; | ||||
| import {SuccessorOption, Wish} from "./datatypes.ts"; | ||||
| import mapZoo from "./newmaps/zoo/map.ts"; | ||||
| import mapHub from "./newmaps/hub/map.ts"; | ||||
|  | ||||
| const N_TURNS: number = 9; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user