/** * ============================================================ * FarmingSystem.js — Nova Farma * ============================================================ * Farming logika: * - [E] blizu tal → Ore (Hoe) → tilled_soil tile * - [E] blizu tilled tile (z semenom v roki) → Posadi * - [E] blizu rastline (z zalivalko v roki) → Zalij * - Rast po N dnevih (tick ob sleep/new day) * - [E] blizu zrele rastline → Harvest → item v inventar * - Brez zalivanja 1 dan → rastlina uvene → izguba * ============================================================ * * CROP DEFINITIONS: * wheat: 4 stopnje, 3 dni, 30g semena, 3×30g harvest * carrot: 5 stopenj, 4 dni, 15g semena, 2×40g harvest * cannabis: 6 stopenj, 7 dni, 50g semena, 4×200g harvest (Faza 1) */ export default class FarmingSystem { /** * @param {Phaser.Scene} scene — GrassSceneClean instanca * @param {object} options — { islandX, islandY, tileSize } */ constructor(scene, options = {}) { this.scene = scene; this.islandX = options.islandX || 2000; this.islandY = options.islandY || 2000; this.tileSize = options.tileSize || 128; // Crop definicije this.CROPS = { wheat: { key: 'wheat', stages: 4, // 0=sprout,1=small,2=medium,3=ripe daysToGrow: 3, // Dan od saditve do žetve seedCost: 10, harvestItem: 'wheat', harvestCount: [1, 3], // min,max harvestValue: 30, stageKeys: [ 'crop_wheat_stage0', 'crop_wheat_stage1', 'crop_wheat_stage2', 'crop_wheat_stage3', ], }, carrot: { key: 'carrot', stages: 5, daysToGrow: 4, seedCost: 15, harvestItem: 'carrot', harvestCount: [1, 2], harvestValue: 40, stageKeys: [ 'crop_carrot_stage0', 'crop_carrot_stage1', 'crop_carrot_stage2', 'crop_carrot_stage3', 'crop_carrot_stage4', ], }, cannabis: { key: 'cannabis', stages: 6, daysToGrow: 7, seedCost: 50, harvestItem: 'cannabis_bud', harvestCount: [3, 5], harvestValue: 200, stageKeys: [ 'crop_cannabis_stage0', 'crop_cannabis_stage1', 'crop_cannabis_stage2', 'crop_cannabis_stage3', 'crop_cannabis_stage4', 'crop_cannabis_stage5', ], }, }; // Stanje this.tilledPlots = []; // { x, y, tileSprite, crop, dayPlanted, dayWatered, stage, sprite, wilted } this.currentDay = 1; this.interactRange = 60; // px — razdalja za interakcijo this.TILE_DISPLAY = 40; // Vizualna velikost tilled tile — majhna kocka! // Aktiven tool v roki (set from InventorySystem) this.activeTool = 'hoe'; // 'hoe' | 'watering_can' | null this.activeSeed = 'wheat'; // 'wheat' | 'carrot' | 'cannabis' // Physics group za tilled tile colliderje this.plotGroup = scene.physics.add.staticGroup(); // Graphics za highlight (Kai blizu) this.highlightGfx = scene.add.graphics().setDepth(9000); // Input — E tipka this.keyE = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E); // Debounce this._lastInteract = 0; console.log('🌱 FarmingSystem initialized'); } // ───────────────────────────────────────────────────────────────────────── // PRELOAD — Assets (kliči iz scene.preload PRED new FarmingSystem) // ───────────────────────────────────────────────────────────────────────── static preload(scene) { // Tilled soil scene.load.image('tilled_soil', 'DEMO_FAZA1/Crops/tilled_soil.png'); // Wheat (4 stopnje) for (let i = 0; i < 4; i++) { scene.load.image(`crop_wheat_stage${i}`, `DEMO_FAZA1/Crops/crop_wheat_stage${i}.png`); } // Carrot (5 stopenj) for (let i = 0; i < 5; i++) { scene.load.image(`crop_carrot_stage${i}`, `DEMO_FAZA1/Crops/crop_carrot_stage${i}.png`); } // Cannabis (6 stopenj) for (let i = 0; i < 6; i++) { scene.load.image(`crop_cannabis_stage${i}`, `DEMO_FAZA1/Crops/crop_cannabis_stage${i}.png`); } } // ───────────────────────────────────────────────────────────────────────── // UPDATE — Kliči iz scene.update() // ───────────────────────────────────────────────────────────────────────── update(kai, time) { this.highlightGfx.clear(); const nearPlot = this._getPlotNear(kai); const nearEmpty = this._getEmptyGroundNear(kai); // ── Highlight najbližji plot ── const W = 48, H = 24; // Velikost tilled plot kocke if (nearPlot) { this.highlightGfx.lineStyle(2, 0x44ffcc, 0.9); this.highlightGfx.strokeRect( nearPlot.x - W / 2, nearPlot.y - H / 2, W, H ); // Tooltip nad plotom this._drawTooltip(nearPlot); } else if (nearEmpty && this.activeTool === 'hoe') { // Pregled oranja this.highlightGfx.lineStyle(2, 0xaabb88, 0.5); this.highlightGfx.strokeRect( nearEmpty.x - W / 2, nearEmpty.y - H / 2, W, H ); } // ── E tipka — interakcija ── if (Phaser.Input.Keyboard.JustDown(this.keyE)) { if (nearPlot) { this._interactWithPlot(nearPlot, kai); } else if (nearEmpty && this.activeTool === 'hoe') { this._tillGround(nearEmpty.x, nearEmpty.y); } } } // ───────────────────────────────────────────────────────────────────────── // ORE (TILL) — Ustvari nov tilled plot // ───────────────────────────────────────────────────────────────────────── _tillGround(x, y) { // Snap na grid — center tile-a const ts = this.tileSize; const snappedX = Math.floor(x / ts) * ts + ts / 2; const snappedY = Math.floor(y / ts) * ts + ts / 2; // Ali plot že obstaja? if (this.tilledPlots.some(p => Math.abs(p.x - snappedX) < 4 && Math.abs(p.y - snappedY) < 4)) return; // === Nariši tilled soil z Graphics — točna velikost, brez skaliranja slik === const W = 48; // širina kocke (world px) const H = 24; // višina kocke (world px) const gfx = this.scene.add.graphics(); gfx.setDepth(snappedY - 50); // Ozadje — temna rjava zemlja gfx.fillStyle(0x3a2010, 1); gfx.fillRect(snappedX - W / 2, snappedY - H / 2, W, H); // Brazde (bela linija × 3, horizontalne) gfx.lineStyle(1, 0x6b4020, 0.7); const lines = 3; for (let i = 1; i <= lines; i++) { const ly = snappedY - H / 2 + (H / (lines + 1)) * i; gfx.beginPath(); gfx.moveTo(snappedX - W / 2 + 3, ly); gfx.lineTo(snappedX + W / 2 - 3, ly); gfx.strokePath(); } // Tanka okvirna linija gfx.lineStyle(1, 0x8b5a2b, 0.5); gfx.strokeRect(snappedX - W / 2, snappedY - H / 2, W, H); // Pop-in animacija — skali iz 0 gfx.setScale(0.1); this.scene.tweens.add({ targets: gfx, scaleX: 1.0, scaleY: 1.0, duration: 250, ease: 'Back.out', }); // Shrani referenčni objekt (tile = gfx) const tile = gfx; // Physics static body za collider const body = this.plotGroup.create(snappedX, snappedY); body.setDisplaySize(this.tileSize - 4, this.tileSize - 4); body.setAlpha(0); body.refreshBody(); // Shrani plot const plot = { x: snappedX, y: snappedY, tileSprite: tile, bodyRef: body, crop: null, dayPlanted: null, dayWatered: null, stage: 0, sprite: null, wilted: false, }; this.tilledPlots.push(plot); // Animacija: "Pop" ob oranju this.scene.tweens.add({ targets: tile, scaleX: { from: 1.1, to: 1.0 }, scaleY: { from: 0.8, to: 1.0 }, duration: 200, ease: 'Bounce.out', }); this._showFloatingText(snappedX, snappedY, '🪱 Poorano!', '#aabb88'); console.log(`🌱 Poorano: ${snappedX}, ${snappedY}`); } // ───────────────────────────────────────────────────────────────────────── // INTERAKCIJA Z PLOTOM (posadi / zalij / poženi) // ───────────────────────────────────────────────────────────────────────── _interactWithPlot(plot, kai) { if (plot.wilted) { // Uvela rastlina — odstrani in reciklira plot this._removeCrop(plot); this._showFloatingText(plot.x, plot.y, '🍂 Odstranjeno', '#cc6666'); return; } if (!plot.crop) { // 1. Prazno poorano → Posadi if (this.activeTool === 'hoe' || this.activeSeed) { this._plantCrop(plot, this.activeSeed); } } else if (plot.crop && plot.stage < this._getCropDef(plot.crop).stages - 1) { // 2. Rastlina raste → Zalij (samo z zalivalko) if (this.activeTool === 'watering_can') { this._waterCrop(plot); } else { this._showFloatingText(plot.x, plot.y - 30, '💧 Vzemi zalivalko!', '#aaaaff'); } } else if (plot.stage >= this._getCropDef(plot.crop).stages - 1 && !plot.wilted) { // 3. Zrela → Poženi this._harvestCrop(plot); } } // ───────────────────────────────────────────────────────────────────────── // POSADI // ───────────────────────────────────────────────────────────────────────── _plantCrop(plot, cropKey) { const def = this._getCropDef(cropKey); if (!def) { console.warn('Unknown crop:', cropKey); return; } plot.crop = cropKey; plot.dayPlanted = this.currentDay; plot.dayWatered = this.currentDay; // Zaliješ takoj ob sajenju plot.stage = 0; plot.wilted = false; // Ustvari sprite za rastlino (stage 0) // Stage 0 = 20px visoka (klica), max stage = 70px (zrela rastlina) const stageKey = def.stageKeys[0]; const cropH = 20; const texture = this.scene.textures.get(stageKey); const srcH = texture?.getSourceImage()?.height || 100; const scale = cropH / srcH; // Posadi na center tilled tile-a z offset za vizualni izgled plot.sprite = this.scene.add.image(plot.x, plot.y, stageKey) .setScale(scale) .setOrigin(0.5, 1.0) .setDepth(plot.y + 1) .setAlpha(0); // Pop-in animacija this.scene.tweens.add({ targets: plot.sprite, alpha: 1, scaleX: { from: scale * 1.4, to: scale }, scaleY: { from: scale * 0.5, to: scale }, duration: 350, ease: 'Back.out', }); this._showFloatingText(plot.x, plot.y - 30, `🌱 ${cropKey}!`, '#88ff88', 18); console.log(`🌱 Posajeno: ${cropKey} @ day ${this.currentDay}`); } // ───────────────────────────────────────────────────────────────────────── // ZALIJ // ───────────────────────────────────────────────────────────────────────── _waterCrop(plot) { if (!plot.crop) return; if (plot.dayWatered === this.currentDay) { this._showFloatingText(plot.x, plot.y - 30, '✅ Že zalivano!', '#44ffcc'); return; } // === PREVERI VODO v vedru === if (this.waterSystem) { if (!this.waterSystem.hasWater(1)) { this._showFloatingText(plot.x, plot.y - 30, '❌ Vedro prazno! [F] pri RC', '#ff6666', 16); return; } this.waterSystem.useWater(1); // Porabi 1L } plot.dayWatered = this.currentDay; plot.wilted = false; // Ripple efekt — modri krogci this._waterRipple(plot.x, plot.y); this._showFloatingText(plot.x, plot.y - 20, '💧 Zalivano!', '#44aaff'); // Tla (Graphics) - prebarva temnejše ko so mokra if (plot.tileSprite) { plot.tileSprite.clear(); const W = 48, H = 24; const snappedX = plot.x, snappedY = plot.y; // Mokra zemlja = temnejša plot.tileSprite.fillStyle(0x25140a, 1); plot.tileSprite.fillRect(snappedX - W / 2, snappedY - H / 2, W, H); plot.tileSprite.lineStyle(1, 0x4a2c10, 0.7); for (let i = 1; i <= 3; i++) { const ly = snappedY - H / 2 + (H / 4) * i; plot.tileSprite.beginPath(); plot.tileSprite.moveTo(snappedX - W / 2 + 3, ly); plot.tileSprite.lineTo(snappedX + W / 2 - 3, ly); plot.tileSprite.strokePath(); } } } // ───────────────────────────────────────────────────────────────────────── // ŽETEV // ───────────────────────────────────────────────────────────────────────── _harvestCrop(plot) { const def = this._getCropDef(plot.crop); const count = Phaser.Math.Between(def.harvestCount[0], def.harvestCount[1]); const value = count * def.harvestValue; // Notify scene (UIScene bo prikazal) this.scene.events.emit('harvest', { item: def.harvestItem, count, value, x: plot.x, y: plot.y, }); // Floating nagrade tekst this._showFloatingText(plot.x, plot.y - 20, `🌾 ×${count} (+${value}g)`, '#ffd700', 28); // Bounce animacija preden izgine if (plot.sprite) { this.scene.tweens.add({ targets: plot.sprite, scaleX: plot.sprite.scaleX * 1.3, scaleY: plot.sprite.scaleY * 1.3, alpha: 0, duration: 400, ease: 'Back.in', onComplete: () => { if (plot.sprite) plot.sprite.destroy(); } }); } // Reset plota (poorano ostane, crop zbrisano) plot.crop = null; plot.sprite = null; plot.dayPlanted = null; plot.dayWatered = null; plot.stage = 0; plot.wilted = false; // Tla postanejo svetlejša (prazna) this.scene.tweens.add({ targets: plot.tileSprite, alpha: 0.9, duration: 500, }); console.log(`🌾 Harvest: ${def.harvestItem} ×${count} = ${value}g`); } // ───────────────────────────────────────────────────────────────────────── // DNEVNI TICK — Kliči enkrat na dan (ob sleep/new day) // ───────────────────────────────────────────────────────────────────────── onNewDay(day) { this.currentDay = day; for (const plot of this.tilledPlots) { if (!plot.crop || plot.wilted) continue; const def = this._getCropDef(plot.crop); // ── Preveri zalivanje ── if (plot.dayWatered < day - 1) { // Ni bilo zalivano včeraj → Uvene! this._wiltCrop(plot); continue; } // ── Rast ── const daysGrown = day - plot.dayPlanted; // Enakomerno razporedimo stopnje čez daysToGrow const newStage = Math.min( Math.floor((daysGrown / def.daysToGrow) * def.stages), def.stages - 1 ); if (newStage > plot.stage) { plot.stage = newStage; this._updateCropSprite(plot); if (newStage === def.stages - 1) { // ZRELA! this._showFloatingText(plot.x, plot.y - 40, '🌟 Zrelo!', '#ffd700', 24); this._glowPulse(plot.sprite); } } } console.log(`🌞 Dan ${day} tick — ${this.tilledPlots.filter(p => p.crop).length} crop-ov aktiv`); } // ───────────────────────────────────────────────────────────────────────── // UVENJANJE // ───────────────────────────────────────────────────────────────────────── _wiltCrop(plot) { plot.wilted = true; // Sprite potemni in se trese if (plot.sprite) { this.scene.tweens.add({ targets: plot.sprite, tint: 0x554433, alpha: 0.5, duration: 800, ease: 'Sine.in', onComplete: () => { if (plot.sprite) plot.sprite.setTint(0x554433).setAlpha(0.45); } }); } this._showFloatingText(plot.x, plot.y - 30, '🍂 Uvelo!', '#cc6633'); console.warn(`🍂 Rastlina uvela @ ${plot.x},${plot.y}`); } // ───────────────────────────────────────────────────────────────────────── // POMOŽNE METODE // ───────────────────────────────────────────────────────────────────────── _getCropDef(key) { return this.CROPS[key] || null; } _updateCropSprite(plot) { const def = this._getCropDef(plot.crop); const stageKey = def.stageKeys[plot.stage]; // Ciljana višina: 20px (stage0) → 70px (zrela) const minH = 20, maxH = 70; const cropH = minH + (plot.stage / (def.stages - 1)) * (maxH - minH); const texture = this.scene.textures.get(stageKey); const srcH = texture?.getSourceImage()?.height || 150; const scale = cropH / srcH; if (plot.sprite) { // Animiran prehod na novo stopnjo this.scene.tweens.add({ targets: plot.sprite, scaleX: { from: plot.sprite.scaleX, to: scale }, scaleY: { from: plot.sprite.scaleY, to: scale }, duration: 600, ease: 'Back.out', onStart: () => { if (plot.sprite) plot.sprite.setTexture(stageKey); } }); } } _removeCrop(plot) { if (plot.sprite) { plot.sprite.destroy(); plot.sprite = null; } plot.crop = null; plot.dayPlanted = null; plot.dayWatered = null; plot.stage = 0; plot.wilted = false; if (plot.tileSprite) plot.tileSprite.setAlpha(0.9).clearTint(); } _getPlotNear(kai) { let closest = null; let minDist = this.interactRange; for (const plot of this.tilledPlots) { const d = Phaser.Math.Distance.Between(kai.x, kai.y, plot.x, plot.y); if (d < minDist) { minDist = d; closest = plot; } } return closest; } _getEmptyGroundNear(kai) { // Snap na tile center — Kai mora biti v bližini centra tile-a const ts = this.tileSize; const snappedX = Math.floor(kai.x / ts) * ts + ts / 2; const snappedY = Math.floor(kai.y / ts) * ts + ts / 2; const dist = Phaser.Math.Distance.Between(kai.x, kai.y, snappedX, snappedY); if (dist > this.interactRange) return null; if (this.tilledPlots.some(p => Math.abs(p.x - snappedX) < 8 && Math.abs(p.y - snappedY) < 8)) return null; return { x: snappedX, y: snappedY }; } _drawTooltip(plot) { const def = plot.crop ? this._getCropDef(plot.crop) : null; let label = ''; if (!plot.crop) label = '🌱 [E] Posadi'; else if (plot.wilted) label = '🍂 [E] Odstrani'; else if (plot.stage >= (def?.stages - 1)) label = '🌾 [E] Poženi!'; else if (this.activeTool === 'watering_can') label = '💧 [E] Zalij'; else label = `🌿 ${plot.crop} (${plot.stage + 1}/${def?.stages})`; // Tooltip — svetla, jasna, pri vrhu tilled tile-a if (!this._tooltipText) { this._tooltipText = this.scene.add.text(0, 0, '', { fontFamily: 'Arial Black', fontSize: '13px', color: '#ffffff', stroke: '#000000', strokeThickness: 4, backgroundColor: '#00000099', padding: { x: 8, y: 4 }, }).setDepth(9999).setScrollFactor(1); } const W = 48, H = 24; this._tooltipText .setText(label) .setPosition(plot.x - this._tooltipText.width / 2, plot.y - H / 2 - 20); } _showFloatingText(x, y, msg, color = '#ffffff', fontSize = 20) { const txt = this.scene.add.text(x, y, msg, { fontFamily: 'Arial Black', fontSize: `${fontSize}px`, color, stroke: '#000000', strokeThickness: 4, }).setOrigin(0.5).setDepth(9998); this.scene.tweens.add({ targets: txt, y: y - 60, alpha: { from: 1, to: 0 }, duration: 1400, ease: 'Cubic.out', onComplete: () => txt.destroy(), }); } _waterRipple(x, y) { const gfx = this.scene.add.graphics().setDepth(9997); let r = 4, alpha = 0.7; const expand = this.scene.time.addEvent({ delay: 40, repeat: 8, callback: () => { gfx.clear(); gfx.lineStyle(2, 0x4499ff, alpha); gfx.strokeCircle(x, y, r); r += 6; alpha -= 0.08; if (r > 55) { gfx.destroy(); expand.remove(); } } }); } _glowPulse(sprite) { if (!sprite) return; this.scene.tweens.add({ targets: sprite, alpha: { from: 1, to: 0.5 }, duration: 400, yoyo: true, repeat: 3, ease: 'Sine.inOut', }); } // ───────────────────────────────────────────────────────────────────────── // PUBLIC API — Za GrassScene // ───────────────────────────────────────────────────────────────────────── /** Nastavi aktiven tool (kliče InventorySystem) */ setTool(tool) { this.activeTool = tool; } /** Nastavi aktivno seme (kliče InventorySystem) */ setSeed(seed) { this.activeSeed = seed; } /** Vrni vse activate plots (za save) */ getState() { return this.tilledPlots.map(p => ({ x: p.x, y: p.y, crop: p.crop, dayPlanted: p.dayPlanted, dayWatered: p.dayWatered, stage: p.stage, wilted: p.wilted, })); } /** Destroy */ destroy() { this.highlightGfx?.destroy(); this._tooltipText?.destroy(); this.tilledPlots.forEach(p => { p.tileSprite?.destroy(); p.sprite?.destroy(); }); this.tilledPlots = []; } }