From 097d35da1b1acc8b2335c94c0870a5ae5fbb51e1 Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Mon, 8 Dec 2025 17:31:18 +0100 Subject: [PATCH] popravki --- CHANGELOG.md | 241 +++++++++++++++------------ index.html | 2 + src/scenes/StoryScene.js | 79 +++++++++ src/scenes/UIScene.js | 173 +++++++++++++++++++ src/systems/CollectionSystem.js | 6 +- src/systems/LocalizationSystem.js | 176 +++++++++++++++++++ src/systems/PlaytimeTrackerSystem.js | 141 ++++++++++++++++ 7 files changed, 713 insertions(+), 105 deletions(-) create mode 100644 src/systems/LocalizationSystem.js create mode 100644 src/systems/PlaytimeTrackerSystem.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 87b3035..126d1f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,124 +1,159 @@ -# NovaFarma - Changelog +# CHANGELOG - NovaFarma Development -## Version 2.5.0 (2025-12-08) +## [Session: 8.12.2025] - Phase 13 & Polish -### ๐ŸŽฎ Major Features +### โœ… IMPLEMENTIRANO -#### **Elite Zombies** ๐Ÿ‘น -- Added Elite Zombie enemy type (dark red with glowing pink eyes) -- 50 HP (2.5x more than normal zombies) -- 50% faster movement speed -- Spawn in City area (15 elite zombies) -- Drop better loot: Scrap Metal + 50% chance for Chip -- Scale: 0.2 (smaller but deadlier) +#### ๐Ÿ™๏ธ **City Content & Combat Polish** +- **Unique City Loot** + - Added `scrap_metal` (5 units, 80% chance) to city chests + - Added `chips` (electronics, 2 units, 60% chance) to city chests + - Added `scrap_metal` (15 units) and `chips` (5 units, 90% chance) to elite chests + +- **Combat Visual Feedback** + - `NPC.takeDamage(amount, attacker)` - Full implementation + - โœจ White flash effect on hit (100ms) + - ๐ŸŽฏ Knockback physics (0.5 tile pushback) + - ๐Ÿ’ฅ Floating damage text (`-HP` in red) + - ๐ŸŽจ Color-coded health bars (green โ†’ orange โ†’ red) + - ๐Ÿ’€ Fade-out death animation (300ms) -#### **City Content** ๐Ÿ™๏ธ -- **City Wall (Obzidje)**: 15x15 fortified city with stone walls - - WALL_EDGE tiles on perimeter (solid collision) - - Pavement interior with ruins -- **Treasure Chests**: 3 chests in ruins with random loot (2-4 items) - - Interact with 'E' key to open - - Loot: scrap, chips, wood, stone, bones -- **Zombie Spawners**: 2 dark portals in city - - Visual: Dark stone with red glow -- **5 Ruin Structures**: Scattered throughout city -- **Arena**: Boss battle area at (75, 55) +#### ๐ŸŒก๏ธ **Weather System v2.0 - Temperature & Survival** +- **Seasonal Temperature System** + - Spring: 15ยฐC (safe) + - Summer: 30ยฐC base + - Autumn: 10ยฐC (safe) + - Winter: -5ยฐC base + - Day/night variation: ยฑ5ยฐC (sine wave) + +- **Survival Mechanics** + - Cold damage: `Temp < 0ยฐC` โ†’ -5 HP every 5s (without Winter Coat) + - Heat damage: `Temp > 35ยฐC` โ†’ -3 HP every 5s (without Summer Hat) + - Protection items: `winter_coat`, `summer_hat` + - Visual indicators: โ„๏ธ Freezing! / ๐Ÿ”ฅ Overheating! -#### **Roads & Navigation** ๐Ÿ›ฃ๏ธ -- **Gray Stone Roads**: Connecting Farm (20,20) and City (65,65) - - Horizontal road from farm - - Vertical road to city - - L-shaped path -- **Signposts**: 3 wooden navigation markers - - Farm signpost: โ†’ (points to city) - - City signpost: โ† (points to farm) - - Crossroads signpost: โ‡… (both directions) +- **Greenhouse Building** + - Recipe: 20 Glass + 15 Wood + - Size: 2x2 tiles + - Purpose: Enables winter farming + - Glass Crafting: Sand + Coal โ†’ Glass (Furnace, 3000ms) -#### **Farm Starter Zone** ๐ŸŒพ -- **Cleared Farm Area**: 16x16 tiles at (20, 20) - - All trees and rocks removed - - Green grass terrain -- **Starter Resources**: Treasure chest with initial items -- **Fence Posts**: 4 corner markers for farm boundary +#### ๐ŸŽฎ **Demo Mode** +- 3-day play limit + - Triggers `triggerDemoEnd()` after Day 3 + - Pauses game physics + - Shows demo end screen (via UIScene) + - Call-to-action for full version -#### **Seasonal System** ๐ŸŒธโ˜€๏ธ๐Ÿ‚โ„๏ธ -- **4 Seasons**: Spring, Summer, Autumn, Winter - - Each season lasts 7 in-game days - - Visual overlays with season-specific colors -- **Season Indicators**: Emoji icons in UI clock - - ๐ŸŒธ Spring (green tint) - - โ˜€๏ธ Summer (yellow tint) - - ๐Ÿ‚ Autumn (orange tint) - - โ„๏ธ Winter (blue-white tint) +#### โœจ **Visual Effects System** +- **New System:** `VisualEffectsSystem.js` + - `screenshake(intensity, duration)` - Camera shake + - `createHitParticles(x, y, color)` - Sparks on hit + - `createExplosion(x, y, color)` - Explosion particles + - `createDustPuff(x, y)` - Movement dust + - `flash(color, duration)` - Screen flash + - `fadeOut(duration, callback)` / `fadeIn(duration)` - Transitions + - Particle texture generator: `particle_white` (8x8 white circle) -### ๐Ÿ’ฅ Combat Polish +#### ๐Ÿ—๏ธ **Building System Updates** +- Added `greenhouse` to buildingsData +- Cost: `{ glass: 20, wood: 15 }` +- Size: 2x2 grid placement -#### **Visual Effects** -- **White Flash**: Enemy flashes white โ†’ red when hit -- **Knockback Effect**: Enemies get pushed back on impact -- **Floating Damage Numbers**: Red numbers float up showing damage dealt - - Font: Courier New, 16px, bold - - Fades out while rising +### ๐Ÿ“ FILES MODIFIED -### ๐Ÿ”’ Collision System +``` +src/ +โ”œโ”€โ”€ entities/ +โ”‚ โ”œโ”€โ”€ NPC.js (+60 lines) - takeDamage(), die() methods +โ”‚ โ””โ”€โ”€ LootChest.js (+4 lines) - City loot tables +โ”œโ”€โ”€ systems/ +โ”‚ โ”œโ”€โ”€ WeatherSystem.js (+75 lines) - Temperature system, triggerDemoEnd() +โ”‚ โ”œโ”€โ”€ BuildingSystem.js (+1 line) - Greenhouse +โ”‚ โ””โ”€โ”€ VisualEffectsSystem.js (NEW, 130 lines) - Juice effects +โ”œโ”€โ”€ scenes/ +โ”‚ โ””โ”€โ”€ (UIScene.js will need showDemoEndScreen() method) +โ””โ”€โ”€ index.html (+1 line) - VisualEffectsSystem script tag +``` -#### **Tile Collision** -- **Dynamic `solid` Property**: Each tile has `solid: boolean` -- **setSolid(x, y, true/false)**: Runtime collision modification -- **isSolid(x, y)**: Check if tile is solid -- **Solid Tiles**: - - water, MINE_WALL, WALL_EDGE, ORE_STONE, ORE_IRON, lava, void +### ๐ŸŽฏ TASKS COMPLETED -#### **Decoration Collision** -- **Enhanced Pattern Matching**: Case-insensitive checks -- **Blocked Objects**: - - All trees (contains "tree" or "sapling") - - All rocks (contains "rock" or "stone") - - All signposts (contains "signpost" or "sign") - - Structures: chest, spawner, ruin, arena, fence, house, gravestone, bush, hill +**Phase 8:** +- [x] City Content (Scrap metal, Chips) +- [x] Elite Zombies (already active) +- [x] Combat Polish (White flash, Knockback) +- [x] World Details (Roads, Signposts - already done) -### โš™๏ธ Performance Optimizations +**Phase 13:** +- [x] Weather System v2.0 + - [x] Seasonal temperatures + - [x] Temperature damage logic + - [x] Greenhouse building + - [x] Glass crafting recipe -#### **Rendering** -- **60 FPS Target**: Explicit FPS configuration -- **Pixel-Perfect Positioning**: `Math.round()` for player x/y -- **Camera Lerp 0.1**: Smooth camera following -- **NEAREST_NEIGHBOR Filtering**: Crisp pixel art (no blur) +**Phase 14:** +- [x] Demo Mode (3-day limit) +- [x] Visual Polish (VisualEffectsSystem) -#### **Culling** -- **Viewport Culling**: NPCs outside camera view aren't rendered -- **Distance Culling**: NPCs far from player (>30-50 tiles) hidden -- **AI Skip**: Invisible NPCs skip AI updates +### ๐Ÿ› KNOWN ISSUES -### ๐ŸŽจ New Assets -- `elite_zombie.png` - Elite zombie sprite -- `chest.png` - Treasure chest -- `spawner.png` - Zombie spawner portal -- `signpost_city.png` - City direction marker -- `signpost_farm.png` - Farm direction marker -- `signpost_both.png` - Crossroads marker -- `city_wall.png` - City wall texture -- `road_tile.png` - Road/pavement tile -- `farm_zone.png` - Farm area overview +1. **NPC Sprite Transparency** + - AI-generated sprites show checkerboard pattern in some contexts + - Chrome/Electron rendering issue with PNG alpha channels + - Current workaround: Using AI sprites, considered low priority -### ๐Ÿ› Bug Fixes -- Fixed inconsistent tree/rock collision (some were passable) -- Fixed merchant scale (now 0.2 vs 0.5 for player/zombies) -- Improved decoration collision detection with case-insensitive matching +2. **NPC.js Scaling** + - Had to revert scaling changes due to syntax errors + - Individual NPC scales work but need careful editing + - Current state: Reverted to last working Git version -### ๐Ÿ“ Technical Changes -- Added `initializeFarmWorld()` method in GameScene -- Added `setSolid()` and `isSolid()` methods in TerrainSystem -- Enhanced `placeStructure()` to support chest, spawner, signposts -- Added season tracking in WeatherSystem -- Improved NPC.takeDamage() with visual effects -- Added WALL_EDGE terrain type +### ๐Ÿ“‹ TODO NEXT SESSION + +**Phase 13 - Remaining:** +- [ ] Localization (JSON translations, Language selector) +- [ ] Steam Integration (Achievements, Cloud Save) +- [ ] Additional Entities (Donkey, Apple Tree, Seasonal Crops) +- [ ] Bone Tools crafting +- [ ] Gems as rare drops + +**Phase 14 - Remaining:** +- [ ] UI Polish (Rustic/Post-apo theme) +- [ ] Trailer Tools (Camera smooth movement) + +**Phase 15:** +- [ ] Antigravity namespace refactor +- [ ] Final polish & optimization + +### ๐ŸŽจ VISUAL IMPROVEMENTS + +- Combat feels more impactful (flash + knockback + damage numbers) +- Temperature survival adds strategic layer (equipment management) +- Greenhouse enables year-round farming +- Visual effects ready for integration (screenshake on explosions, etc.) + +### ๐Ÿ’ก DESIGN DECISIONS + +1. **Temperature Damage:** + - Chose 5-second intervals to avoid spam + - Different damage for cold (-5 HP) vs heat (-3 HP) + - Requires specific protective gear (encourages crafting) + +2. **Demo Limit:** + - 3 days = ~15 minutes real-time (5min/day) + - Enough to experience core gameplay loop + - Hooks into WeatherSystem for clean trigger + +3. **Visual Effects as Separate System:** + - Modular approach for easy integration + - Can be called from any scene/entity + - Particle textures generated procedurally --- -## Version 1.0.0 (Previous) -- Initial release -- Basic farm mechanics -- Zombie combat -- Day/night cycle -- Save system +**Session Duration:** ~2h 45min +**Lines of Code Added:** ~270 +**Systems Enhanced:** 5 +**New Systems Created:** 1 +**Features Completed:** 12 + +**Next Milestone:** Phase 13 completion (Localization + Entities) diff --git a/index.html b/index.html index c4addc4..d7a5ca4 100644 --- a/index.html +++ b/index.html @@ -100,6 +100,8 @@ + + diff --git a/src/scenes/StoryScene.js b/src/scenes/StoryScene.js index fa3bdaf..5fada0a 100644 --- a/src/scenes/StoryScene.js +++ b/src/scenes/StoryScene.js @@ -57,6 +57,9 @@ Pripravi se... NOฤŒ PRIHAJA.`; fontSize: '16px', fill: '#666' }).setOrigin(1); + // LANGUAGE SELECTOR + this.createLanguageSelector(width, height); + // Input to skip this.input.keyboard.on('keydown-SPACE', () => { this.scene.start('GameScene'); @@ -66,4 +69,80 @@ Pripravi se... NOฤŒ PRIHAJA.`; this.scene.start('GameScene'); }); } + + createLanguageSelector(width, height) { + // Initialize localization + if (!window.i18n) { + window.i18n = new LocalizationSystem(); + } + + const languages = [ + { code: 'slo', flag: '๐Ÿ‡ธ๐Ÿ‡ฎ', name: 'SLO' }, + { code: 'en', flag: '๐Ÿ‡ฌ๐Ÿ‡ง', name: 'ENG' }, + { code: 'de', flag: '๐Ÿ‡ฉ๐Ÿ‡ช', name: 'DEU' }, + { code: 'it', flag: '๐Ÿ‡ฎ๐Ÿ‡น', name: 'ITA' }, + { code: 'cn', flag: '๐Ÿ‡จ๐Ÿ‡ณ', name: 'ไธญๆ–‡' } + ]; + + const startX = 20; + const startY = 20; + const spacing = 80; + + // Title + this.add.text(startX, startY, '๐ŸŒ Language:', { + fontSize: '18px', + fill: '#ffffff', + fontFamily: 'Courier New' + }); + + // Language buttons + languages.forEach((lang, index) => { + const x = startX; + const y = startY + 40 + (index * spacing); + + // 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); + }); + bg.on('pointerout', () => { + bg.setFillStyle(0x333333, 0.8); + text.setScale(1.0); + }); + bg.on('pointerdown', () => { + // Set language + 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 + }); + }); + }); + } } diff --git a/src/scenes/UIScene.js b/src/scenes/UIScene.js index e1364d6..fec03e7 100644 --- a/src/scenes/UIScene.js +++ b/src/scenes/UIScene.js @@ -1541,4 +1541,177 @@ class UIScene extends Phaser.Scene { onComplete: () => text.destroy() }); } + + createSettingsButton() { + // Settings gear icon (top-right corner) + const settingsBtn = this.add.text(this.cameras.main.width - 60, 20, 'โš™๏ธ', { + fontSize: '32px', + color: '#ffffff' + }); + settingsBtn.setScrollFactor(0); + settingsBtn.setDepth(9999); + settingsBtn.setInteractive({ useHandCursor: true }); + + settingsBtn.on('pointerover', () => { + settingsBtn.setScale(1.2); + }); + settingsBtn.on('pointerout', () => { + settingsBtn.setScale(1.0); + }); + settingsBtn.on('pointerdown', () => { + this.toggleSettingsMenu(); + }); + } + + toggleSettingsMenu() { + if (this.settingsContainer) { + // Close existing menu + this.settingsContainer.destroy(); + this.settingsContainer = null; + // Resume game + if (this.gameScene) { + this.gameScene.physics.resume(); + } + return; + } + + // Pause game + if (this.gameScene) { + this.gameScene.physics.pause(); + } + + // Create settings menu + const width = this.cameras.main.width; + const height = this.cameras.main.height; + + this.settingsContainer = this.add.container(0, 0); + this.settingsContainer.setScrollFactor(0); + this.settingsContainer.setDepth(10000); + + // Semi-transparent background + const bg = this.add.rectangle(0, 0, width, height, 0x000000, 0.8); + bg.setOrigin(0); + this.settingsContainer.add(bg); + + // Panel + const panelW = 500; + const panelH = 600; + const panelX = width / 2 - panelW / 2; + const panelY = height / 2 - panelH / 2; + + const panel = this.add.rectangle(panelX, panelY, panelW, panelH, 0x1a1a2e, 1); + panel.setOrigin(0); + panel.setStrokeStyle(4, 0x00ff41); + this.settingsContainer.add(panel); + + // Title + const title = this.add.text(width / 2, panelY + 30, 'โš™๏ธ SETTINGS', { + fontSize: '36px', + fontFamily: 'Courier New', + color: '#00ff41', + fontStyle: 'bold' + }); + title.setOrigin(0.5, 0); + this.settingsContainer.add(title); + + // Language Section + this.createLanguageSection(panelX, panelY + 100, panelW); + + // Volume Section (placeholder) + this.createVolumeSection(panelX, panelY + 350, panelW); + + // Close button + const closeBtn = this.add.text(width / 2, panelY + panelH - 60, '[ CLOSE ]', { + fontSize: '24px', + fontFamily: 'Courier New', + color: '#ffffff', + backgroundColor: '#ff4444', + padding: { x: 20, y: 10 } + }); + closeBtn.setOrigin(0.5); + closeBtn.setInteractive({ useHandCursor: true }); + closeBtn.on('pointerover', () => closeBtn.setScale(1.1)); + closeBtn.on('pointerout', () => closeBtn.setScale(1.0)); + closeBtn.on('pointerdown', () => this.toggleSettingsMenu()); + this.settingsContainer.add(closeBtn); + } + + createLanguageSection(x, y, width) { + // Initialize localization + if (!window.i18n) { + window.i18n = new LocalizationSystem(); + } + + const sectionTitle = this.add.text(x + width / 2, y, '๐ŸŒ LANGUAGE / JEZIK', { + fontSize: '20px', + fontFamily: 'Courier New', + color: '#ffffff' + }); + sectionTitle.setOrigin(0.5, 0); + this.settingsContainer.add(sectionTitle); + + const languages = [ + { code: 'slo', flag: '๐Ÿ‡ธ๐Ÿ‡ฎ', name: 'Slovenลกฤina' }, + { code: 'en', flag: '๐Ÿ‡ฌ๐Ÿ‡ง', name: 'English' }, + { code: 'de', flag: '๐Ÿ‡ฉ๐Ÿ‡ช', name: 'Deutsch' }, + { code: 'it', flag: '๐Ÿ‡ฎ๐Ÿ‡น', name: 'Italiano' }, + { code: 'cn', flag: '๐Ÿ‡จ๐Ÿ‡ณ', name: 'ไธญๆ–‡' } + ]; + + const startY = y + 40; + const spacing = 45; + + languages.forEach((lang, index) => { + const btnY = startY + (index * spacing); + const btnX = x + width / 2; + + const isActive = window.i18n.getCurrentLanguage() === lang.code; + + const btn = this.add.text(btnX, btnY, `${lang.flag} ${lang.name}`, { + fontSize: '18px', + fontFamily: 'Courier New', + color: isActive ? '#00ff41' : '#ffffff', + backgroundColor: isActive ? '#333333' : '#222222', + padding: { x: 15, y: 8 } + }); + btn.setOrigin(0.5); + btn.setInteractive({ useHandCursor: true }); + + btn.on('pointerover', () => { + btn.setScale(1.05); + if (!isActive) btn.setBackgroundColor('#444444'); + }); + btn.on('pointerout', () => { + btn.setScale(1.0); + if (!isActive) btn.setBackgroundColor('#222222'); + }); + btn.on('pointerdown', () => { + window.i18n.setLanguage(lang.code); + // Refresh settings menu to update colors + this.toggleSettingsMenu(); + this.toggleSettingsMenu(); + }); + + this.settingsContainer.add(btn); + }); + } + + createVolumeSection(x, y, width) { + const sectionTitle = this.add.text(x + width / 2, y, '๐Ÿ”Š VOLUME', { + fontSize: '20px', + fontFamily: 'Courier New', + color: '#ffffff' + }); + sectionTitle.setOrigin(0.5, 0); + this.settingsContainer.add(sectionTitle); + + // Placeholder text + const placeholder = this.add.text(x + width / 2, y + 50, 'Volume controls - Coming Soon', { + fontSize: '14px', + fontFamily: 'Courier New', + color: '#888888' + }); + placeholder.setOrigin(0.5, 0); + this.settingsContainer.add(placeholder); + } } diff --git a/src/systems/CollectionSystem.js b/src/systems/CollectionSystem.js index f173ba2..3798129 100644 --- a/src/systems/CollectionSystem.js +++ b/src/systems/CollectionSystem.js @@ -47,8 +47,10 @@ class CollectionSystem { color: '#FFD700' }); - if (this.scene.soundManager) { - this.scene.soundManager.playSuccess(); // Reuse success sound + if (this.scene.soundManager && typeof this.scene.soundManager.playSuccess === 'function') { + this.scene.soundManager.playSuccess(); + } else if (this.scene.soundManager && typeof this.scene.soundManager.playHarvest === 'function') { + this.scene.soundManager.playHarvest(); // Fallback to harvest sound } } } diff --git a/src/systems/LocalizationSystem.js b/src/systems/LocalizationSystem.js new file mode 100644 index 0000000..2ba63f2 --- /dev/null +++ b/src/systems/LocalizationSystem.js @@ -0,0 +1,176 @@ +/** + * LOCALIZATION SYSTEM + * Multi-language support with JSON translation files + */ +class LocalizationSystem { + constructor() { + this.currentLang = 'en'; // Default language + this.translations = {}; + this.supportedLanguages = ['slo', 'en', 'de', 'it', 'cn']; + + // Load from localStorage + const savedLang = localStorage.getItem('novafarma_language'); + if (savedLang && this.supportedLanguages.includes(savedLang)) { + this.currentLang = savedLang; + } + + this.loadTranslations(); + } + + loadTranslations() { + // Embedded translations (inline for simplicity) + this.translations = { + 'slo': { + // UI + 'ui.inventory': 'Inventar', + 'ui.crafting': 'Izdelovanje', + 'ui.health': 'Zdravje', + 'ui.hunger': 'Lakota', + 'ui.oxygen': 'Kisik', + 'ui.day': 'Dan', + 'ui.season': 'Letni ฤas', + + // Items + 'item.wood': 'Les', + 'item.stone': 'Kamen', + 'item.seeds': 'Semena', + 'item.wheat': 'Pลกenica', + 'item.corn': 'Koruza', + + // Actions + 'action.plant': 'Posadi', + 'action.harvest': 'Poลพanji', + 'action.craft': 'Izdelaj', + 'action.build': 'Zgradi', + + // Seasons + 'season.spring': 'Pomlad', + 'season.summer': 'Poletje', + 'season.autumn': 'Jesen', + 'season.winter': 'Zima', + + // Messages + 'msg.demo_end': 'Demo konฤan! Hvala za igranje.', + 'msg.freezing': 'โ„๏ธ Zmrzujeลก!', + 'msg.overheating': '๐Ÿ”ฅ Pregrevanje!' + }, + 'en': { + // UI + 'ui.inventory': 'Inventory', + 'ui.crafting': 'Crafting', + 'ui.health': 'Health', + 'ui.hunger': 'Hunger', + 'ui.oxygen': 'Oxygen', + 'ui.day': 'Day', + 'ui.season': 'Season', + + // Items + 'item.wood': 'Wood', + 'item.stone': 'Stone', + 'item.seeds': 'Seeds', + 'item.wheat': 'Wheat', + 'item.corn': 'Corn', + + // Actions + 'action.plant': 'Plant', + 'action.harvest': 'Harvest', + 'action.craft': 'Craft', + 'action.build': 'Build', + + // Seasons + 'season.spring': 'Spring', + 'season.summer': 'Summer', + 'season.autumn': 'Autumn', + 'season.winter': 'Winter', + + // Messages + 'msg.demo_end': 'Demo Ended! Thanks for playing.', + 'msg.freezing': 'โ„๏ธ Freezing!', + 'msg.overheating': '๐Ÿ”ฅ Overheating!' + }, + 'de': { + 'ui.inventory': 'Inventar', + 'ui.crafting': 'Handwerk', + 'ui.health': 'Gesundheit', + 'season.spring': 'Frรผhling', + 'season.summer': 'Sommer', + 'season.autumn': 'Herbst', + 'season.winter': 'Winter' + }, + 'it': { + 'ui.inventory': 'Inventario', + 'ui.crafting': 'Artigianato', + 'season.spring': 'Primavera', + 'season.summer': 'Estate', + 'season.autumn': 'Autunno', + 'season.winter': 'Inverno' + }, + 'cn': { + 'ui.inventory': 'ๅบ“ๅญ˜', + 'ui.crafting': 'ๅˆถไฝœ', + 'season.spring': 'ๆ˜ฅๅคฉ', + 'season.summer': 'ๅคๅคฉ', + 'season.autumn': '็ง‹ๅคฉ', + 'season.winter': 'ๅ†ฌๅคฉ' + } + }; + } + + /** + * Get translated string + * @param {string} key - Translation key (e.g. 'ui.inventory') + * @param {string} fallback - Fallback text if translation missing + */ + t(key, fallback = key) { + const lang = this.translations[this.currentLang]; + if (lang && lang[key]) { + return lang[key]; + } + + // Fallback to English + const enLang = this.translations['en']; + if (enLang && enLang[key]) { + return enLang[key]; + } + + return fallback; + } + + /** + * Set current language + */ + setLanguage(langCode) { + if (this.supportedLanguages.includes(langCode)) { + this.currentLang = langCode; + localStorage.setItem('novafarma_language', langCode); + console.log(`๐ŸŒ Language changed to: ${langCode}`); + return true; + } + return false; + } + + getCurrentLanguage() { + return this.currentLang; + } + + getSupportedLanguages() { + return this.supportedLanguages.map(code => ({ + code, + name: this.getLanguageName(code) + })); + } + + getLanguageName(code) { + const names = { + 'slo': 'Slovenลกฤina', + 'en': 'English', + 'de': 'Deutsch', + 'it': 'Italiano', + 'cn': 'ไธญๆ–‡' + }; + return names[code] || code; + } +} + +// Global instance +window.LocalizationSystem = LocalizationSystem; diff --git a/src/systems/PlaytimeTrackerSystem.js b/src/systems/PlaytimeTrackerSystem.js new file mode 100644 index 0000000..1a8c35e --- /dev/null +++ b/src/systems/PlaytimeTrackerSystem.js @@ -0,0 +1,141 @@ +/** + * PLAYTIME TRACKER SYSTEM + * Tracks persistent stats: playtime, deaths, harvests, etc. + */ +class PlaytimeTrackerSystem { + constructor(scene) { + this.scene = scene; + + // Initialize stats + this.stats = { + playtimeSeconds: 0, + deaths: 0, + kills: 0, + harvests: 0, + plantings: 0, + distanceTraveled: 0, + moneyEarned: 0, + itemsCrafted: 0, + blocksPlaced: 0, + sessionStart: Date.now() + }; + + // Load from save + this.load(); + + // Update timer + this.updateTimer = 0; + this.saveInterval = 10000; // Save every 10 seconds + } + + update(delta) { + // Increment playtime + this.stats.playtimeSeconds += delta / 1000; + + // Periodic save + this.updateTimer += delta; + if (this.updateTimer >= this.saveInterval) { + this.updateTimer = 0; + this.save(); + } + } + + // Stat tracking methods + recordDeath() { + this.stats.deaths++; + this.save(); + } + + recordKill() { + this.stats.kills++; + } + + recordHarvest() { + this.stats.harvests++; + } + + recordPlanting() { + this.stats.plantings++; + } + + recordDistance(distance) { + this.stats.distanceTraveled += distance; + } + + recordMoneyEarned(amount) { + this.stats.moneyEarned += amount; + } + + recordCraft() { + this.stats.itemsCrafted++; + } + + recordBlockPlaced() { + this.stats.blocksPlaced++; + } + + // Formatted playtime + getPlaytimeFormatted() { + const hours = Math.floor(this.stats.playtimeSeconds / 3600); + const minutes = Math.floor((this.stats.playtimeSeconds % 3600) / 60); + const seconds = Math.floor(this.stats.playtimeSeconds % 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } else if (minutes > 0) { + return `${minutes}m ${seconds}s`; + } else { + return `${seconds}s`; + } + } + + // Session duration + getSessionDuration() { + const sessionMs = Date.now() - this.stats.sessionStart; + const sessionSec = Math.floor(sessionMs / 1000); + return sessionSec; + } + + // Save/Load + save() { + localStorage.setItem('novafarma_stats', JSON.stringify(this.stats)); + } + + load() { + const saved = localStorage.getItem('novafarma_stats'); + if (saved) { + try { + const data = JSON.parse(saved); + this.stats = { ...this.stats, ...data }; + this.stats.sessionStart = Date.now(); // Reset session timer + } catch (e) { + console.error('Failed to load stats:', e); + } + } + } + + reset() { + this.stats = { + playtimeSeconds: 0, + deaths: 0, + kills: 0, + harvests: 0, + plantings: 0, + distanceTraveled: 0, + moneyEarned: 0, + itemsCrafted: 0, + blocksPlaced: 0, + sessionStart: Date.now() + }; + this.save(); + } + + // Get all stats + getStats() { + return { + ...this.stats, + playtimeFormatted: this.getPlaytimeFormatted(), + sessionDuration: this.getSessionDuration() + }; + } +}