343 lines
9.6 KiB
JavaScript
343 lines
9.6 KiB
JavaScript
/**
|
|
* CROP GROWTH & SEASON SYSTEM
|
|
* Manages dynamic crop growth through 8 stages + 4 seasonal variations
|
|
* Integrates with Faza 1 crop sprites (corn, tomatoes, carrots, potatoes, lettuce, pumpkin)
|
|
*/
|
|
|
|
class CropGrowthSeasonSystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Growth system constants
|
|
this.GROWTH_STAGES = 8;
|
|
this.SEASONS = ['spring', 'summer', 'fall', 'winter'];
|
|
|
|
// Active crops in world
|
|
this.crops = new Map(); // cropId -> {type, stage, season, sprite, growthTimer}
|
|
|
|
// Growth rates (minutes per stage in real time)
|
|
this.growthRates = {
|
|
corn: 5,
|
|
tomatoes: 4,
|
|
carrots: 3,
|
|
potatoes: 4,
|
|
lettuce: 2,
|
|
pumpkin: 6
|
|
};
|
|
|
|
// Seasonal color tints (applied to environment, NOT crops)
|
|
this.seasonalTints = {
|
|
spring: 0x8BC34A, // Fresh green
|
|
summer: 0xFFC107, // Bright yellow-gold
|
|
fall: 0xFF9800, // Orange-amber
|
|
winter: 0x2196F3 // Cool blue
|
|
};
|
|
|
|
// Current season (synced with TimeSystem)
|
|
this.currentSeason = 'spring';
|
|
|
|
// Environment layer (sky/ambient tint)
|
|
this.environmentTint = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize system
|
|
*/
|
|
init() {
|
|
// Create environment tint overlay
|
|
this.createEnvironmentTint();
|
|
|
|
// Subscribe to season changes from TimeSystem
|
|
if (this.scene.time_system) {
|
|
this.scene.events.on('seasonChanged', this.onSeasonChange, this);
|
|
}
|
|
|
|
console.log('🌱 CropGrowthSeasonSystem initialized');
|
|
}
|
|
|
|
/**
|
|
* Create environment tint overlay for seasonal color shifts
|
|
*/
|
|
createEnvironmentTint() {
|
|
// Create semi-transparent overlay
|
|
const width = this.scene.cameras.main.width;
|
|
const height = this.scene.cameras.main.height;
|
|
|
|
this.environmentTint = this.scene.add.rectangle(
|
|
width / 2,
|
|
height / 2,
|
|
width * 2, // Make it larger to cover camera movement
|
|
height * 2,
|
|
this.seasonalTints[this.currentSeason],
|
|
0.15 // 15% opacity
|
|
);
|
|
|
|
this.environmentTint.setScrollFactor(1); // Moves with camera
|
|
this.environmentTint.setDepth(1000); // Above most objects
|
|
this.environmentTint.setBlendMode(Phaser.BlendModes.MULTIPLY);
|
|
}
|
|
|
|
/**
|
|
* Handle season change
|
|
*/
|
|
onSeasonChange(newSeason) {
|
|
console.log(`🍂 Season changing to: ${newSeason}`);
|
|
this.currentSeason = newSeason;
|
|
|
|
// Animate environment tint transition
|
|
if (this.environmentTint) {
|
|
this.scene.tweens.add({
|
|
targets: this.environmentTint,
|
|
tint: this.seasonalTints[newSeason],
|
|
duration: 2000,
|
|
ease: 'Sine.easeInOut'
|
|
});
|
|
}
|
|
|
|
// Update all existing crops to new season sprites
|
|
this.updateAllCropsForSeason(newSeason);
|
|
}
|
|
|
|
/**
|
|
* Plant a crop
|
|
*/
|
|
plantCrop(x, y, cropType) {
|
|
const cropId = `crop_${Date.now()}_${Math.random()}`;
|
|
|
|
// Load sprite for current season, stage 1
|
|
const spritePath = this.getCropSpritePath(cropType, this.currentSeason, 1);
|
|
|
|
// Create sprite
|
|
const sprite = this.scene.add.sprite(x, y, spritePath);
|
|
sprite.setDepth(2); // Above ground
|
|
|
|
// Store crop data
|
|
this.crops.set(cropId, {
|
|
id: cropId,
|
|
type: cropType,
|
|
stage: 1,
|
|
season: this.currentSeason,
|
|
sprite: sprite,
|
|
growthTimer: 0,
|
|
x: x,
|
|
y: y
|
|
});
|
|
|
|
console.log(`🌱 Planted ${cropType} at ${x},${y}`);
|
|
|
|
return cropId;
|
|
}
|
|
|
|
/**
|
|
* Get crop sprite path based on type, season, and stage
|
|
*/
|
|
getCropSpritePath(cropType, season, stage) {
|
|
// Path: assets/crops/faza1/{type}/{season}/stage{N}.png
|
|
return `crops/faza1/${this.normalizeCropName(cropType)}/${season}/stage${stage}`;
|
|
}
|
|
|
|
/**
|
|
* Normalize crop names to match folder structure
|
|
*/
|
|
normalizeCropName(cropType) {
|
|
const nameMap = {
|
|
'tomatoes': 'tomatoes',
|
|
'tomato': 'tomatoes',
|
|
'carrots': 'carrots',
|
|
'carrot': 'carrots',
|
|
'potatoes': 'potatos', // Note: folder is 'potatos' not 'potatoes'
|
|
'potato': 'potatos',
|
|
'lettuce': 'lettuces',
|
|
'pumpkin': 'pumpkins',
|
|
'corn': 'corn'
|
|
};
|
|
|
|
return nameMap[cropType.toLowerCase()] || cropType.toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Update crop growth (called every frame or fixed interval)
|
|
*/
|
|
update(deltaTime) {
|
|
for (const [cropId, crop] of this.crops.entries()) {
|
|
// Don't grow if already at max stage or dead
|
|
if (crop.stage >= this.GROWTH_STAGES) {
|
|
continue;
|
|
}
|
|
|
|
// Increment growth timer
|
|
crop.growthTimer += deltaTime / 1000; // Convert to seconds
|
|
|
|
// Check if it's time to advance stage
|
|
const growthRate = this.growthRates[crop.type] || 5;
|
|
const secondsPerStage = growthRate * 60; // Convert to seconds
|
|
|
|
if (crop.growthTimer >= secondsPerStage) {
|
|
this.advanceCropStage(cropId);
|
|
crop.growthTimer = 0; // Reset timer
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Advance crop to next growth stage
|
|
*/
|
|
advanceCropStage(cropId) {
|
|
const crop = this.crops.get(cropId);
|
|
if (!crop) return;
|
|
|
|
// Advance stage
|
|
crop.stage = Math.min(crop.stage + 1, this.GROWTH_STAGES);
|
|
|
|
// Update sprite
|
|
const newSpritePath = this.getCropSpritePath(crop.type, crop.season, crop.stage);
|
|
crop.sprite.setTexture(newSpritePath);
|
|
|
|
console.log(`🌾 ${crop.type} advanced to stage ${crop.stage}`);
|
|
|
|
// Special events
|
|
if (crop.stage === 6) {
|
|
// HARVEST READY
|
|
this.scene.events.emit('cropReady', { cropId, crop });
|
|
|
|
// Add visual indicator (glow effect)
|
|
this.addHarvestGlow(crop.sprite);
|
|
}
|
|
|
|
if (crop.stage === 7) {
|
|
console.warn(`⚠️ ${crop.type} is overripe! Harvest soon or it will die.`);
|
|
}
|
|
|
|
if (crop.stage === 8) {
|
|
console.log(`💀 ${crop.type} has died.`);
|
|
// Crop is now dead (stage 8)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add glow effect to harvest-ready crops
|
|
*/
|
|
addHarvestGlow(sprite) {
|
|
// Pulsing glow animation
|
|
this.scene.tweens.add({
|
|
targets: sprite,
|
|
alpha: 0.7,
|
|
duration: 800,
|
|
yoyo: true,
|
|
repeat: -1,
|
|
ease: 'Sine.easeInOut'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Harvest a crop
|
|
*/
|
|
harvestCrop(cropId) {
|
|
const crop = this.crops.get(cropId);
|
|
if (!crop) return null;
|
|
|
|
// Can only harvest at stage 6 (perfect) or 5-7 (acceptable)
|
|
if (crop.stage < 5 || crop.stage === 8) {
|
|
console.warn(`Cannot harvest ${crop.type} at stage ${crop.stage}`);
|
|
return null;
|
|
}
|
|
|
|
// Calculate yield based on stage
|
|
let yield_quality = 1.0;
|
|
if (crop.stage === 6) {
|
|
yield_quality = 1.5; // Perfect harvest bonus
|
|
} else if (crop.stage === 7) {
|
|
yield_quality = 0.7; // Overripe penalty
|
|
}
|
|
|
|
// Remove crop
|
|
crop.sprite.destroy();
|
|
this.crops.delete(cropId);
|
|
|
|
console.log(`✅ Harvested ${crop.type} with ${yield_quality}x quality`);
|
|
|
|
return {
|
|
type: crop.type,
|
|
quality: yield_quality,
|
|
stage: crop.stage
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update all crops when season changes
|
|
*/
|
|
updateAllCropsForSeason(newSeason) {
|
|
for (const [cropId, crop] of this.crops.entries()) {
|
|
crop.season = newSeason;
|
|
|
|
// Update sprite to new season
|
|
const newSpritePath = this.getCropSpritePath(crop.type, newSeason, crop.stage);
|
|
crop.sprite.setTexture(newSpritePath);
|
|
}
|
|
|
|
console.log(`🍂 Updated ${this.crops.size} crops for ${newSeason}`);
|
|
}
|
|
|
|
/**
|
|
* Get crop info
|
|
*/
|
|
getCropInfo(cropId) {
|
|
return this.crops.get(cropId);
|
|
}
|
|
|
|
/**
|
|
* Save system state
|
|
*/
|
|
save() {
|
|
const cropsData = [];
|
|
|
|
for (const [cropId, crop] of this.crops.entries()) {
|
|
cropsData.push({
|
|
id: cropId,
|
|
type: crop.type,
|
|
stage: crop.stage,
|
|
season: crop.season,
|
|
growthTimer: crop.growthTimer,
|
|
x: crop.x,
|
|
y: crop.y
|
|
});
|
|
}
|
|
|
|
return {
|
|
currentSeason: this.currentSeason,
|
|
crops: cropsData
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Load system state
|
|
*/
|
|
load(data) {
|
|
if (data.currentSeason) {
|
|
this.currentSeason = data.currentSeason;
|
|
this.onSeasonChange(this.currentSeason);
|
|
}
|
|
|
|
if (data.crops) {
|
|
// Recreate crops
|
|
for (const cropData of data.crops) {
|
|
const spritePath = this.getCropSpritePath(cropData.type, cropData.season, cropData.stage);
|
|
const sprite = this.scene.add.sprite(cropData.x, cropData.y, spritePath);
|
|
sprite.setDepth(2);
|
|
|
|
this.crops.set(cropData.id, {
|
|
...cropData,
|
|
sprite: sprite
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log(`📂 Loaded ${this.crops.size} crops`);
|
|
}
|
|
}
|
|
|
|
// Export for use in GameScene
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = CropGrowthSeasonSystem;
|
|
}
|