Files
novafarma/nova farma TRAE/src/systems/FarmingSystem.js

661 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ============================================================
* 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 = [];
}
}