treehouse/static/js/tairu/tairu.js
2024-02-17 21:03:45 +01:00

252 lines
8.5 KiB
JavaScript

import { Frame, defineFrame } from "./framework.js";
import tilemapRegistry from "./tilemap-registry.js";
export function canConnect(tile) {
return tile == 1;
}
export function shouldConnect(a, b) {
return a == b;
}
const dirs47 = {
E: 0b0000_0001,
SE: 0b0000_0010,
S: 0b0000_0100,
SW: 0b0000_1000,
W: 0b0001_0000,
NW: 0b0010_0000,
N: 0b0100_0000,
NE: 0b1000_0000,
};
function isSet(integer, bit) {
return (integer & bit) == bit;
}
function removeRedundancies(t) {
if (isSet(t, dirs47.SE) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.E))) {
t &= ~dirs47.SE;
}
if (isSet(t, dirs47.SW) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.W))) {
t &= ~dirs47.SW;
}
if (isSet(t, dirs47.NW) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.W))) {
t &= ~dirs47.NW;
}
if (isSet(t, dirs47.NE) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.E))) {
t &= ~dirs47.NE;
}
return t;
}
function ordinalDirections() {
let unique = new Set();
for (let i = 0; i <= 0b1111_1111; ++i) {
unique.add(removeRedundancies(i));
}
return Array.from(unique).sort((a, b) => a - b);
}
let xToConnectionBitSet = ordinalDirections();
let connectionBitSetToX = new Uint8Array(256);
for (let i = 0; i < xToConnectionBitSet.length; ++i) {
connectionBitSetToX[xToConnectionBitSet[i]] = i;
}
console.log(connectionBitSetToX);
export class TileEditor extends Frame {
constructor() {
super();
this.tileCursor = { x: 0, y: 0 };
this.colorScheme = {
background: "#F7F7F7",
grid: "#00000011",
tileCursor: "#222222",
tiles: [
"transparent",
"#eb134a",
],
};
this.tileColorPalette = [
"transparent",
"#eb134a",
];
}
connectedCallback() {
super.connectedCallback();
this.tileSize = parseInt(this.getAttribute("data-tile-size"));
let tilemapId = this.getAttribute("data-tilemap-id");
if (tilemapId != null) {
this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")];
} else {
throw new ReferenceError(`tilemap '${tilemapId}' does not exist`);
}
// 0st element is explicitly null because it represents the empty tile.
this.tilesets = [null];
this.tilesets47 = [null];
let attachedImages = this.getElementsByTagName("img");
for (let image of attachedImages) {
if (image.hasAttribute("data-tairu-tileset")) {
let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset"));
this.tilesets[tilesetIndex] = image;
} else if (image.hasAttribute("data-tairu-tileset-47")) {
let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset-47"));
this.tilesets47[tilesetIndex] = image;
}
}
this.width = this.tilemap.width * this.tileSize;
this.height = this.tilemap.height * this.tileSize;
this.hasFocus = false;
this.paintingTile = null;
this.addEventListener("mousemove", event => this.mouseMoved(event));
this.addEventListener("mousedown", event => this.mousePressed(event));
this.addEventListener("mouseup", event => this.mouseReleased(event));
this.addEventListener("mouseenter", _ => this.hasFocus = true);
this.addEventListener("mouseleave", _ => this.hasFocus = false);
this.addEventListener("contextmenu", event => event.preventDefault());
// TODO: This should also work on mobile.
}
draw() {
this.ctx.fillStyle = this.colorScheme.background;
this.ctx.fillRect(0, 0, this.width, this.height);
this.drawTiles();
this.drawGrid();
if (this.hasFocus) {
this.drawTileCursor();
}
}
drawGrid() {
this.ctx.beginPath();
for (let x = 0; x < this.tilemap.width; ++x) {
this.ctx.moveTo(x * this.tileSize, 0);
this.ctx.lineTo(x * this.tileSize, this.height);
}
for (let y = 0; y < this.tilemap.width; ++y) {
this.ctx.moveTo(0, y * this.tileSize);
this.ctx.lineTo(this.width, y * this.tileSize);
}
this.ctx.strokeStyle = this.colorScheme.grid;
this.ctx.lineWidth = 1;
this.ctx.stroke();
}
drawTileCursor() {
this.ctx.strokeStyle = this.colorScheme.tileCursor;
this.ctx.lineWidth = 5;
this.ctx.strokeRect(this.tileCursor.x * this.tileSize, this.tileCursor.y * this.tileSize, this.tileSize, this.tileSize);
}
get hasTilesets() {
// Remember that tile 0 represents emptiness.
return this.tilesets.length > 1 || this.tilesets47.length > 1;
}
drawTiles() {
if (this.hasTilesets) {
this.drawTexturedTiles();
} else {
this.drawColoredTiles();
}
}
drawColoredTiles() {
for (let y = 0; y < this.tilemap.height; ++y) {
for (let x = 0; x < this.tilemap.width; ++x) {
let tile = this.tilemap.at(x, y);
if (tile != 0) {
this.ctx.fillStyle = this.colorScheme.tiles[tile];
this.ctx.fillRect(x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize);
}
}
}
}
drawTexturedTiles() {
this.ctx.imageSmoothingEnabled = false;
for (let y = 0; y < this.tilemap.height; ++y) {
for (let x = 0; x < this.tilemap.width; ++x) {
let tile = this.tilemap.at(x, y);
if (tile != 0) {
let tileset16 = this.tilesets[tile];
let tileset47 = this.tilesets47[tile];
let tileset = tileset47 != null ? tileset47 : tileset16;
let tileIndex = 0;
if (tileset47 != null) {
let rawTileIndex = 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? dirs47.E : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y + 1)) ? dirs47.SE : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? dirs47.S : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y + 1)) ? dirs47.SW : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? dirs47.W : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y - 1)) ? dirs47.NW : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? dirs47.N : 0;
rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y - 1)) ? dirs47.NE : 0;
tileIndex = connectionBitSetToX[removeRedundancies(rawTileIndex)];
} else {
tileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0;
tileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0;
tileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0;
tileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0;
}
let tilesetTileSize = tileset.height;
let tilesetX = tileIndex * tilesetTileSize;
let tilesetY = 0;
this.ctx.drawImage(
tileset,
tilesetX, tilesetY, tilesetTileSize, tilesetTileSize,
x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize,
);
}
}
}
}
mouseMoved(event) {
let mouse = this.getMousePositionFromEvent(event);
this.tileCursor.x = Math.floor(mouse.x / this.tileSize);
this.tileCursor.y = Math.floor(mouse.y / this.tileSize);
this.paintTileUnderCursor();
}
mousePressed(event) {
event.preventDefault();
if (event.button == 0) {
this.paintingTile = 1;
} else if (event.button == 2) {
this.paintingTile = 0;
}
this.paintTileUnderCursor();
}
mouseReleased() {
this.paintingTile = null;
}
paintTileUnderCursor() {
if (this.paintingTile != null) {
this.tilemap.setAt(this.tileCursor.x, this.tileCursor.y, this.paintingTile);
}
}
}
defineFrame("tairu-editor", TileEditor);