353 lines
10 KiB
JavaScript
353 lines
10 KiB
JavaScript
// Crafting System - Handles recipe management and item crafting
|
|
class CraftingSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.recipes = {};
|
|
this.categories = [];
|
|
this.unlockedRecipes = new Set();
|
|
|
|
// Crafting queue
|
|
this.craftingQueue = [];
|
|
this.isCrafting = false;
|
|
this.currentCraft = null;
|
|
this.craftProgress = 0;
|
|
|
|
console.log('🛠️ CraftingSystem initialized');
|
|
}
|
|
|
|
async loadRecipes() {
|
|
try {
|
|
// Load recipes from JSON file
|
|
const response = await fetch('data/recipes.json');
|
|
const data = await response.json();
|
|
|
|
this.recipes = data.recipes;
|
|
this.categories = data.categories;
|
|
|
|
// Initialize unlocked recipes
|
|
Object.keys(this.recipes).forEach(recipeId => {
|
|
const recipe = this.recipes[recipeId];
|
|
if (recipe.unlocked) {
|
|
this.unlockedRecipes.add(recipeId);
|
|
}
|
|
});
|
|
|
|
console.log(`✅ Loaded ${Object.keys(this.recipes).length} recipes`);
|
|
console.log(`🔓 ${this.unlockedRecipes.size} unlocked recipes`);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('❌ Failed to load recipes:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get all recipes (optionally filtered by category)
|
|
getRecipes(category = 'all') {
|
|
const recipeList = Object.values(this.recipes);
|
|
|
|
if (category === 'all') {
|
|
return recipeList;
|
|
}
|
|
|
|
return recipeList.filter(recipe => recipe.category === category);
|
|
}
|
|
|
|
// Get only unlocked recipes
|
|
getUnlockedRecipes(category = 'all') {
|
|
return this.getRecipes(category).filter(recipe =>
|
|
this.unlockedRecipes.has(recipe.id)
|
|
);
|
|
}
|
|
|
|
// Check if recipe is unlocked
|
|
isUnlocked(recipeId) {
|
|
return this.unlockedRecipes.has(recipeId);
|
|
}
|
|
|
|
// Unlock a recipe
|
|
unlockRecipe(recipeId) {
|
|
if (this.recipes[recipeId]) {
|
|
this.unlockedRecipes.add(recipeId);
|
|
console.log(`🔓 Unlocked recipe: ${this.recipes[recipeId].name}`);
|
|
|
|
// Notify UI
|
|
this.scene.events.emit('recipe-unlocked', recipeId);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check if player has required ingredients
|
|
canCraft(recipeId) {
|
|
const recipe = this.recipes[recipeId];
|
|
if (!recipe) return false;
|
|
|
|
// Check if unlocked
|
|
if (!this.isUnlocked(recipeId)) {
|
|
return { canCraft: false, reason: 'locked' };
|
|
}
|
|
|
|
// Check ingredients
|
|
const inventory = this.scene.inventorySystem;
|
|
if (!inventory) {
|
|
return { canCraft: false, reason: 'no_inventory' };
|
|
}
|
|
|
|
const missing = [];
|
|
|
|
for (const [itemId, requiredAmount] of Object.entries(recipe.ingredients)) {
|
|
const hasAmount = inventory.getItemCount(itemId);
|
|
|
|
if (hasAmount < requiredAmount) {
|
|
missing.push({
|
|
item: itemId,
|
|
required: requiredAmount,
|
|
has: hasAmount,
|
|
need: requiredAmount - hasAmount
|
|
});
|
|
}
|
|
}
|
|
|
|
if (missing.length > 0) {
|
|
return { canCraft: false, reason: 'missing_ingredients', missing };
|
|
}
|
|
|
|
return { canCraft: true };
|
|
}
|
|
|
|
// Start crafting an item
|
|
craftItem(recipeId) {
|
|
const recipe = this.recipes[recipeId];
|
|
if (!recipe) {
|
|
console.warn(`⚠️ Recipe not found: ${recipeId}`);
|
|
return false;
|
|
}
|
|
|
|
// Check if can craft
|
|
const check = this.canCraft(recipeId);
|
|
if (!check.canCraft) {
|
|
console.warn(`⚠️ Cannot craft ${recipe.name}: ${check.reason}`);
|
|
return false;
|
|
}
|
|
|
|
// Consume ingredients
|
|
const inventory = this.scene.inventorySystem;
|
|
for (const [itemId, amount] of Object.entries(recipe.ingredients)) {
|
|
inventory.removeItem(itemId, amount);
|
|
}
|
|
|
|
// Add to crafting queue
|
|
this.craftingQueue.push({
|
|
recipeId: recipeId,
|
|
recipe: recipe,
|
|
startTime: Date.now(),
|
|
duration: recipe.craftTime || 1000
|
|
});
|
|
|
|
console.log(`🔨 Started crafting: ${recipe.name}`);
|
|
|
|
// Start crafting if not already crafting
|
|
if (!this.isCrafting) {
|
|
this.startNextCraft();
|
|
}
|
|
|
|
// Emit event
|
|
this.scene.events.emit('craft-started', recipeId);
|
|
|
|
// Play sound
|
|
if (this.scene.soundManager && this.scene.soundManager.playCraftSound) {
|
|
this.scene.soundManager.playCraftSound();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Start next item in queue
|
|
startNextCraft() {
|
|
if (this.craftingQueue.length === 0) {
|
|
this.isCrafting = false;
|
|
this.currentCraft = null;
|
|
this.craftProgress = 0;
|
|
return;
|
|
}
|
|
|
|
this.isCrafting = true;
|
|
this.currentCraft = this.craftingQueue[0];
|
|
this.craftProgress = 0;
|
|
|
|
console.log(`⏳ Processing: ${this.currentCraft.recipe.name}`);
|
|
}
|
|
|
|
// Update crafting progress
|
|
update(delta) {
|
|
if (!this.isCrafting || !this.currentCraft) return;
|
|
|
|
const elapsed = Date.now() - this.currentCraft.startTime;
|
|
this.craftProgress = Math.min(1.0, elapsed / this.currentCraft.duration);
|
|
|
|
// Check if finished
|
|
if (this.craftProgress >= 1.0) {
|
|
this.completeCraft();
|
|
}
|
|
|
|
// Emit progress event for UI
|
|
this.scene.events.emit('craft-progress', {
|
|
recipe: this.currentCraft.recipe,
|
|
progress: this.craftProgress
|
|
});
|
|
}
|
|
|
|
// Complete current craft
|
|
completeCraft() {
|
|
if (!this.currentCraft) return;
|
|
|
|
const recipe = this.currentCraft.recipe;
|
|
|
|
// Add result to inventory
|
|
const inventory = this.scene.inventorySystem;
|
|
inventory.addItem(recipe.result.item, recipe.result.quantity);
|
|
|
|
console.log(`✅ Crafted: ${recipe.result.quantity}x ${recipe.name}`);
|
|
|
|
// Emit event
|
|
this.scene.events.emit('craft-complete', {
|
|
recipeId: recipe.id,
|
|
item: recipe.result.item,
|
|
quantity: recipe.result.quantity
|
|
});
|
|
|
|
// Play sound
|
|
if (this.scene.soundManager && this.scene.soundManager.playSuccessSound) {
|
|
this.scene.soundManager.playSuccessSound();
|
|
}
|
|
|
|
// Show floating text
|
|
if (this.scene.player && this.scene.player.sprite) {
|
|
const text = `+${recipe.result.quantity} ${recipe.name}`;
|
|
this.scene.events.emit('floating-text', {
|
|
x: this.scene.player.sprite.x,
|
|
y: this.scene.player.sprite.y - 50,
|
|
text: text,
|
|
color: '#00ff00'
|
|
});
|
|
|
|
// ✨ PARTICLE EFFECT - Craft sparkles
|
|
if (this.scene.particleEnhancements) {
|
|
this.scene.particleEnhancements.craftSparkles(
|
|
this.scene.player.sprite.x,
|
|
this.scene.player.sprite.y
|
|
);
|
|
}
|
|
}
|
|
|
|
// Remove from queue
|
|
this.craftingQueue.shift();
|
|
|
|
// Start next craft
|
|
this.startNextCraft();
|
|
}
|
|
|
|
// Cancel current craft
|
|
cancelCraft() {
|
|
if (!this.currentCraft) return false;
|
|
|
|
const recipe = this.currentCraft.recipe;
|
|
|
|
// Refund ingredients (partial refund based on progress)
|
|
const refundPercent = 1.0 - this.craftProgress;
|
|
const inventory = this.scene.inventorySystem;
|
|
|
|
for (const [itemId, amount] of Object.entries(recipe.ingredients)) {
|
|
const refundAmount = Math.floor(amount * refundPercent);
|
|
if (refundAmount > 0) {
|
|
inventory.addItem(itemId, refundAmount);
|
|
}
|
|
}
|
|
|
|
console.log(`❌ Cancelled crafting: ${recipe.name} (${Math.floor(refundPercent * 100)}% refund)`);
|
|
|
|
// Remove from queue
|
|
this.craftingQueue.shift();
|
|
|
|
// Emit event
|
|
this.scene.events.emit('craft-cancelled', recipe.id);
|
|
|
|
// Start next
|
|
this.startNextCraft();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get current crafting info
|
|
getCurrentCraft() {
|
|
if (!this.currentCraft) return null;
|
|
|
|
return {
|
|
recipe: this.currentCraft.recipe,
|
|
progress: this.craftProgress,
|
|
timeRemaining: this.currentCraft.duration * (1 - this.craftProgress)
|
|
};
|
|
}
|
|
|
|
// Get queue length
|
|
getQueueLength() {
|
|
return this.craftingQueue.length;
|
|
}
|
|
|
|
// Clear entire queue
|
|
clearQueue() {
|
|
// Refund all queued items
|
|
this.craftingQueue.forEach(craft => {
|
|
const inventory = this.scene.inventorySystem;
|
|
for (const [itemId, amount] of Object.entries(craft.recipe.ingredients)) {
|
|
inventory.addItem(itemId, amount);
|
|
}
|
|
});
|
|
|
|
this.craftingQueue = [];
|
|
this.isCrafting = false;
|
|
this.currentCraft = null;
|
|
this.craftProgress = 0;
|
|
|
|
console.log('🗑️ Cleared crafting queue');
|
|
}
|
|
|
|
// Save crafting state
|
|
getSaveData() {
|
|
return {
|
|
unlockedRecipes: Array.from(this.unlockedRecipes),
|
|
craftingQueue: this.craftingQueue.map(craft => ({
|
|
recipeId: craft.recipeId,
|
|
startTime: craft.startTime,
|
|
duration: craft.duration
|
|
})),
|
|
currentProgress: this.craftProgress
|
|
};
|
|
}
|
|
|
|
// Load crafting state
|
|
loadSaveData(data) {
|
|
if (!data) return;
|
|
|
|
// Restore unlocked recipes
|
|
if (data.unlockedRecipes) {
|
|
this.unlockedRecipes = new Set(data.unlockedRecipes);
|
|
}
|
|
|
|
// Restore crafting queue
|
|
if (data.craftingQueue && data.craftingQueue.length > 0) {
|
|
this.craftingQueue = data.craftingQueue.map(saved => ({
|
|
recipeId: saved.recipeId,
|
|
recipe: this.recipes[saved.recipeId],
|
|
startTime: saved.startTime,
|
|
duration: saved.duration
|
|
}));
|
|
|
|
this.startNextCraft();
|
|
}
|
|
|
|
console.log('💾 Loaded crafting state');
|
|
}
|
|
}
|