Add new assets and update room definitions: Introduce a comprehensive list of available object assets in available_objects.txt. Add multiple new object images and room JSON/TMJ files for room_ceo2, room_closet2, room_office2, and room_servers2. Update existing room files to enhance gameplay experience and ensure proper asset integration. Implement scripts for managing corrupted GID references and external tileset removal to maintain map integrity.

This commit is contained in:
Z. Cliffe Schreuders
2025-10-10 00:18:04 +01:00
parent 16f57e8ffe
commit e8db636f45
64 changed files with 20152 additions and 124 deletions

131
scripts/check_corrupted_gids.py Executable file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python3
import json
import os
"""
Script to identify corrupted GID references in Tiled map
This will help find objects that reference non-existent tileset entries
"""
MAP_FILE = "assets/rooms/room_reception2.json"
def get_valid_gid_ranges(map_data):
"""Get all valid GID ranges from tilesets"""
valid_ranges = []
for tileset in map_data.get('tilesets', []):
firstgid = tileset.get('firstgid', 0)
tilecount = tileset.get('tilecount', 0)
if tilecount:
max_gid = firstgid + tilecount
valid_ranges.append((firstgid, max_gid, tileset.get('name', 'unknown')))
else:
# Handle tilesets with undefined tilecount
valid_ranges.append((firstgid, firstgid + 1, tileset.get('name', 'unknown')))
return valid_ranges
def is_gid_valid(gid, valid_ranges):
"""Check if a GID is valid (exists in any tileset)"""
for start, end, name in valid_ranges:
if start <= gid < end:
return True, name
return False, None
def check_layer_objects(layer_name, objects, valid_ranges):
"""Check objects in a layer for invalid GIDs"""
corrupted_objects = []
for obj in objects:
gid = obj.get('gid', 0)
if gid > 0: # Only check objects with GIDs
is_valid, tileset_name = is_gid_valid(gid, valid_ranges)
if not is_valid:
corrupted_objects.append({
'id': obj.get('id', 'unknown'),
'gid': gid,
'x': obj.get('x', 0),
'y': obj.get('y', 0),
'name': obj.get('name', ''),
'layer': layer_name
})
return corrupted_objects
def main():
"""Main function to check for corrupted GIDs"""
print("🔍 Checking for Corrupted GID References")
print("=" * 50)
if not os.path.exists(MAP_FILE):
print(f"❌ Map file not found: {MAP_FILE}")
return
try:
with open(MAP_FILE, 'r') as f:
map_data = json.load(f)
except Exception as e:
print(f"❌ Error reading map file: {e}")
return
# Get valid GID ranges
valid_ranges = get_valid_gid_ranges(map_data)
print(f"📊 Found {len(valid_ranges)} tilesets with valid GID ranges:")
for start, end, name in valid_ranges:
print(f" {name}: GIDs {start}-{end-1}")
print()
# Check each layer
all_corrupted = []
for layer in map_data.get('layers', []):
layer_name = layer.get('name', 'unknown')
objects = layer.get('objects', [])
if objects:
corrupted = check_layer_objects(layer_name, objects, valid_ranges)
if corrupted:
all_corrupted.extend(corrupted)
print(f"❌ Layer '{layer_name}': {len(corrupted)} corrupted objects")
for obj in corrupted[:5]: # Show first 5
print(f" - Object ID {obj['id']}: GID {obj['gid']} at ({obj['x']}, {obj['y']})")
if len(corrupted) > 5:
print(f" ... and {len(corrupted) - 5} more")
else:
print(f"✅ Layer '{layer_name}': All objects valid")
print()
if all_corrupted:
print(f"🚨 Found {len(all_corrupted)} corrupted objects total")
print()
print("📋 Summary by GID:")
# Group by GID
gid_counts = {}
for obj in all_corrupted:
gid = obj['gid']
if gid not in gid_counts:
gid_counts[gid] = []
gid_counts[gid].append(obj)
for gid in sorted(gid_counts.keys()):
count = len(gid_counts[gid])
print(f" GID {gid}: {count} objects")
print()
print("🔧 Recommended Actions:")
print("1. Open the map in Tiled Editor")
print("2. Look for pink/magenta placeholder tiles")
print("3. Replace corrupted objects with valid ones from the tileset")
print("4. Save the map")
else:
print("✅ No corrupted GID references found!")
print("The map should work correctly in Phaser.")
if __name__ == "__main__":
main()

