Game Systems: RecipeSystem + ProgressionSystem + BreedingSystem implemented
This commit is contained in:
536
src/systems/RecipeSystem.js
Normal file
536
src/systems/RecipeSystem.js
Normal file
@@ -0,0 +1,536 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user