diff --git a/src/systems/BreedingSystem.js b/src/systems/BreedingSystem.js new file mode 100644 index 0000000..f13e7dd --- /dev/null +++ b/src/systems/BreedingSystem.js @@ -0,0 +1,565 @@ +/** + * BreedingSystem.js + * ================= + * Manages animal breeding, family trees, and baby growth + * + * Features: + * - Breeding compatibility checks (species, gender, age) + * - Baby creation with inherited traits + * - Growth stages: baby → young → adult + * - Family tree tracking + * - Breeding cooldowns + * - Animal happiness/relationship system + * + * Uses: farm_animals_family_grid_1766099078030.tsx + * children_5_growth_stages_1766097043062.tsx + * + * @author NovaFarma Team + * @date 2025-12-22 + */ + +export default class BreedingSystem { + constructor(scene) { + this.scene = scene; + + // Animal registry + this.animals = new Map(); // id -> animal data + this.families = new Map(); // family id -> family tree + + // Breeding pairs currently in progress + this.breedingPairs = new Map(); + + // Timing constants (in milliseconds) + this.BREEDING_COOLDOWN = 7 * 24 * 60 * 60 * 1000; // 7 days + this.BABY_TO_YOUNG_TIME = 2 * 24 * 60 * 60 * 1000; // 2 days + this.YOUNG_TO_ADULT_TIME = 5 * 24 * 60 * 60 * 1000; // 5 days + + // Debug mode: faster times for testing + if (this.scene.registry.get('debugMode')) { + this.BREEDING_COOLDOWN = 30000; // 30 seconds + this.BABY_TO_YOUNG_TIME = 10000; // 10 seconds + this.YOUNG_TO_ADULT_TIME = 20000; // 20 seconds + } + + // Species data + this.speciesData = this.defineSpecies(); + + // Growth sprites + this.growthSprites = this.defineGrowthSprites(); + + console.log('🐄 BreedingSystem initialized'); + } + + defineSpecies() { + return { + cow: { + name: 'Cow', + basePrice: 500, + products: ['milk', 'cheese'], + gestationTime: 3.5 * 24 * 60 * 60 * 1000, // 3.5 days + breedingCooldown: 7 * 24 * 60 * 60 * 1000, + lifespan: 100 * 24 * 60 * 60 * 1000, // 100 days + sprites: { + male_baby: 'cow_male_baby', + male_young: 'cow_male_young', + male_adult: 'cow_male_adult', + female_baby: 'cow_female_baby', + female_young: 'cow_female_young', + female_adult: 'cow_female_adult' + } + }, + chicken: { + name: 'Chicken', + basePrice: 100, + products: ['egg', 'feather'], + gestationTime: 21 * 60 * 60 * 1000, // 21 hours + breedingCooldown: 3 * 24 * 60 * 60 * 1000, // 3 days + lifespan: 50 * 24 * 60 * 60 * 1000, + sprites: { + male_baby: 'chicken_male_baby', + male_young: 'chicken_male_young', + male_adult: 'chicken_rooster_adult', + female_baby: 'chicken_female_baby', + female_young: 'chicken_female_young', + female_adult: 'chicken_hen_adult' + } + }, + pig: { + name: 'Pig', + basePrice: 300, + products: ['truffle'], + gestationTime: 3 * 24 * 60 * 60 * 1000, + breedingCooldown: 5 * 24 * 60 * 60 * 1000, + lifespan: 80 * 24 * 60 * 60 * 1000, + sprites: { + male_baby: 'pig_male_baby', + male_young: 'pig_male_young', + male_adult: 'pig_male_adult', + female_baby: 'pig_female_baby', + female_young: 'pig_female_young', + female_adult: 'pig_female_adult' + } + }, + sheep: { + name: 'Sheep', + basePrice: 400, + products: ['wool', 'milk'], + gestationTime: 4 * 24 * 60 * 60 * 1000, + breedingCooldown: 6 * 24 * 60 * 60 * 1000, + lifespan: 90 * 24 * 60 * 60 * 1000, + sprites: { + male_baby: 'sheep_male_baby', + male_young: 'sheep_male_young', + male_adult: 'sheep_ram_adult', + female_baby: 'sheep_female_baby', + female_young: 'sheep_female_young', + female_adult: 'sheep_ewe_adult' + } + } + }; + } + + defineGrowthSprites() { + return { + baby: 0, // Frame index + young: 1, + adult: 2 + }; + } + + // ===== ANIMAL REGISTRATION ===== + + registerAnimal(animalData) { + const id = animalData.id || this.generateId(); + + const animal = { + id, + species: animalData.species, + gender: animalData.gender || this.randomGender(), + age: animalData.age || 'adult', + name: animalData.name || this.generateName(animalData.species, animalData.gender), + birthDate: animalData.birthDate || Date.now(), + parents: animalData.parents || null, // [parentId1, parentId2] + children: animalData.children || [], + sprite: animalData.sprite || null, + happiness: animalData.happiness || 100, + lastBred: animalData.lastBred || null, + x: animalData.x || 0, + y: animalData.y || 0 + }; + + this.animals.set(id, animal); + + // Create sprite if needed + if (!animal.sprite && animalData.x && animalData.y) { + animal.sprite = this.createAnimalSprite(animal); + } + + // Setup growth timer if not adult + if (animal.age !== 'adult') { + this.setupGrowthTimer(animal); + } + + console.log(`🐾 Registered ${animal.species} (${animal.gender}) - ${animal.name}`); + return animal; + } + + createAnimalSprite(animal) { + const speciesData = this.speciesData[animal.species]; + const spriteKey = `${animal.species}_${animal.gender}_${animal.age}`; + + const sprite = this.scene.add.sprite( + animal.x, + animal.y, + 'farm_animals_family_grid', // tileset name + this.getAnimalFrame(animal.species, animal.gender, animal.age) + ); + + sprite.setData('animalId', animal.id); + sprite.setInteractive(); + + // Click to view animal details + sprite.on('pointerdown', () => { + this.scene.events.emit('animalClicked', animal); + }); + + return sprite; + } + + getAnimalFrame(species, gender, age) { + // Map to sprite sheet frames + // This depends on your actual sprite sheet layout + const frameMap = { + cow: { male: { baby: 0, young: 1, adult: 2 }, female: { baby: 3, young: 4, adult: 5 } }, + chicken: { male: { baby: 6, young: 7, adult: 8 }, female: { baby: 9, young: 10, adult: 11 } }, + pig: { male: { baby: 12, young: 13, adult: 14 }, female: { baby: 15, young: 16, adult: 17 } }, + sheep: { male: { baby: 18, young: 19, adult: 20 }, female: { baby: 21, young: 22, adult: 23 } } + }; + + return frameMap[species]?.[gender]?.[age] || 0; + } + + // ===== BREEDING MECHANICS ===== + + canBreed(animalId1, animalId2) { + const animal1 = this.animals.get(animalId1); + const animal2 = this.animals.get(animalId2); + + if (!animal1 || !animal2) { + return { canBreed: false, reason: 'Animal not found' }; + } + + // Must be same species + if (animal1.species !== animal2.species) { + return { canBreed: false, reason: 'Different species cannot breed' }; + } + + // Must be opposite genders + if (animal1.gender === animal2.gender) { + return { canBreed: false, reason: 'Same gender cannot breed' }; + } + + // Both must be adults + if (animal1.age !== 'adult' || animal2.age !== 'adult') { + return { canBreed: false, reason: 'Both must be adults' }; + } + + // Check happiness (must be >50 to breed) + if (animal1.happiness < 50 || animal2.happiness < 50) { + return { canBreed: false, reason: 'Animals must be happy (>50 happiness)' }; + } + + // Check breeding cooldown + const now = Date.now(); + if (animal1.lastBred && (now - animal1.lastBred) < this.BREEDING_COOLDOWN) { + const waitTime = Math.ceil((this.BREEDING_COOLDOWN - (now - animal1.lastBred)) / 1000 / 60); + return { canBreed: false, reason: `${animal1.name} needs ${waitTime} more minutes` }; + } + + if (animal2.lastBred && (now - animal2.lastBred) < this.BREEDING_COOLDOWN) { + const waitTime = Math.ceil((this.BREEDING_COOLDOWN - (now - animal2.lastBred)) / 1000 / 60); + return { canBreed: false, reason: `${animal2.name} needs ${waitTime} more minutes` }; + } + + // Check barn capacity + if (this.scene.progressionSystem) { + const barnBenefits = this.scene.progressionSystem.getCurrentBenefits('barn'); + const currentAnimals = this.animals.size; + if (currentAnimals >= barnBenefits.capacity) { + return { canBreed: false, reason: 'Barn is full - upgrade needed!' }; + } + } + + return { canBreed: true }; + } + + breed(animalId1, animalId2) { + const check = this.canBreed(animalId1, animalId2); + if (!check.canBreed) { + this.scene.uiSystem?.showError(check.reason); + console.warn('Cannot breed:', check.reason); + return null; + } + + const parent1 = this.animals.get(animalId1); + const parent2 = this.animals.get(animalId2); + + // Determine which is female (for gestation) + const female = parent1.gender === 'female' ? parent1 : parent2; + const male = parent1.gender === 'male' ? parent1 : parent2; + + // Get gestation time + const speciesData = this.speciesData[parent1.species]; + const gestationTime = speciesData.gestationTime; + + // Start gestation period + this.scene.time.delayedCall(gestationTime, () => { + this.giveBirth(female, male); + }); + + // Update breeding timestamps + parent1.lastBred = Date.now(); + parent2.lastBred = Date.now(); + + // Show notification + this.scene.uiSystem?.showNotification({ + title: '💕 Breeding Started!', + message: `${male.name} and ${female.name} are breeding...`, + icon: parent1.species, + duration: 3000 + }); + + // Add to breeding pairs + this.breedingPairs.set(`${animalId1}_${animalId2}`, { + parent1: animalId1, + parent2: animalId2, + startTime: Date.now(), + endTime: Date.now() + gestationTime + }); + + console.log(`💕 ${male.name} (♂) + ${female.name} (♀) breeding...`); + return true; + } + + giveBirth(mother, father) { + // Create baby + const baby = this.createBaby(mother, father); + + // Update parent records + mother.children.push(baby.id); + father.children.push(baby.id); + + // Show notification + this.scene.uiSystem?.showNotification({ + title: '🎉 Baby Born!', + message: `${mother.name} gave birth to ${baby.name}!`, + icon: baby.species, + duration: 5000, + color: '#FFD700' + }); + + // Create family tree if doesn't exist + const familyId = mother.parents?.[0] || mother.id; + if (!this.families.has(familyId)) { + this.families.set(familyId, { + id: familyId, + founder: mother, + members: [] + }); + } + this.families.get(familyId).members.push(baby.id); + + // Emit event + this.scene.events.emit('babyBorn', { mother, father, baby }); + + // Play birth animation/particles + if (this.scene.particleSystem) { + this.scene.particleSystem.createBirthEffect(mother.sprite.x, mother.sprite.y); + } + + console.log(`🍼 Baby born: ${baby.name} (${baby.species})`); + return baby; + } + + createBaby(parent1, parent2) { + const baby = { + id: this.generateId(), + species: parent1.species, + age: 'baby', + gender: this.randomGender(), + parents: [parent1.id, parent2.id], + children: [], + birthDate: Date.now(), + happiness: 100, + lastBred: null, + // Spawn near mother + x: parent1.x + Phaser.Math.Between(-20, 20), + y: parent1.y + Phaser.Math.Between(-20, 20), + sprite: null + }; + + // Generate name + baby.name = this.generateName(baby.species, baby.gender); + + // Create sprite + baby.sprite = this.createAnimalSprite(baby); + + // Register animal + this.animals.set(baby.id, baby); + + // Setup growth timer + this.setupGrowthTimer(baby); + + return baby; + } + + setupGrowthTimer(animal) { + if (animal.age === 'baby') { + // Baby → Young + this.scene.time.delayedCall(this.BABY_TO_YOUNG_TIME, () => { + this.growAnimal(animal, 'young'); + }); + } else if (animal.age === 'young') { + // Young → Adult + this.scene.time.delayedCall(this.YOUNG_TO_ADULT_TIME, () => { + this.growAnimal(animal, 'adult'); + }); + } + } + + growAnimal(animal, newAge) { + const oldAge = animal.age; + animal.age = newAge; + + // Update sprite + if (animal.sprite) { + const newFrame = this.getAnimalFrame(animal.species, animal.gender, newAge); + animal.sprite.setFrame(newFrame); + + // Play growth animation + this.scene.tweens.add({ + targets: animal.sprite, + scale: { from: 0.8, to: 1.0 }, + duration: 500, + ease: 'Back.easeOut' + }); + } + + // Show notification + this.scene.uiSystem?.showNotification({ + title: `${animal.name} Grew Up!`, + message: `${oldAge} → ${newAge}`, + icon: animal.species, + duration: 3000 + }); + + // If became adult, can now breed + if (newAge === 'adult') { + this.scene.events.emit('animalBecameAdult', animal); + } + + // Continue growth timer if not yet adult + if (newAge !== 'adult') { + this.setupGrowthTimer(animal); + } + + console.log(`📅 ${animal.name} grew from ${oldAge} to ${newAge}`); + } + + // ===== HELPER FUNCTIONS ===== + + generateId() { + return `animal_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + randomGender() { + return Math.random() > 0.5 ? 'male' : 'female'; + } + + generateName(species, gender) { + const names = { + cow: { + male: ['Toro', 'Bruno', 'Max', 'Duke'], + female: ['Daisy', 'Bessie', 'Bella', 'Rosie'] + }, + chicken: { + male: ['Rocky', 'Cluck', 'Red', 'Rex'], + female: ['Penny', 'Clucky', 'Henrietta', 'Ginger'] + }, + pig: { + male: ['Porky', 'Hamlet', 'Bacon', 'Wilbur'], + female: ['Petunia', 'Piglet', 'Penelope', 'Truffle'] + }, + sheep: { + male: ['Woolly', 'Ram', 'Cloud', 'Fluff'], + female: ['Fluffy', 'Snowball', 'Cotton', 'Lamb'] + } + }; + + const nameList = names[species]?.[gender] || ['Animal']; + return nameList[Math.floor(Math.random() * nameList.length)]; + } + + // ===== GETTERS ===== + + getAnimal(animalId) { + return this.animals.get(animalId); + } + + getAllAnimals() { + return Array.from(this.animals.values()); + } + + getAnimalsBySpecies(species) { + return this.getAllAnimals().filter(a => a.species === species); + } + + getAnimalsByGender(gender) { + return this.getAllAnimals().filter(a => a.gender === gender); + } + + getAdults() { + return this.getAllAnimals().filter(a => a.age === 'adult'); + } + + getBabies() { + return this.getAllAnimals().filter(a => a.age === 'baby'); + } + + getBreedablePairs() { + const adults = this.getAdults(); + const pairs = []; + + for (let i = 0; i < adults.length; i++) { + for (let j = i + 1; j < adults.length; j++) { + const check = this.canBreed(adults[i].id, adults[j].id); + if (check.canBreed) { + pairs.push([adults[i], adults[j]]); + } + } + } + + return pairs; + } + + getFamilyTree(animalId) { + const animal = this.animals.get(animalId); + if (!animal) return null; + + return { + animal, + parents: animal.parents ? animal.parents.map(id => this.animals.get(id)) : [], + children: animal.children.map(id => this.animals.get(id)), + grandparents: this.getGrandparents(animal), + siblings: this.getSiblings(animal) + }; + } + + getGrandparents(animal) { + if (!animal.parents) return []; + + const grandparents = []; + for (const parentId of animal.parents) { + const parent = this.animals.get(parentId); + if (parent && parent.parents) { + grandparents.push(...parent.parents.map(id => this.animals.get(id))); + } + } + return grandparents.filter(Boolean); + } + + getSiblings(animal) { + if (!animal.parents) return []; + + const siblings = []; + for (const parentId of animal.parents) { + const parent = this.animals.get(parentId); + if (parent && parent.children) { + siblings.push(...parent.children + .filter(id => id !== animal.id) + .map(id => this.animals.get(id)) + ); + } + } + return [...new Set(siblings)]; // Remove duplicates + } + + // ===== UPDATE ===== + + update(time, delta) { + // Update breeding pairs progress + for (const [pairId, pair] of this.breedingPairs.entries()) { + if (Date.now() >= pair.endTime) { + this.breedingPairs.delete(pairId); + } + } + } + + // ===== CLEANUP ===== + + destroy() { + this.animals.clear(); + this.families.clear(); + this.breedingPairs.clear(); + } +} diff --git a/src/systems/ProgressionSystem.js b/src/systems/ProgressionSystem.js new file mode 100644 index 0000000..4c26bf5 --- /dev/null +++ b/src/systems/ProgressionSystem.js @@ -0,0 +1,527 @@ +/** + * ProgressionSystem.js + * ==================== + * Manages building upgrades and player progression + * - House upgrades (5 levels) + * - Barn upgrades (4 levels) + * - Storage upgrades (4 levels) + * - Greenhouse upgrades + * + * Integrates with RecipeSystem for upgrade crafting + * + * @author NovaFarma Team + * @date 2025-12-22 + */ + +export default class ProgressionSystem { + constructor(scene) { + this.scene = scene; + + // Building levels (current state) + this.buildingLevels = { + house: 1, // Max: 5 + barn: 1, // Max: 4 + storage: 1, // Max: 4 + greenhouse: 1 // Max: 3 + }; + + // Building sprites (references to map objects) + this.buildingSprites = { + house: null, + barn: null, + storage: null, + greenhouse: null + }; + + // Upgrade requirements data + this.upgradeData = this.loadUpgradeData(); + + // Upgrade benefits + this.upgradeBenefits = this.defineUpgradeBenefits(); + + // Load saved progress + this.loadProgress(); + + console.log('📈 ProgressionSystem initialized'); + } + + loadUpgradeData() { + return { + house: { + maxLevel: 5, + levels: { + 2: { + name: 'Bedroom Addition', + description: 'Adds a bedroom - can get married!', + materials: { wood: 100, stone: 50, gold: 500 }, + craftTime: 10000, + blueprint: 'blueprint_house_2' + }, + 3: { + name: 'Kitchen Expansion', + description: 'Larger kitchen - cook better meals', + materials: { wood: 200, stone: 100, gold: 1500 }, + craftTime: 15000, + blueprint: 'blueprint_house_3' + }, + 4: { + name: 'Second Floor', + description: 'Two stories - children can have rooms', + materials: { wood: 400, stone: 200, iron_ingot: 50, gold: 3500 }, + craftTime: 20000, + blueprint: 'blueprint_house_4' + }, + 5: { + name: 'Mansion', + description: 'Ultimate house - full family home', + materials: { wood: 800, stone: 400, iron_ingot: 100, gold: 7500 }, + craftTime: 30000, + blueprint: 'blueprint_house_5' + } + } + }, + barn: { + maxLevel: 4, + levels: { + 2: { + name: 'Barn Extension', + description: 'Hold 8 animals, adds milking station', + materials: { wood: 75, stone: 30, gold: 400 }, + craftTime: 8000, + blueprint: 'blueprint_barn_2' + }, + 3: { + name: 'Large Barn', + description: 'Hold 12 animals, auto-feeder', + materials: { wood: 150, stone: 75, iron_ingot: 20, gold: 1000 }, + craftTime: 12000, + blueprint: 'blueprint_barn_3' + }, + 4: { + name: 'Deluxe Barn', + description: 'Hold 16 animals, auto-petter', + materials: { wood: 300, stone: 150, iron_ingot: 50, gold: 2500 }, + craftTime: 18000, + blueprint: 'blueprint_barn_4' + } + } + }, + storage: { + maxLevel: 4, + levels: { + 2: { + name: 'Upgraded Chest', + description: 'Inventory +40 slots', + materials: { wood: 50, stone: 25, gold: 300 }, + craftTime: 5000, + blueprint: null // Available from start + }, + 3: { + name: 'Large Warehouse', + description: 'Inventory +80 slots', + materials: { wood: 100, stone: 50, iron_ingot: 15, gold: 800 }, + craftTime: 8000, + blueprint: 'blueprint_storage_3' + }, + 4: { + name: 'Massive Storehouse', + description: 'Inventory +120 slots', + materials: { wood: 200, stone: 100, iron_ingot: 30, gold: 2000 }, + craftTime: 12000, + blueprint: 'blueprint_storage_4' + } + } + }, + greenhouse: { + maxLevel: 3, + levels: { + 2: { + name: 'Medium Greenhouse', + description: 'Grow 20 crops year-round', + materials: { wood: 80, glass: 50, gold: 600 }, + craftTime: 10000, + blueprint: 'blueprint_greenhouse_2' + }, + 3: { + name: 'Large Greenhouse', + description: 'Grow 50 crops year-round', + materials: { wood: 160, glass: 100, iron_ingot: 25, gold: 1500 }, + craftTime: 15000, + blueprint: 'blueprint_greenhouse_3' + } + } + } + }; + } + + defineUpgradeBenefits() { + return { + house: { + 1: { bedrooms: 0, kitchen: 'basic', canMarry: false }, + 2: { bedrooms: 1, kitchen: 'basic', canMarry: true }, + 3: { bedrooms: 1, kitchen: 'upgraded', canMarry: true, cookingBonus: 1.25 }, + 4: { bedrooms: 3, kitchen: 'upgraded', canMarry: true, canHaveKids: true, cookingBonus: 1.5 }, + 5: { bedrooms: 5, kitchen: 'deluxe', canMarry: true, canHaveKids: true, maxKids: 3, cookingBonus: 2.0 } + }, + barn: { + 1: { capacity: 4, autoFeeder: false, autoPetter: false }, + 2: { capacity: 8, autoFeeder: false, autoPetter: false, hasMilkingStation: true }, + 3: { capacity: 12, autoFeeder: true, autoPetter: false, hasMilkingStation: true }, + 4: { capacity: 16, autoFeeder: true, autoPetter: true, hasMilkingStation: true } + }, + storage: { + 1: { slots: 40 }, + 2: { slots: 80 }, + 3: { slots: 160 }, + 4: { slots: 280 } + }, + greenhouse: { + 1: { capacity: 10, seasonal: false }, + 2: { capacity: 20, seasonal: false }, + 3: { capacity: 50, seasonal: false, qualityBonus: 1.5 } + } + }; + } + + // ===== UPGRADE CHECKS ===== + + canUpgrade(buildingType) { + const currentLevel = this.buildingLevels[buildingType]; + const maxLevel = this.upgradeData[buildingType].maxLevel; + + // Check if already max level + if (currentLevel >= maxLevel) { + return { + canUpgrade: false, + reason: `Already at max level (${maxLevel})` + }; + } + + const nextLevel = currentLevel + 1; + const requirements = this.getUpgradeRequirements(buildingType, nextLevel); + + if (!requirements) { + return { canUpgrade: false, reason: 'Upgrade data not found' }; + } + + // Check if blueprint is unlocked (if required) + if (requirements.blueprint) { + const recipeId = `${buildingType}_upgrade_${nextLevel}`; + if (this.scene.recipeSystem && + !this.scene.recipeSystem.unlockedRecipes.has(recipeId)) { + return { + canUpgrade: false, + reason: 'Blueprint not unlocked' + }; + } + } + + // Check materials + const missingMaterials = []; + for (const [item, quantity] of Object.entries(requirements.materials)) { + const hasQuantity = this.getItemQuantity(item); + if (hasQuantity < quantity) { + missingMaterials.push({ + item: item, + need: quantity, + have: hasQuantity + }); + } + } + + if (missingMaterials.length > 0) { + return { + canUpgrade: false, + reason: 'Missing materials', + missing: missingMaterials + }; + } + + return { canUpgrade: true, nextLevel }; + } + + // ===== UPGRADE EXECUTION ===== + + upgrade(buildingType) { + const check = this.canUpgrade(buildingType); + if (!check.canUpgrade) { + this.scene.uiSystem?.showError(check.reason); + console.warn('Cannot upgrade', buildingType, ':', check.reason); + return false; + } + + const currentLevel = this.buildingLevels[buildingType]; + const nextLevel = check.nextLevel; + const requirements = this.getUpgradeRequirements(buildingType, nextLevel); + + // Consume resources + this.consumeResources(requirements.materials); + + // Show upgrade progress UI + this.scene.uiSystem?.showUpgradeProgress(buildingType, requirements); + + // Wait for construction time + this.scene.time.delayedCall(requirements.craftTime, () => { + this.completeUpgrade(buildingType, nextLevel); + }); + + // Emit event + this.scene.events.emit('upgradeStarted', { + buildingType, + fromLevel: currentLevel, + toLevel: nextLevel, + requirements + }); + + console.log(`🏗️ Started upgrading ${buildingType} to level ${nextLevel}`); + return true; + } + + completeUpgrade(buildingType, newLevel) { + const oldLevel = this.buildingLevels[buildingType]; + + // Update level + this.buildingLevels[buildingType] = newLevel; + + // Update sprite + this.updateBuildingSprite(buildingType, newLevel); + + // Apply benefits + const benefits = this.getBenefits(buildingType, newLevel); + this.applyBenefits(buildingType, benefits); + + // Save progress + this.saveProgress(); + + // Show notification + const upgradeInfo = this.getUpgradeRequirements(buildingType, newLevel); + this.scene.uiSystem?.showNotification({ + title: `${buildingType.toUpperCase()} UPGRADED!`, + message: upgradeInfo.description, + icon: buildingType, + duration: 5000, + color: '#00FF00' + }); + + // Emit event + this.scene.events.emit('upgradeComplete', { + buildingType, + oldLevel, + newLevel, + benefits + }); + + console.log(`✅ ${buildingType} upgraded to level ${newLevel}`); + } + + updateBuildingSprite(buildingType, level) { + const sprite = this.buildingSprites[buildingType]; + + if (!sprite) { + console.warn(`Building sprite not found for ${buildingType}`); + return; + } + + // Update frame based on level (assumes sprite sheet has frames for each level) + // Frame index = level - 1 (0-indexed) + const frameIndex = level - 1; + + if (sprite.setFrame) { + sprite.setFrame(frameIndex); + } else if (sprite.setTexture) { + // If using separate textures per level + sprite.setTexture(`${buildingType}_level_${level}`); + } + + // Play upgrade animation (optional) + if (this.scene.particleSystem) { + this.scene.particleSystem.createUpgradeEffect(sprite.x, sprite.y); + } + + console.log(`🖼️ Updated ${buildingType} sprite to level ${level}`); + } + + applyBenefits(buildingType, benefits) { + // Apply building-specific benefits + switch (buildingType) { + case 'house': + if (benefits.canMarry) { + this.scene.events.emit('marriageUnlocked'); + } + if (benefits.canHaveKids) { + this.scene.events.emit('kidsUnlocked'); + } + // Update cooking multiplier + if (this.scene.farmingSystem) { + this.scene.farmingSystem.cookingMultiplier = benefits.cookingBonus || 1.0; + } + break; + + case 'barn': + // Update animal capacity + if (this.scene.animalSystem) { + this.scene.animalSystem.maxAnimals = benefits.capacity; + this.scene.animalSystem.autoFeeder = benefits.autoFeeder; + this.scene.animalSystem.autoPetter = benefits.autoPetter; + } + break; + + case 'storage': + // Update inventory capacity + if (this.scene.inventorySystem) { + this.scene.inventorySystem.maxSlots = benefits.slots; + } + break; + + case 'greenhouse': + // Update greenhouse capacity + if (this.scene.farmingSystem) { + this.scene.farmingSystem.greenhouseCapacity = benefits.capacity; + this.scene.farmingSystem.greenhouseQualityBonus = benefits.qualityBonus || 1.0; + } + break; + } + + console.log(`✨ Applied benefits for ${buildingType}:`, benefits); + } + + // ===== HELPER FUNCTIONS ===== + + registerBuildingSprite(buildingType, sprite) { + this.buildingSprites[buildingType] = sprite; + + // Set initial sprite frame based on current level + const currentLevel = this.buildingLevels[buildingType]; + this.updateBuildingSprite(buildingType, currentLevel); + + console.log(`📍 Registered ${buildingType} sprite at level ${currentLevel}`); + } + + getUpgradeRequirements(buildingType, level) { + return this.upgradeData[buildingType]?.levels[level]; + } + + getBenefits(buildingType, level) { + return this.upgradeBenefits[buildingType]?.[level]; + } + + getCurrentBenefits(buildingType) { + const level = this.buildingLevels[buildingType]; + return this.getBenefits(buildingType, level); + } + + consumeResources(materials) { + for (const [item, quantity] of Object.entries(materials)) { + this.removeItem(item, quantity); + } + console.log('💰 Consumed resources:', materials); + } + + // ===== INVENTORY INTEGRATION ===== + + getItemQuantity(itemId) { + if (this.scene.inventorySystem) { + return this.scene.inventorySystem.getQuantity(itemId); + } + return this.scene.player?.inventory?.[itemId] || 0; + } + + removeItem(itemId, quantity) { + if (this.scene.inventorySystem) { + this.scene.inventorySystem.removeItem(itemId, quantity); + } else if (this.scene.player?.inventory) { + this.scene.player.inventory[itemId] -= quantity; + } + } + + // ===== DATA PERSISTENCE ===== + + saveProgress() { + const data = { + buildingLevels: this.buildingLevels, + timestamp: Date.now() + }; + localStorage.setItem('buildingProgress', JSON.stringify(data)); + console.log('💾 Saved building progress'); + } + + loadProgress() { + const saved = localStorage.getItem('buildingProgress'); + if (saved) { + const data = JSON.parse(saved); + this.buildingLevels = data.buildingLevels; + console.log('📂 Loaded building progress:', this.buildingLevels); + } + } + + // ===== GETTERS ===== + + getLevel(buildingType) { + return this.buildingLevels[buildingType]; + } + + getMaxLevel(buildingType) { + return this.upgradeData[buildingType]?.maxLevel; + } + + isMaxLevel(buildingType) { + return this.getLevel(buildingType) >= this.getMaxLevel(buildingType); + } + + getNextUpgradeInfo(buildingType) { + const currentLevel = this.getLevel(buildingType); + const nextLevel = currentLevel + 1; + const maxLevel = this.getMaxLevel(buildingType); + + if (currentLevel >= maxLevel) { + return null; + } + + return { + currentLevel, + nextLevel, + maxLevel, + requirements: this.getUpgradeRequirements(buildingType, nextLevel), + benefits: this.getBenefits(buildingType, nextLevel), + canUpgrade: this.canUpgrade(buildingType) + }; + } + + getAllUpgradeInfo() { + const info = {}; + for (const buildingType of Object.keys(this.buildingLevels)) { + info[buildingType] = this.getNextUpgradeInfo(buildingType); + } + return info; + } + + getProgressPercentage(buildingType) { + const current = this.getLevel(buildingType); + const max = this.getMaxLevel(buildingType); + return (current / max) * 100; + } + + getTotalProgress() { + const buildings = Object.keys(this.buildingLevels); + let totalCurrent = 0; + let totalMax = 0; + + for (const building of buildings) { + totalCurrent += this.getLevel(building); + totalMax += this.getMaxLevel(building); + } + + return { + current: totalCurrent, + max: totalMax, + percentage: (totalCurrent / totalMax) * 100 + }; + } + + // ===== CLEANUP ===== + + destroy() { + this.saveProgress(); + this.buildingSprites = {}; + } +} diff --git a/src/systems/RecipeSystem.js b/src/systems/RecipeSystem.js new file mode 100644 index 0000000..2cdb9dc --- /dev/null +++ b/src/systems/RecipeSystem.js @@ -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; + } +}