165
scripts/fix_corrupted_gids.py Executable file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
import json
import os
import shutil
from datetime import datetime
"""
Script to remove objects with corrupted GID references from Tiled map
This will clean up the JSON file by removing objects that reference non-existent tileset entries
"""
MAP_FILE = "assets/rooms/room_reception2.json"
BACKUP_FILE = f"assets/rooms/room_reception2.json.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
def get_valid_gid_ranges(map_data):
"""Get all valid GID ranges from tilesets"""
valid_ranges = []
for tileset in map_data.get('tilesets', []):
firstgid = tileset.get('firstgid', 0)
tilecount = tileset.get('tilecount', 0)
if tilecount:
max_gid = firstgid + tilecount
valid_ranges.append((firstgid, max_gid, tileset.get('name', 'unknown')))
else:
# Handle tilesets with undefined tilecount
valid_ranges.append((firstgid, firstgid + 1, tileset.get('name', 'unknown')))
return valid_ranges
def is_gid_valid(gid, valid_ranges):
"""Check if a GID is valid (exists in any tileset)"""
for start, end, name in valid_ranges:
if start <= gid < end:
return True, name
return False, None
def clean_layer_objects(layer_name, objects, valid_ranges):
"""Remove objects with invalid GIDs from a layer"""
original_count = len(objects)
valid_objects = []
removed_objects = []
for obj in objects:
gid = obj.get('gid', 0)
if gid > 0: # Only check objects with GIDs
is_valid, tileset_name = is_gid_valid(gid, valid_ranges)
if is_valid:
valid_objects.append(obj)
else:
removed_objects.append({
'id': obj.get('id', 'unknown'),
'gid': gid,
'x': obj.get('x', 0),
'y': obj.get('y', 0),
'name': obj.get('name', ''),
'layer': layer_name
})
else:
# Keep objects without GIDs (like collision shapes, etc.)
valid_objects.append(obj)
removed_count = original_count - len(valid_objects)
return valid_objects, removed_objects, removed_count
def main():
"""Main function to clean corrupted GIDs"""
print("🧹 Cleaning Corrupted GID References")
print("=" * 50)
if not os.path.exists(MAP_FILE):
print(f"❌ Map file not found: {MAP_FILE}")
return
# Create backup
print(f"📁 Creating backup: {BACKUP_FILE}")
shutil.copy2(MAP_FILE, BACKUP_FILE)
try:
with open(MAP_FILE, 'r') as f:
map_data = json.load(f)
except Exception as e:
print(f"❌ Error reading map file: {e}")
return
# Get valid GID ranges
valid_ranges = get_valid_gid_ranges(map_data)
print(f"📊 Valid GID ranges:")
for start, end, name in valid_ranges:
print(f" {name}: GIDs {start}-{end-1}")
print()
# Clean each layer
total_removed = 0
all_removed_objects = []
for layer in map_data.get('layers', []):
layer_name = layer.get('name', 'unknown')
objects = layer.get('objects', [])
if objects:
valid_objects, removed_objects, removed_count = clean_layer_objects(layer_name, objects, valid_ranges)
if removed_count > 0:
# Update the layer with cleaned objects
layer['objects'] = valid_objects
total_removed += removed_count
all_removed_objects.extend(removed_objects)
print(f"🧹 Layer '{layer_name}': Removed {removed_count} corrupted objects")
for obj in removed_objects:
print(f" - Object ID {obj['id']}: GID {obj['gid']} at ({obj['x']}, {obj['y']})")
else:
print(f"✅ Layer '{layer_name}': No corrupted objects found")
print()
if total_removed > 0:
# Write cleaned map file
try:
with open(MAP_FILE, 'w') as f:
json.dump(map_data, f, indent=2)
print(f"✅ Successfully removed {total_removed} corrupted objects")
print(f"💾 Updated map file: {MAP_FILE}")
print(f"📁 Backup created: {BACKUP_FILE}")
print()
print("📋 Summary of removed objects:")
gid_counts = {}
for obj in all_removed_objects:
gid = obj['gid']
if gid not in gid_counts:
gid_counts[gid] = 0
gid_counts[gid] += 1
for gid in sorted(gid_counts.keys()):
count = gid_counts[gid]
print(f" GID {gid}: {count} objects removed")
print()
print("🎯 Next steps:")
print("1. Open the map in Tiled Editor to verify the cleanup")
print("2. Add any missing objects back using valid tileset entries")
print("3. Save the map")
print("4. Test the game to ensure it loads without errors")
except Exception as e:
print(f"❌ Error writing cleaned map file: {e}")
print(f"🔄 Restoring from backup...")
shutil.copy2(BACKUP_FILE, MAP_FILE)
return
else:
print("✅ No corrupted objects found!")
print("The map is already clean.")
# Remove backup since no changes were made
os.remove(BACKUP_FILE)
print(f"🗑️ Removed unnecessary backup: {BACKUP_FILE}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
import json
import os
import shutil
from datetime import datetime
"""
Script to remove external tileset references from Tiled map
This will remove tilesets that reference external .tsx files, keeping only embedded tilesets
"""
MAP_FILE = "assets/rooms/room_reception2.json"
BACKUP_FILE = f"assets/rooms/room_reception2.json.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
def main():
"""Main function to remove external tileset references"""
print("🧹 Removing External Tileset References")
print("=" * 50)
if not os.path.exists(MAP_FILE):
print(f"❌ Map file not found: {MAP_FILE}")
return
# Create backup
print(f"📁 Creating backup: {BACKUP_FILE}")
shutil.copy2(MAP_FILE, BACKUP_FILE)
try:
with open(MAP_FILE, 'r') as f:
map_data = json.load(f)
except Exception as e:
print(f"❌ Error reading map file: {e}")
return
# Check for external tilesets
tilesets = map_data.get('tilesets', [])
external_tilesets = []
embedded_tilesets = []
for i, tileset in enumerate(tilesets):
if 'source' in tileset:
external_tilesets.append((i, tileset))
print(f"❌ External tileset found: {tileset.get('source', 'unknown')} (firstgid: {tileset.get('firstgid', 'unknown')})")
else:
embedded_tilesets.append((i, tileset))
print(f"✅ Embedded tileset: {tileset.get('name', 'unknown')} (firstgid: {tileset.get('firstgid', 'unknown')})")
print()
if not external_tilesets:
print("✅ No external tileset references found!")
print("The map is already clean.")
# Remove backup since no changes were made
os.remove(BACKUP_FILE)
print(f"🗑️ Removed unnecessary backup: {BACKUP_FILE}")
return
print(f"🚨 Found {len(external_tilesets)} external tileset references")
print(f"✅ Found {len(embedded_tilesets)} embedded tilesets")
# Remove external tilesets (in reverse order to maintain indices)
removed_count = 0
for i, tileset in reversed(external_tilesets):
tileset_name = tileset.get('name', 'unknown')
tileset_source = tileset.get('source', 'unknown')
firstgid = tileset.get('firstgid', 'unknown')
print(f"🗑️ Removing external tileset: {tileset_name} (source: {tileset_source}, firstgid: {firstgid})")
tilesets.pop(i)
removed_count += 1
# Update the map data
map_data['tilesets'] = tilesets
print()
print(f"✅ Successfully removed {removed_count} external tileset references")
# Write cleaned map file
try:
with open(MAP_FILE, 'w') as f:
json.dump(map_data, f, indent=2)
print(f"💾 Updated map file: {MAP_FILE}")
print(f"📁 Backup created: {BACKUP_FILE}")
print()
print("📊 Remaining tilesets:")
for tileset in tilesets:
name = tileset.get('name', 'unknown')
firstgid = tileset.get('firstgid', 'unknown')
tilecount = tileset.get('tilecount', 'unknown')
print(f" {name}: firstgid={firstgid}, tilecount={tilecount}")
print()
print("🎯 Next steps:")
print("1. Test the game - it should now load without external tileset errors")
print("2. If you need the removed tileset, re-embed it in Tiled Editor")
print("3. Save the map after making any changes")
except Exception as e:
print(f"❌ Error writing cleaned map file: {e}")
print(f"🔄 Restoring from backup...")
shutil.copy2(BACKUP_FILE, MAP_FILE)
return
if __name__ == "__main__":
main()

216
scripts/update_tileset.js Executable file
View File

@@ -0,0 +1,216 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
/**
* Script to update Tiled map with all objects from assets directory
* This ensures all objects are included in the tileset with proper GIDs
*/
const ASSETS_DIR = path.join(__dirname, '../assets/objects');
const MAP_FILE = path.join(__dirname, '../assets/rooms/room_reception2.json');
// Object types to include
const OBJECT_TYPES = [
'bag', 'bin', 'briefcase', 'laptop', 'phone', 'pc', 'note', 'notes',
'safe', 'suitcase', 'office-misc', 'plant', 'chair', 'picture', 'table'
];
function getAllObjectFiles() {
const files = [];
try {
const entries = fs.readdirSync(ASSETS_DIR, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith('.png')) {
const baseName = entry.name.replace('.png', '');
const category = getObjectCategory(baseName);
files.push({
filename: entry.name,
basename: baseName,
category: category,
path: `../objects/${entry.name}`
});
}
}
} catch (error) {
console.error('Error reading assets directory:', error);
return [];
}
return files.sort((a, b) => a.basename.localeCompare(b.basename));
}
function getObjectCategory(filename) {
const baseName = filename.replace('.png', '');
for (const type of OBJECT_TYPES) {
if (baseName.includes(type)) {
return type;
}
}
return 'misc';
}
function findHighestGID(mapData) {
let highestGID = 0;
// Find the highest GID in the most recent objects tileset
const objectsTilesets = mapData.tilesets.filter(ts =>
ts.name === 'objects' || ts.name.includes('objects/')
);
if (objectsTilesets.length > 0) {
const latestTileset = objectsTilesets[objectsTilesets.length - 1];
highestGID = latestTileset.firstgid + (latestTileset.tilecount || 0);
}
return highestGID;
}
function createTilesetEntry(file, gid) {
return {
id: gid - 1, // Tiled uses 0-based indexing
image: file.path,
imageheight: 16, // Default size, will be updated by Tiled
imagewidth: 16
};
}
function updateMapFile(objectFiles) {
try {
const mapData = JSON.parse(fs.readFileSync(MAP_FILE, 'utf8'));
// Find the latest objects tileset
const objectsTilesets = mapData.tilesets.filter(ts =>
ts.name === 'objects' || ts.name.includes('objects/')
);
if (objectsTilesets.length === 0) {
console.error('No objects tileset found in map file');
return;
}
const latestTileset = objectsTilesets[objectsTilesets.length - 1];
const startGID = latestTileset.firstgid + (latestTileset.tilecount || 0);
console.log(`Found latest objects tileset with firstgid: ${latestTileset.firstgid}`);
console.log(`Current tilecount: ${latestTileset.tilecount || 0}`);
console.log(`Next available GID: ${startGID}`);
// Check which objects are missing
const existingImages = new Set();
if (latestTileset.tiles) {
latestTileset.tiles.forEach(tile => {
if (tile.image) {
const filename = path.basename(tile.image);
existingImages.add(filename);
}
});
}
const missingObjects = objectFiles.filter(file =>
!existingImages.has(file.filename)
);
console.log(`Found ${objectFiles.length} total objects`);
console.log(`Found ${existingImages.size} existing objects in tileset`);
console.log(`Missing ${missingObjects.length} objects`);
if (missingObjects.length === 0) {
console.log('All objects are already in the tileset!');
return;
}
// Add missing objects to tileset
const newTiles = [];
let currentGID = startGID;
for (const file of missingObjects) {
const tileEntry = createTilesetEntry(file, currentGID);
newTiles.push(tileEntry);
currentGID++;
}
// Update tileset
if (!latestTileset.tiles) {
latestTileset.tiles = [];
}
latestTileset.tiles.push(...newTiles);
latestTileset.tilecount = (latestTileset.tilecount || 0) + newTiles.length;
console.log(`Added ${newTiles.length} new objects to tileset`);
console.log(`New tilecount: ${latestTileset.tilecount}`);
// Write updated map file
fs.writeFileSync(MAP_FILE, JSON.stringify(mapData, null, 2));
console.log(`Updated map file: ${MAP_FILE}`);
// Generate Tiled command to open the map
console.log('\nNext steps:');
console.log('1. Open the map in Tiled Editor');
console.log('2. The new objects should be available in the tileset');
console.log('3. Place objects in your layers as needed');
console.log('4. Save the map');
} catch (error) {
console.error('Error updating map file:', error);
}
}
function main() {
console.log('🔧 Tileset Update Script');
console.log('========================');
// Check if assets directory exists
if (!fs.existsSync(ASSETS_DIR)) {
console.error(`Assets directory not found: ${ASSETS_DIR}`);
process.exit(1);
}
// Check if map file exists
if (!fs.existsSync(MAP_FILE)) {
console.error(`Map file not found: ${MAP_FILE}`);
process.exit(1);
}
console.log(`Scanning assets directory: ${ASSETS_DIR}`);
const objectFiles = getAllObjectFiles();
if (objectFiles.length === 0) {
console.log('No object files found in assets directory');
return;
}
console.log(`Found ${objectFiles.length} object files`);
// Group by category
const byCategory = {};
objectFiles.forEach(file => {
if (!byCategory[file.category]) {
byCategory[file.category] = [];
}
byCategory[file.category].push(file);
});
console.log('\nObjects by category:');
Object.entries(byCategory).forEach(([category, files]) => {
console.log(` ${category}: ${files.length} files`);
});
console.log('\nUpdating map file...');
updateMapFile(objectFiles);
console.log('\n✅ Script completed!');
}
if (require.main === module) {
main();
}
module.exports = { getAllObjectFiles, updateMapFile };

