Generalize game ID normalization for all _game suffixes
Previously the system only handled the specific case of minetest_game -> minetest. Based on Luanti developer feedback that "_game is removed from IDs as part of normalisation for all game IDs not just MTG", this change generalizes the pattern. ## Changes Made ### Enhanced Game ID Normalization - `mapToActualGameId()`: Now automatically removes "_game" suffix from any game ID - `mapGameIdForWorldCreation()`: Generalizes suffix removal for world.mt files - `mapInternalGameIdToDirectory()`: Enhanced to dynamically check filesystem for directories ### Backwards Compatibility - All existing minetest_game -> minetest mappings continue to work - Now also handles any other games with _game suffix (e.g., survival_game -> survival) - Original EJS templates already compatible via world.gameTitle || world.gameid pattern ### Technical Implementation - Replaces hardcoded mapping tables with general suffix detection - Maintains proper fallback behavior for games without _game suffix - Filesystem-aware directory resolution for reverse mapping 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -182,33 +182,72 @@ class LuantiPaths {
|
||||
|
||||
mapToActualGameId(directoryName) {
|
||||
// Map directory names to the actual game IDs that Luanti recognizes
|
||||
// For most cases, the directory name IS the game ID
|
||||
const gameIdMap = {
|
||||
// Luanti internal alias mapping - see https://github.com/luanti-org/luanti/blob/c9d4c33174c87ede1f49c5fe5e8e49a784798eb6/src/content/subgames.cpp#L21
|
||||
'minetest_game': 'minetest',
|
||||
};
|
||||
// Luanti normalizes game IDs by removing "_game" suffix from all games
|
||||
// See https://github.com/luanti-org/luanti/blob/c9d4c33174c87ede1f49c5fe5e8e49a784798eb6/src/content/subgames.cpp#L21
|
||||
|
||||
return gameIdMap[directoryName] || directoryName;
|
||||
// Remove "_game" suffix if present
|
||||
if (directoryName.endsWith('_game')) {
|
||||
return directoryName.slice(0, -5); // Remove last 5 characters ("_game")
|
||||
}
|
||||
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
mapGameIdForWorldCreation(gameId) {
|
||||
// When creating worlds, map game IDs that Luanti expects to use different IDs internally
|
||||
// This is the reverse of directory-based detection - we're setting what goes in world.mt
|
||||
const worldGameIdMap = {
|
||||
'minetest_game': 'minetest', // Luanti expects 'minetest' in world.mt even for minetest_game
|
||||
};
|
||||
// Luanti normalizes all game IDs by removing "_game" suffix
|
||||
|
||||
return worldGameIdMap[gameId] || gameId;
|
||||
// Remove "_game" suffix if present
|
||||
if (gameId.endsWith('_game')) {
|
||||
return gameId.slice(0, -5); // Remove last 5 characters ("_game")
|
||||
}
|
||||
|
||||
mapInternalGameIdToDirectory(internalGameId) {
|
||||
// Reverse mapping: convert internal game ID back to directory name for display/reference
|
||||
// This helps when we read a world.mt with "minetest" but want to show "Minetest Game"
|
||||
const reverseGameIdMap = {
|
||||
'minetest': 'minetest_game', // world.mt has 'minetest' but directory is 'minetest_game'
|
||||
};
|
||||
return gameId;
|
||||
}
|
||||
|
||||
return reverseGameIdMap[internalGameId] || internalGameId;
|
||||
async mapInternalGameIdToDirectory(internalGameId) {
|
||||
// Reverse mapping: convert internal game ID back to directory name for display/reference
|
||||
// This helps when we read a world.mt with normalized game ID but want to show directory name
|
||||
// Since Luanti normalizes by removing "_game" suffix, we need to check if a directory
|
||||
// with "_game" suffix exists for this normalized ID
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
// Check both system and user games directories
|
||||
const gameDirs = [this.getGamesPath()];
|
||||
if (this.getSystemGamesPath() && this.getSystemGamesPath() !== this.getGamesPath()) {
|
||||
gameDirs.push(this.getSystemGamesPath());
|
||||
}
|
||||
|
||||
for (const gameDir of gameDirs) {
|
||||
try {
|
||||
// First check if the internalGameId as-is exists as a directory
|
||||
const directPath = path.join(gameDir, internalGameId);
|
||||
const stat = await fs.stat(directPath);
|
||||
if (stat.isDirectory()) {
|
||||
return internalGameId;
|
||||
}
|
||||
} catch (error) {
|
||||
// Directory doesn't exist, try with "_game" suffix
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if there's a directory with "_game" suffix
|
||||
const gameIdWithSuffix = internalGameId + '_game';
|
||||
const suffixPath = path.join(gameDir, gameIdWithSuffix);
|
||||
const stat = await fs.stat(suffixPath);
|
||||
if (stat.isDirectory()) {
|
||||
return gameIdWithSuffix;
|
||||
}
|
||||
} catch (error) {
|
||||
// Directory doesn't exist either
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: return the original ID
|
||||
return internalGameId;
|
||||
}
|
||||
|
||||
async getInstalledGames() {
|
||||
|
Reference in New Issue
Block a user