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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user