popravki
This commit is contained in:
241
CHANGELOG.md
241
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** 👹
|
#### 🏙️ **City Content & Combat Polish**
|
||||||
- Added Elite Zombie enemy type (dark red with glowing pink eyes)
|
- **Unique City Loot**
|
||||||
- 50 HP (2.5x more than normal zombies)
|
- Added `scrap_metal` (5 units, 80% chance) to city chests
|
||||||
- 50% faster movement speed
|
- Added `chips` (electronics, 2 units, 60% chance) to city chests
|
||||||
- Spawn in City area (15 elite zombies)
|
- Added `scrap_metal` (15 units) and `chips` (5 units, 90% chance) to elite chests
|
||||||
- Drop better loot: Scrap Metal + 50% chance for Chip
|
|
||||||
- Scale: 0.2 (smaller but deadlier)
|
|
||||||
|
|
||||||
#### **City Content** 🏙️
|
- **Combat Visual Feedback**
|
||||||
- **City Wall (Obzidje)**: 15x15 fortified city with stone walls
|
- `NPC.takeDamage(amount, attacker)` - Full implementation
|
||||||
- WALL_EDGE tiles on perimeter (solid collision)
|
- ✨ White flash effect on hit (100ms)
|
||||||
- Pavement interior with ruins
|
- 🎯 Knockback physics (0.5 tile pushback)
|
||||||
- **Treasure Chests**: 3 chests in ruins with random loot (2-4 items)
|
- 💥 Floating damage text (`-HP` in red)
|
||||||
- Interact with 'E' key to open
|
- 🎨 Color-coded health bars (green → orange → red)
|
||||||
- Loot: scrap, chips, wood, stone, bones
|
- 💀 Fade-out death animation (300ms)
|
||||||
- **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)
|
|
||||||
|
|
||||||
#### **Roads & Navigation** 🛣️
|
#### 🌡️ **Weather System v2.0 - Temperature & Survival**
|
||||||
- **Gray Stone Roads**: Connecting Farm (20,20) and City (65,65)
|
- **Seasonal Temperature System**
|
||||||
- Horizontal road from farm
|
- Spring: 15°C (safe)
|
||||||
- Vertical road to city
|
- Summer: 30°C base
|
||||||
- L-shaped path
|
- Autumn: 10°C (safe)
|
||||||
- **Signposts**: 3 wooden navigation markers
|
- Winter: -5°C base
|
||||||
- Farm signpost: → (points to city)
|
- Day/night variation: ±5°C (sine wave)
|
||||||
- City signpost: ← (points to farm)
|
|
||||||
- Crossroads signpost: ⇅ (both directions)
|
|
||||||
|
|
||||||
#### **Farm Starter Zone** 🌾
|
- **Survival Mechanics**
|
||||||
- **Cleared Farm Area**: 16x16 tiles at (20, 20)
|
- Cold damage: `Temp < 0°C` → -5 HP every 5s (without Winter Coat)
|
||||||
- All trees and rocks removed
|
- Heat damage: `Temp > 35°C` → -3 HP every 5s (without Summer Hat)
|
||||||
- Green grass terrain
|
- Protection items: `winter_coat`, `summer_hat`
|
||||||
- **Starter Resources**: Treasure chest with initial items
|
- Visual indicators: ❄️ Freezing! / 🔥 Overheating!
|
||||||
- **Fence Posts**: 4 corner markers for farm boundary
|
|
||||||
|
|
||||||
#### **Seasonal System** 🌸☀️🍂❄️
|
- **Greenhouse Building**
|
||||||
- **4 Seasons**: Spring, Summer, Autumn, Winter
|
- Recipe: 20 Glass + 15 Wood
|
||||||
- Each season lasts 7 in-game days
|
- Size: 2x2 tiles
|
||||||
- Visual overlays with season-specific colors
|
- Purpose: Enables winter farming
|
||||||
- **Season Indicators**: Emoji icons in UI clock
|
- Glass Crafting: Sand + Coal → Glass (Furnace, 3000ms)
|
||||||
- 🌸 Spring (green tint)
|
|
||||||
- ☀️ Summer (yellow tint)
|
|
||||||
- 🍂 Autumn (orange tint)
|
|
||||||
- ❄️ Winter (blue-white tint)
|
|
||||||
|
|
||||||
### 💥 Combat Polish
|
#### 🎮 **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
|
||||||
|
|
||||||
#### **Visual Effects**
|
#### ✨ **Visual Effects System**
|
||||||
- **White Flash**: Enemy flashes white → red when hit
|
- **New System:** `VisualEffectsSystem.js`
|
||||||
- **Knockback Effect**: Enemies get pushed back on impact
|
- `screenshake(intensity, duration)` - Camera shake
|
||||||
- **Floating Damage Numbers**: Red numbers float up showing damage dealt
|
- `createHitParticles(x, y, color)` - Sparks on hit
|
||||||
- Font: Courier New, 16px, bold
|
- `createExplosion(x, y, color)` - Explosion particles
|
||||||
- Fades out while rising
|
- `createDustPuff(x, y)` - Movement dust
|
||||||
|
- `flash(color, duration)` - Screen flash
|
||||||
|
- `fadeOut(duration, callback)` / `fadeIn(duration)` - Transitions
|
||||||
|
- Particle texture generator: `particle_white` (8x8 white circle)
|
||||||
|
|
||||||
### 🔒 Collision System
|
#### 🏗️ **Building System Updates**
|
||||||
|
- Added `greenhouse` to buildingsData
|
||||||
|
- Cost: `{ glass: 20, wood: 15 }`
|
||||||
|
- Size: 2x2 grid placement
|
||||||
|
|
||||||
#### **Tile Collision**
|
### 📁 FILES MODIFIED
|
||||||
- **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
|
|
||||||
|
|
||||||
#### **Decoration Collision**
|
```
|
||||||
- **Enhanced Pattern Matching**: Case-insensitive checks
|
src/
|
||||||
- **Blocked Objects**:
|
├── entities/
|
||||||
- All trees (contains "tree" or "sapling")
|
│ ├── NPC.js (+60 lines) - takeDamage(), die() methods
|
||||||
- All rocks (contains "rock" or "stone")
|
│ └── LootChest.js (+4 lines) - City loot tables
|
||||||
- All signposts (contains "signpost" or "sign")
|
├── systems/
|
||||||
- Structures: chest, spawner, ruin, arena, fence, house, gravestone, bush, hill
|
│ ├── 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
|
||||||
|
```
|
||||||
|
|
||||||
### ⚙️ Performance Optimizations
|
### 🎯 TASKS COMPLETED
|
||||||
|
|
||||||
#### **Rendering**
|
**Phase 8:**
|
||||||
- **60 FPS Target**: Explicit FPS configuration
|
- [x] City Content (Scrap metal, Chips)
|
||||||
- **Pixel-Perfect Positioning**: `Math.round()` for player x/y
|
- [x] Elite Zombies (already active)
|
||||||
- **Camera Lerp 0.1**: Smooth camera following
|
- [x] Combat Polish (White flash, Knockback)
|
||||||
- **NEAREST_NEIGHBOR Filtering**: Crisp pixel art (no blur)
|
- [x] World Details (Roads, Signposts - already done)
|
||||||
|
|
||||||
#### **Culling**
|
**Phase 13:**
|
||||||
- **Viewport Culling**: NPCs outside camera view aren't rendered
|
- [x] Weather System v2.0
|
||||||
- **Distance Culling**: NPCs far from player (>30-50 tiles) hidden
|
- [x] Seasonal temperatures
|
||||||
- **AI Skip**: Invisible NPCs skip AI updates
|
- [x] Temperature damage logic
|
||||||
|
- [x] Greenhouse building
|
||||||
|
- [x] Glass crafting recipe
|
||||||
|
|
||||||
### 🎨 New Assets
|
**Phase 14:**
|
||||||
- `elite_zombie.png` - Elite zombie sprite
|
- [x] Demo Mode (3-day limit)
|
||||||
- `chest.png` - Treasure chest
|
- [x] Visual Polish (VisualEffectsSystem)
|
||||||
- `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
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 KNOWN ISSUES
|
||||||
- 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
|
|
||||||
|
|
||||||
### 📝 Technical Changes
|
1. **NPC Sprite Transparency**
|
||||||
- Added `initializeFarmWorld()` method in GameScene
|
- AI-generated sprites show checkerboard pattern in some contexts
|
||||||
- Added `setSolid()` and `isSolid()` methods in TerrainSystem
|
- Chrome/Electron rendering issue with PNG alpha channels
|
||||||
- Enhanced `placeStructure()` to support chest, spawner, signposts
|
- Current workaround: Using AI sprites, considered low priority
|
||||||
- Added season tracking in WeatherSystem
|
|
||||||
- Improved NPC.takeDamage() with visual effects
|
2. **NPC.js Scaling**
|
||||||
- Added WALL_EDGE terrain type
|
- 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
|
||||||
|
|
||||||
|
### 📋 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)
|
**Session Duration:** ~2h 45min
|
||||||
- Initial release
|
**Lines of Code Added:** ~270
|
||||||
- Basic farm mechanics
|
**Systems Enhanced:** 5
|
||||||
- Zombie combat
|
**New Systems Created:** 1
|
||||||
- Day/night cycle
|
**Features Completed:** 12
|
||||||
- Save system
|
|
||||||
|
**Next Milestone:** Phase 13 completion (Localization + Entities)
|
||||||
|
|||||||
@@ -100,6 +100,8 @@
|
|||||||
<script src="src/systems/HybridSkillSystem.js"></script>
|
<script src="src/systems/HybridSkillSystem.js"></script>
|
||||||
<script src="src/systems/OceanSystem.js"></script>
|
<script src="src/systems/OceanSystem.js"></script>
|
||||||
<script src="src/systems/VisualEffectsSystem.js"></script>
|
<script src="src/systems/VisualEffectsSystem.js"></script>
|
||||||
|
<script src="src/systems/PlaytimeTrackerSystem.js"></script>
|
||||||
|
<script src="src/systems/LocalizationSystem.js"></script>
|
||||||
|
|
||||||
<!-- Multiplayer -->
|
<!-- Multiplayer -->
|
||||||
<!-- <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script> -->
|
<!-- <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script> -->
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ Pripravi se... NOČ PRIHAJA.`;
|
|||||||
fontSize: '16px', fill: '#666'
|
fontSize: '16px', fill: '#666'
|
||||||
}).setOrigin(1);
|
}).setOrigin(1);
|
||||||
|
|
||||||
|
// LANGUAGE SELECTOR
|
||||||
|
this.createLanguageSelector(width, height);
|
||||||
|
|
||||||
// Input to skip
|
// Input to skip
|
||||||
this.input.keyboard.on('keydown-SPACE', () => {
|
this.input.keyboard.on('keydown-SPACE', () => {
|
||||||
this.scene.start('GameScene');
|
this.scene.start('GameScene');
|
||||||
@@ -66,4 +69,80 @@ Pripravi se... NOČ PRIHAJA.`;
|
|||||||
this.scene.start('GameScene');
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1541,4 +1541,177 @@ class UIScene extends Phaser.Scene {
|
|||||||
onComplete: () => text.destroy()
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ class CollectionSystem {
|
|||||||
color: '#FFD700'
|
color: '#FFD700'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.scene.soundManager) {
|
if (this.scene.soundManager && typeof this.scene.soundManager.playSuccess === 'function') {
|
||||||
this.scene.soundManager.playSuccess(); // Reuse success sound
|
this.scene.soundManager.playSuccess();
|
||||||
|
} else if (this.scene.soundManager && typeof this.scene.soundManager.playHarvest === 'function') {
|
||||||
|
this.scene.soundManager.playHarvest(); // Fallback to harvest sound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
176
src/systems/LocalizationSystem.js
Normal file
176
src/systems/LocalizationSystem.js
Normal file
@@ -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;
|
||||||
141
src/systems/PlaytimeTrackerSystem.js
Normal file
141
src/systems/PlaytimeTrackerSystem.js
Normal file
@@ -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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user