// Debug alert for mobile debugging
if (typeof debugAlert === 'function') {
debugAlert('object.js loaded');
}
// Simple game object
let gameObject = {
id: 'game_001',
name: 'My Tile Game',
version: '1.0.0'
};
// Attributes will be loaded from attributes.json
let gameAttributes = null;
let tileGroupAttributes = null;
let tileMapAttributes = null;
function openGameController() {
const overlayContent = document.getElementById('overlayContent');
overlayContent.innerHTML = `
<div style="height: 100%; overflow: auto; padding: 10px;">
<div id="gameController"></div>
</div>
`;
// Load attributes first, then render
loadAttributesAndRender();
}
function loadAttributesAndRender() {
fetch('attributes.json')
.then(response => {
if (!response.ok) {
throw new Error('Failed to load attributes.json');
}
return response.json();
})
.then(data => {
gameAttributes = data.game;
tileGroupAttributes = data.tileGroup;
tileMapAttributes = data.tileMap;
if (typeof debugAlert === 'function') {
debugAlert('Attributes loaded successfully');
}
renderGameController();
})
.catch(error => {
if (typeof debugAlert === 'function') {
debugAlert('Failed to load attributes.json: ' + error.message);
}
// Fallback to basic view if attributes.json fails to load
renderBasicGameController();
});
}
function renderBasicGameController() {
const container = document.getElementById('gameController');
container.innerHTML = `
<div style="padding: 15px; background: #1a1a1a; border-radius: 8px; margin-bottom: 20px;">
<h3 style="color: #6cf; margin: 0 0 15px 0;">Game Object</h3>
<div style="color: #f44; padding: 10px;">
Could not load attributes.json. Using basic view.
</div>
<div style="margin: 8px 0; padding: 10px; background: #333; border-radius: 4px; color: #ccc; font-size: 12px;">
<div>ID: ${gameObject.id}</div>
<div>Name: ${gameObject.name}</div>
<div>Version: ${gameObject.version}</div>
<div>Groups: ${typeof groups !== 'undefined' ? groups.length : 0}</div>
<div>Tilemaps: ${typeof tilemaps !== 'undefined' ? tilemaps.length : 0}</div>
</div>
</div>
`;
}
function renderGameController() {
const container = document.getElementById('gameController');
if (!gameAttributes || !tileGroupAttributes || !tileMapAttributes) {
container.innerHTML = `
<div style="padding: 15px; background: #1a1a1a; border-radius: 8px;">
<h3 style="color: #6cf;">Loading attributes...</h3>
<div style="color: #888;">Please wait while attributes.json loads.</div>
</div>
`;
return;
}
container.innerHTML = `
<div style="padding: 15px; background: #1a1a1a; border-radius: 8px; margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3 style="color: #6cf; margin: 0;">Game Object</h3>
<button onclick="copyGameObjectJSON()" style="background: #4a4; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 10px;">Copy JSON</button>
</div>
<div style="margin-bottom: 15px;">
<div onclick="toggleSection('gameMandatory')" style="cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;">
<span id="gameMandatoryToggle" style="margin-right: 8px; color: #f44;">▼</span>
<strong style="color: #f44;">Mandatory:</strong>
</div>
<div id="gameMandatory" style="margin: 8px 0; padding: 10px; background: #333; border-radius: 4px; color: #ccc; font-size: 12px;">
<div>ID: ${gameObject.id}</div>
<div>Name: ${gameObject.name}</div>
<div>Version: ${gameObject.version}</div>
</div>
</div>
<div id="gameChangeable" style="margin-bottom: 15px;">
<div onclick="toggleSection('gameChangeableContent')" style="cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;">
<span id="gameChangeableContentToggle" style="margin-right: 8px; color: #4a4;">▼</span>
<strong style="color: #4a4;">Changeable:</strong>
</div>
<div id="gameChangeableContent" style="margin: 8px 0; padding: 10px; background: #333; border-radius: 4px; font-size: 11px;"></div>
</div>
<div id="gameOptional">
<div onclick="toggleSection('gameOptionalContent')" style="cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;">
<span id="gameOptionalContentToggle" style="margin-right: 8px; color: #888;">▼</span>
<strong style="color: #888;">Optional:</strong>
</div>
<div id="gameOptionalContent" style="margin: 8px 0; padding: 10px; background: #333; border-radius: 4px; font-size: 11px;"></div>
</div>
</div>
<div id="tileGroupsContainer"></div>
<div id="tilemapsContainer"></div>
`;
// Add attributes using DOM manipulation
addGameAttributes();
addTileGroupsSection();
addTilemapsSection();
}
function toggleSection(sectionId) {
const section = document.getElementById(sectionId);
const toggle = document.getElementById(sectionId + 'Toggle');
if (section && toggle) {
if (section.style.display === 'none') {
section.style.display = 'block';
toggle.textContent = '▼';
} else {
section.style.display = 'none';
toggle.textContent = '►';
}
}
}
function copyGameObjectJSON() {
try {
// Collect current attribute values from dropdowns
const gameChangeable = {};
const gameOptional = {};
// Get all select elements in the game sections
const changeableSelects = document.querySelectorAll('#gameChangeableContent select');
const optionalSelects = document.querySelectorAll('#gameOptionalContent select');
// Process changeable attributes
changeableSelects.forEach((select, index) => {
const parentDiv = select.parentElement;
const label = parentDiv ? parentDiv.querySelector('span') : null;
if (label) {
const key = label.textContent.replace(':', '').trim();
let value = select.value;
// Convert types
if (value === 'null') value = null;
else if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
gameChangeable[key] = value;
}
});
// Process optional attributes
optionalSelects.forEach((select, index) => {
const parentDiv = select.parentElement;
const label = parentDiv ? parentDiv.querySelector('span') : null;
if (label) {
const key = label.textContent.replace(':', '').trim();
let value = select.value;
// Convert types
if (value === 'null') value = null;
else if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
gameOptional[key] = value;
}
});
// Collect tile group attributes - FIXED VERSION
const collectGroupAttributes = (groupIndex) => {
const groupChangeable = {};
const groupOptional = {};
// Find changeable selects in this group
const changeableContainer = document.getElementById(`tileGroup${groupIndex}Changeable`);
if (changeableContainer) {
const selects = changeableContainer.querySelectorAll('select');
selects.forEach(select => {
const label = select.parentElement.querySelector('span');
if (label) {
const key = label.textContent.replace(':', '').trim();
let value = select.value;
// Convert types
if (value === 'null') value = null;
else if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
groupChangeable[key] = value;
}
});
}
// Find optional selects in this group
const optionalContainer = document.getElementById(`tileGroup${groupIndex}Optional`);
if (optionalContainer) {
const selects = optionalContainer.querySelectorAll('select');
selects.forEach(select => {
const label = select.parentElement.querySelector('span');
if (label) {
const key = label.textContent.replace(':', '').trim();
let value = select.value;
// Convert types
if (value === 'null') value = null;
else if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
groupOptional[key] = value;
}
});
}
return { changeable: groupChangeable, optional: groupOptional };
};
// Collect tilemap attributes - FIXED VERSION
const collectMapAttributes = (mapIndex) => {
const mapChangeable = {};
const mapOptional = {};
// Find changeable selects in this map
const changeableContainer = document.getElementById(`tileMap${mapIndex}Changeable`);
if (changeableContainer) {
const selects = changeableContainer.querySelectorAll('select');
selects.forEach(select => {
const label = select.parentElement.querySelector('span');
if (label) {
const key = label.textContent.replace(':', '').trim();
let value = select.value;
// Convert types
if (value === 'null') value = null;
else if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
mapChangeable[key] = value;
}
});
}
// Find optional selects in this map
const optionalContainer = document.getElementById(`tileMap${mapIndex}Optional`);
if (optionalContainer) {
const selects = optionalContainer.querySelectorAll('select');
selects.forEach(select => {
const label = select.parentElement.querySelector('span');
if (label) {
const key = label.textContent.replace(':', '').trim();
let value = select.value;
// Convert types
if (value === 'null') value = null;
else if (value === 'true') value = true;
else if (value === 'false') value = false;
else if (!isNaN(value) && value !== '') value = Number(value);
mapOptional[key] = value;
}
});
}
return { changeable: mapChangeable, optional: mapOptional };
};
// Helper function to remove null values from objects
function removeNulls(obj) {
if (Array.isArray(obj)) {
return obj.map(removeNulls);
} else if (obj !== null && typeof obj === 'object') {
const cleaned = {};
for (const [key, value] of Object.entries(obj)) {
if (value !== null) {
cleaned[key] = removeNulls(value);
}
}
return cleaned;
}
return obj;
}
const completeGameObject = {
metadata: {
id: gameObject.id,
name: gameObject.name,
version: gameObject.version,
exportDate: new Date().toISOString(),
editor: "Tile Game Editor"
},
// Actual game configuration
gameObject: removeNulls({
changeable: gameChangeable,
optional: gameOptional
}),
// Complete tile groups data with attributes
tileGroups: typeof groups !== 'undefined' ? groups.map((group, index) => removeNulls({
name: group.name,
url: group.url,
tileCount: group.tiles ? group.tiles.length : 0,
tileSize: group.tiles && group.tiles[0] ? group.tiles[0].size : null,
attributes: collectGroupAttributes(index),
tiles: group.tiles ? group.tiles.map(tile => ({
uniqueId: tile.uniqueId,
sourceX: tile.sourceX,
sourceY: tile.sourceY,
size: tile.size,
linearIndex: Math.floor(tile.sourceY / tile.size) * 8 + Math.floor(tile.sourceX / tile.size)
})) : []
})) : [],
// Complete tilemaps data with attributes
tilemaps: typeof tilemaps !== 'undefined' ? tilemaps.map((tilemap, index) => removeNulls({
id: tilemap.id,
name: tilemap.name,
width: tilemap.width,
height: tilemap.height,
tileSize: tilemap.tileSize,
attributes: collectMapAttributes(index),
data: tilemap.data,
fillPercentage: tilemap.width && tilemap.height ?
((tilemap.data ? tilemap.data.filter(t => t !== 0).length : 0) / (tilemap.width * tilemap.height) * 100).toFixed(1) : 0
})) : [],
// Project statistics
statistics: {
totalTileGroups: typeof groups !== 'undefined' ? groups.length : 0,
totalTiles: typeof groups !== 'undefined' ? groups.reduce((sum, group) => sum + (group.tiles ? group.tiles.length : 0), 0) : 0,
totalTilemaps: typeof tilemaps !== 'undefined' ? tilemaps.length : 0,
totalPlacedTiles: typeof tilemaps !== 'undefined' ? tilemaps.reduce((sum, map) => sum + (map.data ? map.data.filter(t => t !== 0).length : 0), 0) : 0,
uniqueSourceImages: typeof groups !== 'undefined' ? new Set(groups.map(g => g.url).filter(url => url)).size : 0
}
};
// Custom JSON stringify with compact arrays
const jsonString = JSON.stringify(completeGameObject, null, 2).replace(
/\[\s*(\d+(?:,\s*\d+)*)\s*\]/g,
(match, numbers) => '[' + numbers.replace(/\s+/g, ' ') + ']'
);
// Try modern clipboard API first, then fallback
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(jsonString).then(() => {
if (typeof debugAlert === 'function') {
debugAlert('Complete game data copied! ' + Math.round(jsonString.length / 1024) + 'KB');
}
}).catch(() => {
fallbackCopy(jsonString);
});
} else {
fallbackCopy(jsonString);
}
function fallbackCopy(text) {
// Create temporary textarea for fallback copy
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
const successful = document.execCommand('copy');
if (successful && typeof debugAlert === 'function') {
debugAlert('Game data copied (fallback)! ' + Math.round(text.length / 1024) + 'KB');
} else {
throw new Error('Copy command failed');
}
} catch (err) {
// If all copy methods fail, show the data
if (typeof debugAlert === 'function') {
debugAlert('Copy failed. Data: ' + text.substring(0, 200) + '...');
}
console.log('Full game data:', text);
} finally {
document.body.removeChild(textarea);
}
}
} catch (error) {
if (typeof debugAlert === 'function') {
debugAlert('Copy failed: ' + error.message);
}
}
}
function addGameAttributes() {
// Add changeable attributes
const changeableContainer = document.querySelector('#gameChangeableContent');
addAttributesToContainer(changeableContainer, gameAttributes.changeable, 'game', 'changeable');
// Add optional attributes
const optionalContainer = document.querySelector('#gameOptionalContent');
addAttributesToContainer(optionalContainer, gameAttributes.optional, 'game', 'optional');
}
function addAttributesToContainer(container, attributes, section, type) {
for (const categoryKey in attributes) {
const category = attributes[categoryKey];
if (category.value !== undefined) {
// Single attribute
addSingleAttribute(container, categoryKey, category, section, type);
} else {
// Category with sub-attributes
const categoryLabel = document.createElement('div');
categoryLabel.textContent = categoryKey + ':';
categoryLabel.style.cssText = 'font-weight: bold; margin: 8px 0 4px 0; color: #fff;';
container.appendChild(categoryLabel);
for (const attrKey in category) {
const attr = category[attrKey];
addSingleAttribute(container, categoryKey + '.' + attrKey, attr, section, type);
}
}
}
}
function addSingleAttribute(container, key, attr, section, type) {
const attrDiv = document.createElement('div');
attrDiv.style.cssText = 'margin: 4px 0; display: flex; align-items: center; gap: 10px;';
const label = document.createElement('span');
label.textContent = key + ':';
label.style.cssText = 'min-width: 100px; color: #ccc;';
attrDiv.appendChild(label);
const select = document.createElement('select');
select.style.cssText = 'background: #222; color: #fff; border: 1px solid #555; padding: 2px 4px; border-radius: 2px; font-size: 11px;';
attr.options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option;
optionElement.textContent = option === null ? 'null' : option;
optionElement.selected = option === attr.value;
select.appendChild(optionElement);
});
select.onchange = function() {
updateAttribute(section, type, key, this.value);
};
attrDiv.appendChild(select);
container.appendChild(attrDiv);
}
function updateAttribute(section, type, key, value) {
let convertedValue = value;
if (value === 'null') convertedValue = null;
else if (value === 'true') convertedValue = true;
else if (value === 'false') convertedValue = false;
else if (!isNaN(value) && value !== '') convertedValue = Number(value);
if (typeof debugAlert === 'function') {
debugAlert('Updated ' + section + '.' + key + ' = ' + convertedValue);
}
}
function addTileGroupsSection() {
const container = document.getElementById('tileGroupsContainer');
if (!container) return;
const section = document.createElement('div');
section.style.cssText = 'padding: 15px; background: #1a1a1a; border-radius: 8px; margin-bottom: 20px;';
const titleContainer = document.createElement('div');
titleContainer.onclick = () => toggleSection('tileGroupsContent');
titleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 15px;';
const toggle = document.createElement('span');
toggle.id = 'tileGroupsContentToggle';
toggle.textContent = '▼';
toggle.style.cssText = 'margin-right: 8px; color: #6cf;';
const title = document.createElement('h3');
title.textContent = 'Tile Groups';
title.style.cssText = 'color: #6cf; margin: 0;';
titleContainer.appendChild(toggle);
titleContainer.appendChild(title);
section.appendChild(titleContainer);
const content = document.createElement('div');
content.id = 'tileGroupsContent';
if (typeof groups === 'undefined' || !groups || groups.length === 0) {
const noGroups = document.createElement('div');
noGroups.textContent = 'No tile groups created yet. Use the Tile Picker to create groups.';
noGroups.style.cssText = 'color: #888; font-style: italic; padding: 10px;';
content.appendChild(noGroups);
} else {
groups.forEach((group, index) => {
const groupDiv = document.createElement('div');
groupDiv.style.cssText = 'margin-bottom: 20px; padding: 15px; background: #2a2a2a; border-radius: 6px; border-left: 4px solid #6cf;';
const groupTitleContainer = document.createElement('div');
groupTitleContainer.onclick = () => toggleSection(`tileGroup${index}Content`);
groupTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 10px;';
const groupToggle = document.createElement('span');
groupToggle.id = `tileGroup${index}ContentToggle`;
groupToggle.textContent = '▼';
groupToggle.style.cssText = 'margin-right: 8px; color: #6cf;';
const groupTitle = document.createElement('h4');
groupTitle.textContent = 'Group ' + (index + 1) + ': ' + (group.name || 'Unnamed');
groupTitle.style.cssText = 'margin: 0; color: #fff;';
groupTitleContainer.appendChild(groupToggle);
groupTitleContainer.appendChild(groupTitle);
groupDiv.appendChild(groupTitleContainer);
const groupContent = document.createElement('div');
groupContent.id = `tileGroup${index}Content`;
// Mandatory info
const mandatoryDiv = document.createElement('div');
mandatoryDiv.style.cssText = 'margin-bottom: 10px;';
const mandatoryTitleContainer = document.createElement('div');
mandatoryTitleContainer.onclick = () => toggleSection(`tileGroup${index}Mandatory`);
mandatoryTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const mandatoryToggle = document.createElement('span');
mandatoryToggle.id = `tileGroup${index}MandatoryToggle`;
mandatoryToggle.textContent = '▼';
mandatoryToggle.style.cssText = 'margin-right: 8px; color: #f44;';
const mandatoryLabel = document.createElement('strong');
mandatoryLabel.textContent = 'Mandatory:';
mandatoryLabel.style.cssText = 'color: #f44;';
mandatoryTitleContainer.appendChild(mandatoryToggle);
mandatoryTitleContainer.appendChild(mandatoryLabel);
mandatoryDiv.appendChild(mandatoryTitleContainer);
const mandatoryInfo = document.createElement('div');
mandatoryInfo.id = `tileGroup${index}Mandatory`;
mandatoryInfo.style.cssText = 'margin: 8px 0; padding: 8px; background: #333; border-radius: 4px; font-size: 11px; color: #ccc;';
mandatoryInfo.innerHTML = 'ID: group_' + (index + 1) + '<br>' +
'Atlas Key: ' + (group.url ? group.url.split('/').pop() : 'none') + '<br>' +
'Tile Size: ' + (group.tiles && group.tiles[0] ? group.tiles[0].size : 32) + 'px<br>' +
'Members: ' + (group.tiles ? group.tiles.length : 0) + ' tiles';
mandatoryDiv.appendChild(mandatoryInfo);
groupContent.appendChild(mandatoryDiv);
// Changeable attributes
const changeableDiv = document.createElement('div');
changeableDiv.style.cssText = 'margin-bottom: 10px;';
const changeableTitleContainer = document.createElement('div');
changeableTitleContainer.onclick = () => toggleSection(`tileGroup${index}Changeable`);
changeableTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const changeableToggle = document.createElement('span');
changeableToggle.id = `tileGroup${index}ChangeableToggle`;
changeableToggle.textContent = '▼';
changeableToggle.style.cssText = 'margin-right: 8px; color: #4a4;';
const changeableLabel = document.createElement('strong');
changeableLabel.textContent = 'Changeable:';
changeableLabel.style.cssText = 'color: #4a4;';
changeableTitleContainer.appendChild(changeableToggle);
changeableTitleContainer.appendChild(changeableLabel);
changeableDiv.appendChild(changeableTitleContainer);
const changeableContainer = document.createElement('div');
changeableContainer.id = `tileGroup${index}Changeable`;
changeableContainer.style.cssText = 'margin: 8px 0; padding: 8px; background: #333; border-radius: 4px; font-size: 11px;';
addAttributesToContainer(changeableContainer, tileGroupAttributes.changeable, 'tileGroup', 'changeable');
changeableDiv.appendChild(changeableContainer);
groupContent.appendChild(changeableDiv);
// Optional attributes
const optionalDiv = document.createElement('div');
optionalDiv.style.cssText = 'margin-bottom: 10px;';
const optionalTitleContainer = document.createElement('div');
optionalTitleContainer.onclick = () => toggleSection(`tileGroup${index}Optional`);
optionalTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const optionalToggle = document.createElement('span');
optionalToggle.id = `tileGroup${index}OptionalToggle`;
optionalToggle.textContent = '▼';
optionalToggle.style.cssText = 'margin-right: 8px; color: #888;';
const optionalLabel = document.createElement('strong');
optionalLabel.textContent = 'Optional:';
optionalLabel.style.cssText = 'color: #888;';
optionalTitleContainer.appendChild(optionalToggle);
optionalTitleContainer.appendChild(optionalLabel);
optionalDiv.appendChild(optionalTitleContainer);
const optionalContainer = document.createElement('div');
optionalContainer.id = `tileGroup${index}Optional`;
optionalContainer.style.cssText = 'margin: 8px 0; padding: 8px; background: #333; border-radius: 4px; font-size: 11px;';
addAttributesToContainer(optionalContainer, tileGroupAttributes.optional, 'tileGroup', 'optional');
optionalDiv.appendChild(optionalContainer);
groupContent.appendChild(optionalDiv);
// Add tile previews if tiles exist
if (group.tiles && group.tiles.length > 0) {
const previewTitleContainer = document.createElement('div');
previewTitleContainer.onclick = () => toggleSection(`tileGroup${index}Previews`);
previewTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const previewToggle = document.createElement('span');
previewToggle.id = `tileGroup${index}PreviewsToggle`;
previewToggle.textContent = '▼';
previewToggle.style.cssText = 'margin-right: 8px; color: #6cf;';
const previewLabel = document.createElement('strong');
previewLabel.textContent = 'Tile Previews:';
previewLabel.style.cssText = 'color: #6cf;';
previewTitleContainer.appendChild(previewToggle);
previewTitleContainer.appendChild(previewLabel);
groupContent.appendChild(previewTitleContainer);
const previewContainer = document.createElement('div');
previewContainer.id = `tileGroup${index}Previews`;
addTilePreviewsToGroup(previewContainer, group);
groupContent.appendChild(previewContainer);
}
groupDiv.appendChild(groupContent);
content.appendChild(groupDiv);
});
}
section.appendChild(content);
container.appendChild(section);
}
function addTilePreviewsToGroup(previewContainer, group) {
previewContainer.style.cssText = 'display: flex; gap: 5px; flex-wrap: wrap; margin-top: 10px;';
group.tiles.forEach(tile => {
const tileWrapper = document.createElement('div');
tileWrapper.style.cssText = 'position: relative; display: inline-block;';
// Create canvas for tile display
const canvas = document.createElement('canvas');
canvas.width = 32;
canvas.height = 32;
canvas.style.cssText = 'border: 2px solid #666; border-radius: 4px; background: #000;';
// Draw the tile
const ctx = canvas.getContext('2d');
const tempCanvas = document.createElement('canvas');
tempCanvas.width = tile.size;
tempCanvas.height = tile.size;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.putImageData(tile.data, 0, 0);
// Scale to 32x32 for preview
ctx.drawImage(tempCanvas, 0, 0, tile.size, tile.size, 0, 0, 32, 32);
// Create ID badge (bottom-right)
const idBadge = document.createElement('span');
idBadge.textContent = tile.uniqueId;
idBadge.style.cssText = 'position: absolute; bottom: -2px; right: -2px; background: rgba(0,0,0,0.8); color: #fff; font-size: 8px; padding: 1px 3px; border-radius: 2px; border: 1px solid #6cf;';
// Create linear index badge (top-left)
const tileX = Math.floor(tile.sourceX / tile.size);
const tileY = Math.floor(tile.sourceY / tile.size);
const tilesPerRow = 8; // Default assumption
const linearIndex = tileY * tilesPerRow + tileX;
const indexBadge = document.createElement('span');
indexBadge.textContent = linearIndex.toString();
indexBadge.style.cssText = 'position: absolute; top: -2px; left: -2px; background: rgba(0,0,0,0.8); color: #fff; font-size: 7px; padding: 1px 2px; border-radius: 2px; border: 1px solid #4a4;';
// Assemble the tile preview
tileWrapper.appendChild(canvas);
tileWrapper.appendChild(idBadge);
tileWrapper.appendChild(indexBadge);
previewContainer.appendChild(tileWrapper);
});
}
function addTilemapsSection() {
const container = document.getElementById('tilemapsContainer');
if (!container) return;
const section = document.createElement('div');
section.style.cssText = 'padding: 15px; background: #1a1a1a; border-radius: 8px; margin-bottom: 20px;';
const titleContainer = document.createElement('div');
titleContainer.onclick = () => toggleSection('tilemapsContent');
titleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 15px;';
const toggle = document.createElement('span');
toggle.id = 'tilemapsContentToggle';
toggle.textContent = '▼';
toggle.style.cssText = 'margin-right: 8px; color: #6cf;';
const title = document.createElement('h3');
title.textContent = 'Tile Maps';
title.style.cssText = 'color: #6cf; margin: 0;';
titleContainer.appendChild(toggle);
titleContainer.appendChild(title);
section.appendChild(titleContainer);
const content = document.createElement('div');
content.id = 'tilemapsContent';
if (typeof tilemaps === 'undefined' || !tilemaps || tilemaps.length === 0) {
const noMaps = document.createElement('div');
noMaps.textContent = 'No tilemaps created yet. Use the Tilemap Editor to create maps.';
noMaps.style.cssText = 'color: #888; font-style: italic; padding: 10px;';
content.appendChild(noMaps);
} else {
tilemaps.forEach((tilemap, index) => {
const isActive = typeof currentTilemapIndex !== 'undefined' && currentTilemapIndex === index;
const totalTiles = tilemap.data ? tilemap.data.filter(t => t !== 0).length : 0;
const fillPercent = tilemap.width && tilemap.height ? ((totalTiles / (tilemap.width * tilemap.height)) * 100).toFixed(1) : '0.0';
const mapDiv = document.createElement('div');
mapDiv.style.cssText = 'margin-bottom: 20px; padding: 15px; background: #2a2a2a; border-radius: 6px; border-left: 4px solid #6cf;';
const mapTitleContainer = document.createElement('div');
mapTitleContainer.onclick = () => toggleSection(`tileMap${index}Content`);
mapTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 10px;';
const mapToggle = document.createElement('span');
mapToggle.id = `tileMap${index}ContentToggle`;
mapToggle.textContent = '▼';
mapToggle.style.cssText = 'margin-right: 8px; color: #6cf;';
const mapTitle = document.createElement('h4');
mapTitle.textContent = tilemap.name + (isActive ? ' (Active)' : '');
mapTitle.style.cssText = 'margin: 0; color: ' + (isActive ? '#6cf' : '#fff') + ';';
mapTitleContainer.appendChild(mapToggle);
mapTitleContainer.appendChild(mapTitle);
mapDiv.appendChild(mapTitleContainer);
const mapContent = document.createElement('div');
mapContent.id = `tileMap${index}Content`;
// Mandatory info
const mandatoryDiv = document.createElement('div');
mandatoryDiv.style.cssText = 'margin-bottom: 10px;';
const mandatoryTitleContainer = document.createElement('div');
mandatoryTitleContainer.onclick = () => toggleSection(`tileMap${index}Mandatory`);
mandatoryTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const mandatoryToggle = document.createElement('span');
mandatoryToggle.id = `tileMap${index}MandatoryToggle`;
mandatoryToggle.textContent = '▼';
mandatoryToggle.style.cssText = 'margin-right: 8px; color: #f44;';
const mandatoryLabel = document.createElement('strong');
mandatoryLabel.textContent = 'Mandatory:';
mandatoryLabel.style.cssText = 'color: #f44;';
mandatoryTitleContainer.appendChild(mandatoryToggle);
mandatoryTitleContainer.appendChild(mandatoryLabel);
mandatoryDiv.appendChild(mandatoryTitleContainer);
const mandatoryInfo = document.createElement('div');
mandatoryInfo.id = `tileMap${index}Mandatory`;
mandatoryInfo.style.cssText = 'margin: 8px 0; padding: 8px; background: #333; border-radius: 4px; font-size: 11px; color: #ccc;';
mandatoryInfo.innerHTML = 'ID: map_' + tilemap.id + '<br>' +
'Dimensions: ' + tilemap.width + ' x ' + tilemap.height + '<br>' +
'Fill: ' + fillPercent + '% (' + totalTiles + ' tiles)';
mandatoryDiv.appendChild(mandatoryInfo);
mapContent.appendChild(mandatoryDiv);
// Changeable attributes
const changeableDiv = document.createElement('div');
changeableDiv.style.cssText = 'margin-bottom: 10px;';
const changeableTitleContainer = document.createElement('div');
changeableTitleContainer.onclick = () => toggleSection(`tileMap${index}Changeable`);
changeableTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const changeableToggle = document.createElement('span');
changeableToggle.id = `tileMap${index}ChangeableToggle`;
changeableToggle.textContent = '▼';
changeableToggle.style.cssText = 'margin-right: 8px; color: #4a4;';
const changeableLabel = document.createElement('strong');
changeableLabel.textContent = 'Changeable:';
changeableLabel.style.cssText = 'color: #4a4;';
changeableTitleContainer.appendChild(changeableToggle);
changeableTitleContainer.appendChild(changeableLabel);
changeableDiv.appendChild(changeableTitleContainer);
const changeableContainer = document.createElement('div');
changeableContainer.id = `tileMap${index}Changeable`;
changeableContainer.style.cssText = 'margin: 8px 0; padding: 8px; background: #333; border-radius: 4px; font-size: 11px;';
addAttributesToContainer(changeableContainer, tileMapAttributes.changeable, 'tileMap', 'changeable');
changeableDiv.appendChild(changeableContainer);
mapContent.appendChild(changeableDiv);
// Optional attributes
const optionalDiv = document.createElement('div');
optionalDiv.style.cssText = 'margin-bottom: 10px;';
const optionalTitleContainer = document.createElement('div');
optionalTitleContainer.onclick = () => toggleSection(`tileMap${index}Optional`);
optionalTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const optionalToggle = document.createElement('span');
optionalToggle.id = `tileMap${index}OptionalToggle`;
optionalToggle.textContent = '▼';
optionalToggle.style.cssText = 'margin-right: 8px; color: #888;';
const optionalLabel = document.createElement('strong');
optionalLabel.textContent = 'Optional:';
optionalLabel.style.cssText = 'color: #888;';
optionalTitleContainer.appendChild(optionalToggle);
optionalTitleContainer.appendChild(optionalLabel);
optionalDiv.appendChild(optionalTitleContainer);
const optionalContainer = document.createElement('div');
optionalContainer.id = `tileMap${index}Optional`;
optionalContainer.style.cssText = 'margin: 8px 0; padding: 8px; background: #333; border-radius: 4px; font-size: 11px;';
addAttributesToContainer(optionalContainer, tileMapAttributes.optional, 'tileMap', 'optional');
optionalDiv.appendChild(optionalContainer);
mapContent.appendChild(optionalDiv);
// Add Phaser array if data exists
if (tilemap.data && tilemap.data.length > 0) {
const arrayTitleContainer = document.createElement('div');
arrayTitleContainer.onclick = () => toggleSection(`tileMap${index}Array`);
arrayTitleContainer.style.cssText = 'cursor: pointer; display: flex; align-items: center; margin-bottom: 8px;';
const arrayToggle = document.createElement('span');
arrayToggle.id = `tileMap${index}ArrayToggle`;
arrayToggle.textContent = '▼';
arrayToggle.style.cssText = 'margin-right: 8px; color: #6cf;';
const arrayLabel = document.createElement('strong');
arrayLabel.textContent = 'Phaser 2D Array:';
arrayLabel.style.cssText = 'color: #6cf;';
arrayTitleContainer.appendChild(arrayToggle);
arrayTitleContainer.appendChild(arrayLabel);
mapContent.appendChild(arrayTitleContainer);
const arrayContainer = document.createElement('div');
arrayContainer.id = `tileMap${index}Array`;
addPhaserArrayToMap(arrayContainer, tilemap, index);
mapContent.appendChild(arrayContainer);
}
mapDiv.appendChild(mapContent);
content.appendChild(mapDiv);
});
}
section.appendChild(content);
container.appendChild(section);
}
function addPhaserArrayToMap(arrayContainer, tilemap, mapIndex) {
arrayContainer.style.cssText = 'background: #1a1a1a; padding: 10px; border-radius: 4px; margin-top: 5px;';
const arrayDisplay = document.createElement('div');
arrayDisplay.style.cssText = 'font-family: "Courier New", monospace; font-size: 10px; color: #ccc; overflow-x: auto; max-height: 150px; overflow-y: auto;';
// Build the array text
let arrayText = '[\n';
for (let y = 0; y < tilemap.height; y++) {
let row = ' [';
for (let x = 0; x < tilemap.width; x++) {
const dataIndex = y * tilemap.width + x;
const tileId = tilemap.data[dataIndex] || 0;
row += tileId.toString().padStart(3, ' ');
if (x < tilemap.width - 1) row += ',';
}
row += ']';
if (y < tilemap.height - 1) row += ',';
arrayText += row + '\n';
}
arrayText += ']';
// Display the formatted array
const lines = arrayText.split('\n');
lines.forEach(line => {
const lineDiv = document.createElement('div');
lineDiv.textContent = line;
lineDiv.style.cssText = 'line-height: 1.2;';
arrayDisplay.appendChild(lineDiv);
});
arrayContainer.appendChild(arrayDisplay);
// Add copy button
const copyBtn = document.createElement('button');
copyBtn.textContent = 'Copy Array';
copyBtn.style.cssText = 'margin-top: 5px; background: #444; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 10px;';
copyBtn.onclick = function() {
copyTilemapArray(arrayText, mapIndex);
};
arrayContainer.appendChild(copyBtn);
}
function copyTilemapArray(arrayText, mapIndex) {
try {
navigator.clipboard.writeText(arrayText).then(function() {
if (typeof debugAlert === 'function') {
debugAlert('Array copied to clipboard!');
}
}).catch(function() {
if (typeof debugAlert === 'function') {
debugAlert('Array preview: ' + arrayText.substring(0, 50) + '...');
}
});
} catch (error) {
if (typeof debugAlert === 'function') {
debugAlert('Copy failed: ' + error.message);
}
}
}
if (typeof debugAlert === 'function') {
debugAlert('object.js loaded successfully');
}