This commit is contained in:
2025-12-08 17:49:30 +01:00
parent 2c446abbbb
commit 3096490a0c
8 changed files with 905 additions and 179 deletions

View File

@@ -88,9 +88,20 @@ class NPC {
if (this.scene.textures.exists(this.type)) {
texKey = this.type;
} else {
console.warn(`Texture for ${this.type} not found, generating fallback.`);
// Fallback generation triggers for known types if missing?
// We already generated them in TextureGenerator.generateAll()
console.warn(`Texture for ${this.type} not found, generating procedural...`);
// Generate procedural texture as fallback
if (this.type === 'cow' || this.type === 'cow_mutant') {
TextureGenerator.createCowSprite(this.scene, this.type, this.type.includes('mutant'));
} else if (this.type === 'chicken') {
TextureGenerator.createChickenSprite(this.scene, this.type, false);
} else if (this.type === 'troll') {
TextureGenerator.createTrollSprite(this.scene, this.type);
} else if (this.type === 'elf') {
TextureGenerator.createElfSprite(this.scene, this.type);
} else if (this.type === 'villager') {
TextureGenerator.createVillagerSprite(this.scene, this.type);
}
texKey = this.type; // Use type directly after generation
}
}

View File

@@ -7,66 +7,90 @@ class StoryScene extends Phaser.Scene {
const width = this.cameras.main.width;
const height = this.cameras.main.height;
// Black background
this.add.rectangle(0, 0, width, height, 0x000000).setOrigin(0);
// Dark background with gradient
const bg = this.add.rectangle(0, 0, width, height, 0x0a0a0a);
bg.setOrigin(0);
const storyText =
`LETO 2084.
SVET JE PADEL V TEMO.
Virus je večino spremenil v pošasti.
Mesta so grobnice.
Toda našel si upanje.
Zapuščeno kmetijo na robu divjine.
Zadnje varno zatočišče.
Tvoja naloga:
1. Obnovi kmetijo in preživi.
2. Zgradi obrambo pred hordo.
3. Najdi zdravilo in reši svet.
Pripravi se... NOČ PRIHAJA.`;
const textObj = this.add.text(width / 2, height, storyText, {
// Title
const title = this.add.text(width / 2, 80, 'NOVAFARMA', {
fontSize: '72px',
fontFamily: 'Courier New',
fontSize: '28px',
fill: '#00ff41',
align: 'center',
lineSpacing: 15,
color: '#00ff41',
fontStyle: 'bold',
stroke: '#000000',
strokeThickness: 4
strokeThickness: 6
});
textObj.setOrigin(0.5, 0);
title.setOrigin(0.5);
// Scroll animation
this.tweens.add({
targets: textObj,
y: 50,
duration: 15000, // 15s scroll
ease: 'Linear',
onComplete: () => {
this.time.delayedCall(2000, () => {
this.scene.start('GameScene');
});
}
// Subtitle
const subtitle = this.add.text(width / 2, 150, '2084 - Survival Farm', {
fontSize: '20px',
fontFamily: 'Courier New',
color: '#888888'
});
subtitle.setOrigin(0.5);
// Skip instructions
const skip = this.add.text(width - 20, height - 20, '[SPACE] Skip', {
fontSize: '16px', fill: '#666'
}).setOrigin(1);
// Main Menu Buttons
this.createMainMenu(width, height);
// LANGUAGE SELECTOR
// Language selector (bottom)
this.createLanguageSelector(width, height);
// Input to skip
this.input.keyboard.on('keydown-SPACE', () => {
this.scene.start('GameScene');
// Version info
const version = this.add.text(10, height - 30, 'v0.9.0 ALPHA', {
fontSize: '14px',
color: '#444444',
fontFamily: 'Courier New'
});
}
this.input.on('pointerdown', () => {
this.scene.start('GameScene');
createMainMenu(width, height) {
const buttons = [
{ label: '▶ NEW GAME', color: '#00ff41', action: () => this.startNewGame() },
{ label: '📁 LOAD GAME', color: '#4477ff', action: () => this.loadGame() },
{ label: '⚙️ SETTINGS', color: '#ffaa00', action: () => this.showSettings() },
{ label: '❌ EXIT', color: '#ff4444', action: () => this.exitGame() }
];
const startY = 250;
const spacing = 80;
buttons.forEach((btn, index) => {
const y = startY + (index * spacing);
// Button background
const bg = this.add.rectangle(width / 2, y, 400, 60, 0x1a1a2e, 1);
bg.setStrokeStyle(3, 0x00ff41);
// Button text
const text = this.add.text(width / 2, y, btn.label, {
fontSize: '28px',
fontFamily: 'Courier New',
color: btn.color,
fontStyle: 'bold'
});
text.setOrigin(0.5);
// Make interactive
bg.setInteractive({ useHandCursor: true });
bg.on('pointerover', () => {
bg.setFillStyle(0x2a2a4e);
text.setScale(1.1);
});
bg.on('pointerout', () => {
bg.setFillStyle(0x1a1a2e);
text.setScale(1.0);
});
bg.on('pointerdown', () => {
// Flash effect
this.tweens.add({
targets: bg,
alpha: 0.5,
yoyo: true,
duration: 100,
onComplete: btn.action
});
});
});
}
@@ -76,6 +100,45 @@ Pripravi se... NOČ PRIHAJA.`;
window.i18n = new LocalizationSystem();
}
// Globe button (bottom-right)
const globeBtn = this.add.text(width - 80, height - 60, '🌍', {
fontSize: '48px'
});
globeBtn.setOrigin(0.5);
globeBtn.setInteractive({ useHandCursor: true });
let langMenuOpen = false;
let langMenu = null;
globeBtn.on('pointerover', () => {
globeBtn.setScale(1.2);
});
globeBtn.on('pointerout', () => {
if (!langMenuOpen) globeBtn.setScale(1.0);
});
globeBtn.on('pointerdown', () => {
if (langMenuOpen) {
// Close menu
if (langMenu) langMenu.destroy();
langMenu = null;
langMenuOpen = false;
globeBtn.setScale(1.0);
} else {
// Open menu
langMenuOpen = true;
langMenu = this.createLanguageMenu(width, height, () => {
langMenuOpen = false;
globeBtn.setScale(1.0);
if (langMenu) langMenu.destroy();
langMenu = null;
});
}
});
}
createLanguageMenu(width, height, onClose) {
const container = this.add.container(0, 0);
const languages = [
{ code: 'slo', flag: '🇸🇮', name: 'SLO' },
{ code: 'en', flag: '🇬🇧', name: 'ENG' },
@@ -84,65 +147,73 @@ Pripravi se... NOČ PRIHAJA.`;
{ code: 'cn', flag: '🇨🇳', name: '中文' }
];
const startX = 20;
const startY = 20;
const spacing = 80;
const menuX = width - 220;
const menuY = height - 350;
const menuW = 200;
const menuH = 280;
// Title
this.add.text(startX, startY, '🌍 Language:', {
fontSize: '18px',
fill: '#ffffff',
fontFamily: 'Courier New'
});
// Panel background
const panel = this.add.rectangle(menuX, menuY, menuW, menuH, 0x1a1a2e, 0.98);
panel.setStrokeStyle(3, 0x00ff41);
container.add(panel);
// Language buttons
languages.forEach((lang, index) => {
const x = startX;
const y = startY + 40 + (index * spacing);
const btnY = menuY - 100 + (index * 55);
const isActive = window.i18n.getCurrentLanguage() === lang.code;
// Background
const bg = this.add.rectangle(x, y, 200, 60, 0x333333, 0.8);
bg.setOrigin(0, 0);
// Flag + Name
const text = this.add.text(x + 100, y + 30, `${lang.flag} ${lang.name}`, {
fontSize: '24px',
fill: window.i18n.getCurrentLanguage() === lang.code ? '#00ff41' : '#ffffff',
fontFamily: 'Courier New'
}).setOrigin(0.5);
// Make interactive
bg.setInteractive({ useHandCursor: true });
bg.on('pointerover', () => {
bg.setFillStyle(0x555555, 0.9);
text.setScale(1.1);
const btn = this.add.text(menuX, btnY, `${lang.flag} ${lang.name}`, {
fontSize: '20px',
fontFamily: 'Courier New',
color: isActive ? '#00ff41' : '#ffffff',
backgroundColor: isActive ? '#333333' : '#1a1a2e',
padding: { x: 15, y: 8 }
});
bg.on('pointerout', () => {
bg.setFillStyle(0x333333, 0.8);
text.setScale(1.0);
btn.setOrigin(0.5);
btn.setInteractive({ useHandCursor: true });
btn.on('pointerover', () => {
btn.setScale(1.05);
if (!isActive) btn.setBackgroundColor('#2a2a4e');
});
bg.on('pointerdown', () => {
// Set language
btn.on('pointerout', () => {
btn.setScale(1.0);
if (!isActive) btn.setBackgroundColor('#1a1a2e');
});
btn.on('pointerdown', () => {
window.i18n.setLanguage(lang.code);
// Update all text colors
languages.forEach((l, i) => {
const langText = this.children.list.find(child =>
child.text && child.text.includes(l.flag)
);
if (langText) {
langText.setColor(window.i18n.getCurrentLanguage() === l.code ? '#00ff41' : '#ffffff');
}
});
// Visual feedback
this.tweens.add({
targets: bg,
alpha: 0.5,
yoyo: true,
duration: 100
});
onClose();
});
container.add(btn);
});
return container;
}
startNewGame() {
console.log('🎮 Starting New Game...');
this.scene.start('GameScene');
}
loadGame() {
console.log('📁 Loading Game...');
// TODO: Implement save/load system
alert('Load Game - Coming Soon!');
}
showSettings() {
console.log('⚙️ Opening Settings...');
// TODO: Settings menu
alert('Settings - Use ⚙️ button in-game!');
}
exitGame() {
console.log('❌ Exiting...');
if (window.close) {
window.close();
} else {
alert('Close the window to exit.');
}
}
}

193
src/systems/MountSystem.js Normal file
View File

@@ -0,0 +1,193 @@
/**
* MOUNT SYSTEM
* Handles rideable animals (Donkey, Horse, etc.)
*/
class MountSystem {
constructor(scene, player) {
this.scene = scene;
this.player = player;
this.currentMount = null;
this.isMounted = false;
// Mount definitions
this.mountData = {
'donkey': {
name: 'Donkey',
speed: 200, // Movement speed when mounted
texture: 'donkey',
inventorySlots: 10, // Extra inventory from saddlebags
color: 0x8B7355 // Brown
},
'horse': {
name: 'Horse',
speed: 300,
texture: 'horse',
inventorySlots: 5,
color: 0x654321
}
};
}
/**
* Spawn a mount in the world
*/
spawnMount(gridX, gridY, type) {
if (!this.mountData[type]) {
console.error('Unknown mount type:', type);
return null;
}
const data = this.mountData[type];
const screenPos = this.scene.iso.toScreen(gridX, gridY);
const mount = {
type,
gridX,
gridY,
sprite: null,
inventory: [], // Saddlebag storage
tamed: true
};
// Create sprite
mount.sprite = this.scene.add.sprite(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY,
data.texture || 'npc_zombie' // Fallback
);
mount.sprite.setOrigin(0.5, 1);
mount.sprite.setScale(0.4);
mount.sprite.setTint(data.color);
mount.sprite.setDepth(this.scene.iso.getDepth(gridX, gridY, this.scene.iso.LAYER_OBJECTS));
mount.sprite.setInteractive({ useHandCursor: true });
mount.sprite.on('pointerdown', () => {
this.mountUp(mount);
});
console.log(`🐴 Spawned ${data.name} at ${gridX},${gridY}`);
return mount;
}
/**
* Mount up on a donkey/horse
*/
mountUp(mount) {
if (this.isMounted) {
console.log('Already mounted!');
return;
}
this.currentMount = mount;
this.isMounted = true;
const data = this.mountData[mount.type];
// Hide mount sprite
mount.sprite.setVisible(false);
// Increase player speed
if (this.player) {
this.player.originalSpeed = this.player.speed || 100;
this.player.speed = data.speed;
}
// Visual feedback
this.scene.events.emit('show-floating-text', {
x: this.player.sprite.x,
y: this.player.sprite.y - 40,
text: `🐴 Mounted ${data.name}!`,
color: '#ffaa00'
});
console.log(`🐴 Mounted on ${data.name}! Speed: ${data.speed}`);
}
/**
* Dismount
*/
dismount() {
if (!this.isMounted || !this.currentMount) {
return;
}
const mount = this.currentMount;
const data = this.mountData[mount.type];
// Restore player speed
if (this.player && this.player.originalSpeed) {
this.player.speed = this.player.originalSpeed;
}
// Place mount at player location
const playerPos = this.player.getPosition();
mount.gridX = Math.round(playerPos.x);
mount.gridY = Math.round(playerPos.y);
const screenPos = this.scene.iso.toScreen(mount.gridX, mount.gridY);
mount.sprite.setPosition(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY
);
mount.sprite.setVisible(true);
// Visual feedback
this.scene.events.emit('show-floating-text', {
x: this.player.sprite.x,
y: this.player.sprite.y - 40,
text: `Dismounted`,
color: '#888888'
});
this.currentMount = null;
this.isMounted = false;
console.log(`🐴 Dismounted from ${data.name}`);
}
/**
* Toggle mount/dismount (E key)
*/
toggleMount() {
if (this.isMounted) {
this.dismount();
} else {
// Try to find nearby mount
const nearbyMount = this.findNearbyMount();
if (nearbyMount) {
this.mountUp(nearbyMount);
}
}
}
findNearbyMount() {
// TODO: Search for mounts near player
return null;
}
/**
* Access saddlebag inventory
*/
openSaddlebag() {
if (!this.isMounted || !this.currentMount) {
return null;
}
const data = this.mountData[this.currentMount.type];
console.log(`🎒 Opening saddlebag (${data.inventorySlots} slots)`);
return this.currentMount.inventory;
}
update(delta) {
// Update mount position if mounted
if (this.isMounted && this.currentMount && this.player) {
const playerPos = this.player.getPosition();
this.currentMount.gridX = Math.round(playerPos.x);
this.currentMount.gridY = Math.round(playerPos.y);
}
}
isMountedOnAnything() {
return this.isMounted;
}
}

View File

@@ -0,0 +1,163 @@
/**
* PERENNIAL CROPS SYSTEM
* Handles multi-season crops like Apple Trees
*/
class PerennialCropSystem {
constructor(scene) {
this.scene = scene;
this.perennials = []; // Array of perennial objects
// Perennial definitions
this.perennialData = {
'apple_tree': {
name: 'Apple Tree',
growthTime: 120000, // 2 minutes to maturity
harvestSeason: 'autumn', // Only harvest in autumn
harvestYield: { 'apple': 5 },
regrowthTime: 30000, // 30s to regrow apples
texture: 'apple_tree',
stages: ['sapling', 'young', 'mature', 'fruiting']
}
};
}
/**
* Plant a perennial crop
*/
plant(gridX, gridY, type) {
if (!this.perennialData[type]) {
console.error('Unknown perennial type:', type);
return false;
}
const perennial = {
type,
gridX,
gridY,
plantedTime: Date.now(),
stage: 0, // 0=sapling, 1=young, 2=mature, 3=fruiting
canHarvest: false,
sprite: null,
healthBar: null
};
this.perennials.push(perennial);
this.createSprite(perennial);
console.log(`🌳 Planted ${type} at ${gridX},${gridY}`);
return true;
}
createSprite(perennial) {
const data = this.perennialData[perennial.type];
const screenPos = this.scene.iso.toScreen(perennial.gridX, perennial.gridY);
// Create tree sprite
perennial.sprite = this.scene.add.sprite(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY,
data.texture || 'tree'
);
perennial.sprite.setOrigin(0.5, 1);
perennial.sprite.setScale(0.5);
perennial.sprite.setDepth(this.scene.iso.getDepth(perennial.gridX, perennial.gridY, this.scene.iso.LAYER_OBJECTS));
this.updateSprite(perennial);
}
updateSprite(perennial) {
const data = this.perennialData[perennial.type];
// Update tint based on stage
if (perennial.stage === 0) {
perennial.sprite.setTint(0x88aa88); // Light green (sapling)
perennial.sprite.setScale(0.3);
} else if (perennial.stage === 1) {
perennial.sprite.setTint(0x44aa44); // Medium green (young)
perennial.sprite.setScale(0.4);
} else if (perennial.stage === 2) {
perennial.sprite.clearTint(); // Normal (mature)
perennial.sprite.setScale(0.5);
} else if (perennial.stage === 3) {
perennial.sprite.setTint(0xff8844); // Orange tint (fruiting)
perennial.sprite.setScale(0.5);
}
}
update(delta, currentSeason) {
this.perennials.forEach(perennial => {
const data = this.perennialData[perennial.type];
const age = Date.now() - perennial.plantedTime;
// Growth stages
if (perennial.stage < 3) {
const stageTime = data.growthTime / 3;
const newStage = Math.min(3, Math.floor(age / stageTime));
if (newStage !== perennial.stage) {
perennial.stage = newStage;
this.updateSprite(perennial);
if (perennial.stage === 3) {
console.log(`🌳 ${data.name} reached maturity!`);
}
}
}
// Fruiting logic (only in correct season and when mature)
if (perennial.stage === 3 && currentSeason === data.harvestSeason) {
if (!perennial.canHarvest) {
perennial.canHarvest = true;
perennial.sprite.setTint(0xff6644); // Bright orange (ready to harvest)
}
} else if (perennial.canHarvest && currentSeason !== data.harvestSeason) {
perennial.canHarvest = false;
perennial.sprite.clearTint();
}
});
}
/**
* Attempt to harvest perennial
*/
harvest(gridX, gridY) {
const perennial = this.perennials.find(p =>
p.gridX === gridX && p.gridY === gridY
);
if (!perennial) return null;
if (!perennial.canHarvest) {
return { error: 'Not ready to harvest' };
}
const data = this.perennialData[perennial.type];
// Reset harvest state (will regrow)
perennial.canHarvest = false;
perennial.sprite.setTint(0x88aa88); // Back to green
console.log(`🍎 Harvested ${data.name}!`);
return data.harvestYield;
}
/**
* Check if position has perennial
*/
hasPerennial(gridX, gridY) {
return this.perennials.some(p => p.gridX === gridX && p.gridY === gridY);
}
/**
* Get perennial at position
*/
getPerennial(gridX, gridY) {
return this.perennials.find(p => p.gridX === gridX && p.gridY === gridY);
}
destroy() {
this.perennials.forEach(p => {
if (p.sprite) p.sprite.destroy();
});
this.perennials = [];
}
}

View File

@@ -0,0 +1,194 @@
/**
* STEAM INTEGRATION SYSTEM
* Handles Steam achievements and cloud saves
*
* NOTE: Requires Steamworks SDK and Greenworks library
* This is a placeholder/mock implementation for development
*/
class SteamIntegrationSystem {
constructor() {
this.steamAvailable = false;
this.achievements = {};
this.cloudSaveEnabled = false;
// Mock achievement definitions
this.achievementDefs = {
'FIRST_HARVEST': { name: 'First Harvest', desc: 'Harvest your first crop' },
'GOLD_RUSH': { name: 'Gold Rush', desc: 'Earn 1000 gold coins' },
'ZOMBIE_SLAYER': { name: 'Zombie Slayer', desc: 'Kill 100 zombies' },
'MASTER_FARMER': { name: 'Master Farmer', desc: 'Harvest 1000 crops' },
'DAY_30': { name: 'Survivor', desc: 'Survive 30 days' },
'GREENHOUSE': { name: 'Greenhouse Builder', desc: 'Build a greenhouse' },
'TAMED_ZOMBIE': { name: 'Zombie Tamer', desc: 'Tame your first zombie' },
'OCEAN_EXPLORER': { name: 'Ocean Explorer', desc: 'Discover 5 islands' }
};
this.init();
}
init() {
// Check if Steamworks is available
if (typeof greenworks !== 'undefined') {
try {
greenworks.init();
this.steamAvailable = true;
this.cloudSaveEnabled = greenworks.isCloudEnabled();
console.log('✅ Steam Integration: Active');
console.log(`☁️ Cloud Saves: ${this.cloudSaveEnabled ? 'Enabled' : 'Disabled'}`);
} catch (e) {
console.warn('⚠️ Steam Integration: Failed to initialize', e);
this.steamAvailable = false;
}
} else {
console.log(' Steam Integration: Not available (running outside Steam)');
}
}
/**
* Unlock achievement
*/
unlockAchievement(achievementId) {
if (!this.achievementDefs[achievementId]) {
console.error('Unknown achievement:', achievementId);
return false;
}
const achievement = this.achievementDefs[achievementId];
if (this.steamAvailable && typeof greenworks !== 'undefined') {
try {
greenworks.activateAchievement(achievementId, () => {
console.log(`🏆 Achievement Unlocked: ${achievement.name}`);
}, (err) => {
console.error('Failed to unlock achievement:', err);
});
} catch (e) {
console.error('Steam achievement error:', e);
}
} else {
// Mock/local achievement
if (!this.achievements[achievementId]) {
this.achievements[achievementId] = true;
console.log(`🏆 [MOCK] Achievement Unlocked: ${achievement.name}`);
// Show notification
if (this.scene && this.scene.events) {
this.scene.events.emit('show-floating-text', {
x: 320,
y: 100,
text: `🏆 ${achievement.name}`,
color: '#ffd700'
});
}
// Save to localStorage
this.saveLocalAchievements();
return true;
}
}
return false;
}
/**
* Save game to Steam Cloud
*/
saveToCloud(saveData) {
if (!this.cloudSaveEnabled) {
console.log('☁️ Cloud saves not available, saving locally');
localStorage.setItem('novafarma_save', JSON.stringify(saveData));
return false;
}
try {
const saveString = JSON.stringify(saveData);
greenworks.saveTextToFile('novafarma_save.json', saveString, () => {
console.log('☁️ Game saved to Steam Cloud');
}, (err) => {
console.error('Failed to save to cloud:', err);
// Fallback to local
localStorage.setItem('novafarma_save', saveString);
});
return true;
} catch (e) {
console.error('Cloud save error:', e);
return false;
}
}
/**
* Load game from Steam Cloud
*/
loadFromCloud(callback) {
if (!this.cloudSaveEnabled) {
console.log('☁️ Cloud saves not available, loading locally');
const localSave = localStorage.getItem('novafarma_save');
if (localSave && callback) {
callback(JSON.parse(localSave));
}
return;
}
try {
greenworks.readTextFromFile('novafarma_save.json', (data) => {
console.log('☁️ Loaded from Steam Cloud');
if (callback) callback(JSON.parse(data));
}, (err) => {
console.error('Failed to load from cloud:', err);
// Fallback to local
const localSave = localStorage.getItem('novafarma_save');
if (localSave && callback) {
callback(JSON.parse(localSave));
}
});
} catch (e) {
console.error('Cloud load error:', e);
}
}
/**
* Get achievement status
*/
getAchievementStatus(achievementId) {
return this.achievements[achievementId] || false;
}
/**
* Get all unlocked achievements
*/
getUnlockedAchievements() {
return Object.keys(this.achievements).filter(id => this.achievements[id]);
}
/**
* Save achievements to localStorage (fallback)
*/
saveLocalAchievements() {
localStorage.setItem('novafarma_achievements', JSON.stringify(this.achievements));
}
/**
* Load achievements from localStorage
*/
loadLocalAchievements() {
const saved = localStorage.getItem('novafarma_achievements');
if (saved) {
this.achievements = JSON.parse(saved);
}
}
/**
* Set overlay position (for Steam overlay)
*/
activateOverlay(dialog = 'Friends') {
if (this.steamAvailable && typeof greenworks !== 'undefined') {
try {
greenworks.activateGameOverlay(dialog);
} catch (e) {
console.error('Failed to activate Steam overlay:', e);
}
}
}
}
// Global instance
window.SteamIntegration = SteamIntegrationSystem;