🔥 FAZE SPRINT 1-3 COMPLETION: DayNight, Inventory, Core Farming System, Mrtvi Zombiji (ShamblerAI & Alfa Moć Taming), Sredili assets & daily UI

This commit is contained in:
2026-03-03 17:46:41 +01:00
parent 35153eeacf
commit 188bd769cf
49 changed files with 5054 additions and 286 deletions

View File

@@ -0,0 +1,660 @@
/**
* ============================================================
* 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 = [];
}
}