📜
tilemap_copy.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Debug alert for mobile debugging if (typeof debugAlert === 'function') { debugAlert('tilemap.js starting to load'); } /* ============================================================ Tile groups bridge (from tilepicker.js) ============================================================ */ let groupsCache = []; // local cache from Tilepicker function readGroups() { if (window.Tilepicker && typeof window.Tilepicker.getGroups === 'function') { const g = window.Tilepicker.getGroups(); if (Array.isArray(g)) groupsCache = g; } else if (typeof groups !== 'undefined' && Array.isArray(groups)) { groupsCache = groups; } else { groupsCache = []; } if (activePaletteGroup >= groupsCache.length) { activePaletteGroup = Math.max(0, groupsCache.length - 1); } } function getTileDims(tile) { const w = tile.width != null ? tile.width : tile.size; const h = tile.height != null ? tile.height : tile.size; return { w, h }; } window.addEventListener('tiles:updated', () => { readGroups(); try { createTilePalette(); } catch (_) {} }); /* ============================================================ Multiple tilemap support (+ stretched anchors per map) ============================================================ */ let tilemaps = [ { id: 0, name: "Map 1", width: 20, height: 15, tileSize: 32, data: [], stretchedAnchors: new Map() } ]; let currentTilemapIndex = 0; let nextTilemapId = 1; // Current tilemap state let selectedMapTile = null; let activePaletteGroup = 0; // ===== Brush state ===== let brushW = 1; let brushH = 1; let isMouseDown = false; let stretchSingle = true; // << NEW: when true and brush > 1 cell, stretch one tile instead of duplicating /* ===== Helpers ===== */ function getCurrentTilemap() { const tm = tilemaps[currentTilemapIndex]; if (!tm.stretchedAnchors) tm.stretchedAnchors = new Map(); return tm; } /** * Initialize the map data array for current tilemap */ function initializeMapData() { const tilemap = getCurrentTilemap(); tilemap.data = new Array(tilemap.width * tilemap.height).fill(0); tilemap.stretchedAnchors.clear(); } /** * Create a new tilemap */ function createNewTilemap() { const name = prompt("Enter tilemap name:", `Map ${tilemaps.length + 1}`); if (!name) return; const width = parseInt(prompt("Enter width (5-100):", "20")); const height = parseInt(prompt("Enter height (5-100):", "15")); if (isNaN(width) || isNaN(height) || width < 5 || width > 100 || height < 5 || height > 100) { alert("Invalid dimensions. Must be between 5 and 100."); return; } const newTilemap = { id: nextTilemapId++, name, width, height, tileSize: 32, data: new Array(width * height).fill(0), stretchedAnchors: new Map() }; tilemaps.push(newTilemap); currentTilemapIndex = tilemaps.length - 1; updateTilemapTabs(); updateControls(); createMapGrid(); createTilePalette(); } /** * Switch to a different tilemap */ function switchTilemap(index) { if (index >= 0 && index < tilemaps.length) { currentTilemapIndex = index; updateTilemapTabs(); updateControls(); createMapGrid(); createTilePalette(); } } /** * Remove a tilemap */ function removeTilemap(index) { if (tilemaps.length <= 1) { alert("Cannot delete the last tilemap"); return; } if (confirm(`Delete "${tilemaps[index].name}"?`)) { tilemaps.splice(index, 1); if (currentTilemapIndex >= tilemaps.length) { currentTilemapIndex = tilemaps.length - 1; } else if (currentTilemapIndex > index) { currentTilemapIndex--; } updateTilemapTabs(); updateControls(); createMapGrid(); createTilePalette(); } } /** * Update tilemap tabs */ function updateTilemapTabs() { const container = document.getElementById('tilemapTabs'); if (!container) return; container.innerHTML = ''; tilemaps.forEach((tilemap, index) => { const tab = document.createElement('button'); tab.textContent = tilemap.name; tab.style.cssText = ` background: ${index === currentTilemapIndex ? '#6cf' : '#555'}; color: ${index === currentTilemapIndex ? '#000' : '#fff'}; border: none; padding: 4px 8px; margin: 0 2px; border-radius: 4px; cursor: pointer; font-size: 11px; position: relative; `; tab.addEventListener('click', () => switchTilemap(index)); if (tilemaps.length > 1 && index !== currentTilemapIndex) { const deleteBtn = document.createElement('span'); deleteBtn.textContent = '×'; deleteBtn.style.cssText = ` position: absolute; top: -2px; right: -2px; background: #f44; color: white; border-radius: 50%; width: 14px; height: 14px; font-size: 8px; display: flex; align-items: center; justify-content: center; cursor: pointer; `; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); removeTilemap(index); }); tab.appendChild(deleteBtn); } container.appendChild(tab); }); const addBtn = document.createElement('button'); addBtn.textContent = '+'; addBtn.style.cssText = ` background: #4a4; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; margin-left: 5px; `; addBtn.addEventListener('click', createNewTilemap); container.appendChild(addBtn); } /** * Update controls to reflect current tilemap */ function updateControls() { const tilemap = getCurrentTilemap(); const tileSizeSelect = document.getElementById('tileSizeSelect'); const gridWidth = document.getElementById('gridWidth'); const gridHeight = document.getElementById('gridHeight'); const brushWInput = document.getElementById('brushW'); const brushHInput = document.getElementById('brushH'); const stretchToggle = document.getElementById('stretchToggle'); if (tileSizeSelect) tileSizeSelect.value = tilemap.tileSize; if (gridWidth) gridWidth.value = tilemap.width; if (gridHeight) gridHeight.value = tilemap.height; if (brushWInput) brushWInput.value = brushW; if (brushHInput) brushHInput.value = brushH; if (stretchToggle) stretchToggle.checked = stretchSingle; } /** * Resize the grid with data preservation */ function resizeGrid(newWidth, newHeight) { const tilemap = getCurrentTilemap(); if (newWidth === tilemap.width && newHeight === tilemap.height) return; const hasData = tilemap.data.some(tile => tile !== 0); if (hasData && !confirm(`Resize grid from ${tilemap.width}×${tilemap.height} to ${newWidth}×${newHeight}? This may crop or clear some tiles.`)) { return; } const oldData = [...tilemap.data]; const oldWidth = tilemap.width; const oldHeight = tilemap.height; tilemap.width = newWidth; tilemap.height = newHeight; tilemap.data = new Array(newWidth * newHeight).fill(0); tilemap.stretchedAnchors.clear(); for (let y = 0; y < Math.min(oldHeight, newHeight); y++) { for (let x = 0; x < Math.min(oldWidth, newWidth); x++) { const oldIndex = y * oldWidth + x; const newIndex = y * newWidth + x; tilemap.data[newIndex] = oldData[oldIndex]; } } createMapGrid(); updateControls(); } /** * Utility: overlap check for stretched rects */ function rectsOverlap(ax, ay, aw, ah, bx, by, bw, bh) { return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by; } /** * Clean up any stretched anchors that overlap a given area */ function removeOverlappingStretched(areaX, areaY, areaW, areaH) { const tilemap = getCurrentTilemap(); const toDelete = []; for (const [key, meta] of tilemap.stretchedAnchors.entries()) { const [sx, sy] = key.split(',').map(n => parseInt(n, 10)); if (rectsOverlap(areaX, areaY, areaW, areaH, sx, sy, meta.w, meta.h)) { toDelete.push(key); } } for (const key of toDelete) { const [sx, sy] = key.split(',').map(n => parseInt(n, 10)); const meta = tilemap.stretchedAnchors.get(key); // clear covered cells to 0 for (let dy = 0; dy < meta.h; dy++) { for (let dx = 0; dx < meta.w; dx++) { const x = sx + dx, y = sy + dy; const idx = y * tilemap.width + x; if (idx >= 0 && idx < tilemap.data.length) tilemap.data[idx] = 0; } } tilemap.stretchedAnchors.delete(key); } } /* ===== Brush placement (with stretch) ===== */ function placeBrushAt(topLeftX, topLeftY) { const tileId = selectedMapTile ?? 0; const tilemap = getCurrentTilemap(); // Erase path: if tileId == 0, just clear cells and overlapping stretched rects if (tileId === 0) { removeOverlappingStretched(topLeftX, topLeftY, brushW, brushH); for (let dy = 0; dy < brushH; dy++) { for (let dx = 0; dx < brushW; dx++) { const x = topLeftX + dx, y = topLeftY + dy; if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { setMapTile(x, y, 0); } } } return; } // Stretch mode across area (>1 cell) if (stretchSingle && (brushW * brushH > 1)) { // Remove any existing stretched rect overlapping our target area removeOverlappingStretched(topLeftX, topLeftY, brushW, brushH); // Mark all covered cells as children (-tileId), anchor as +tileId for (let dy = 0; dy < brushH; dy++) { for (let dx = 0; dx < brushW; dx++) { const x = topLeftX + dx, y = topLeftY + dy; if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { const isAnchor = (dx === 0 && dy === 0); setMapTile(x, y, isAnchor ? tileId : -tileId); } } } // Record anchor meta tilemap.stretchedAnchors.set(`${topLeftX},${topLeftY}`, { w: brushW, h: brushH, tileId }); return; } // Normal (non-stretched) stamping: duplicates per cell for (let dy = 0; dy < brushH; dy++) { for (let dx = 0; dx < brushW; dx++) { const x = topLeftX + dx, y = topLeftY + dy; if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { // Overwriting a stretched child/anchor? Clean overlapping stretched first for safe overwrite. removeOverlappingStretched(x, y, 1, 1); setMapTile(x, y, tileId); } } } } /** * Set a tile in the current map */ function setMapTile(x, y, tileId) { const tilemap = getCurrentTilemap(); if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { const index = y * tilemap.width + x; tilemap.data[index] = tileId; const cell = document.querySelector(`[data-map-x="${x}"][data-map-y="${y}"]`); if (cell) updateMapCell(cell, tileId); } } /** * Get a tile from the current map */ function getMapTile(x, y) { const tilemap = getCurrentTilemap(); if (x >= 0 && x < tilemap.width && y >= 0 && y < tilemap.height) { const index = y * tilemap.width + x; return tilemap.data[index]; } return 0; } /** * Update a visual map cell with tile data */ function updateMapCell(cell, tileId) { const tilemap = getCurrentTilemap(); const x = parseInt(cell.dataset.mapX); const y = parseInt(cell.dataset.mapY); // Clear previous content but preserve coordinate label const coordLabel = cell.querySelector('.coord-label'); cell.innerHTML = ''; if (coordLabel) { cell.appendChild(coordLabel); } else { const label = document.createElement('span'); label.className = 'coord-label'; label.textContent = `${x},${y}`; label.style.cssText = ` position: absolute; top: 2px; left: 2px; font-size: 8px; color: rgba(255,255,255,0.5); pointer-events: none; z-index: 1; `; cell.appendChild(label); } cell.style.backgroundColor = ''; // Empty if (tileId === 0) { cell.style.backgroundColor = 'transparent'; return; } // If negative, this is a covered child of a stretch; render nothing. if (tileId < 0) return; // Positive: may be normal or an anchor of a stretch readGroups(); if (!groupsCache.length) { console.warn('No tile groups available'); return; } // Find the tile by id let foundTile = null; for (const group of groupsCache) { const t = group.tiles.find(tt => tt.uniqueId === tileId); if (t) { foundTile = t; break; } } if (!foundTile) return; const { w, h } = getTileDims(foundTile); // Is this cell an anchor? const anchorMeta = getCurrentTilemap().stretchedAnchors.get(`${x},${y}`); if (anchorMeta) { // Draw SINGLE scaled sprite across brush area const totalW = anchorMeta.w * tilemap.tileSize; const totalH = anchorMeta.h * tilemap.tileSize; const canvas = document.createElement('canvas'); canvas.width = totalW; canvas.height = totalH; // Let it overflow the cell to cover neighbors (simple and effective) canvas.style.cssText = ` position: absolute; top: 0; left: 0; width: ${totalW}px; height: ${totalH}px; pointer-events: none; `; const ctx = canvas.getContext('2d'); const tempCanvas = document.createElement('canvas'); tempCanvas.width = w; tempCanvas.height = h; const tempCtx = tempCanvas.getContext('2d'); tempCtx.putImageData(foundTile.data, 0, 0); ctx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, totalW, totalH); cell.appendChild(canvas); // Anchor ID badge const idBadge = document.createElement('span'); idBadge.className = 'id-badge'; idBadge.textContent = tileId; idBadge.style.cssText = ` position: absolute; bottom: 2px; right: 2px; font-size: 8px; color: #fff; background: rgba(0,0,0,0.7); padding: 1px 3px; border-radius: 3px; pointer-events: none; z-index: 2; `; cell.appendChild(idBadge); return; } // Normal single-cell draw const canvas = document.createElement('canvas'); canvas.width = tilemap.tileSize; canvas.height = tilemap.tileSize; canvas.style.cssText = 'width: 100%; height: 100%; position: absolute; top: 0; left: 0;'; const ctx = canvas.getContext('2d'); const tempCanvas = document.createElement('canvas'); tempCanvas.width = w; tempCanvas.height = h; const tempCtx = tempCanvas.getContext('2d'); tempCtx.putImageData(foundTile.data, 0, 0); ctx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, tilemap.tileSize, tilemap.tileSize); cell.appendChild(canvas); const idBadge = document.createElement('span'); idBadge.className = 'id-badge'; idBadge.textContent = tileId; idBadge.style.cssText = ` position: absolute; bottom: 2px; right: 2px; font-size: 8px; color: #fff; background: rgba(0,0,0,0.7); padding: 1px 3px; border-radius: 3px; pointer-events: none; z-index: 2; `; cell.appendChild(idBadge); } /** * Create the map grid */ function createMapGrid() { const container = document.getElementById('mapGrid'); if (!container) return; const tilemap = getCurrentTilemap(); container.innerHTML = ''; container.style.cssText = ` display: grid; grid-template-columns: repeat(${tilemap.width}, ${tilemap.tileSize}px); grid-template-rows: repeat(${tilemap.height}, ${tilemap.tileSize}px); gap: 1px; background: #333; padding: 10px; overflow: auto; max-height: 400px; user-select: none; position: relative; `; // Mouse-up cleanup even if cursor leaves const onUp = () => { isMouseDown = false; }; document.addEventListener('mouseup', onUp, { passive: true }); for (let y = 0; y < tilemap.height; y++) { for (let x = 0; x < tilemap.width; x++) { const cell = document.createElement('div'); cell.className = 'map-cell'; cell.dataset.mapX = x; cell.dataset.mapY = y; cell.style.cssText = ` width: ${tilemap.tileSize}px; height: ${tilemap.tileSize}px; border: 1px solid rgba(255,255,255,0.2); background: #222; cursor: crosshair; position: relative; overflow: visible; `; const label = document.createElement('span'); label.className = 'coord-label'; label.textContent = `${x},${y}`; label.style.cssText = ` position: absolute; top: 2px; left: 2px; font-size: 8px; color: rgba(255,255,255,0.5); pointer-events: none; z-index: 1; `; cell.appendChild(label); cell.addEventListener('mousedown', (e) => { if (e.button !== 0) return; isMouseDown = true; placeBrushAt(x, y); }); cell.addEventListener('mouseenter', () => { if (isMouseDown) placeBrushAt(x, y); cell.style.backgroundColor = '#444'; }); cell.addEventListener('mouseleave', () => { if (getMapTile(x, y) === 0) cell.style.backgroundColor = '#222'; }); container.appendChild(cell); } } // Render existing data (anchors & singles) for (let y = 0; y < tilemap.height; y++) { for (let x = 0; x < tilemap.width; x++) { const tileId = getMapTile(x, y); if (tileId !== 0) { const cell = container.querySelector(`[data-map-x="${x}"][data-map-y="${y}"]`); if (cell) updateMapCell(cell, tileId); } } } } /** * Create the tile palette from picked tiles */ function createTilePalette() { const container = document.getElementById('tilePalette'); if (!container) return; readGroups(); const tilemap = getCurrentTilemap(); container.innerHTML = ''; if (!groupsCache.length) { container.innerHTML = '<div style="color: #888; padding: 10px;">No tile groups available. Use the Tile Picker first.</div>'; return; } const controlsBar = document.createElement('div'); controlsBar.style.cssText = 'margin-bottom: 6px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;'; // Group tabs const tabContainer = document.createElement('div'); tabContainer.style.cssText = 'display: flex; gap: 2px;'; groupsCache.forEach((group, index) => { const tab = document.createElement('button'); tab.textContent = `G${index + 1}`; tab.style.cssText = ` background: ${index === activePaletteGroup ? '#6cf' : '#444'}; color: ${index === activePaletteGroup ? '#000' : '#fff'}; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; `; tab.addEventListener('click', () => { activePaletteGroup = index; createTilePalette(); }); tabContainer.appendChild(tab); }); // Clear map button const clearBtn = document.createElement('button'); clearBtn.textContent = 'Clear All'; clearBtn.style.cssText = ` background: #d44; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; `; clearBtn.addEventListener('click', () => clearMap()); // Brush controls const brushWrap = document.createElement('div'); brushWrap.style.cssText = 'display:flex; align-items:center; gap:6px; background:#2b2b2b; border:1px solid #444; padding:6px 8px; border-radius:6px;'; const labelBW = document.createElement('span'); labelBW.textContent = 'Brush'; labelBW.style.cssText = 'color:#ccc;font-size:11px;'; const bw = document.createElement('input'); bw.type = 'number'; bw.min = '1'; bw.value = String(brushW); bw.id = 'brushW'; bw.title = 'Brush width (columns)'; bw.style.cssText = 'width:48px;background:#333;color:#fff;border:1px solid #666;border-radius:4px;padding:3px 4px;font-size:11px;'; bw.addEventListener('change', () => { brushW = Math.max(1, parseInt(bw.value || '1', 10)); updateControls(); }); const xLabel = document.createElement('span'); xLabel.textContent = '×'; xLabel.style.cssText = 'color:#aaa;font-size:11px;'; const bh = document.createElement('input'); bh.type = 'number'; bh.min = '1'; bh.value = String(brushH); bh.id = 'brushH'; bh.title = 'Brush height (rows)'; bh.style.cssText = 'width:48px;background:#333;color:#fff;border:1px solid #666;border-radius:4px;padding:3px 4px;font-size:11px;'; bh.addEventListener('change', () => { brushH = Math.max(1, parseInt(bh.value || '1', 10)); updateControls(); }); const preset11 = document.createElement('button'); preset11.textContent = '1×1'; preset11.style.cssText = 'background:#555;color:#fff;border:1px solid #777;border-radius:4px;padding:4px 6px;font-size:11px;cursor:pointer;'; preset11.addEventListener('click', () => { brushW = 1; brushH = 1; updateControls(); }); const preset12 = document.createElement('button'); preset12.textContent = '1×2 (iso)'; preset12.style.cssText = 'background:#555;color:#fff;border:1px solid #777;border-radius:4px;padding:4px 6px;font-size:11px;cursor:pointer;'; preset12.title = 'Quick isometric-style vertical stretch'; preset12.addEventListener('click', () => { brushW = 1; brushH = 2; stretchSingle = true; updateControls(); }); const preset21 = document.createElement('button'); preset21.textContent = '2×1'; preset21.style.cssText = 'background:#555;color:#fff;border:1px solid #777;border-radius:4px;padding:4px 6px;font-size:11px;cursor:pointer;'; preset21.addEventListener('click', () => { brushW = 2; brushH = 1; updateControls(); }); const preset22 = document.createElement('button'); preset22.textContent = '2×2'; preset22.style.cssText = 'background:#555;color:#fff;border:1px solid #777;border-radius:4px;padding:4px 6px;font-size:11px;cursor:pointer;'; preset22.addEventListener('click', () => { brushW = 2; brushH = 2; updateControls(); }); // Stretch toggle const stretchLbl = document.createElement('label'); stretchLbl.style.cssText = 'display:flex;align-items:center;gap:6px;color:#ccc;font-size:11px;'; const stretchChk = document.createElement('input'); stretchChk.id = 'stretchToggle'; stretchChk.type = 'checkbox'; stretchChk.checked = stretchSingle; stretchChk.addEventListener('change', () => { stretchSingle = !!stretchChk.checked; }); stretchLbl.appendChild(stretchChk); stretchLbl.appendChild(document.createTextNode('Stretch brush')); brushWrap.appendChild(labelBW); brushWrap.appendChild(bw); brushWrap.appendChild(xLabel); brushWrap.appendChild(bh); brushWrap.appendChild(preset11); brushWrap.appendChild(preset12); brushWrap.appendChild(preset21); brushWrap.appendChild(preset22); brushWrap.appendChild(stretchLbl); controlsBar.appendChild(tabContainer); controlsBar.appendChild(clearBtn); controlsBar.appendChild(brushWrap); container.appendChild(controlsBar); // Tiles container const tilesContainer = document.createElement('div'); tilesContainer.style.cssText = 'display: flex; align-items: center; gap: 3px; flex-wrap: wrap;'; // Eraser const eraserBtn = document.createElement('div'); eraserBtn.className = 'palette-tile'; eraserBtn.style.cssText = ` width: ${tilemap.tileSize}px; height: ${tilemap.tileSize}px; border: 2px solid #666; cursor: pointer; background: #333; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 12px; position: relative; `; eraserBtn.textContent = 'X'; eraserBtn.title = 'Eraser'; eraserBtn.addEventListener('click', () => { selectedMapTile = 0; document.querySelectorAll('.palette-tile').forEach(t => t.classList.remove('selected')); eraserBtn.classList.add('selected'); }); tilesContainer.appendChild(eraserBtn); // Tiles from active group if (activePaletteGroup < groupsCache.length) { const activeGroup = groupsCache[activePaletteGroup]; activeGroup.tiles.forEach(tile => { const { w, h } = getTileDims(tile); const tileDiv = document.createElement('div'); tileDiv.className = 'palette-tile'; tileDiv.style.cssText = ` width: ${tilemap.tileSize}px; height: ${tilemap.tileSize}px; border: 2px solid #666; cursor: pointer; overflow: hidden; position: relative; background: #222; `; const canvas = document.createElement('canvas'); canvas.width = tilemap.tileSize; canvas.height = tilemap.tileSize; canvas.style.cssText = 'width: 100%; height: 100%; position: absolute; top: 0; left: 0;'; const ctx = canvas.getContext('2d'); const tempCanvas = document.createElement('canvas'); tempCanvas.width = w; tempCanvas.height = h; const tempCtx = tempCanvas.getContext('2d'); tempCtx.putImageData(tile.data, 0, 0); ctx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, tilemap.tileSize, tilemap.tileSize); tileDiv.appendChild(canvas); const idBadge = document.createElement('span'); idBadge.textContent = tile.uniqueId; idBadge.style.cssText = ` position: absolute; bottom: 1px; right: 1px; font-size: 7px; color: #fff; background: rgba(0,0,0,0.8); padding: 1px 2px; border-radius: 2px; pointer-events: none; z-index: 2; line-height: 1; `; tileDiv.appendChild(idBadge); tileDiv.addEventListener('click', () => { selectedMapTile = tile.uniqueId; document.querySelectorAll('.palette-tile').forEach(t => t.classList.remove('selected')); tileDiv.classList.add('selected'); }); tilesContainer.appendChild(tileDiv); }); } container.appendChild(tilesContainer); } /** * Open the tilemap overlay - main entry point */ function openTilemapOverlay() { const overlayContent = document.getElementById('overlayContent'); overlayContent.innerHTML = ` <div style="margin-bottom: 10px; padding: 0 10px;"> <div id="tilemapTabs" style="margin-bottom: 10px;"></div> <div style="display: flex; justify-content: flex-start; align-items: center; gap: 10px; flex-wrap: wrap;"> <select id="tileSizeSelect" style="background: #333; color: #fff; border: 1px solid #666; padding: 4px 8px; border-radius: 4px;"> <option value="8">8px</option> <option value="16">16px</option> <option value="32" selected>32px</option> <option value="64">64px</option> <option value="128">128px</option> </select> <input type="number" id="gridWidth" min="5" max="100" style="width: 50px; background: #333; color: #fff; border: 1px solid #666; padding: 4px; border-radius: 4px;"> <span style="color: #888;">×</span> <input type="number" id="gridHeight" min="5" max="100" style="width: 50px; background: #333; color: #fff; border: 1px solid #666; padding: 4px; border-radius: 4px;"> <button id="resizeGrid" style="background: #666; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px;">Resize</button> </div> </div> <div style="margin-bottom: 10px; padding: 0 10px;"> <div id="tilePalette"></div> </div> <div style="height: calc(100% - 140px); overflow: auto; padding: 0 10px;"> <div id="mapGrid"></div> </div> `; setupEventHandlers(); readGroups(); updateTilemapTabs(); updateControls(); const tilemap = getCurrentTilemap(); if (!tilemap.data || tilemap.data.length === 0) initializeMapData(); createMapGrid(); createTilePalette(); } /** * Set up event handlers for controls */ function setupEventHandlers() { document.getElementById('tileSizeSelect').addEventListener('change', (e) => { const tilemap = getCurrentTilemap(); tilemap.tileSize = parseInt(e.target.value); createMapGrid(); createTilePalette(); }); document.getElementById('resizeGrid').addEventListener('click', () => { const newWidth = parseInt(document.getElementById('gridWidth').value); const newHeight = parseInt(document.getElementById('gridHeight').value); if (newWidth >= 5 && newWidth <= 100 && newHeight >= 5 && newHeight <= 100) { resizeGrid(newWidth, newHeight); } else { alert('Grid size must be between 5 and 100'); } }); ['gridWidth', 'gridHeight'].forEach(id => { document.getElementById(id).addEventListener('keypress', (e) => { if (e.key === 'Enter') document.getElementById('resizeGrid').click(); }); }); // Toggle brush 1×1 / 1×2 with "B" window.addEventListener('keydown', (e) => { if (e.key.toLowerCase() === 'b') { if (brushW === 1 && brushH === 1) { brushW = 1; brushH = 2; stretchSingle = true; } else { brushW = 1; brushH = 1; } updateControls(); } }); } /** * Clear the entire current map */ function clearMap() { if (confirm('Clear the entire map?')) { const tilemap = getCurrentTilemap(); tilemap.data.fill(0); tilemap.stretchedAnchors.clear(); for (let y = 0; y < tilemap.height; y++) { for (let x = 0; x < tilemap.width; x++) { const cell = document.querySelector(`[data-map-x="${x}"][data-map-y="${y}"]`); if (cell) updateMapCell(cell, 0); } } } } // Add CSS for selected palette tiles const style = document.createElement('style'); style.textContent = ` .palette-tile.selected { border-color: #6cf !important; box-shadow: 0 0 5px #6cf; } .map-cell:hover { border-color: #6cf !important; } `; document.head.appendChild(style); // Debug alert for mobile debugging - success if (typeof debugAlert === 'function') { debugAlert('tilemap.js loaded successfully'); }