184
scripts/update_tileset.py Executable file
View File

@@ -0,0 +1,184 @@
#!/usr/bin/env python3
import os
import json
import glob
from pathlib import Path
"""
Script to update Tiled map with all objects from assets directory
This ensures all objects are included in the tileset with proper GIDs
"""
ASSETS_DIR = "assets/objects"
MAP_FILE = "assets/rooms/room_reception2.json"
# Object types to include
OBJECT_TYPES = [
'bag', 'bin', 'briefcase', 'laptop', 'phone', 'pc', 'note', 'notes',
'safe', 'suitcase', 'office-misc', 'plant', 'chair', 'picture', 'table'
]
def get_all_object_files():
"""Get all object files from the assets directory"""
files = []
if not os.path.exists(ASSETS_DIR):
print(f"❌ Assets directory not found: {ASSETS_DIR}")
return files
for file_path in glob.glob(os.path.join(ASSETS_DIR, "*.png")):
filename = os.path.basename(file_path)
basename = filename.replace('.png', '')
category = get_object_category(basename)
files.append({
'filename': filename,
'basename': basename,
'category': category,
'path': f"../objects/{filename}"
})
return sorted(files, key=lambda x: x['basename'])
def get_object_category(filename):
"""Determine the category of an object based on its filename"""
for obj_type in OBJECT_TYPES:
if obj_type in filename:
return obj_type
return 'misc'
def find_latest_objects_tileset(map_data):
"""Find the latest objects tileset in the map data"""
objects_tilesets = []
for tileset in map_data.get('tilesets', []):
if tileset.get('name') == 'objects' or 'objects/' in tileset.get('name', ''):
objects_tilesets.append(tileset)
if not objects_tilesets:
return None
# Return the last one (most recent)
return objects_tilesets[-1]
def create_tileset_entry(file_info, gid):
"""Create a tileset entry for a file"""
return {
"id": gid - 1, # Tiled uses 0-based indexing
"image": file_info['path'],
"imageheight": 16, # Default size, will be updated by Tiled
"imagewidth": 16
}
def update_map_file(object_files):
"""Update the map file with missing objects"""
try:
with open(MAP_FILE, 'r') as f:
map_data = json.load(f)
# Find the latest objects tileset
latest_tileset = find_latest_objects_tileset(map_data)
if not latest_tileset:
print("❌ No objects tileset found in map file")
return False
print(f"📋 Found latest objects tileset with firstgid: {latest_tileset.get('firstgid', 0)}")
print(f"📊 Current tilecount: {latest_tileset.get('tilecount', 0)}")
# Check which objects are missing
existing_images = set()
if 'tiles' in latest_tileset:
for tile in latest_tileset['tiles']:
if 'image' in tile:
filename = os.path.basename(tile['image'])
existing_images.add(filename)
missing_objects = [f for f in object_files if f['filename'] not in existing_images]
print(f"📁 Found {len(object_files)} total objects")
print(f"✅ Found {len(existing_images)} existing objects in tileset")
print(f"❌ Missing {len(missing_objects)} objects")
if not missing_objects:
print("🎉 All objects are already in the tileset!")
return True
# Add missing objects to tileset
start_gid = latest_tileset.get('firstgid', 0) + latest_tileset.get('tilecount', 0)
new_tiles = []
for i, file_info in enumerate(missing_objects):
gid = start_gid + i
tile_entry = create_tileset_entry(file_info, gid)
new_tiles.append(tile_entry)
# Update tileset
if 'tiles' not in latest_tileset:
latest_tileset['tiles'] = []
latest_tileset['tiles'].extend(new_tiles)
latest_tileset['tilecount'] = latest_tileset.get('tilecount', 0) + len(new_tiles)
print(f" Added {len(new_tiles)} new objects to tileset")
print(f"📊 New tilecount: {latest_tileset['tilecount']}")
# Write updated map file
with open(MAP_FILE, 'w') as f:
json.dump(map_data, f, indent=2)
print(f"💾 Updated map file: {MAP_FILE}")
return True
except Exception as e:
print(f"❌ Error updating map file: {e}")
return False
def main():
"""Main function"""
print("🔧 Tileset Update Script")
print("========================")
# Check if map file exists
if not os.path.exists(MAP_FILE):
print(f"❌ Map file not found: {MAP_FILE}")
return
print(f"📂 Scanning assets directory: {ASSETS_DIR}")
object_files = get_all_object_files()
if not object_files:
print("❌ No object files found in assets directory")
return
print(f"📁 Found {len(object_files)} object files")
# Group by category
by_category = {}
for file_info in object_files:
category = file_info['category']
if category not in by_category:
by_category[category] = []
by_category[category].append(file_info)
print("\n📊 Objects by category:")
for category, files in by_category.items():
print(f" {category}: {len(files)} files")
print("\n🔄 Updating map file...")
success = update_map_file(object_files)
if success:
print("\n✅ Script completed successfully!")
print("\n📝 Next steps:")
print("1. Open the map in Tiled Editor")
print("2. Check that all objects are available in the tileset")
print("3. Place any missing objects in your layers")
print("4. Save the map")
print("\n🎯 This script ensures all objects from assets/objects/ are included in the tileset!")
else:
print("\n❌ Script failed. Please check the errors above.")
if __name__ == "__main__":
main()