537 lines
16 KiB
JavaScript
537 lines
16 KiB
JavaScript
/**
|
|
* RecipeSystem.js
|
|
* ===============
|
|
* Manages crafting recipes, blueprints, and progression unlocks
|
|
*
|
|
* Features:
|
|
* - Recipe database with material requirements
|
|
* - Blueprint unlock mechanics (find, level, quest, buy)
|
|
* - Crafting validation and execution
|
|
* - Material consumption
|
|
* - Crafting time delays
|
|
* - UI integration
|
|
*
|
|
* @author NovaFarma Team
|
|
* @date 2025-12-22
|
|
*/
|
|
|
|
export default class RecipeSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Recipe storage
|
|
this.recipes = new Map();
|
|
this.unlockedRecipes = new Set();
|
|
this.blueprintLocations = new Map(); // World spawn locations
|
|
|
|
// Crafting state
|
|
this.currentlyCrafting = null;
|
|
this.craftQueue = [];
|
|
|
|
// Categories
|
|
this.categories = {
|
|
BUILDING: 'building',
|
|
EQUIPMENT: 'equipment',
|
|
FURNITURE: 'furniture',
|
|
MAGIC: 'magic',
|
|
TRANSPORT: 'transport',
|
|
UPGRADE: 'upgrade'
|
|
};
|
|
|
|
// Initialize recipe database
|
|
this.initializeRecipes();
|
|
|
|
// Load saved unlocks
|
|
this.loadUnlockedRecipes();
|
|
|
|
console.log('🔨 RecipeSystem initialized with', this.recipes.size, 'recipes');
|
|
}
|
|
|
|
initializeRecipes() {
|
|
// ===== BUILDING RECIPES =====
|
|
this.addRecipe('wooden_fence', {
|
|
name: 'Wooden Fence',
|
|
category: this.categories.BUILDING,
|
|
description: 'Basic fence to keep animals in',
|
|
materials: [
|
|
{ item: 'wood', quantity: 5, displayName: 'Wood' },
|
|
{ item: 'nails', quantity: 2, displayName: 'Nails' }
|
|
],
|
|
craftTime: 2000, // 2 seconds
|
|
unlockLevel: 1,
|
|
blueprint: null, // Available from start
|
|
output: { item: 'fence_wooden', quantity: 1 },
|
|
sprite: 'fence_horizontal'
|
|
});
|
|
|
|
this.addRecipe('stone_fence', {
|
|
name: 'Stone Fence',
|
|
category: this.categories.BUILDING,
|
|
description: 'Durable stone fence',
|
|
materials: [
|
|
{ item: 'stone', quantity: 10, displayName: 'Stone' },
|
|
{ item: 'mortar', quantity: 3, displayName: 'Mortar' }
|
|
],
|
|
craftTime: 4000,
|
|
unlockLevel: 5,
|
|
blueprint: 'blueprint_stone_fence',
|
|
output: { item: 'fence_stone', quantity: 1 },
|
|
sprite: 'fence_full'
|
|
});
|
|
|
|
this.addRecipe('house_upgrade_2', {
|
|
name: 'House Upgrade Level 2',
|
|
category: this.categories.UPGRADE,
|
|
description: 'Expand your house - adds bedroom',
|
|
materials: [
|
|
{ item: 'wood', quantity: 100, displayName: 'Wood' },
|
|
{ item: 'stone', quantity: 50, displayName: 'Stone' },
|
|
{ item: 'gold', quantity: 500, displayName: 'Gold' }
|
|
],
|
|
craftTime: 10000, // 10 seconds
|
|
unlockLevel: 3,
|
|
blueprint: 'blueprint_house_2',
|
|
output: { item: 'house_level_2', quantity: 1 },
|
|
sprite: 'house_sprite',
|
|
prerequisites: ['house_level_1']
|
|
});
|
|
|
|
this.addRecipe('barn_upgrade_2', {
|
|
name: 'Barn Upgrade Level 2',
|
|
category: this.categories.UPGRADE,
|
|
description: 'Expand barn capacity to 8 animals',
|
|
materials: [
|
|
{ item: 'wood', quantity: 75, displayName: 'Wood' },
|
|
{ item: 'stone', quantity: 30, displayName: 'Stone' },
|
|
{ item: 'gold', quantity: 400, displayName: 'Gold' }
|
|
],
|
|
craftTime: 8000,
|
|
unlockLevel: 4,
|
|
blueprint: 'blueprint_barn_2',
|
|
output: { item: 'barn_level_2', quantity: 1 },
|
|
sprite: 'barn_isometric'
|
|
});
|
|
|
|
// ===== EQUIPMENT RECIPES =====
|
|
this.addRecipe('iron_axe', {
|
|
name: 'Iron Axe',
|
|
category: this.categories.EQUIPMENT,
|
|
description: 'Chops trees faster than basic axe',
|
|
materials: [
|
|
{ item: 'iron_ingot', quantity: 3, displayName: 'Iron Ingot' },
|
|
{ item: 'wood', quantity: 2, displayName: 'Wood' }
|
|
],
|
|
craftTime: 3000,
|
|
unlockLevel: 2,
|
|
blueprint: null,
|
|
output: { item: 'axe_iron', quantity: 1 },
|
|
sprite: 'tools'
|
|
});
|
|
|
|
this.addRecipe('steel_pickaxe', {
|
|
name: 'Steel Pickaxe',
|
|
category: this.categories.EQUIPMENT,
|
|
description: 'Mine rare ores',
|
|
materials: [
|
|
{ item: 'steel_ingot', quantity: 5, displayName: 'Steel Ingot' },
|
|
{ item: 'wood', quantity: 3, displayName: 'Wood' }
|
|
],
|
|
craftTime: 5000,
|
|
unlockLevel: 6,
|
|
blueprint: 'blueprint_steel_pickaxe',
|
|
output: { item: 'pickaxe_steel', quantity: 1 },
|
|
sprite: 'tools'
|
|
});
|
|
|
|
// ===== FURNITURE RECIPES =====
|
|
this.addRecipe('wooden_bed', {
|
|
name: 'Wooden Bed',
|
|
category: this.categories.FURNITURE,
|
|
description: 'Comfortable place to sleep',
|
|
materials: [
|
|
{ item: 'wood', quantity: 30, displayName: 'Wood' },
|
|
{ item: 'fabric', quantity: 10, displayName: 'Fabric' }
|
|
],
|
|
craftTime: 4000,
|
|
unlockLevel: 2,
|
|
blueprint: null,
|
|
output: { item: 'bed_wooden', quantity: 1 },
|
|
sprite: 'house_sprite'
|
|
});
|
|
|
|
// ===== MAGIC RECIPES =====
|
|
this.addRecipe('fire_staff', {
|
|
name: 'Fire Staff',
|
|
category: this.categories.MAGIC,
|
|
description: 'Cast fire spells',
|
|
materials: [
|
|
{ item: 'magic_crystal', quantity: 5, displayName: 'Magic Crystal' },
|
|
{ item: 'wood', quantity: 10, displayName: 'Enchanted Wood' },
|
|
{ item: 'ruby', quantity: 1, displayName: 'Ruby' }
|
|
],
|
|
craftTime: 8000,
|
|
unlockLevel: 10,
|
|
blueprint: 'blueprint_fire_staff',
|
|
output: { item: 'staff_fire', quantity: 1 },
|
|
sprite: 'magic_staffs_6_types'
|
|
});
|
|
|
|
// ===== TRANSPORT RECIPES =====
|
|
this.addRecipe('wooden_cart', {
|
|
name: 'Wooden Cart',
|
|
category: this.categories.TRANSPORT,
|
|
description: 'Haul resources faster',
|
|
materials: [
|
|
{ item: 'wood', quantity: 50, displayName: 'Wood' },
|
|
{ item: 'iron_ingot', quantity: 10, displayName: 'Iron' },
|
|
{ item: 'rope', quantity: 5, displayName: 'Rope' }
|
|
],
|
|
craftTime: 6000,
|
|
unlockLevel: 5,
|
|
blueprint: 'blueprint_cart',
|
|
output: { item: 'cart_wooden', quantity: 1 },
|
|
sprite: 'cart_wagon_system'
|
|
});
|
|
|
|
console.log('📜 Loaded', this.recipes.size, 'recipes');
|
|
}
|
|
|
|
addRecipe(id, data) {
|
|
this.recipes.set(id, {
|
|
id,
|
|
...data,
|
|
timescrafted: 0
|
|
});
|
|
}
|
|
|
|
canCraft(recipeId) {
|
|
const recipe = this.recipes.get(recipeId);
|
|
if (!recipe) {
|
|
console.warn('Recipe not found:', recipeId);
|
|
return { canCraft: false, reason: 'Recipe not found' };
|
|
}
|
|
|
|
// Check if unlocked
|
|
if (recipe.blueprint && !this.unlockedRecipes.has(recipeId)) {
|
|
return { canCraft: false, reason: 'Blueprint not unlocked' };
|
|
}
|
|
|
|
// Check level requirement
|
|
const playerLevel = this.scene.player?.stats?.level || 1;
|
|
if (playerLevel < recipe.unlockLevel) {
|
|
return {
|
|
canCraft: false,
|
|
reason: `Requires level ${recipe.unlockLevel}`
|
|
};
|
|
}
|
|
|
|
// Check prerequisites
|
|
if (recipe.prerequisites) {
|
|
for (const prereq of recipe.prerequisites) {
|
|
if (!this.hasItem(prereq)) {
|
|
return {
|
|
canCraft: false,
|
|
reason: `Requires ${prereq}`
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check materials
|
|
const missingMaterials = [];
|
|
for (const material of recipe.materials) {
|
|
const hasQuantity = this.getItemQuantity(material.item);
|
|
if (hasQuantity < material.quantity) {
|
|
missingMaterials.push({
|
|
item: material.displayName,
|
|
need: material.quantity,
|
|
have: hasQuantity
|
|
});
|
|
}
|
|
}
|
|
|
|
if (missingMaterials.length > 0) {
|
|
return {
|
|
canCraft: false,
|
|
reason: 'Missing materials',
|
|
missing: missingMaterials
|
|
};
|
|
}
|
|
|
|
return { canCraft: true };
|
|
}
|
|
|
|
craft(recipeId) {
|
|
const check = this.canCraft(recipeId);
|
|
if (!check.canCraft) {
|
|
this.scene.uiSystem?.showError(check.reason);
|
|
return false;
|
|
}
|
|
|
|
const recipe = this.recipes.get(recipeId);
|
|
|
|
// Consume materials
|
|
this.consumeMaterials(recipe);
|
|
|
|
// Show crafting UI
|
|
this.scene.uiSystem?.showCraftingProgress(recipe);
|
|
|
|
// Set crafting state
|
|
this.currentlyCrafting = {
|
|
recipeId,
|
|
startTime: Date.now(),
|
|
endTime: Date.now() + recipe.craftTime
|
|
};
|
|
|
|
// Wait for craft time
|
|
this.scene.time.delayedCall(recipe.craftTime, () => {
|
|
this.completeCraft(recipe);
|
|
});
|
|
|
|
// Emit event
|
|
this.scene.events.emit('craftingStarted', { recipeId, recipe });
|
|
|
|
return true;
|
|
}
|
|
|
|
consumeMaterials(recipe) {
|
|
for (const material of recipe.materials) {
|
|
this.removeItem(material.item, material.quantity);
|
|
}
|
|
console.log('💰 Consumed materials for:', recipe.name);
|
|
}
|
|
|
|
completeCraft(recipe) {
|
|
// Add item to inventory
|
|
this.addItem(recipe.output.item, recipe.output.quantity);
|
|
|
|
// Update stats
|
|
recipe.timescrafted++;
|
|
|
|
// Clear crafting state
|
|
this.currentlyCrafting = null;
|
|
|
|
// Show notification
|
|
this.scene.uiSystem?.showNotification({
|
|
title: 'Crafting Complete!',
|
|
message: `Created ${recipe.name}`,
|
|
icon: recipe.sprite,
|
|
duration: 3000
|
|
});
|
|
|
|
// Emit event
|
|
this.scene.events.emit('craftingComplete', {
|
|
recipeId: recipe.id,
|
|
recipe,
|
|
output: recipe.output
|
|
});
|
|
|
|
// Process queue if any
|
|
if (this.craftQueue.length > 0) {
|
|
const nextRecipe = this.craftQueue.shift();
|
|
this.craft(nextRecipe);
|
|
}
|
|
|
|
console.log('✅ Crafted:', recipe.name);
|
|
}
|
|
|
|
// ===== BLUEPRINT UNLOCK SYSTEM =====
|
|
|
|
unlockBlueprint(recipeId, method = 'find') {
|
|
if (this.unlockedRecipes.has(recipeId)) {
|
|
console.warn('Blueprint already unlocked:', recipeId);
|
|
return false;
|
|
}
|
|
|
|
const recipe = this.recipes.get(recipeId);
|
|
if (!recipe) {
|
|
console.warn('Recipe not found:', recipeId);
|
|
return false;
|
|
}
|
|
|
|
// Add to unlocked
|
|
this.unlockedRecipes.add(recipeId);
|
|
|
|
// Show notification
|
|
this.scene.uiSystem?.showNotification({
|
|
title: '📜 Blueprint Unlocked!',
|
|
message: `You can now craft: ${recipe.name}`,
|
|
icon: 'blueprint',
|
|
duration: 5000,
|
|
color: '#FFD700'
|
|
});
|
|
|
|
// Save unlocks
|
|
this.saveUnlockedRecipes();
|
|
|
|
// Emit event
|
|
this.scene.events.emit('blueprintUnlocked', {
|
|
recipeId,
|
|
recipe,
|
|
method
|
|
});
|
|
|
|
console.log('📜 Blueprint unlocked:', recipe.name, `(${method})`);
|
|
return true;
|
|
}
|
|
|
|
findBlueprintInWorld(x, y) {
|
|
// Check if player is near a blueprint object
|
|
const nearbyBlueprints = this.scene.blueprintObjects?.filter(bp => {
|
|
const distance = Phaser.Math.Distance.Between(x, y, bp.x, bp.y);
|
|
return distance < 50; // Within 50 pixels
|
|
});
|
|
|
|
if (nearbyBlueprints && nearbyBlueprints.length > 0) {
|
|
const blueprint = nearbyBlueprints[0];
|
|
this.unlockBlueprint(blueprint.recipeId, 'find');
|
|
blueprint.destroy(); // Remove from world
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unlockBlueprintByLevel(level) {
|
|
// Auto-unlock recipes at certain levels
|
|
const levelUnlocks = {
|
|
2: ['iron_axe', 'wooden_bed'],
|
|
5: ['stone_fence', 'wooden_cart'],
|
|
10: ['fire_staff']
|
|
};
|
|
|
|
if (levelUnlocks[level]) {
|
|
for (const recipeId of levelUnlocks[level]) {
|
|
this.unlockBlueprint(recipeId, 'level');
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== INVENTORY INTEGRATION =====
|
|
|
|
hasItem(itemId) {
|
|
// Integrate with InventorySystem
|
|
if (this.scene.inventorySystem) {
|
|
return this.scene.inventorySystem.hasItem(itemId);
|
|
}
|
|
// Fallback: check player stats
|
|
return this.scene.player?.inventory?.[itemId] > 0;
|
|
}
|
|
|
|
getItemQuantity(itemId) {
|
|
// Integrate with InventorySystem
|
|
if (this.scene.inventorySystem) {
|
|
return this.scene.inventorySystem.getQuantity(itemId);
|
|
}
|
|
// Fallback
|
|
return this.scene.player?.inventory?.[itemId] || 0;
|
|
}
|
|
|
|
addItem(itemId, quantity) {
|
|
// Integrate with InventorySystem
|
|
if (this.scene.inventorySystem) {
|
|
this.scene.inventorySystem.addItem(itemId, quantity);
|
|
} else {
|
|
// Fallback
|
|
if (!this.scene.player.inventory) {
|
|
this.scene.player.inventory = {};
|
|
}
|
|
this.scene.player.inventory[itemId] =
|
|
(this.scene.player.inventory[itemId] || 0) + quantity;
|
|
}
|
|
}
|
|
|
|
removeItem(itemId, quantity) {
|
|
// Integrate with InventorySystem
|
|
if (this.scene.inventorySystem) {
|
|
this.scene.inventorySystem.removeItem(itemId, quantity);
|
|
} else {
|
|
// Fallback
|
|
this.scene.player.inventory[itemId] -= quantity;
|
|
}
|
|
}
|
|
|
|
// ===== DATA PERSISTENCE =====
|
|
|
|
saveUnlockedRecipes() {
|
|
const unlocked = Array.from(this.unlockedRecipes);
|
|
localStorage.setItem('unlockedRecipes', JSON.stringify(unlocked));
|
|
}
|
|
|
|
loadUnlockedRecipes() {
|
|
const saved = localStorage.getItem('unlockedRecipes');
|
|
if (saved) {
|
|
const unlocked = JSON.parse(saved);
|
|
this.unlockedRecipes = new Set(unlocked);
|
|
console.log('📜 Loaded', this.unlockedRecipes.size, 'unlocked recipes');
|
|
}
|
|
}
|
|
|
|
// ===== GETTERS =====
|
|
|
|
getRecipe(recipeId) {
|
|
return this.recipes.get(recipeId);
|
|
}
|
|
|
|
getAllRecipes() {
|
|
return Array.from(this.recipes.values());
|
|
}
|
|
|
|
getRecipesByCategory(category) {
|
|
return this.getAllRecipes().filter(r => r.category === category);
|
|
}
|
|
|
|
getUnlockedRecipes() {
|
|
return this.getAllRecipes().filter(r =>
|
|
!r.blueprint || this.unlockedRecipes.has(r.id)
|
|
);
|
|
}
|
|
|
|
getLockedRecipes() {
|
|
return this.getAllRecipes().filter(r =>
|
|
r.blueprint && !this.unlockedRecipes.has(r.id)
|
|
);
|
|
}
|
|
|
|
getCraftableRecipes() {
|
|
return this.getUnlockedRecipes().filter(r =>
|
|
this.canCraft(r.id).canCraft
|
|
);
|
|
}
|
|
|
|
getCraftingProgress() {
|
|
if (!this.currentlyCrafting) return null;
|
|
|
|
const elapsed = Date.now() - this.currentlyCrafting.startTime;
|
|
const total = this.currentlyCrafting.endTime - this.currentlyCrafting.startTime;
|
|
const progress = Math.min(1, elapsed / total);
|
|
|
|
return {
|
|
recipeId: this.currentlyCrafting.recipeId,
|
|
progress,
|
|
timeRemaining: Math.max(0, this.currentlyCrafting.endTime - Date.now())
|
|
};
|
|
}
|
|
|
|
// ===== UPDATE =====
|
|
|
|
update(time, delta) {
|
|
// Update crafting progress UI
|
|
if (this.currentlyCrafting) {
|
|
const progress = this.getCraftingProgress();
|
|
this.scene.events.emit('craftingProgress', progress);
|
|
}
|
|
}
|
|
|
|
// ===== CLEANUP =====
|
|
|
|
destroy() {
|
|
this.saveUnlockedRecipes();
|
|
this.recipes.clear();
|
|
this.unlockedRecipes.clear();
|
|
this.blueprintLocations.clear();
|
|
this.craftQueue = [];
|
|
this.currentlyCrafting = null;
|
|
}
|
|
}
|