part of 'sitemode.dart'; // We render each thing as a 2x2 block. // We want the player's cell to be // _actually centered_, and the terminal is an even number of cells across // So, shifting the camera an extra cell to the north and then // drawing one extra tile offscreen? That should accomplish it! const cameraViewWidth = Terminal.width ~/ 2 + 1; const cameraViewHeight = Terminal.height ~/ 2 + 1; const cameraMargin = 4; // how far the camera is from the ideal position before it pans const cameraTileOffset = geo.Offset(-1, -1); extension CameraParts on SiteMode { void cameraInit() { camera = _cameraIdeal(); } void cameraMaintain() { var ideal = _cameraIdeal(); while (camera.x < ideal.x - cameraMargin) { camera = geo.Offset(camera.x + 1, camera.y); } while (camera.x > ideal.x + cameraMargin) { camera = geo.Offset(camera.x - 1, camera.y); } while (camera.y < ideal.y - cameraMargin) { camera = geo.Offset(camera.x, camera.y + 1); } while (camera.y > ideal.y + cameraMargin) { camera = geo.Offset(camera.x, camera.y - 1); } } geo.Offset _cameraIdeal() { return geo.Offset(playerPosition.x - cameraViewWidth ~/ 2 - 1, playerPosition.y - cameraViewHeight ~/ 2 - 1); } void cameraDraw() { // Draw the world! // Work in columns, top to bottom, which should facilitate our fake 3D effects for (var cx = 0; cx < cameraViewWidth; cx++) { for (var cy = 0; cy < cameraViewHeight; cy++) { var tx = cameraTileOffset.x + cx * 2; var ty = cameraTileOffset.y + cy * 2; cameraDrawFloor(tx, ty, camera.x + cx, camera.y + cy); } } for (var cx = 0; cx < cameraViewWidth; cx++) { for (var cy = 0; cy < cameraViewHeight; cy++) { var tx = cameraTileOffset.x + cx * 2; var ty = cameraTileOffset.y + cy * 2; cameraDrawCell(tx, ty, camera.x + cx, camera.y + cy); } } } void cameraDrawFloor(int tx, int ty, int cx, int cy) { var cxy = geo.Offset(cx, cy); if (!fovVisible.contains(cxy)) { return; } var tile = level.tiles.get(cx, cy); at(tx, ty).bg(Palette.sitemodeSeenFloor).puts(" "); } void cameraDrawCell(int tx, int ty, int cx, int cy) { var cursorAbove = at(tx, ty); var cursorBelow = at(tx, ty + 2); LevelTile? tile; // TODO: Fade tiles when loaded from memory // TODO: Darken live floors bool seenLive; var cxy = geo.Offset(cx, cy); if (fovVisible.contains(cxy)) { tile = level.tiles.get(cx, cy); seenLive = true; } else if (fovMemory.contains(cxy)) { tile = level.tiles.get(cx, cy); seenLive = false; } else { return; } if (fovMovable.contains(geo.Offset(cx, cy))) { cursorAbove .highlight() .act(Act(label: "Move", callback: () => playerMoveTo(cx, cy))); } var colorWall = Palette.sitemodeSeenWall; var colorWallInner = Palette.sitemodeSeenWall; var colorExit = Palette.sitemodeSeenExit; var colorDoor = Palette.sitemodeSeenDoor; if (cy >= playerPosition.y) { colorWallInner = Palette.sitemodeUnseenWall; // we wouldn't see it anyway } if (!seenLive) { colorWallInner = colorWall = Palette.sitemodeUnseenWall; colorExit = Palette.sitemodeUnseenExit; colorDoor = Palette.sitemodeUnseenDoor; } switch (tile) { case null: case LevelTile.floor: case LevelTile.openDoor: cursorAbove.touch(2); case LevelTile.exit: cursorAbove.big().fg(colorExit).puts("X"); case LevelTile.wall: cursorAbove.fg(colorWall).puts("██"); cursorBelow.small().fg(colorWallInner).puts("░░"); case LevelTile.closedDoor: cursorAbove.big().fg(colorDoor).puts("+"); } var cursorContent = at(tx, ty); if (geo.Offset(cx, cy) == playerPosition) { cursorContent.fg(Palette.sitemodePlayer).big().putc(0xff); } } }