Game Systems: RecipeSystem + ProgressionSystem + BreedingSystem implemented
This commit is contained in:
565
src/systems/BreedingSystem.js
Normal file
565
src/systems/BreedingSystem.js
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
527
src/systems/ProgressionSystem.js
Normal file
527
src/systems/ProgressionSystem.js
Normal file
@@ -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 = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
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