diff --git a/Dockerfile b/Dockerfile index 5eeeabf..f0eab75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,9 @@ COPY icon-*.png ./ # Copy monster images COPY mapgameimgs ./mapgameimgs +# Copy game music +COPY mapgamemusic ./mapgamemusic + # Copy .well-known directory for app verification COPY .well-known ./.well-known diff --git a/admin.html b/admin.html index 5e47de7..a1d5875 100644 --- a/admin.html +++ b/admin.html @@ -137,6 +137,15 @@ background: #d32f2f; } + .btn-warning { + background: #ff9800; + color: #fff; + } + + .btn-warning:hover { + background: #f57c00; + } + .btn-small { padding: 6px 12px; font-size: 0.85rem; @@ -827,6 +836,16 @@ +
+
+ + + Meters walked to regen 1 MP (0 = disabled) +
+
+ +
+
@@ -993,6 +1012,7 @@
+
@@ -2288,6 +2308,20 @@ } } + async function resetUserHomeBase() { + const id = document.getElementById('userId').value; + if (!confirm('Are you sure you want to reset this user\'s home base? They will need to set a new one.')) return; + + try { + await api(`/api/admin/users/${id}/home-base`, { method: 'DELETE' }); + showToast('Home base reset'); + closeUserModal(); + loadUsers(); + } catch (e) { + showToast('Failed to reset home base: ' + e.message, 'error'); + } + } + document.getElementById('userForm').addEventListener('submit', async (e) => { e.preventDefault(); @@ -2331,6 +2365,7 @@ document.getElementById('setting-maxMonstersPerPlayer').value = settings.maxMonstersPerPlayer || 10; document.getElementById('setting-xpMultiplier').value = settings.xpMultiplier || 1.0; document.getElementById('setting-combatEnabled').checked = settings.combatEnabled !== 'false' && settings.combatEnabled !== false; + document.getElementById('setting-mpRegenDistance').value = settings.mpRegenDistance || 5; } catch (e) { showToast('Failed to load settings: ' + e.message, 'error'); } @@ -2345,7 +2380,8 @@ monsterSpawnDistance: parseInt(document.getElementById('setting-monsterSpawnDistance').value) || 10, maxMonstersPerPlayer: parseInt(document.getElementById('setting-maxMonstersPerPlayer').value) || 10, xpMultiplier: parseFloat(document.getElementById('setting-xpMultiplier').value) || 1.0, - combatEnabled: document.getElementById('setting-combatEnabled').checked + combatEnabled: document.getElementById('setting-combatEnabled').checked, + mpRegenDistance: parseInt(document.getElementById('setting-mpRegenDistance').value) || 5 }; try { diff --git a/database.js b/database.js index b5b1b4f..727da12 100644 --- a/database.js +++ b/database.js @@ -1823,6 +1823,19 @@ class HikeMapDB { return result; } + // Admin: Reset user home base + resetUserHomeBase(userId) { + const stmt = this.db.prepare(` + UPDATE rpg_stats SET + home_base_lat = NULL, + home_base_lng = NULL, + last_home_set = NULL, + updated_at = datetime('now') + WHERE user_id = ? + `); + return stmt.run(userId); + } + // Game settings methods getSetting(key) { const stmt = this.db.prepare(`SELECT value FROM game_settings WHERE key = ?`); @@ -1861,7 +1874,8 @@ class HikeMapDB { monsterSpawnDistance: 10, // Meters player must move for new spawns (10m) maxMonstersPerPlayer: 10, xpMultiplier: 1.0, - combatEnabled: true + combatEnabled: true, + mpRegenDistance: 5 // Meters walked per 1 MP regenerated }; for (const [key, value] of Object.entries(defaults)) { diff --git a/docker-compose.yml b/docker-compose.yml index a56bcb5..2c846e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - ./database.js:/app/database.js:ro - ./:/app/data - ./mapgameimgs:/app/mapgameimgs + - ./mapgamemusic:/app/mapgamemusic restart: unless-stopped environment: - NODE_ENV=production diff --git a/index.html b/index.html index 338ce98..222dd7f 100644 --- a/index.html +++ b/index.html @@ -2432,6 +2432,99 @@ 0%, 100% { box-shadow: 0 0 0 0 rgba(243, 156, 18, 0.7); } 50% { box-shadow: 0 0 0 15px rgba(243, 156, 18, 0); } } + .music-toggle-btn { + position: fixed; + bottom: 160px; + right: 15px; + width: 50px; + height: 50px; + border-radius: 50%; + background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%); + border: 3px solid #fff; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + cursor: pointer; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + transition: all 0.2s; + } + .music-toggle-btn:hover { + transform: scale(1.1); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); + } + .music-toggle-btn.muted { + background: linear-gradient(135deg, #7f8c8d 0%, #606c70 100%); + } + + /* WASD Control Pad */ + .wasd-controls { + position: fixed; + bottom: 20px; + left: 20px; + z-index: 2100; + display: grid; + grid-template-columns: 50px 50px 50px; + grid-template-rows: 50px 50px; + gap: 4px; + opacity: 0.85; + } + .wasd-controls.hidden { + display: none; + } + .wasd-btn { + width: 50px; + height: 50px; + border-radius: 8px; + background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%); + border: 2px solid #4a6785; + color: #fff; + font-size: 18px; + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.4); + transition: all 0.1s; + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; + } + .wasd-btn:active { + transform: scale(0.95); + background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%); + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + } + .wasd-btn.w-btn { + grid-column: 2; + grid-row: 1; + } + .wasd-btn.a-btn { + grid-column: 1; + grid-row: 2; + } + .wasd-btn.s-btn { + grid-column: 2; + grid-row: 2; + } + .wasd-btn.d-btn { + grid-column: 3; + grid-row: 2; + } + .wasd-mode-indicator { + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%); + background: rgba(0, 0, 0, 0.7); + color: #4fc3f7; + padding: 3px 8px; + border-radius: 4px; + font-size: 10px; + white-space: nowrap; + } /* Home Base Marker */ .home-base-marker { @@ -2658,8 +2751,20 @@ + + + + +
+
TEST MODE
+ + + + +
+ - +