1273 lines
44 KiB
JavaScript
1273 lines
44 KiB
JavaScript
/**
|
|
* AssetManager - Main Coordinator for All Asset Systems
|
|
*
|
|
* Coordinates music, backgrounds, avatars, and contexts into unified
|
|
* asset management with recommendations based on game descriptions.
|
|
*
|
|
* Dependencies (must be loaded before this file):
|
|
* - window.MusicEngine (from /assets/music/)
|
|
* - window.BackgroundEngine (from /assets/backgrounds/)
|
|
* - window.AvatarSystem (from /assets/avatar/)
|
|
* - window.ContextRenderer (from /assets/avatar/)
|
|
* - window.ContextCatalog (from /assets/avatar/)
|
|
* - window.AssetCatalog (unified catalog)
|
|
* - window.AssetPresets (preset combinations)
|
|
* - window.AssetCompatibility (compatibility rules)
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
var initialized = false;
|
|
var loadedAssets = {
|
|
music: null,
|
|
background: null,
|
|
avatar: null,
|
|
context: null
|
|
};
|
|
var cache = new Map();
|
|
|
|
// =========================================================================
|
|
// INITIALIZATION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Initialize the AssetManager and verify all dependencies
|
|
* @param {Object} options - Configuration options
|
|
* @returns {Promise} Resolves with system stats on success
|
|
*/
|
|
function initialize(options) {
|
|
options = options || {};
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
try {
|
|
// Verify all dependencies loaded
|
|
var missing = [];
|
|
if (!window.MusicEngine) missing.push('MusicEngine');
|
|
if (!window.BackgroundEngine) missing.push('BackgroundEngine');
|
|
if (!window.AvatarSystem) missing.push('AvatarSystem');
|
|
if (!window.ContextRenderer) missing.push('ContextRenderer');
|
|
if (!window.ContextCatalog) missing.push('ContextCatalog');
|
|
if (!window.AssetCatalog) missing.push('AssetCatalog');
|
|
if (!window.AssetPresets) missing.push('AssetPresets');
|
|
if (!window.AssetCompatibility) missing.push('AssetCompatibility');
|
|
|
|
if (missing.length > 0) {
|
|
reject(new Error('Missing dependencies: ' + missing.join(', ')));
|
|
return;
|
|
}
|
|
|
|
// Initialize subsystems if they have init methods
|
|
var initPromises = [];
|
|
|
|
// MusicEngine needs Tone.js - may be async
|
|
if (window.MusicEngine.initialize && !window.MusicEngine.isInitialized()) {
|
|
initPromises.push(window.MusicEngine.initialize());
|
|
}
|
|
|
|
// BackgroundEngine - typically sync
|
|
if (window.BackgroundEngine.initialize && !window.BackgroundEngine.isInitialized()) {
|
|
var bgInit = window.BackgroundEngine.initialize();
|
|
if (bgInit && bgInit.then) {
|
|
initPromises.push(bgInit);
|
|
}
|
|
}
|
|
|
|
// AvatarSystem - typically sync
|
|
if (window.AvatarSystem.initialize && !window.AvatarSystem.isInitialized()) {
|
|
var avInit = window.AvatarSystem.initialize();
|
|
if (avInit && avInit.then) {
|
|
initPromises.push(avInit);
|
|
}
|
|
}
|
|
|
|
// Wait for all subsystem initializations
|
|
Promise.all(initPromises).then(function() {
|
|
initialized = true;
|
|
|
|
// Gather stats
|
|
var stats = {
|
|
music: {
|
|
songs: window.MusicEngine.getCatalog ? window.MusicEngine.getCatalog().length : 0
|
|
},
|
|
backgrounds: {
|
|
count: window.BackgroundEngine.getTotalCount ? window.BackgroundEngine.getTotalCount() : 0
|
|
},
|
|
avatarContexts: {
|
|
count: window.ContextCatalog.getAll ? window.ContextCatalog.getAll().length : 0
|
|
},
|
|
presets: {
|
|
count: window.AssetPresets.getAll ? window.AssetPresets.getAll().length : 0
|
|
}
|
|
};
|
|
|
|
resolve(stats);
|
|
}).catch(function(err) {
|
|
reject(new Error('Subsystem initialization failed: ' + err.message));
|
|
});
|
|
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// KEYWORD PARSING HELPERS
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Parse keywords from string or array input
|
|
* @param {string|Array} input - Keywords as string or array
|
|
* @returns {Array} Normalized array of keywords
|
|
*/
|
|
function parseKeywords(input) {
|
|
if (Array.isArray(input)) {
|
|
return input.map(function(w) {
|
|
return String(w).toLowerCase().trim();
|
|
}).filter(function(w) {
|
|
return w.length > 2;
|
|
});
|
|
}
|
|
if (typeof input !== 'string') return [];
|
|
return input.toLowerCase()
|
|
.replace(/[^a-z0-9\s]/g, '')
|
|
.split(/\s+/)
|
|
.filter(function(w) { return w.length > 2; });
|
|
}
|
|
|
|
// =========================================================================
|
|
// THEME DETECTION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Detect theme from keywords
|
|
* @param {Array} keywords - Parsed keywords
|
|
* @returns {string} Detected theme name
|
|
*/
|
|
function detectTheme(keywords) {
|
|
var themeKeywords = {
|
|
space: ['space', 'star', 'planet', 'galaxy', 'alien', 'rocket', 'asteroid', 'nebula', 'cosmic', 'orbit', 'moon', 'mars', 'saturn', 'comet', 'meteor', 'ufo', 'astronaut', 'spaceship'],
|
|
nature: ['forest', 'jungle', 'tree', 'mountain', 'ocean', 'river', 'nature', 'animal', 'garden', 'flower', 'grass', 'wildlife', 'deer', 'bear', 'wolf', 'leaf', 'plant', 'wilderness'],
|
|
urban: ['city', 'street', 'building', 'urban', 'neon', 'downtown', 'traffic', 'skyscraper', 'rooftop', 'subway', 'metro', 'alley', 'nightlife', 'cyberpunk', 'futuristic'],
|
|
fantasy: ['magic', 'wizard', 'dragon', 'castle', 'knight', 'fairy', 'enchanted', 'mythical', 'unicorn', 'elf', 'dwarf', 'potion', 'spell', 'medieval', 'kingdom', 'sorcerer', 'quest'],
|
|
spooky: ['ghost', 'haunted', 'scary', 'zombie', 'halloween', 'dark', 'creepy', 'monster', 'vampire', 'skeleton', 'graveyard', 'horror', 'nightmare', 'witch', 'pumpkin', 'bat'],
|
|
underwater: ['ocean', 'fish', 'coral', 'submarine', 'underwater', 'sea', 'diving', 'aquatic', 'whale', 'shark', 'dolphin', 'reef', 'seaweed', 'mermaid', 'treasure', 'deep'],
|
|
retro: ['retro', 'pixel', 'arcade', '8bit', 'classic', 'vintage', 'oldschool', 'nostalgia', 'gameboy', 'nes', 'snes', 'atari', 'commodore', 'synthwave'],
|
|
sports: ['soccer', 'basketball', 'football', 'tennis', 'sports', 'athlete', 'stadium', 'baseball', 'hockey', 'golf', 'racing', 'olympics', 'gym', 'fitness', 'championship'],
|
|
sky: ['sky', 'cloud', 'flying', 'bird', 'airplane', 'air', 'balloon', 'wind', 'glider', 'parachute', 'kite', 'helicopter', 'jet', 'eagle', 'hawk', 'soar'],
|
|
colorful: ['rainbow', 'colorful', 'bright', 'candy', 'party', 'disco', 'fun', 'vibrant', 'neon', 'carnival', 'festival', 'celebration', 'confetti', 'balloon', 'fireworks'],
|
|
desert: ['desert', 'sand', 'pyramid', 'egypt', 'cactus', 'oasis', 'camel', 'dune', 'sahara', 'temple', 'ancient', 'scorpion', 'mummy'],
|
|
arctic: ['ice', 'snow', 'arctic', 'polar', 'penguin', 'frozen', 'winter', 'glacier', 'cold', 'blizzard', 'igloo', 'eskimo', 'north', 'south', 'pole'],
|
|
western: ['western', 'cowboy', 'desert', 'saloon', 'sheriff', 'outlaw', 'horse', 'rodeo', 'wild', 'west', 'cactus', 'tumbleweed']
|
|
};
|
|
|
|
var scores = {};
|
|
for (var theme in themeKeywords) {
|
|
scores[theme] = 0;
|
|
themeKeywords[theme].forEach(function(kw) {
|
|
keywords.forEach(function(word) {
|
|
// Exact match
|
|
if (word === kw) {
|
|
scores[theme] += 3;
|
|
}
|
|
// Partial match
|
|
else if (word.includes(kw) || kw.includes(word)) {
|
|
scores[theme] += 1;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Find highest scoring theme
|
|
var best = 'nature'; // default
|
|
var bestScore = 0;
|
|
for (var t in scores) {
|
|
if (scores[t] > bestScore) {
|
|
best = t;
|
|
bestScore = scores[t];
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// =========================================================================
|
|
// MOOD DETECTION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Detect mood from keywords
|
|
* @param {Array} keywords - Parsed keywords
|
|
* @returns {string} Detected mood name
|
|
*/
|
|
function detectMood(keywords) {
|
|
var moodKeywords = {
|
|
epic: ['epic', 'battle', 'hero', 'adventure', 'quest', 'warrior', 'legendary', 'conquest', 'victory', 'triumph', 'glory', 'champion', 'mighty', 'powerful'],
|
|
chill: ['chill', 'relax', 'calm', 'peaceful', 'zen', 'meditation', 'serene', 'tranquil', 'mellow', 'easy', 'gentle', 'soothing', 'quiet'],
|
|
intense: ['intense', 'fast', 'action', 'rush', 'speed', 'chase', 'urgent', 'adrenaline', 'extreme', 'rapid', 'frantic', 'wild', 'crazy'],
|
|
playful: ['fun', 'playful', 'silly', 'cute', 'happy', 'joy', 'cheerful', 'whimsical', 'bouncy', 'bubbly', 'friendly', 'goofy', 'cartoon'],
|
|
mysterious: ['mystery', 'scary', 'dark', 'haunted', 'ghost', 'creepy', 'strange', 'enigma', 'secret', 'hidden', 'shadow', 'fog', 'eerie'],
|
|
heroic: ['hero', 'brave', 'save', 'rescue', 'champion', 'courage', 'noble', 'defender', 'protect', 'guardian', 'justice', 'valor'],
|
|
quirky: ['weird', 'quirky', 'strange', 'unusual', 'unique', 'odd', 'wacky', 'bizarre', 'eccentric', 'offbeat', 'random'],
|
|
ambient: ['ambient', 'atmosphere', 'background', 'subtle', 'minimal', 'soft', 'dreamy', 'floating', 'ethereal', 'space'],
|
|
dramatic: ['dramatic', 'tension', 'suspense', 'climax', 'serious', 'grave', 'tense', 'thriller', 'danger'],
|
|
upbeat: ['upbeat', 'energetic', 'lively', 'peppy', 'dynamic', 'exciting', 'vibrant', 'active', 'bouncy'],
|
|
nostalgic: ['nostalgic', 'retro', 'classic', 'vintage', 'memories', 'throwback', 'oldschool', 'reminiscent']
|
|
};
|
|
|
|
var scores = {};
|
|
for (var mood in moodKeywords) {
|
|
scores[mood] = 0;
|
|
moodKeywords[mood].forEach(function(kw) {
|
|
keywords.forEach(function(word) {
|
|
// Exact match
|
|
if (word === kw) {
|
|
scores[mood] += 3;
|
|
}
|
|
// Partial match
|
|
else if (word.includes(kw) || kw.includes(word)) {
|
|
scores[mood] += 1;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Find highest scoring mood
|
|
var best = 'playful'; // default for games
|
|
var bestScore = 0;
|
|
for (var m in scores) {
|
|
if (scores[m] > bestScore) {
|
|
best = m;
|
|
bestScore = scores[m];
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// =========================================================================
|
|
// MECHANICS DETECTION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Detect game mechanics from keywords to determine avatar display mode
|
|
* @param {Array} keywords - Parsed keywords
|
|
* @returns {Object} { mode: string, context: string|null }
|
|
*/
|
|
function detectMechanics(keywords) {
|
|
var result = { mode: 'FULL_BODY', context: null };
|
|
|
|
// Flying mechanics → HEAD_ONLY with flappy-style
|
|
var flyingKeywords = ['fly', 'flying', 'flap', 'bird', 'wings', 'soar', 'glide', 'hover', 'flutter', 'airborne'];
|
|
if (keywords.some(function(w) { return flyingKeywords.includes(w); })) {
|
|
result.mode = 'HEAD_ONLY';
|
|
result.context = 'flappy-style';
|
|
return result;
|
|
}
|
|
|
|
// Racing mechanics → HEAD_AND_SHOULDERS with race-car
|
|
var racingKeywords = ['race', 'racing', 'car', 'drive', 'kart', 'drift', 'speed', 'vehicle', 'steering', 'track'];
|
|
if (keywords.some(function(w) { return racingKeywords.includes(w); })) {
|
|
result.mode = 'HEAD_AND_SHOULDERS';
|
|
result.context = 'race-car';
|
|
return result;
|
|
}
|
|
|
|
// Space combat → HEAD_ONLY with spaceship-cockpit
|
|
var spaceKeywords = ['spaceship', 'spacecraft', 'cockpit', 'pilot', 'starship', 'fighter', 'turret'];
|
|
if (keywords.some(function(w) { return spaceKeywords.includes(w); })) {
|
|
result.mode = 'HEAD_ONLY';
|
|
result.context = 'spaceship-cockpit';
|
|
return result;
|
|
}
|
|
|
|
// Submarine/underwater vehicle → HEAD_AND_SHOULDERS
|
|
var subKeywords = ['submarine', 'sub', 'bathysphere', 'underwater', 'diving'];
|
|
if (keywords.some(function(w) { return subKeywords.includes(w); })) {
|
|
result.mode = 'HEAD_AND_SHOULDERS';
|
|
result.context = 'submarine-cockpit';
|
|
return result;
|
|
}
|
|
|
|
// Platformer → FULL_BODY
|
|
var platformerKeywords = ['jump', 'platform', 'run', 'runner', 'side', 'scrolling', 'platformer', 'hop', 'bounce'];
|
|
if (keywords.some(function(w) { return platformerKeywords.includes(w); })) {
|
|
result.mode = 'FULL_BODY';
|
|
result.context = 'platformer-standard';
|
|
return result;
|
|
}
|
|
|
|
// Puzzle/static → HEAD_AND_SHOULDERS
|
|
var puzzleKeywords = ['puzzle', 'match', 'block', 'tetris', 'tile', 'grid', 'board'];
|
|
if (keywords.some(function(w) { return puzzleKeywords.includes(w); })) {
|
|
result.mode = 'HEAD_AND_SHOULDERS';
|
|
result.context = 'puzzle-frame';
|
|
return result;
|
|
}
|
|
|
|
// Shooter → HEAD_ONLY with targeting-hud
|
|
var shooterKeywords = ['shoot', 'shooter', 'gun', 'blast', 'laser', 'fire', 'bullet', 'aim'];
|
|
if (keywords.some(function(w) { return shooterKeywords.includes(w); })) {
|
|
result.mode = 'HEAD_ONLY';
|
|
result.context = 'targeting-hud';
|
|
return result;
|
|
}
|
|
|
|
// Sports → FULL_BODY
|
|
var sportsKeywords = ['soccer', 'basketball', 'football', 'tennis', 'sports', 'athlete', 'ball'];
|
|
if (keywords.some(function(w) { return sportsKeywords.includes(w); })) {
|
|
result.mode = 'FULL_BODY';
|
|
result.context = 'sports-arena';
|
|
return result;
|
|
}
|
|
|
|
// Swimming → FULL_BODY with swim context
|
|
var swimKeywords = ['swim', 'swimming', 'diver', 'snorkel', 'fins'];
|
|
if (keywords.some(function(w) { return swimKeywords.includes(w); })) {
|
|
result.mode = 'FULL_BODY';
|
|
result.context = 'swimmer-underwater';
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// =========================================================================
|
|
// RECOMMENDATION ENGINE
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get asset recommendations based on game description
|
|
* @param {string} gameStyle - One of the 11 game styles
|
|
* @param {string|Array} keywords - Description keywords or array of keywords
|
|
* @param {string} mood - Optional mood override
|
|
* @returns {Array} Top 3 recommendations with explanations
|
|
*/
|
|
function getRecommendations(gameStyle, keywords, mood) {
|
|
// Parse keywords if string
|
|
var parsedKeywords = parseKeywords(keywords);
|
|
|
|
// Detect theme from keywords
|
|
var detectedTheme = detectTheme(parsedKeywords);
|
|
var detectedMood = mood || detectMood(parsedKeywords);
|
|
var detectedMechanics = detectMechanics(parsedKeywords);
|
|
|
|
// Get compatible combinations from AssetCompatibility
|
|
var compatibleMusic = [];
|
|
var compatibleBackgrounds = [];
|
|
var compatibleContexts = [];
|
|
|
|
if (window.AssetCompatibility) {
|
|
if (window.AssetCompatibility.getMusicForMood) {
|
|
compatibleMusic = window.AssetCompatibility.getMusicForMood(detectedMood);
|
|
}
|
|
if (window.AssetCompatibility.getBackgroundsForTheme) {
|
|
compatibleBackgrounds = window.AssetCompatibility.getBackgroundsForTheme(detectedTheme);
|
|
}
|
|
if (window.AssetCompatibility.getContextsForMechanics) {
|
|
compatibleContexts = window.AssetCompatibility.getContextsForMechanics(detectedMechanics);
|
|
}
|
|
}
|
|
|
|
// Score and rank combinations
|
|
var candidates = [];
|
|
|
|
// Use presets as base candidates
|
|
var presets = [];
|
|
if (window.AssetPresets && window.AssetPresets.getByGameStyle) {
|
|
presets = window.AssetPresets.getByGameStyle(gameStyle);
|
|
}
|
|
|
|
// If no presets for this game style, get all presets
|
|
if (presets.length === 0 && window.AssetPresets && window.AssetPresets.getAll) {
|
|
presets = window.AssetPresets.getAll();
|
|
}
|
|
|
|
presets.forEach(function(preset) {
|
|
var score = scorePreset(preset, detectedTheme, detectedMood, parsedKeywords, compatibleMusic, compatibleBackgrounds, compatibleContexts);
|
|
candidates.push({
|
|
preset: preset,
|
|
score: score,
|
|
explanation: generateExplanation(preset, parsedKeywords, detectedTheme, detectedMood)
|
|
});
|
|
});
|
|
|
|
// Also create dynamic combinations from compatible assets if we have few presets
|
|
if (candidates.length < 3 && compatibleMusic.length > 0 && compatibleBackgrounds.length > 0) {
|
|
for (var i = 0; i < Math.min(3, compatibleMusic.length); i++) {
|
|
for (var j = 0; j < Math.min(3, compatibleBackgrounds.length); j++) {
|
|
var dynamicPreset = {
|
|
id: 'dynamic-' + i + '-' + j,
|
|
name: detectedTheme + ' ' + detectedMood + ' combo',
|
|
description: 'Auto-generated combination',
|
|
music: compatibleMusic[i],
|
|
background: compatibleBackgrounds[j],
|
|
avatarContext: compatibleContexts[0] || { mode: detectedMechanics.mode, context: detectedMechanics.context }
|
|
};
|
|
var score = scorePreset(dynamicPreset, detectedTheme, detectedMood, parsedKeywords, compatibleMusic, compatibleBackgrounds, compatibleContexts);
|
|
// Slight penalty for dynamic presets
|
|
score = score * 0.9;
|
|
candidates.push({
|
|
preset: dynamicPreset,
|
|
score: score,
|
|
explanation: generateExplanation(dynamicPreset, parsedKeywords, detectedTheme, detectedMood)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort by score descending
|
|
candidates.sort(function(a, b) { return b.score - a.score; });
|
|
|
|
// Remove duplicates based on preset ID
|
|
var seen = {};
|
|
var uniqueCandidates = candidates.filter(function(c) {
|
|
if (seen[c.preset.id]) return false;
|
|
seen[c.preset.id] = true;
|
|
return true;
|
|
});
|
|
|
|
// Return top 3
|
|
return uniqueCandidates.slice(0, 3).map(function(c) {
|
|
return {
|
|
config: presetToConfig(c.preset),
|
|
score: c.score,
|
|
explanation: c.explanation,
|
|
presetId: c.preset.id,
|
|
detectedTheme: detectedTheme,
|
|
detectedMood: detectedMood,
|
|
detectedMechanics: detectedMechanics
|
|
};
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// SCORING
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Score a preset based on detected attributes
|
|
* @param {Object} preset - The preset to score
|
|
* @param {string} theme - Detected theme
|
|
* @param {string} mood - Detected mood
|
|
* @param {Array} keywords - Parsed keywords
|
|
* @param {Array} compatibleMusic - Compatible music from AssetCompatibility
|
|
* @param {Array} compatibleBackgrounds - Compatible backgrounds
|
|
* @param {Array} compatibleContexts - Compatible contexts
|
|
* @returns {number} Score value
|
|
*/
|
|
function scorePreset(preset, theme, mood, keywords, compatibleMusic, compatibleBackgrounds, compatibleContexts) {
|
|
var score = 0;
|
|
|
|
// Theme match with background (high weight)
|
|
if (preset.background) {
|
|
var bgTheme = (preset.background.theme || '').toLowerCase();
|
|
if (bgTheme === theme.toLowerCase()) {
|
|
score += 15;
|
|
} else if (bgTheme.includes(theme.toLowerCase()) || theme.toLowerCase().includes(bgTheme)) {
|
|
score += 8;
|
|
}
|
|
|
|
// Check if background is in compatible list
|
|
if (compatibleBackgrounds && compatibleBackgrounds.length > 0) {
|
|
var isCompatibleBg = compatibleBackgrounds.some(function(bg) {
|
|
return bg && bg.theme === preset.background.theme;
|
|
});
|
|
if (isCompatibleBg) score += 5;
|
|
}
|
|
}
|
|
|
|
// Mood match with music (high weight)
|
|
if (preset.music) {
|
|
var musicMood = (preset.music.mood || '').toLowerCase();
|
|
if (musicMood === mood.toLowerCase()) {
|
|
score += 15;
|
|
} else if (musicMood.includes(mood.toLowerCase()) || mood.toLowerCase().includes(musicMood)) {
|
|
score += 8;
|
|
}
|
|
|
|
// Check if music is in compatible list
|
|
if (compatibleMusic && compatibleMusic.length > 0) {
|
|
var isCompatibleMusic = compatibleMusic.some(function(m) {
|
|
return m && m.mood === preset.music.mood;
|
|
});
|
|
if (isCompatibleMusic) score += 5;
|
|
}
|
|
}
|
|
|
|
// Context compatibility
|
|
if (preset.avatarContext && compatibleContexts && compatibleContexts.length > 0) {
|
|
var isCompatibleContext = compatibleContexts.some(function(ctx) {
|
|
return ctx && ctx.context === preset.avatarContext.context;
|
|
});
|
|
if (isCompatibleContext) score += 10;
|
|
}
|
|
|
|
// Keyword matches in description (medium weight)
|
|
if (preset.description) {
|
|
var desc = preset.description.toLowerCase();
|
|
keywords.forEach(function(kw) {
|
|
if (desc.includes(kw)) {
|
|
score += 2;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Keyword matches in name (high weight for direct matches)
|
|
if (preset.name) {
|
|
var name = preset.name.toLowerCase();
|
|
keywords.forEach(function(kw) {
|
|
if (name.includes(kw)) {
|
|
score += 4;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Keyword matches in tags
|
|
if (preset.tags && Array.isArray(preset.tags)) {
|
|
var presetTags = preset.tags.map(function(t) { return t.toLowerCase(); });
|
|
keywords.forEach(function(kw) {
|
|
if (presetTags.includes(kw)) {
|
|
score += 3;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Energy level considerations (if present)
|
|
if (preset.music && preset.music.energy !== undefined) {
|
|
// Intense moods prefer high energy
|
|
if (mood === 'intense' || mood === 'epic' || mood === 'dramatic') {
|
|
if (preset.music.energy >= 7) score += 3;
|
|
}
|
|
// Chill moods prefer low energy
|
|
else if (mood === 'chill' || mood === 'ambient') {
|
|
if (preset.music.energy <= 4) score += 3;
|
|
}
|
|
// Playful prefers medium energy
|
|
else if (mood === 'playful' || mood === 'upbeat') {
|
|
if (preset.music.energy >= 5 && preset.music.energy <= 8) score += 3;
|
|
}
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
// =========================================================================
|
|
// EXPLANATION GENERATION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Generate human-readable explanation for why a preset was recommended
|
|
* @param {Object} preset - The recommended preset
|
|
* @param {Array} keywords - User's keywords
|
|
* @param {string} theme - Detected theme
|
|
* @param {string} mood - Detected mood
|
|
* @returns {string} Explanation text
|
|
*/
|
|
function generateExplanation(preset, keywords, theme, mood) {
|
|
var reasons = [];
|
|
|
|
// Music reasoning
|
|
if (preset.music) {
|
|
var musicMood = preset.music.mood || 'dynamic';
|
|
var energy = preset.music.energy || 5;
|
|
var energyDesc = energy >= 7 ? 'high-energy' : (energy <= 3 ? 'calm' : 'balanced');
|
|
reasons.push(musicMood + ' ' + energyDesc + ' music sets the mood');
|
|
}
|
|
|
|
// Background reasoning
|
|
if (preset.background) {
|
|
var bgTheme = preset.background.theme || 'themed';
|
|
var variant = preset.background.variant ? ' (' + preset.background.variant + ')' : '';
|
|
if (bgTheme.toLowerCase() === theme.toLowerCase()) {
|
|
reasons.push(bgTheme + variant + ' background matches your ' + theme + ' theme');
|
|
} else {
|
|
reasons.push(bgTheme + variant + ' background provides atmosphere');
|
|
}
|
|
}
|
|
|
|
// Context reasoning
|
|
if (preset.avatarContext) {
|
|
var contextName = preset.avatarContext.context || preset.avatarContext.mode;
|
|
reasons.push(contextName + ' avatar context complements the gameplay');
|
|
}
|
|
|
|
// Add keyword matches if found
|
|
var matchedKeywords = [];
|
|
if (preset.name) {
|
|
keywords.forEach(function(kw) {
|
|
if (preset.name.toLowerCase().includes(kw)) {
|
|
matchedKeywords.push(kw);
|
|
}
|
|
});
|
|
}
|
|
if (matchedKeywords.length > 0) {
|
|
reasons.push('matches keywords: ' + matchedKeywords.slice(0, 3).join(', '));
|
|
}
|
|
|
|
return reasons.join('; ') || 'Good general-purpose combination';
|
|
}
|
|
|
|
// =========================================================================
|
|
// PRESET LOOKUP
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Find a preset by game style with variety index
|
|
* @param {string} gameStyle - Game style to search for
|
|
* @param {number} variety - Index for variation (cycles through available presets)
|
|
* @returns {Object|null} Preset object or null
|
|
*/
|
|
function findPreset(gameStyle, variety) {
|
|
variety = variety || 0;
|
|
|
|
if (!window.AssetPresets || !window.AssetPresets.getByGameStyle) {
|
|
return null;
|
|
}
|
|
|
|
var presets = window.AssetPresets.getByGameStyle(gameStyle);
|
|
if (presets.length === 0) {
|
|
// Fallback to all presets
|
|
presets = window.AssetPresets.getAll ? window.AssetPresets.getAll() : [];
|
|
}
|
|
|
|
if (presets.length === 0) return null;
|
|
|
|
var index = Math.abs(variety) % presets.length;
|
|
return presets[index];
|
|
}
|
|
|
|
/**
|
|
* Convert a preset to a configuration object
|
|
* @param {Object} preset - Preset to convert
|
|
* @returns {Object} Configuration object
|
|
*/
|
|
function presetToConfig(preset) {
|
|
if (!preset) return null;
|
|
|
|
return {
|
|
music: preset.music ? {
|
|
mood: preset.music.mood,
|
|
energy: preset.music.energy,
|
|
songId: preset.music.songId || preset.music.id,
|
|
tags: preset.music.tags
|
|
} : null,
|
|
background: preset.background ? {
|
|
theme: preset.background.theme,
|
|
variant: preset.background.variant,
|
|
animated: preset.background.animated !== false
|
|
} : null,
|
|
avatarContext: preset.avatarContext ? {
|
|
mode: preset.avatarContext.mode,
|
|
context: preset.avatarContext.context,
|
|
props: preset.avatarContext.props
|
|
} : null,
|
|
colorPalette: preset.colorPalette || null,
|
|
presetId: preset.id,
|
|
name: preset.name,
|
|
description: preset.description
|
|
};
|
|
}
|
|
|
|
// =========================================================================
|
|
// ASSET LOADING
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Load game assets based on configuration
|
|
* @param {Object} config - Asset configuration
|
|
* @returns {Promise} Resolves with loaded assets
|
|
*/
|
|
function loadGameAssets(config) {
|
|
return new Promise(function(resolve, reject) {
|
|
try {
|
|
var results = {};
|
|
var loadPromises = [];
|
|
|
|
// Load music
|
|
if (config.music && window.MusicEngine) {
|
|
var songQuery = {
|
|
mood: config.music.mood,
|
|
energy: config.music.energy
|
|
};
|
|
|
|
var songs = [];
|
|
if (window.MusicEngine.findSongs) {
|
|
songs = window.MusicEngine.findSongs(songQuery);
|
|
}
|
|
|
|
if (songs.length > 0) {
|
|
results.music = songs[0];
|
|
|
|
// If MusicEngine has async load method
|
|
if (window.MusicEngine.loadSong) {
|
|
loadPromises.push(
|
|
window.MusicEngine.loadSong(songs[0].id || songs[0].songId).then(function(loaded) {
|
|
results.music = loaded;
|
|
}).catch(function() {
|
|
// Keep the basic song info even if full load fails
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load background
|
|
if (config.background && window.BackgroundEngine) {
|
|
results.background = {
|
|
theme: config.background.theme,
|
|
variant: config.background.variant,
|
|
animated: config.background.animated !== false,
|
|
render: function(ctx, width, height, time) {
|
|
if (window.BackgroundEngine.render) {
|
|
window.BackgroundEngine.render(
|
|
ctx,
|
|
config.background.theme,
|
|
config.background.variant,
|
|
width,
|
|
height,
|
|
time
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Preload background if method exists
|
|
if (window.BackgroundEngine.preload) {
|
|
loadPromises.push(
|
|
Promise.resolve(window.BackgroundEngine.preload(config.background.theme, config.background.variant))
|
|
);
|
|
}
|
|
}
|
|
|
|
// Load avatar context
|
|
if (config.avatarContext && config.avatar && window.ContextRenderer) {
|
|
if (window.ContextRenderer.apply) {
|
|
results.avatarWithContext = window.ContextRenderer.apply(
|
|
config.avatar,
|
|
config.avatarContext.context,
|
|
config.avatarContext.props
|
|
);
|
|
}
|
|
results.avatarContext = config.avatarContext;
|
|
}
|
|
|
|
// Wait for all async loads
|
|
Promise.all(loadPromises).then(function() {
|
|
loadedAssets = results;
|
|
|
|
// Cache the loaded assets
|
|
if (config.presetId) {
|
|
cache.set(config.presetId, results);
|
|
}
|
|
|
|
resolve(results);
|
|
}).catch(function(err) {
|
|
// Still return what we have even if some loads failed
|
|
loadedAssets = results;
|
|
resolve(results);
|
|
});
|
|
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// VALIDATION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Validate an asset combination for compatibility
|
|
* @param {Object} config - Configuration to validate
|
|
* @returns {Object} { valid: boolean, issues: Array, warnings: Array }
|
|
*/
|
|
function validateCombination(config) {
|
|
// Use AssetCompatibility if available
|
|
if (window.AssetCompatibility && window.AssetCompatibility.validate) {
|
|
return window.AssetCompatibility.validate(config);
|
|
}
|
|
|
|
// Basic validation fallback
|
|
var result = {
|
|
valid: true,
|
|
issues: [],
|
|
warnings: []
|
|
};
|
|
|
|
// Check required fields
|
|
if (!config) {
|
|
result.valid = false;
|
|
result.issues.push('No configuration provided');
|
|
return result;
|
|
}
|
|
|
|
// Validate music config
|
|
if (config.music) {
|
|
if (!config.music.mood) {
|
|
result.warnings.push('Music mood not specified');
|
|
}
|
|
if (config.music.energy !== undefined) {
|
|
if (config.music.energy < 1 || config.music.energy > 10) {
|
|
result.issues.push('Music energy must be between 1 and 10');
|
|
result.valid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate background config
|
|
if (config.background) {
|
|
if (!config.background.theme) {
|
|
result.warnings.push('Background theme not specified');
|
|
}
|
|
}
|
|
|
|
// Validate avatar context
|
|
if (config.avatarContext) {
|
|
var validModes = ['FULL_BODY', 'HEAD_AND_SHOULDERS', 'HEAD_ONLY'];
|
|
if (config.avatarContext.mode && validModes.indexOf(config.avatarContext.mode) === -1) {
|
|
result.issues.push('Invalid avatar mode: ' + config.avatarContext.mode);
|
|
result.valid = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// =========================================================================
|
|
// DESCRIPTION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Generate a human-readable description of asset configuration
|
|
* @param {Object} config - Configuration to describe
|
|
* @returns {string} Description text
|
|
*/
|
|
function describeAssets(config) {
|
|
if (!config) return 'No assets configured';
|
|
|
|
var parts = [];
|
|
|
|
if (config.music) {
|
|
var musicDesc = 'Music: ';
|
|
if (config.music.mood) {
|
|
musicDesc += config.music.mood + ' mood';
|
|
}
|
|
if (config.music.energy !== undefined) {
|
|
musicDesc += ' (energy ' + config.music.energy + '/10)';
|
|
}
|
|
if (config.music.songId) {
|
|
musicDesc += ' [' + config.music.songId + ']';
|
|
}
|
|
parts.push(musicDesc);
|
|
}
|
|
|
|
if (config.background) {
|
|
var bgDesc = 'Background: ';
|
|
if (config.background.theme) {
|
|
bgDesc += config.background.theme;
|
|
}
|
|
if (config.background.variant) {
|
|
bgDesc += ' - ' + config.background.variant;
|
|
}
|
|
if (config.background.animated === false) {
|
|
bgDesc += ' (static)';
|
|
}
|
|
parts.push(bgDesc);
|
|
}
|
|
|
|
if (config.avatarContext) {
|
|
var avDesc = 'Avatar: ';
|
|
if (config.avatarContext.mode) {
|
|
avDesc += config.avatarContext.mode;
|
|
}
|
|
if (config.avatarContext.context) {
|
|
avDesc += ' in ' + config.avatarContext.context;
|
|
}
|
|
parts.push(avDesc);
|
|
}
|
|
|
|
if (config.name) {
|
|
parts.unshift('Preset: ' + config.name);
|
|
}
|
|
|
|
return parts.join(' | ') || 'Default configuration';
|
|
}
|
|
|
|
// =========================================================================
|
|
// VARIATIONS
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get a variation of the current configuration
|
|
* @param {Object} config - Current configuration
|
|
* @param {string} variationType - Type of variation: 'music', 'background', 'context', or 'all'
|
|
* @returns {Object} New configuration with variation
|
|
*/
|
|
function getVariation(config, variationType) {
|
|
if (!config) return null;
|
|
|
|
// Deep clone the config
|
|
var newConfig = JSON.parse(JSON.stringify(config));
|
|
|
|
switch (variationType) {
|
|
case 'music':
|
|
if (newConfig.music && window.MusicEngine && window.MusicEngine.findSongs) {
|
|
var altSongs = window.MusicEngine.findSongs({ mood: config.music.mood });
|
|
if (altSongs.length > 1) {
|
|
// Pick a random different song
|
|
var filtered = altSongs.filter(function(s) {
|
|
return s.id !== config.music.songId && s.songId !== config.music.songId;
|
|
});
|
|
if (filtered.length > 0) {
|
|
var picked = filtered[Math.floor(Math.random() * filtered.length)];
|
|
newConfig.music = {
|
|
mood: picked.mood || config.music.mood,
|
|
energy: picked.energy || config.music.energy,
|
|
songId: picked.id || picked.songId,
|
|
tags: picked.tags
|
|
};
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'background':
|
|
if (newConfig.background && window.BackgroundEngine && window.BackgroundEngine.getVariants) {
|
|
var variants = window.BackgroundEngine.getVariants(config.background.theme);
|
|
if (variants && variants.length > 1) {
|
|
var currentIdx = variants.indexOf(config.background.variant);
|
|
var nextIdx = (currentIdx + 1) % variants.length;
|
|
newConfig.background = Object.assign({}, config.background, {
|
|
variant: variants[nextIdx]
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'context':
|
|
if (newConfig.avatarContext && window.ContextCatalog && window.ContextCatalog.getByMode) {
|
|
var contexts = window.ContextCatalog.getByMode(config.avatarContext.mode);
|
|
if (contexts && contexts.length > 1) {
|
|
var filtered = contexts.filter(function(c) {
|
|
return c.id !== config.avatarContext.context;
|
|
});
|
|
if (filtered.length > 0) {
|
|
var alt = filtered[Math.floor(Math.random() * filtered.length)];
|
|
newConfig.avatarContext = Object.assign({}, config.avatarContext, {
|
|
context: alt.id
|
|
});
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'all':
|
|
default:
|
|
// Apply all variations
|
|
newConfig = getVariation(newConfig, 'music');
|
|
newConfig = getVariation(newConfig, 'background');
|
|
newConfig = getVariation(newConfig, 'context');
|
|
break;
|
|
}
|
|
|
|
// Clear preset info since this is now a custom combination
|
|
if (variationType) {
|
|
newConfig.presetId = null;
|
|
newConfig.name = (config.name || 'Custom') + ' (variation)';
|
|
}
|
|
|
|
return newConfig;
|
|
}
|
|
|
|
// =========================================================================
|
|
// SEARCH
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Search for assets across all subsystems
|
|
* @param {string} query - Search query
|
|
* @returns {Object} Search results by category
|
|
*/
|
|
function searchAssets(query) {
|
|
if (!query || typeof query !== 'string') {
|
|
return { presets: [], music: [], backgrounds: [], contexts: [] };
|
|
}
|
|
|
|
var normalizedQuery = query.toLowerCase().trim();
|
|
var results = {
|
|
presets: [],
|
|
music: [],
|
|
backgrounds: [],
|
|
contexts: []
|
|
};
|
|
|
|
// Search presets
|
|
if (window.AssetPresets) {
|
|
if (window.AssetPresets.search) {
|
|
results.presets = window.AssetPresets.search(normalizedQuery);
|
|
} else if (window.AssetPresets.getAll) {
|
|
// Manual search fallback
|
|
results.presets = window.AssetPresets.getAll().filter(function(p) {
|
|
return (p.name && p.name.toLowerCase().includes(normalizedQuery)) ||
|
|
(p.description && p.description.toLowerCase().includes(normalizedQuery)) ||
|
|
(p.tags && p.tags.some(function(t) { return t.toLowerCase().includes(normalizedQuery); }));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Search music
|
|
if (window.MusicEngine) {
|
|
if (window.MusicEngine.findSongs) {
|
|
results.music = window.MusicEngine.findSongs({ tags: [normalizedQuery] });
|
|
}
|
|
// Also search by mood
|
|
if (results.music.length === 0 && window.MusicEngine.findSongs) {
|
|
results.music = window.MusicEngine.findSongs({ mood: normalizedQuery });
|
|
}
|
|
}
|
|
|
|
// Search backgrounds
|
|
if (window.BackgroundEngine) {
|
|
if (window.BackgroundEngine.find) {
|
|
results.backgrounds = window.BackgroundEngine.find({ tags: [normalizedQuery] });
|
|
} else if (window.BackgroundEngine.getThemes) {
|
|
// Manual search fallback
|
|
var themes = window.BackgroundEngine.getThemes();
|
|
results.backgrounds = themes.filter(function(t) {
|
|
return t.toLowerCase().includes(normalizedQuery);
|
|
}).map(function(t) {
|
|
return { theme: t };
|
|
});
|
|
}
|
|
}
|
|
|
|
// Search contexts
|
|
if (window.ContextCatalog) {
|
|
if (window.ContextCatalog.search) {
|
|
results.contexts = window.ContextCatalog.search(normalizedQuery);
|
|
} else if (window.ContextCatalog.getAll) {
|
|
// Manual search fallback
|
|
results.contexts = window.ContextCatalog.getAll().filter(function(c) {
|
|
return (c.id && c.id.toLowerCase().includes(normalizedQuery)) ||
|
|
(c.name && c.name.toLowerCase().includes(normalizedQuery)) ||
|
|
(c.tags && c.tags.some(function(t) { return t.toLowerCase().includes(normalizedQuery); }));
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// =========================================================================
|
|
// RANDOM COMBINATION
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get a random asset combination, optionally filtered by game style
|
|
* @param {string} gameStyle - Optional game style filter
|
|
* @returns {Object} Random configuration
|
|
*/
|
|
function getRandomCombination(gameStyle) {
|
|
var presets = [];
|
|
|
|
if (window.AssetPresets) {
|
|
if (gameStyle && window.AssetPresets.getByGameStyle) {
|
|
presets = window.AssetPresets.getByGameStyle(gameStyle);
|
|
}
|
|
if (presets.length === 0 && window.AssetPresets.getAll) {
|
|
presets = window.AssetPresets.getAll();
|
|
}
|
|
}
|
|
|
|
if (presets.length > 0) {
|
|
var preset = presets[Math.floor(Math.random() * presets.length)];
|
|
return presetToConfig(preset);
|
|
}
|
|
|
|
// Fallback: build random combination from individual subsystems
|
|
var config = {};
|
|
|
|
// Random music
|
|
if (window.MusicEngine && window.MusicEngine.getCatalog) {
|
|
var songs = window.MusicEngine.getCatalog();
|
|
if (songs.length > 0) {
|
|
var song = songs[Math.floor(Math.random() * songs.length)];
|
|
config.music = {
|
|
mood: song.mood,
|
|
energy: song.energy,
|
|
songId: song.id || song.songId
|
|
};
|
|
}
|
|
}
|
|
|
|
// Random background
|
|
if (window.BackgroundEngine && window.BackgroundEngine.getThemes) {
|
|
var themes = window.BackgroundEngine.getThemes();
|
|
if (themes.length > 0) {
|
|
var theme = themes[Math.floor(Math.random() * themes.length)];
|
|
var variants = window.BackgroundEngine.getVariants ? window.BackgroundEngine.getVariants(theme) : ['default'];
|
|
var variant = variants[Math.floor(Math.random() * variants.length)];
|
|
config.background = {
|
|
theme: theme,
|
|
variant: variant
|
|
};
|
|
}
|
|
}
|
|
|
|
// Random context
|
|
if (window.ContextCatalog && window.ContextCatalog.getAll) {
|
|
var contexts = window.ContextCatalog.getAll();
|
|
if (contexts.length > 0) {
|
|
var context = contexts[Math.floor(Math.random() * contexts.length)];
|
|
config.avatarContext = {
|
|
mode: context.mode || 'FULL_BODY',
|
|
context: context.id
|
|
};
|
|
}
|
|
}
|
|
|
|
config.name = 'Random Combination';
|
|
config.description = 'Randomly generated asset combination';
|
|
|
|
return config;
|
|
}
|
|
|
|
// =========================================================================
|
|
// CACHE MANAGEMENT
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Clear the asset cache
|
|
* @param {string} key - Optional specific key to clear
|
|
*/
|
|
function clearCache(key) {
|
|
if (key) {
|
|
cache.delete(key);
|
|
} else {
|
|
cache.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cached assets
|
|
* @param {string} key - Cache key (usually presetId)
|
|
* @returns {Object|undefined} Cached assets
|
|
*/
|
|
function getCached(key) {
|
|
return cache.get(key);
|
|
}
|
|
|
|
// =========================================================================
|
|
// UTILITY FUNCTIONS
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Get statistics about available assets
|
|
* @returns {Object} Asset statistics
|
|
*/
|
|
function getStats() {
|
|
var stats = {
|
|
initialized: initialized,
|
|
cache: {
|
|
size: cache.size
|
|
},
|
|
subsystems: {}
|
|
};
|
|
|
|
if (window.MusicEngine) {
|
|
stats.subsystems.music = {
|
|
available: true,
|
|
songs: window.MusicEngine.getCatalog ? window.MusicEngine.getCatalog().length : 'unknown'
|
|
};
|
|
}
|
|
|
|
if (window.BackgroundEngine) {
|
|
stats.subsystems.backgrounds = {
|
|
available: true,
|
|
count: window.BackgroundEngine.getTotalCount ? window.BackgroundEngine.getTotalCount() : 'unknown'
|
|
};
|
|
}
|
|
|
|
if (window.AvatarSystem) {
|
|
stats.subsystems.avatars = {
|
|
available: true
|
|
};
|
|
}
|
|
|
|
if (window.ContextCatalog) {
|
|
stats.subsystems.contexts = {
|
|
available: true,
|
|
count: window.ContextCatalog.getAll ? window.ContextCatalog.getAll().length : 'unknown'
|
|
};
|
|
}
|
|
|
|
if (window.AssetPresets) {
|
|
stats.subsystems.presets = {
|
|
available: true,
|
|
count: window.AssetPresets.getAll ? window.AssetPresets.getAll().length : 'unknown'
|
|
};
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
// =========================================================================
|
|
// PUBLIC API
|
|
// =========================================================================
|
|
|
|
window.AssetManager = {
|
|
// Initialization
|
|
initialize: initialize,
|
|
isInitialized: function() { return initialized; },
|
|
|
|
// Recommendations
|
|
getRecommendations: getRecommendations,
|
|
|
|
// Detection helpers (exposed for testing/debugging)
|
|
detectTheme: detectTheme,
|
|
detectMood: detectMood,
|
|
detectMechanics: detectMechanics,
|
|
parseKeywords: parseKeywords,
|
|
|
|
// Preset lookup
|
|
findPreset: findPreset,
|
|
presetToConfig: presetToConfig,
|
|
|
|
// Asset loading
|
|
loadGameAssets: loadGameAssets,
|
|
getLoadedAssets: function() { return loadedAssets; },
|
|
|
|
// Validation
|
|
validateCombination: validateCombination,
|
|
|
|
// Description
|
|
describeAssets: describeAssets,
|
|
|
|
// Variations
|
|
getVariation: getVariation,
|
|
|
|
// Search
|
|
searchAssets: searchAssets,
|
|
|
|
// Random
|
|
getRandomCombination: getRandomCombination,
|
|
|
|
// Cache management
|
|
clearCache: clearCache,
|
|
getCached: getCached,
|
|
|
|
// Statistics
|
|
getStats: getStats,
|
|
|
|
// Direct access to subsystems
|
|
music: function() { return window.MusicEngine; },
|
|
backgrounds: function() { return window.BackgroundEngine; },
|
|
avatars: function() { return window.AvatarSystem; },
|
|
contexts: function() { return window.ContextRenderer; },
|
|
catalog: function() { return window.AssetCatalog; },
|
|
presets: function() { return window.AssetPresets; },
|
|
compatibility: function() { return window.AssetCompatibility; }
|
|
};
|
|
|
|
})();
|