📜
tilepicker_copy2.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Debug alert for mobile debugging if (typeof debugAlert === 'function') debugAlert('tilepicker.js loaded'); // ===== Shared state from files.js ===== // selectedImage, selectedImageName, selectedTileSize are globals on window // We’ll use them directly. let groups = [{ id: 'Group_1', url: null, tiles: [], name: 'Group 1', category: 'None' }]; let currentGroup = 0; let nextUniqueId = 1; // start at 1 (0 = "no object") // Category definitions const TILE_CATEGORIES = [ { value: 'None', label: 'None', description: 'No specific category' }, { value: 'Ground', label: 'Ground', description: 'Static collidable terrain (floors, walls)' }, { value: 'Platform', label: 'Platform', description: 'One-way platform (stand from above, pass through from below)' }, { value: 'Pushable', label: 'Pushable', description: 'Crates/blocks that can be shoved' }, { value: 'Passable', label: 'Passable', description: 'Visuals only, no collision (grass, background tiles)' }, { value: 'Hazard', label: 'Hazard', description: 'Passable but deals damage on touch (spikes, lava)' }, { value: 'Conveyor', label: 'Conveyor', description: 'Collidable, pushes sideways at a set speed' }, { value: 'Climbable', label: 'Climbable', description: 'Ladders, ropes, vines' }, { value: 'Sensor', label: 'Sensor', description: 'Invisible triggers (zone detection, signals)' }, { value: 'Door', label: 'Door', description: 'Blocks path until triggered/opened' }, { value: 'SwitchableToggle', label: 'Switchable Toggle', description: 'Flips back & forth; passable (e.g. gate that opens/closes)' }, { value: 'SwitchableOnce', label: 'Switchable Once', description: 'Changes once to another state; solid (e.g. "?" block → used block)' }, { value: 'AnimationGround', label: 'Animation Ground', description: 'Like Ground but cycles through frames (e.g. glowing floor)' }, { value: 'AnimationPassable', label: 'Animation Passable', description: 'Like Passable but cycles through frames (e.g. flickering torch, water)' }, { value: 'Player', label: 'Player', description: 'The controllable character' }, { value: 'NPC', label: 'NPC', description: 'AI-driven actors' } ]; /** Main entry point (called by files.js via openOverlay('tiles')) */ function openTilePickerOverlay() { const overlayContent = document.getElementById('overlayContent'); const img = window.selectedImage; const name = window.selectedImageName; const tileSize = window.selectedTileSize; if (img && tileSize) { overlayContent.innerHTML = ` <h2>Tile Picker 🧩</h2> <p>Tile size: ${tileSize}px</p> <div id="groupTabs"></div> <div id="groupControls"></div> <div id="pickedImages"></div> <div id="tileViewport"> <div id="tileContainer" style="position:relative; display:inline-block;"> <img id="tileImage" src="${img}" alt="${name}"> </div> </div> `; initializeTilePicker(); } else { overlayContent.innerHTML = ` <h2>Tile Picker 🧩</h2> <p>Select an image and a numeric folder first.</p> `; } } /** Initialize the tile picker functionality */ function initializeTilePicker() { renderTabs(); renderGroupControls(); renderPicked(); setupTileGrid(); } /** Build the grid overlay */ function setupTileGrid() { const imgEl = document.getElementById('tileImage'); if (!imgEl) return; imgEl.onload = () => { const container = document.getElementById('tileContainer'); const w = imgEl.naturalWidth; const h = imgEl.naturalHeight; const size = window.selectedTileSize; // Dimensions imgEl.style.width = w + "px"; imgEl.style.height = h + "px"; container.style.width = w + "px"; container.style.height = h + "px"; // Clear prior cells container.querySelectorAll('.grid-cell').forEach(c => c.remove()); const cols = Math.floor(w / size); for (let y = 0; y < h; y += size) { for (let x = 0; x < w; x += size) { const cell = document.createElement('div'); cell.className = 'grid-cell'; cell.style.cssText = ` position: absolute; left: ${x}px; top: ${y}px; width: ${size}px; height: ${size}px; border: 2px solid rgba(102, 204, 255, 0.7); cursor: pointer; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.3); color: white; font-weight: bold; font-size: 12px; text-shadow: 1px 1px 2px black; box-sizing: border-box; `; const row = Math.floor(y / size); const col = Math.floor(x / size); const tileIndex = row * cols + col + 1; const label = document.createElement('span'); label.textContent = tileIndex; cell.appendChild(label); cell.addEventListener('mouseenter', () => { cell.style.background = 'rgba(102, 204, 255, 0.4)'; cell.style.borderColor = '#6cf'; }); cell.addEventListener('mouseleave', () => { cell.style.background = 'rgba(0, 0, 0, 0.3)'; cell.style.borderColor = 'rgba(102, 204, 255, 0.7)'; }); cell.onclick = () => pickTile(imgEl, x, y, size, window.selectedImage); container.appendChild(cell); } } }; // If the image is cached, onload may not fire; force a tick if (imgEl.complete && imgEl.naturalWidth) { const src = imgEl.src; imgEl.src = ''; imgEl.src = src; } } /** Color helper by category */ function getCategoryColor(category) { const colors = { 'None': '#666', 'Ground': '#8B4513', 'Platform': '#DEB887', 'Pushable': '#CD853F', 'Passable': '#90EE90', 'Hazard': '#FF4500', 'Conveyor': '#4169E1', 'Climbable': '#228B22', 'Sensor': '#9370DB', 'Door': '#B8860B', 'SwitchableToggle': '#FF69B4', 'SwitchableOnce': '#FF1493', 'AnimationGround': '#FF6347', 'AnimationPassable': '#20B2AA', 'Player': '#FFD700', 'NPC': '#87CEEB' }; return colors[category] || '#666'; } /** Modal to choose category for a new group */ function showCategorySelectionDialog(groupName) { const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 10000; `; const dialog = document.createElement('div'); dialog.style.cssText = ` background: #2a2a2a; border-radius: 8px; padding: 20px; max-width: 400px; width: 90%; max-height: 80vh; overflow-y: auto; `; const title = document.createElement('h3'); title.textContent = `Select Category for "${groupName}"`; title.style.cssText = 'color:#6cf;margin:0 0 15px;text-align:center;'; dialog.appendChild(title); const list = document.createElement('div'); list.style.cssText = 'margin-bottom: 20px;'; TILE_CATEGORIES.forEach(category => { const item = document.createElement('div'); item.style.cssText = ` padding:10px;margin:5px 0;background:#333;border-radius:4px;cursor:pointer; border:2px solid transparent;transition:all .2s; `; item.addEventListener('mouseenter', () => { item.style.borderColor = getCategoryColor(category.value); item.style.background = '#444'; }); item.addEventListener('mouseleave', () => { item.style.borderColor = 'transparent'; item.style.background = '#333'; }); const name = document.createElement('div'); name.textContent = category.label; name.style.cssText = 'font-weight:bold;color:#fff;margin-bottom:5px;'; const desc = document.createElement('div'); desc.textContent = category.description; desc.style.cssText = 'font-size:12px;color:#ccc;'; item.appendChild(name); item.appendChild(desc); item.onclick = () => { createGroupWithCategory(groupName, category.value); document.body.removeChild(overlay); }; list.appendChild(item); }); dialog.appendChild(list); const cancel = document.createElement('button'); cancel.textContent = 'Cancel'; cancel.style.cssText = ` background:#666;color:#fff;border:none;padding:8px 16px;border-radius:4px; cursor:pointer;width:100%;font-size:14px; `; cancel.onclick = () => document.body.removeChild(overlay); dialog.appendChild(cancel); overlay.appendChild(dialog); document.body.appendChild(overlay); overlay.onclick = (e) => { if (e.target === overlay) document.body.removeChild(overlay); }; } /** Create a new group with a category */ function createGroupWithCategory(groupName, category) { const groupId = groupName.replace(/[^a-zA-Z0-9]/g, '_'); groups.push({ id: groupId, url: null, tiles: [], name: groupName, category }); currentGroup = groups.length - 1; renderTabs(); renderGroupControls(); renderPicked(); } /** Rename a group */ function renameGroup(groupIndex) { const group = groups[groupIndex]; const currentName = group.name || `Group ${groupIndex + 1}`; const newName = prompt('Enter new group name:', currentName); if (newName !== null && newName.trim() !== '') { group.name = newName.trim(); group.id = newName.trim().replace(/[^a-zA-Z0-9]/g, '_'); renderTabs(); } } /** Change group category */ function changeGroupCategory(groupIndex, newCategory) { if (groupIndex >= 0 && groupIndex < groups.length) { groups[groupIndex].category = newCategory; renderTabs(); renderGroupControls(); } } /** Group controls (category etc.) */ function renderGroupControls() { const controls = document.getElementById('groupControls'); if (!controls) return; controls.innerHTML = ''; controls.style.cssText = ` margin-bottom:10px;padding:10px;background:#333;border-radius:6px; display:flex;align-items:center;gap:10px;flex-wrap:wrap; `; const group = groups[currentGroup]; const label = document.createElement('label'); label.textContent = 'Category:'; label.style.cssText = 'color:#ccc;font-weight:bold;font-size:14px;'; controls.appendChild(label); const sel = document.createElement('select'); sel.style.cssText = ` background:#555;color:#fff;border:1px solid #777;border-radius:4px; padding:5px 8px;font-size:12px;min-width:150px; `; TILE_CATEGORIES.forEach(cat => { const opt = document.createElement('option'); opt.value = cat.value; opt.textContent = cat.label; opt.title = cat.description; opt.selected = (cat.value === group.category); sel.appendChild(opt); }); sel.onchange = () => changeGroupCategory(currentGroup, sel.value); controls.appendChild(sel); const currentCategory = TILE_CATEGORIES.find(c => c.value === group.category); if (currentCategory) { const desc = document.createElement('span'); desc.textContent = currentCategory.description; desc.style.cssText = 'color:#aaa;font-size:12px;font-style:italic;'; controls.appendChild(desc); } } /** Group tabs */ function renderTabs() { const tabBar = document.getElementById('groupTabs'); if (!tabBar) return; tabBar.innerHTML = ''; tabBar.style.cssText = 'margin-bottom:10px;display:flex;gap:5px;align-items:center;flex-wrap:wrap;'; groups.forEach((g, idx) => { const btn = document.createElement('button'); const name = g.name || `Group ${idx + 1}`; const color = getCategoryColor(g.category); btn.textContent = name; btn.style.cssText = ` background:${idx === currentGroup ? '#6cf' : '#555'}; color:${idx === currentGroup ? '#000' : '#fff'}; border:3px solid ${color}; padding:6px 12px;border-radius:4px;cursor:pointer;font-size:12px; position:relative;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; `; btn.onclick = () => { currentGroup = idx; renderTabs(); renderGroupControls(); renderPicked(); }; btn.ondblclick = (e) => { e.stopPropagation(); renameGroup(idx); }; if (g.tiles.length) { const badge = document.createElement('span'); badge.textContent = g.tiles.length; badge.style.cssText = ` position:absolute;top:-5px;right:-5px;background:#f44;color:#fff;border-radius:50%; width:16px;height:16px;font-size:9px;display:flex;align-items:center;justify-content:center; `; btn.appendChild(badge); } const indicator = document.createElement('div'); indicator.style.cssText = ` position:absolute;bottom:-2px;left:50%;transform:translateX(-50%); width:80%;height:3px;background:${color};border-radius:2px; `; btn.appendChild(indicator); tabBar.appendChild(btn); }); const addBtn = document.createElement('button'); addBtn.textContent = '+'; addBtn.style.cssText = ` background:#4a4;color:#fff;border:none;padding:6px 12px;border-radius:4px; cursor:pointer;font-size:14px;font-weight:bold; `; addBtn.onclick = () => { const name = prompt('Enter name for new group:', `Group ${groups.length + 1}`); if (name && name.trim()) showCategorySelectionDialog(name.trim()); }; addBtn.title = 'Create new group'; tabBar.appendChild(addBtn); if (groups[currentGroup] && groups[currentGroup].tiles.length) { const clearBtn = document.createElement('button'); clearBtn.textContent = 'Clear'; clearBtn.style.cssText = ` background:#d44;color:#fff;border:none;padding:6px 12px;border-radius:4px; cursor:pointer;font-size:12px;margin-left:10px; `; clearBtn.onclick = () => { if (confirm('Clear all tiles from this group?')) clearCurrentGroup(); }; clearBtn.title = 'Clear all tiles from current group'; tabBar.appendChild(clearBtn); } } /** Picked tiles panel */ function renderPicked() { const container = document.getElementById('pickedImages'); if (!container) return; container.innerHTML = ''; container.style.cssText = ` margin-bottom:15px;padding:10px;background:#2a2a2a;border-radius:6px; min-height:80px;max-height:200px;overflow-y:auto; `; const group = groups[currentGroup]; if (!group.tiles.length) { container.innerHTML = '<div style="color:#888;text-align:center;padding:20px;">No tiles picked yet. Click on the grid below to select tiles.</div>'; return; } const wrap = document.createElement('div'); wrap.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;'; group.tiles.forEach((tile, idx) => { const card = document.createElement('div'); const color = getCategoryColor(group.category); card.style.cssText = ` position:relative;display:flex;flex-direction:column;align-items:center; padding:5px;background:#333;border-radius:4px;border:2px solid ${color}; `; const canvas = document.createElement('canvas'); canvas.width = Math.min(tile.size, 64); canvas.height = Math.min(tile.size, 64); canvas.style.cssText = 'border:1px solid #666;background:#000;'; const ctx = canvas.getContext('2d'); const temp = document.createElement('canvas'); temp.width = tile.size; temp.height = tile.size; const tctx = temp.getContext('2d'); tctx.putImageData(tile.data, 0, 0); ctx.drawImage(temp, 0, 0, tile.size, tile.size, 0, 0, canvas.width, canvas.height); const rm = document.createElement('button'); rm.textContent = '×'; rm.style.cssText = ` position:absolute;top:-5px;right:-5px;background:#f44;color:#fff;border:none; border-radius:50%;width:20px;height:20px;cursor:pointer;font-size:12px; display:flex;align-items:center;justify-content:center; `; rm.onclick = () => { group.tiles.splice(idx, 1); renderPicked(); }; const id = document.createElement('span'); id.textContent = `ID ${tile.uniqueId}`; id.style.cssText = 'font-size:10px;color:#ccc;margin-top:4px;text-align:center;'; card.appendChild(canvas); card.appendChild(rm); card.appendChild(id); wrap.appendChild(card); }); container.appendChild(wrap); } /** Extract a tile at (x,y) */ function pickTile(imgEl, x, y, size, url) { const group = groups[currentGroup]; // Lock a group to a single spritesheet if (group.url && group.url !== url) { alert('This group already uses a different image. Create a new group for another sheet.'); return; } // Prevent duplicate picks of same cell const duplicate = group.tiles.find(t => t.sourceX === x && t.sourceY === y && t.sourceUrl === url); if (duplicate) { alert(`This tile is already picked (ID ${duplicate.uniqueId})`); return; } group.url = url; if (!group.name) { const base = (url.split('/').pop() || '').split('.')[0] || 'Sheet'; group.name = `${base}_${size}px`; } const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); ctx.drawImage(imgEl, x, y, size, size, 0, 0, size, size); const data = ctx.getImageData(0, 0, size, size); group.tiles.push({ size, data, uniqueId: nextUniqueId++, sourceX: x, sourceY: y, sourceUrl: url }); renderPicked(); renderTabs(); // Flash feedback on picked cell const cells = document.querySelectorAll('.grid-cell'); cells.forEach(c => { // find the one matching this x/y by style match const style = c.getAttribute('style') || ''; if (style.includes(`left: ${x}px`) && style.includes(`top: ${y}px`)) { c.style.background = 'rgba(68,255,68,.6)'; setTimeout(() => { c.style.background = 'rgba(0,0,0,.3)'; }, 500); } }); } /** Helpers exposed (optional) */ function getCurrentGroup() { return groups[currentGroup]; } function getAllGroups() { return groups; } function setCurrentGroup(idx) { if (idx >= 0 && idx < groups.length) { currentGroup = idx; renderTabs(); renderGroupControls(); renderPicked(); } } function clearCurrentGroup() { const group = groups[currentGroup]; group.tiles = []; group.url = null; // keep name/category renderTabs(); renderPicked(); } function removeGroup(idx) { if (groups.length > 1 && idx >= 0 && idx < groups.length) { groups.splice(idx, 1); if (currentGroup >= groups.length) currentGroup = groups.length - 1; renderTabs(); renderGroupControls(); renderPicked(); } } // Debug alert for mobile debugging - success if (typeof debugAlert === 'function') debugAlert('tilepicker.js loaded successfully');