From 9e0e7b532468b55ce8f58620346a06cd70979396 Mon Sep 17 00:00:00 2001 From: MaksSlyzar Date: Tue, 21 Apr 2026 08:48:52 +0300 Subject: [PATCH] Added CombatService, Quests. Fixed CraftModal. --- client/src/services/GameDataManager.js | 25 +- .../components/DungeonScreen.css | 972 ++++-------------- .../components/DungeonScreen.jsx | 316 +++--- .../GameInterface/components/Navigation.css | 1 + .../GameInterface/components/Navigation.jsx | 1 + .../views/GameInterface/tabs/QuestsTab.jsx | 182 +++- .../tabs/components/CraftModal.css | 343 +++--- .../tabs/components/CraftModal.jsx | 202 ++-- .../tabs/components/ItemModal.css | 19 + .../tabs/components/ItemModal.jsx | 25 +- .../GameInterface/tabs/styles/QuestsTab.css | 211 ++++ .../core/assets/languages/en_US.json | 79 +- .../original/assets/languages/en_US.json | 478 ++++----- .../tutorial/tutorial_boss_hostile.json | 2 +- .../hostiles/tutorial/tutorial_hostile.json | 2 +- .../rooms/tutorial/tutorial_enemy_room.json | 5 +- .../{weapns => weapons}/basic_weapon.json | 9 +- .../original/data/quests/starter_kit.json | 55 + .../original/data/quests/tutorial_boss.json | 30 + game-server/src/config/db.js | 2 +- game-server/src/game/CombatService.js | 125 +++ game-server/src/game/DatapackLoader.js | 28 +- game-server/src/game/DungeonManager.js | 204 ++-- game-server/src/game/QuestsManager.js | 163 +++ game-server/src/models/PlayerQuest.js | 41 + game-server/src/models/index.js | 6 + .../src/sockets/handlers/connectionHandler.js | 7 +- .../src/sockets/handlers/dungeonHandler.js | 52 +- .../src/sockets/handlers/questsHandler.js | 89 ++ game-server/src/sockets/socket.js | 2 + 30 files changed, 2094 insertions(+), 1582 deletions(-) rename game-server/datapacks/original/data/items/equipment/personal/{weapns => weapons}/basic_weapon.json (71%) create mode 100644 game-server/datapacks/original/data/quests/starter_kit.json create mode 100644 game-server/datapacks/original/data/quests/tutorial_boss.json create mode 100644 game-server/src/game/CombatService.js create mode 100644 game-server/src/game/QuestsManager.js create mode 100644 game-server/src/models/PlayerQuest.js create mode 100644 game-server/src/sockets/handlers/questsHandler.js diff --git a/client/src/services/GameDataManager.js b/client/src/services/GameDataManager.js index a23f486..a6fdcc7 100644 --- a/client/src/services/GameDataManager.js +++ b/client/src/services/GameDataManager.js @@ -6,6 +6,7 @@ class GameDataManager { this.dungeons = new Map(); this.hostiles = new Map(); this.rooms = new Map(); + this.quests = new Map(); this.translations = {}; this.manifest = {}; this.currentLang = localStorage.getItem("selected_lang") || "en_US"; @@ -31,10 +32,14 @@ class GameDataManager { if (Array.isArray(data.rooms)) { data.rooms.forEach((r) => this.rooms.set(r.id, r)); } + if (Array.isArray(data.quests)) { + data.quests.forEach((q) => this.quests.set(q.id, q)); + } + + console.log(this.quests); if (data.languages) { this.translations = data.languages; } - if (data.manifest) { this.manifest = data.manifest; } @@ -191,6 +196,24 @@ class GameDataManager { }; } + getQuest(id) { + const quest = this.quests.get(id); + if (!quest) return null; + return { + ...quest, + displayName: this.t(quest.displayName), + description: this.t(quest.description), + objectives: (quest.objectives || []).map((obj) => ({ + ...obj, + description: obj.description ? this.t(obj.description) : "", + })), + }; + } + + getAllQuests() { + return Array.from(this.quests.values()).map((q) => this.getQuest(q.id)); + } + setLanguage(langCode) { if (this.translations[langCode]) { this.currentLang = langCode; diff --git a/client/src/views/GameInterface/components/DungeonScreen.css b/client/src/views/GameInterface/components/DungeonScreen.css index 401a82d..e605352 100644 --- a/client/src/views/GameInterface/components/DungeonScreen.css +++ b/client/src/views/GameInterface/components/DungeonScreen.css @@ -4,7 +4,7 @@ height: 100vh; background: radial-gradient(circle at center, #0a1118 0%, #05080c 100%); padding: 30px; - gap: 25px; + gap: 20px; font-family: "Space Mono", monospace; color: #e0e6ed; box-sizing: border-box; @@ -14,845 +14,317 @@ .dungeon-header { display: flex; justify-content: space-between; - align-items: flex-end; + align-items: center; border-bottom: 1px solid rgba(0, 212, 255, 0.3); padding-bottom: 15px; - position: relative; flex-shrink: 0; } -.dungeon-header::after { - content: ""; - position: absolute; - bottom: -1px; - left: 0; - width: 60px; - height: 3px; - background: #00d4ff; - box-shadow: 0 0 15px #00d4ff; +.turn-progress-container { + width: 200px; + background: rgba(0, 0, 0, 0.5); + padding: 8px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); } -.progress-text { - font-size: 0.75rem; +.turn-label { + font-size: 9px; + font-family: "Orbitron", sans-serif; letter-spacing: 2px; - color: #00d4ff; - font-weight: bold; + margin-bottom: 5px; + text-align: center; } -.progress-bar { - width: 250px; - height: 6px; +.turn-timer-bar { + height: 4px; background: rgba(255, 255, 255, 0.05); - margin-top: 8px; - border-radius: 3px; overflow: hidden; } -.progress-bar .fill { +.turn-timer-fill { height: 100%; - background: linear-gradient(90deg, #00d4ff, #0088ff); - box-shadow: 0 0 10px rgba(0, 212, 255, 0.5); - transition: width 0.5s ease-in-out; } - -.dungeon-title-area { - text-align: right; +.player-phase .turn-label { + color: #00d2ff; } - -.dungeon-name { - font-family: "Orbitron", sans-serif; - font-size: 1.4rem; - font-weight: 900; - letter-spacing: 1px; - text-transform: uppercase; - color: #fff; +.player-phase .turn-timer-fill { + background: #00d2ff; + box-shadow: 0 0 10px #00d2ff; } - -.dungeon-status-tag { - font-size: 0.7rem; +.enemy-phase .turn-label { color: #ff4444; - animation: blink 1.5s infinite; +} +.enemy-phase .turn-timer-fill { + background: #ff4444; + box-shadow: 0 0 10px #ff4444; } -.battle-layout { - display: grid; - grid-template-columns: 1.2fr 1fr; +.battle-arena { flex: 1; - gap: 30px; + display: flex; + align-items: center; + justify-content: center; min-height: 0; } -.enemy-display { - position: relative; - height: 100%; - min-height: 0; +.mobs-grid { + display: flex; + gap: 20px; + flex-wrap: wrap; + justify-content: center; } .enemy-card { - background: linear-gradient( - 180deg, - rgba(255, 68, 68, 0.08) 0%, - rgba(0, 0, 0, 0) 100% - ); - border: 1px solid rgba(255, 68, 68, 0.2); - border-radius: 4px; - height: 100%; + width: 180px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 20px; display: flex; flex-direction: column; align-items: center; - justify-content: center; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + transition: all 0.3s; position: relative; - overflow: hidden; } -.enemy-card::before { - content: ""; +.enemy-card.attacking { + border-color: #ff4444; + transform: scale(1.05); + box-shadow: 0 0 20px rgba(255, 68, 68, 0.4); + z-index: 10; +} + +.enemy-action-progress { position: absolute; top: 0; left: 0; - right: 0; - bottom: 0; - background: repeating-linear-gradient( - 0deg, - transparent, - transparent 2px, - rgba(255, 255, 255, 0.02) 3px - ); - pointer-events: none; + width: 100%; + height: 4px; + background: rgba(255, 0, 0, 0.2); } -.enemy-card.defeated { - filter: grayscale(1) brightness(0.5); - border-color: rgba(160, 172, 186, 0.2); - background: rgba(0, 0, 0, 0.4); -} - -.threat-tag { - color: #ff4444; - font-family: "Orbitron", sans-serif; - font-size: 0.8rem; - letter-spacing: 4px; - margin-bottom: 30px; - text-shadow: 0 0 10px rgba(255, 68, 68, 0.5); -} - -.enemy-icon { - font-size: 6rem; - color: #fff; - margin-bottom: 25px; - filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.2)); -} - -.enemy-name { - font-family: "Orbitron", sans-serif; - font-size: 1.8rem; - margin: 0 0 20px 0; - color: #fff; - text-transform: uppercase; -} - -.enemy-hp-container { - width: 60%; - margin-bottom: 30px; -} - -.hp-label { - font-size: 0.6rem; - color: #a0acba; - margin-bottom: 6px; - text-align: center; - letter-spacing: 1px; -} - -.hp-bar-mini { - height: 8px; - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(255, 255, 255, 0.1); - padding: 2px; -} - -.hp-fill-mini { +.inner-progress { height: 100%; background: #ff4444; - box-shadow: 0 0 15px rgba(255, 68, 68, 0.6); - transition: width 0.3s ease-out; + width: 0%; + animation: enemyCharge 2s linear forwards; } -.enemy-info-footer { - display: flex; +@keyframes enemyCharge { + from { + width: 0%; + } + to { + width: 100%; + } +} + +.enemy-card.targetable:hover { + border-color: #00d2ff; + background: rgba(0, 210, 255, 0.05); + cursor: crosshair; +} + +.enemy-hp-mini { + width: 100%; + height: 4px; + background: #000; + margin-bottom: 15px; +} + +.enemy-hp-mini .fill { + height: 100%; + background: #ff4444; + transition: width 0.3s; +} + +.player-section { + display: grid; + grid-template-columns: 300px 1fr; gap: 20px; - font-size: 0.75rem; - color: rgba(160, 172, 186, 0.7); + height: 150px; + flex-shrink: 0; +} + +.player-hp-main { + background: rgba(0, 0, 0, 0.3); + padding: 15px; + border: 1px solid rgba(255, 255, 255, 0.1); + transition: all 0.2s; +} + +.player-hp-main.taking-damage { + animation: playerShake 0.3s infinite; + border-color: #ff4444; +} + +@keyframes playerShake { + 0% { + transform: translate(1px, 1px); + } + 25% { + transform: translate(-2px, -1px); + } + 50% { + transform: translate(-1px, 2px); + } + 75% { + transform: translate(2px, 1px); + } + 100% { + transform: translate(0, 0); + } +} + +.hp-bar-large { + height: 20px; + background: #000; + margin-top: 10px; +} +.hp-bar-large .fill { + height: 100%; + background: linear-gradient(90deg, #ff416c, #ff4b2b); + transition: width 0.4s ease-out; } .combat-log-wrapper { - display: flex; - flex-direction: column; - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(26, 38, 56, 0.8); - min-height: 0; - max-height: 100%; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 212, 255, 0.1); overflow: hidden; } - -.log-header { - background: rgba(26, 38, 56, 0.5); - padding: 10px 15px; - font-size: 0.7rem; - font-weight: bold; - letter-spacing: 1px; - color: #00d4ff; - border-bottom: 1px solid rgba(26, 38, 56, 0.8); - flex-shrink: 0; -} - .combat-log { - flex: 1; - padding: 20px; - font-size: 0.8rem; - display: flex; - flex-direction: column; - gap: 10px; + padding: 15px; + height: 100%; overflow-y: auto; - scrollbar-width: thin; - scrollbar-color: #1a2638 transparent; - min-height: 0; + font-size: 0.75rem; } - .log-entry { - line-height: 1.4; + margin-bottom: 5px; color: #a0acba; - animation: slideIn 0.2s ease-out; - word-break: break-all; } - .log-arrow { - color: #00d4ff; - margin-right: 8px; - font-weight: bold; -} - -.dungeon-controls { - display: flex; - gap: 20px; - height: 70px; - flex-shrink: 0; + color: #00d2ff; + margin-right: 5px; } .ctrl-btn { - flex: 1; + width: 100%; + height: 60px; + background: #00d4ff; border: none; font-family: "Orbitron", sans-serif; font-weight: 900; cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - font-size: 1rem; - letter-spacing: 2px; clip-path: polygon(10px 0%, 100% 0%, calc(100% - 10px) 100%, 0% 100%); - transition: all 0.2s; } -.ctrl-btn.combat { - background: #ff4444; - color: #000; - box-shadow: 0 0 20px rgba(255, 68, 68, 0.3); -} -.ctrl-btn.loot { - background: #ffaa00; - color: #000; - box-shadow: 0 0 20px rgba(255, 170, 0, 0.3); -} -.ctrl-btn.next { - background: #00d4ff; - color: #000; - box-shadow: 0 0 20px rgba(0, 212, 255, 0.3); +.enemy-card.defeated { + filter: grayscale(1) brightness(0.4); } -.ctrl-btn:hover { - filter: brightness(1.2); - transform: scale(1.02); +.enemy-card.targetable { + cursor: pointer; + pointer-events: all; } -.ctrl-btn:active { - transform: scale(0.98); +.hp-bar-large .fill, +.enemy-hp-mini .fill { + transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); } -.empty-room { - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: rgba(0, 212, 255, 0.4); - gap: 15px; - border: 1px dashed rgba(0, 212, 255, 0.2); +/* Рамка для вибраного моба */ +.enemy-card.selected { + border-color: #00d2ff; + box-shadow: + 0 0 15px rgba(0, 210, 255, 0.4), + inset 0 0 10px rgba(0, 210, 255, 0.2); + transform: translateY(-5px); } -.empty-room i { - font-size: 3rem; +.enemy-card.selectable:hover:not(.defeated) { + cursor: crosshair; + border-color: rgba(0, 210, 255, 0.5); } -@keyframes blink { - 0%, - 100% { - opacity: 1; +/* Іконка прицілу */ +.target-aim { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 40px; + color: rgba(0, 210, 255, 0.3); + pointer-events: none; + z-index: 10; + animation: pulseAim 1.5s infinite; +} + +@keyframes pulseAim { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 0.3; } 50% { + transform: translate(-50%, -50%) scale(1.1); + opacity: 0.6; + } + 100% { + transform: translate(-50%, -50%) scale(1); opacity: 0.3; } } -@keyframes slideIn { - from { - opacity: 0; - transform: translateX(-10px); - } - to { - opacity: 1; - transform: translateX(0); - } +/* Контейнер для логу та кнопки */ +.combat-interface-row { + display: flex; + gap: 15px; + height: 120px; + margin-top: 10px; } -.custom-scroll::-webkit-scrollbar { - width: 4px; +.combat-log-wrapper { + flex: 1; } -.custom-scroll::-webkit-scrollbar-track { + +/* Стильна кнопка атаки */ +.btn-execute-combat { + width: 180px; + background: rgba(255, 0, 60, 0.1); + border: 1px solid #ff003c; + color: #ff003c; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + font-family: "Orbitron", sans-serif; + transition: all 0.2s; + position: relative; + overflow: hidden; +} + +.btn-execute-combat:hover:not(.disabled) { + background: #ff003c; + color: #000; + box-shadow: 0 0 20px rgba(255, 0, 60, 0.4); +} + +.btn-execute-combat.disabled { + opacity: 0.3; + border-color: #444; + color: #444; + cursor: not-allowed; background: transparent; } -.custom-scroll::-webkit-scrollbar-thumb { - background: #1a2638; - border-radius: 2px; -} -.enemy-info-footer { - display: flex; - justify-content: space-between; - width: 80%; - margin-top: auto; - padding-bottom: 20px; - font-size: 0.7rem; - text-transform: uppercase; - letter-spacing: 1px; -} - -.enemy-card.taking-damage { - animation: shake 0.2s ease-in-out; - border-color: #ffffff; - background: rgba(255, 255, 255, 0.1); -} - -@keyframes shake { - 0% { - transform: translateX(0); - } - 25% { - transform: translateX(-5px) rotate(-1deg); - } - 50% { - transform: translateX(5px) rotate(1deg); - } - 75% { - transform: translateX(-5px); - } - 100% { - transform: translateX(0); - } -} - -.combat-log-wrapper { - min-width: 300px; -} - -.environment-panel { - background: rgba(0, 20, 40, 0.6); - border: 1px solid rgba(0, 255, 255, 0.2); - margin-bottom: 15px; - padding: 12px; - display: flex; - flex-direction: column; - position: relative; - flex-shrink: 0; -} - -.env-header { - font-size: 0.65rem; - color: #00ffff; - opacity: 0.7; - letter-spacing: 1.5px; - margin-bottom: 8px; -} - -.env-info { - display: flex; - align-items: center; - gap: 12px; -} - -.env-icon { - width: 36px; - height: 36px; - background: rgba(0, 255, 255, 0.1); - border: 1px solid rgba(0, 255, 255, 0.2); - display: flex; - align-items: center; - justify-content: center; - color: #00ffff; - font-size: 1.1rem; -} - -.env-details .env-id { - font-family: "Orbitron", sans-serif; - font-size: 0.8rem; - color: #fff; -} - -.env-details .env-type { - font-size: 0.6rem; - color: #888; - margin-top: 2px; -} - -.card-id { - opacity: 0.4; - font-size: 0.6rem; -} - -.dungeon-active-screen { - display: flex; - flex-direction: column; - height: 100vh; - background: radial-gradient(circle at center, #0a1118 0%, #05080c 100%); - padding: 30px; - gap: 25px; - font-family: "Space Mono", monospace; - color: #e0e6ed; - box-sizing: border-box; - overflow: hidden; -} - -.dungeon-header { - display: flex; - justify-content: space-between; - align-items: flex-end; - border-bottom: 1px solid rgba(0, 212, 255, 0.3); - padding-bottom: 15px; - position: relative; - flex-shrink: 0; -} - -.dungeon-header::after { - content: ""; - position: absolute; - bottom: -1px; - left: 0; - width: 60px; - height: 3px; - background: #00d4ff; - box-shadow: 0 0 15px #00d4ff; -} - -.progress-text { - font-size: 0.75rem; - letter-spacing: 2px; - color: #00d4ff; - font-weight: bold; -} - -.progress-bar { - width: 250px; - height: 6px; - background: rgba(255, 255, 255, 0.05); - margin-top: 8px; - border-radius: 3px; - overflow: hidden; -} - -.progress-bar .fill { - height: 100%; - background: linear-gradient(90deg, #00d4ff, #0088ff); - box-shadow: 0 0 10px rgba(0, 212, 255, 0.5); - transition: width 0.5s ease-in-out; -} - -.dungeon-title-area { - text-align: right; -} - -.dungeon-name { - font-family: "Orbitron", sans-serif; - font-size: 1.4rem; - font-weight: 900; - letter-spacing: 1px; - text-transform: uppercase; - color: #fff; -} - -.dungeon-status-tag { - font-size: 0.7rem; - color: #ff4444; - animation: blink 1.5s infinite; -} - -.battle-layout { - display: grid; - grid-template-columns: 1.2fr 1fr; - flex: 1; - gap: 30px; - min-height: 0; -} - -.enemy-display { - position: relative; - height: 100%; - min-height: 0; -} - -.enemy-card { - background: linear-gradient( - 180deg, - rgba(255, 68, 68, 0.08) 0%, - rgba(0, 0, 0, 0) 100% - ); - border: 1px solid rgba(255, 68, 68, 0.2); - border-radius: 4px; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; -} - -.enemy-card.defeated { - filter: grayscale(1) brightness(0.5); - border-color: rgba(160, 172, 186, 0.2); - background: rgba(0, 0, 0, 0.4); -} - -.threat-tag { - color: #ff4444; - font-family: "Orbitron", sans-serif; - font-size: 0.8rem; - letter-spacing: 4px; - margin-bottom: 30px; - text-shadow: 0 0 10px rgba(255, 68, 68, 0.5); -} - -.enemy-icon { - font-size: 6rem; - color: #fff; - margin-bottom: 25px; - filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.2)); -} - -.enemy-name { - font-family: "Orbitron", sans-serif; - font-size: 1.8rem; - margin: 0 0 20px 0; - color: #fff; - text-transform: uppercase; -} - -.enemy-hp-container { - width: 60%; - margin-bottom: 30px; -} - -.hp-label { - font-size: 0.6rem; - color: #a0acba; - margin-bottom: 6px; - text-align: center; - letter-spacing: 1px; -} - -.hp-bar-mini { - height: 8px; - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(255, 255, 255, 0.1); - padding: 2px; -} - -.hp-fill-mini { - height: 100%; - background: #ff4444; - box-shadow: 0 0 15px rgba(255, 68, 68, 0.6); - transition: width 0.3s ease-out; -} - -.combat-log-wrapper { - display: flex; - flex-direction: column; - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(26, 38, 56, 0.8); - min-height: 0; - max-height: 100%; - overflow: hidden; -} - -.log-header { - background: rgba(26, 38, 56, 0.5); - padding: 10px 15px; - font-size: 0.7rem; - font-weight: bold; - letter-spacing: 1px; - color: #00d4ff; - border-bottom: 1px solid rgba(26, 38, 56, 0.8); - flex-shrink: 0; -} - -.combat-log { - flex: 1; - padding: 20px; - font-size: 0.8rem; - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - scrollbar-width: thin; - min-height: 0; -} - -.log-entry { - line-height: 1.4; - color: #a0acba; - word-break: break-all; -} - -.log-arrow { - color: #00d4ff; - margin-right: 8px; - font-weight: bold; -} - -.dungeon-controls { - display: flex; - gap: 20px; - height: 70px; - flex-shrink: 0; -} - -.ctrl-btn { - flex: 1; - border: none; - font-family: "Orbitron", sans-serif; - font-weight: 900; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 12px; +.btn-glitch-content { font-size: 1rem; - letter-spacing: 2px; - clip-path: polygon(10px 0%, 100% 0%, calc(100% - 10px) 100%, 0% 100%); - transition: all 0.2s; + font-weight: bold; + letter-spacing: 1px; } -.ctrl-btn.combat { - background: #ff4444; - color: #000; -} -.ctrl-btn.loot { - background: #ffaa00; - color: #000; -} -.ctrl-btn.next { - background: #00d4ff; - color: #000; -} - -.environment-panel { - background: rgba(0, 20, 40, 0.6); - border: 1px solid rgba(0, 255, 255, 0.2); - padding: 12px; - display: flex; - flex-direction: column; - flex-shrink: 0; -} - -.env-info { - display: flex; - align-items: center; - gap: 12px; -} - -@media screen and (max-width: 768px) { - .dungeon-active-screen { - padding: 15px; - gap: 15px; - height: 100%; - overflow-y: auto; - } - - .dungeon-header { - flex-direction: column; - align-items: flex-start; - gap: 10px; - } - - .progress-bar { - width: 100%; - } - - .dungeon-title-area { - text-align: left; - width: 100%; - } - - .battle-layout { - grid-template-columns: 1fr; - display: flex; - flex-direction: column; - gap: 15px; - flex: none; - } - - .enemy-display { - min-height: 300px; - } - - .enemy-icon { - font-size: 4rem; - } - - .enemy-name { - font-size: 1.3rem; - } - - .combat-log-wrapper { - min-width: 100%; - max-height: 250px; - } - - .dungeon-controls { - height: 60px; - position: sticky; - bottom: 0; - background: #05080c; - padding-top: 10px; - margin-top: auto; - } - - .ctrl-btn { - font-size: 0.8rem; - letter-spacing: 1px; - } -} - -@media screen and (max-width: 480px) { - .enemy-display { - min-height: 260px; - } - - .enemy-hp-container { - width: 90%; - } - - .ctrl-btn { - gap: 5px; - font-size: 0.75rem; - } -} - -.enemy-info-footer { - display: flex; - align-items: center; - justify-content: space-between; - width: 90%; - margin-top: auto; - padding-bottom: 15px; - gap: 10px; -} - -.card-id { - opacity: 0.4; +.btn-sub-text { font-size: 0.6rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 150px; -} - -.enemy-info-footer span:first-child { - white-space: nowrap;/ -} - - -@media screen and (max-width: 768px) { - .enemy-display { - min-height: 220px; - flex: 0 0 auto; - } - - .enemy-card { - padding: 10px; - } - - .enemy-icon { - font-size: 3rem; - margin-bottom: 10px; - } - - .enemy-name { - font-size: 1.1rem; - margin-bottom: 10px; - } - - .threat-tag { - margin-bottom: 10px; - font-size: 0.65rem; - letter-spacing: 2px; - } - - .enemy-hp-container { - margin-bottom: 15px; - } - - .combat-log-wrapper { - max-height: 180px; - } - - .log-header { - padding: 6px 12px; - font-size: 0.6rem; - } - - .combat-log { - padding: 10px; - gap: 4px; - } - - .log-entry { - font-size: 0.7rem; - line-height: 1.2; - } - - .log-arrow { - margin-right: 4px; - } - - .enemy-info-footer { - padding-bottom: 10px; - font-size: 0.6rem; - } -} - -@media screen and (max-width: 400px) { - .enemy-display { - min-height: 190px; - } - - .enemy-icon { - font-size: 2.5rem; - } - - .combat-log-wrapper { - max-height: 150px; - } + margin-top: 4px; + opacity: 0.8; } diff --git a/client/src/views/GameInterface/components/DungeonScreen.jsx b/client/src/views/GameInterface/components/DungeonScreen.jsx index a4dd728..3481c63 100644 --- a/client/src/views/GameInterface/components/DungeonScreen.jsx +++ b/client/src/views/GameInterface/components/DungeonScreen.jsx @@ -5,68 +5,89 @@ import DungeonFinish from "../tabs/components/DungeonFinish.jsx"; const DungeonScreen = ({ session, socket }) => { const [roomData, setRoomData] = useState(session.room); - const [hostiles, setHostiles] = useState(session.hostiles || []); const [roomIndex, setRoomIndex] = useState(session.roomIndex); - const [enemyHp, setEnemyHp] = useState(null); - const [isEnemyDefeated, setIsEnemyDefeated] = useState(false); - const [isLooted, setIsLooted] = useState(false); + const [battle, setBattle] = useState(session.battle || null); + const [timeLeft, setTimeLeft] = useState(10); const [summary, setSummary] = useState(null); + const [activeAttacker, setActiveAttacker] = useState(null); + + const [selectedTarget, setSelectedTarget] = useState(null); + const [log, setLog] = useState([ "SYSTEM: Neural link established. Scanning sector...", ]); const logEndRef = useRef(null); - - const currentEnemy = hostiles.length > 0 ? hostiles[0] : null; + const timerRef = useRef(null); const dungeonData = GameDataManager.getDungeon(session.dungeonId); - const getEnemyDisplayName = (enemy) => { - if (!enemy) return "UNKNOWN_ENTITY"; - const data = GameDataManager.getEnemy(enemy.id); - return data?.displayName || enemy.displayName || enemy.id; - }; - - const getRoomDisplayName = (room) => { - if (!room) return "UNKNOWN_LOCATION"; - const data = GameDataManager.getRoom(room.id); - return data?.displayName || room.displayName || room.id; - }; - useEffect(() => { logEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [log]); + useEffect(() => { + setSelectedTarget(null); + }, [battle?.currentTurnIndex]); + + useEffect(() => { + if (!battle || battle.isOver || activeAttacker) return; + + const isPlayer = battle.turnOrder[battle.currentTurnIndex] === "player"; + const maxTime = isPlayer ? 10 : 4; + setTimeLeft(maxTime); + + if (timerRef.current) clearInterval(timerRef.current); + + timerRef.current = setInterval(() => { + setTimeLeft((prev) => { + if (prev <= 1) { + if (isPlayer) handleCombatAction(); + return 0; + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(timerRef.current); + }, [battle?.currentTurnIndex, battle?.isOver, activeAttacker]); + useEffect(() => { socket.on("dungeon:room_update", (data) => { setRoomData(data.room); - setHostiles(data.hostiles || []); setRoomIndex(data.roomIndex); - setIsEnemyDefeated(false); - setIsLooted(false); - setEnemyHp(null); + setBattle(data.battle); addLog(`--- ENTERING SECTOR ${data.roomIndex + 1} ---`); }); - socket.on("dungeon:combat_result", (data) => { - if (data.message) addLog(data.message); + socket.on("dungeon:failed", (data) => { + addLog(`--- TERMINAL ERROR: ${data.message} ---`); + setTimeout(() => window.location.reload(), 3000); + }); - if (data.enemyHp !== undefined) { - const maxHp = currentEnemy?.stats?.health || 100; - const hpPercent = (data.enemyHp / maxHp) * 100; - setEnemyHp(hpPercent); + socket.on("dungeon:battle_update", async (data) => { + const turnOrder = data.battle.turnOrder; + const lastIndex = + (data.battle.currentTurnIndex - 1 + turnOrder.length) % + turnOrder.length; + const lastActorId = turnOrder[lastIndex]; + + if (lastActorId !== "player" && !data.battle.isOver) { + setActiveAttacker(lastActorId); + await new Promise((resolve) => setTimeout(resolve, 2000)); + setBattle(data.battle); + if (data.log) data.log.forEach((msg) => addLog(msg)); + setActiveAttacker(null); + } else { + setBattle(data.battle); + if (data.log) data.log.forEach((msg) => addLog(msg)); + setActiveAttacker(null); } - if (data.targetDefeated) { - setIsEnemyDefeated(true); - addLog("TARGET_NEUTRALIZED: Threat eliminated."); - if (data.loot && data.loot.length > 0) { - addLog("SCANNING FOR DROPPED ASSETS..."); - data.loot.forEach((item) => { - const itemData = GameDataManager.getItem(item.id); - const itemName = itemData?.displayName || item.id; - addLog(`RECOVERED: ${itemName} x${item.count}`); - }); - } + if (data.status === "victory") + addLog("MISSION_OBJECTIVE: Threats neutralized."); + if (data.status === "defeat") { + addLog("CRITICAL_ERROR: Bio-sign lost."); + setTimeout(() => window.location.reload(), 3000); } }); @@ -77,10 +98,11 @@ const DungeonScreen = ({ session, socket }) => { return () => { socket.off("dungeon:room_update"); - socket.off("dungeon:combat_result"); + socket.off("dungeon:battle_update"); socket.off("dungeon:completed"); + socket.off("dungeon:failed"); }; - }, [socket, currentEnemy]); + }, [socket]); const addLog = (text) => { const time = new Date().toLocaleTimeString([], { @@ -92,21 +114,22 @@ const DungeonScreen = ({ session, socket }) => { setLog((prev) => [...prev, `[${time}] ${text}`]); }; - const handleCombat = () => { - if (isEnemyDefeated || !currentEnemy) return; - socket.emit("dungeon:combat_step", { enemyId: currentEnemy.id }); - addLog(`Initiating strike sequence...`); - }; + const handleCombatAction = () => { + const targetId = selectedTarget; + if (!battle || battle.isOver || activeAttacker || !targetId) return; + if (battle.turnOrder[battle.currentTurnIndex] !== "player") return; - const handleLoot = () => { - setIsLooted(true); - addLog("Loot encryption bypassed. Resources transferred."); + socket.emit("dungeon:combat_action", { targetInstanceId: targetId }); + addLog(`Initiating strike sequence...`); + setSelectedTarget(null); // Скидаємо вибір після атаки }; const handleNextRoom = () => { socket.emit("dungeon:next_room"); }; + const isPlayerTurn = battle?.turnOrder[battle?.currentTurnIndex] === "player"; + return (
{summary && ( @@ -116,6 +139,7 @@ const DungeonScreen = ({ session, socket }) => { /> )} + {/* Header section remains the same */}
@@ -130,107 +154,129 @@ const DungeonScreen = ({ session, socket }) => { >
-
-
- {dungeonData?.displayName || "MISSION_ACTIVE"} -
-
LIVE_FEED
-
-
-
-
ENVIRONMENT_SCAN
-
-
- -
-
-
{getRoomDisplayName(roomData)}
-
- TYPE: {hostiles.length > 0 ? "COMBAT_ZONE" : "SECURE_AREA"} + {battle && ( +
+
+ {isPlayerTurn ? "YOUR TURN" : "ENEMY ACTION"} +
+
+
-
+ )}
-
-
- {currentEnemy ? ( -
-
- {isEnemyDefeated ? "SIGNAL_LOST" : "HOSTILE_DETECTED"} -
-
- -
-

- {getEnemyDisplayName(currentEnemy)} -

-
-
STRUCTURE INTEGRITY
-
+
+ {battle ? ( +
+ {battle.enemies.map((mob) => ( +
+ !mob.isDead && + isPlayerTurn && + setSelectedTarget(mob.instanceId) + } + > + {selectedTarget === mob.instanceId && ( +
+ +
+ )} + +
-
-
- LVL: {currentEnemy.level || 1} - {currentEnemy.id} -
-
- ) : ( -
- -

NO HOSTILES IN RANGE

-
- )} -
- -
-
COMBAT_LOG_V3.0
-
- {log.map((entry, i) => ( -
- > {entry} +
+ +
+ {GameDataManager.t(mob.name)} + {!mob.isDead && ATK: {mob.atk}}
))} -
+ ) : ( +
+ +

AREA SECURE

+
+ )} +
+ +
+ {battle && ( +
+
+ COMMANDER_INTEGRITY + + {battle.player.hp} / {battle.player.maxHp} + +
+
+
+
+
+ )} + +
+
+
+ {log.map((entry, i) => ( +
+ > {entry} +
+ ))} +
+
+
+ + {battle && !battle.isOver && ( + + )}
- {!isEnemyDefeated && currentEnemy && ( - - )} - - {isEnemyDefeated && !isLooted && ( - - )} - - {(isLooted || !currentEnemy) && ( + {((battle?.isOver && battle.player.hp > 0) || !battle) && ( diff --git a/client/src/views/GameInterface/components/Navigation.css b/client/src/views/GameInterface/components/Navigation.css index c3517eb..1270fd0 100644 --- a/client/src/views/GameInterface/components/Navigation.css +++ b/client/src/views/GameInterface/components/Navigation.css @@ -27,6 +27,7 @@ justify-content: center; align-items: center; padding: 0 12px; + margin-right: 10px; background: transparent; border: none; color: #4a5d75; diff --git a/client/src/views/GameInterface/components/Navigation.jsx b/client/src/views/GameInterface/components/Navigation.jsx index 822edc8..8513283 100644 --- a/client/src/views/GameInterface/components/Navigation.jsx +++ b/client/src/views/GameInterface/components/Navigation.jsx @@ -11,6 +11,7 @@ const Navigation = ({ activeTab, onTabChange }) => { { id: "dashboard", icon: "fa-tachometer-alt" }, { id: "dungeons", icon: "fa-dungeon" }, { id: "skills", icon: "fa-graduation-cap" }, + { id: "quests", icon: "fa-store" }, { id: "inventory", icon: "fa-archive" }, { id: "shop", icon: "fa-store" }, { id: "crafting", icon: "fa-hammer" }, diff --git a/client/src/views/GameInterface/tabs/QuestsTab.jsx b/client/src/views/GameInterface/tabs/QuestsTab.jsx index ff648d8..39a5390 100644 --- a/client/src/views/GameInterface/tabs/QuestsTab.jsx +++ b/client/src/views/GameInterface/tabs/QuestsTab.jsx @@ -1,32 +1,170 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState, useMemo } from "react"; +import Card from "../../../components/ui/Card"; +import { useSocket } from "../../../hooks/useSocket"; +import gameDataManager from "../../../services/GameDataManager"; import "./styles/QuestsTab.css"; const QuestsTab = () => { - const [type, setType] = useState('main'); + const { socket } = useSocket(); + const [quests, setQuests] = useState([]); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState("ACTIVE"); - return ( -
-
-
- {['main', 'daily', 'weekly', 'completed'].map(t => ( - - ))} + useEffect(() => { + if (!socket) return; + + socket.emit("quest:get_list"); + + const localize = (q) => { + const staticData = gameDataManager.getQuest(q.id); + return { + ...q, + displayName: staticData?.displayName || q.id, + description: staticData?.description || "", + objectives: q.objectives.map((obj, idx) => ({ + ...obj, + description: staticData?.objectives[idx]?.description || obj.type, + })), + }; + }; + + const handleQuestData = (data) => { + const uniqueQuests = new Map(); + data.forEach((q) => { + uniqueQuests.set(q.id, localize(q)); + }); + + setQuests(Array.from(uniqueQuests.values())); + setLoading(false); + }; + + const handleQuestUpdate = (updatedQuest) => { + setQuests((prev) => { + const localized = localize(updatedQuest); + const questMap = new Map(prev.map((q) => [q.id, q])); + questMap.set(localized.id, localized); + return Array.from(questMap.values()); + }); + }; + + socket.on("quest:list_data", handleQuestData); + socket.on("quest:update", handleQuestUpdate); + + return () => { + socket.off("quest:list_data", handleQuestData); + socket.off("quest:update", handleQuestUpdate); + }; + }, [socket]); + + const filteredQuests = useMemo(() => { + return quests.filter((q) => + activeTab === "ACTIVE" + ? q.status === "active" || q.status === "ready" + : q.status === "completed", + ); + }, [quests, activeTab]); + + const renderObjective = (obj, index) => { + const progress = Math.min( + (obj.currentAmount / obj.requiredAmount) * 100, + 100, + ); + return ( +
+
+ {obj.description || obj.type} + + {obj.currentAmount} / {obj.requiredAmount} +
- -
Daily quests reset in: 00:00:00
- -
-
-

No {type} quests available at the moment.

-
+
+
+ ); + }; + + return ( +
+
+
+

+ MISSION_LOG +

+
+ + +
+
+
+ {loading ? ( +
SCANNING_NEURAL_NETWORK...
+ ) : ( +
+ {filteredQuests.length > 0 ? ( + filteredQuests.map((quest) => ( + +
{quest.category || "MISSION"}
+
+

{quest.displayName}

+

{quest.description}

+
+
+
OBJECTIVES
+ {quest.objectives.map((obj, idx) => + renderObjective(obj, idx), + )} +
+
+
REWARDS
+
+ {quest.rewards?.credits > 0 && ( + + +{quest.rewards.credits} CR + + )} + {quest.rewards?.xp > 0 && ( + + +{quest.rewards.xp} XP + + )} +
+
+ {quest.status === "ready" && ( + + )} + {quest.status === "completed" && ( +
MISSION_ACCOMPLISHED
+ )} +
+ )) + ) : ( +
+

NO_{activeTab}_SIGNALS_FOUND

+
+ )} +
+ )}
); }; diff --git a/client/src/views/GameInterface/tabs/components/CraftModal.css b/client/src/views/GameInterface/tabs/components/CraftModal.css index 88bf4a8..afee4f2 100644 --- a/client/src/views/GameInterface/tabs/components/CraftModal.css +++ b/client/src/views/GameInterface/tabs/components/CraftModal.css @@ -2,256 +2,197 @@ position: fixed; top: 0; left: 0; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; background: rgba(0, 0, 0, 0.85); display: flex; justify-content: center; align-items: center; - z-index: 2000; - backdrop-filter: blur(4px); + z-index: 9999; + backdrop-filter: blur(6px); } .craft-modal { - background: #151921; - border: 1px solid #00ccff; - border-radius: 12px; + background: #0f1115; + border: 1px solid rgba(0, 210, 255, 0.3); width: 90%; - max-width: 450px; - box-shadow: 0 0 30px rgba(0, 204, 255, 0.15); -} - -.modal-headerr { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; - text-align: center; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); -} - -.modal-header { - margin: 0; - color: #00ccff; - font-family: "Orbitron", sans-serif; - font-size: 0.8rem; -} - -.close-x { - background: none; - border: none; + max-width: 400px; /* Трохи вужча для компактності */ + border-radius: 12px; + position: relative; + padding: 20px; + box-shadow: 0 15px 40px rgba(0, 0, 0, 0.9); + animation: modalSlideUp 0.3s ease-out; color: #fff; - font-size: 1.5rem; - cursor: pointer; } -.requirements-section, -.outcome-section { - margin-bottom: 25px; -} - -.res-grid { +/* Header Section */ +.modal-header-compact { display: flex; - flex-direction: column; - gap: 8px; - background: rgba(255, 255, 255, 0.02); - padding: 12px; - border-radius: 6px; + align-items: center; + gap: 15px; + margin-bottom: 18px; } -.res-item { - display: flex; - justify-content: space-between; - color: #e0e0e0; -} - -.btn-start-craft { - width: 100%; - padding: 12px; - background: #00ccff; - border: none; - border-radius: 6px; - color: #000; - font-weight: bold; - text-transform: uppercase; - cursor: pointer; - margin-bottom: 10px; - transition: 0.2s; -} - -.btn-start-craft:hover { - background: #0099cc; - box-shadow: 0 0 10px rgba(0, 204, 255, 0.4); -} - -.btn-cancel { - width: 100%; - padding: 10px; - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.1); - color: #888; - border-radius: 6px; - cursor: pointer; -} - -.active-craft-panel { - background: rgba(0, 204, 255, 0.1); - border: 1px solid var(--primary-color); - padding: 15px; +.item-icon-box { + width: 70px; + height: 70px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 210, 255, 0.4); border-radius: 8px; - margin-bottom: 20px; - animation: pulse 2s infinite; -} - -.craft-info { display: flex; - justify-content: space-between; - margin-bottom: 8px; + align-items: center; + justify-content: center; + position: relative; + box-shadow: inset 0 0 10px rgba(0, 210, 255, 0.1); +} + +.item-icon-box img { + width: 50px; + height: 50px; + object-fit: contain; +} + +.item-info-title h3 { + margin: 0; font-family: "Orbitron", sans-serif; - font-size: 0.9rem; - color: var(--primary-color); + font-size: 1.1rem; + color: #00d2ff; + text-transform: uppercase; } -.progress-bar-bg { - width: 100%; - height: 10px; - background: rgba(255, 255, 255, 0.1); - border-radius: 5px; - overflow: hidden; +.item-tag { + font-size: 0.65rem; + color: #888; + letter-spacing: 1px; } -.progress-bar-fill { - height: 100%; - background: var(--primary-color); - box-shadow: 0 0 10px var(--primary-color); - transition: width 1s linear; +/* Sections */ +.details-section { + margin-bottom: 15px; } -@keyframes pulse { - 0% { - box-shadow: 0 0 5px rgba(0, 204, 255, 0.2); - } - 50% { - box-shadow: 0 0 15px rgba(0, 204, 255, 0.4); - } - 100% { - box-shadow: 0 0 5px rgba(0, 204, 255, 0.2); - } +.section-label { + font-size: 0.75rem; + text-transform: uppercase; + color: #00d2ff; + margin-bottom: 8px; + opacity: 0.8; + display: block; } -.res-item { +.description-text { + font-size: 0.85rem; + color: #aaa; + font-style: italic; + line-height: 1.4; +} + +/* Resource List */ +.res-container { + background: rgba(0, 0, 0, 0.3); + border-radius: 8px; + padding: 10px; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.res-row { display: flex; justify-content: space-between; align-items: center; - padding: 8px 12px; - background: rgba(255, 255, 255, 0.05); - border-radius: 4px; - margin-bottom: 5px; - border-left: 4px solid transparent; + padding: 6px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); } -.res-item.enough { - border-left-color: #44ff44; +.res-row:last-child { + border-bottom: none; } -.res-item.not-enough { - border-left-color: #ff4444; - background: rgba(255, 68, 68, 0.1); +.res-name { + font-size: 0.9rem; + color: #ccc; + display: flex; + align-items: center; + gap: 8px; } -.val-red { - color: #ff4444; - font-weight: bold; -} -.val-green { - color: #44ff44; -} -.icon-red { - color: #ff4444; -} -.icon-green { - color: #44ff44; -} - -.res-quantity-info { +.res-amount { font-family: "Courier New", monospace; font-size: 0.9rem; } -.btn-start-craft { - background: #28a745; - color: white; - border: none; - padding: 10px 20px; - border-radius: 4px; - font-weight: bold; - transition: 0.2s; +.val-bad { + color: #ff4444; +} +.val-good { + color: #00ff88; } -.btn-start-craft.disabled { - background: #444 !important; - opacity: 0.6; - cursor: not-allowed !important; -} - -.btn-start-craft:not(.disabled):hover { - background: #218838; - box-shadow: 0 0 10px rgba(40, 167, 69, 0.4); -} - -.item-preview-header { +/* Progress & Outcome */ +.outcome-bar { display: flex; - gap: 20px; - padding: 15px; - background: rgba(0, 212, 255, 0.05); - border: 1px solid rgba(0, 212, 255, 0.1); - margin-bottom: 20px; + justify-content: space-between; + font-size: 0.85rem; + margin-top: 10px; + color: #888; +} + +.outcome-bar strong { + color: #fff; +} + +/* Buttons */ +.btn-craft-action { + width: 100%; + padding: 12px; + margin-top: 20px; + font-family: "Orbitron", sans-serif; + font-size: 0.8rem; + cursor: pointer; + transition: all 0.2s; border-radius: 4px; + text-transform: uppercase; } -.item-icon-container { - position: relative; - width: 90px; - height: 90px; - background: #000; - border: 1px solid var(--border-color); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; +.btn-primary-craft { + background: rgba(0, 210, 255, 0.1); + border: 1px solid #00d2ff; + color: #00d2ff; } -.item-display-icon { - max-width: 80%; - max-height: 80%; - object-fit: contain; - filter: drop-shadow(0 0 5px var(--primary-color)); -} - -.item-qty-badge { - position: absolute; - top: -8px; - right: -8px; - background: var(--primary-color); +.btn-primary-craft:hover:not(:disabled) { + background: #00d2ff; color: #000; - padding: 2px 8px; - font-size: 11px; - font-weight: bold; - border-radius: 2px; + box-shadow: 0 0 15px rgba(0, 210, 255, 0.3); } -.item-type-tag { - display: block; - font-size: 10px; - color: var(--primary-color); - letter-spacing: 1px; - margin-bottom: 5px; - opacity: 0.8; +.btn-primary-craft:disabled { + border-color: #444; + color: #444; + cursor: not-allowed; } -.item-description { - font-size: 13px; - color: #ccc; - line-height: 1.4; - margin: 0; +.close-btn-top { + position: absolute; + top: 12px; + right: 12px; + background: none; + border: none; + color: #555; + font-size: 20px; + cursor: pointer; +} + +.close-btn-top:hover { + color: #fff; +} + +@keyframes modalSlideUp { + from { + transform: translateY(15px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } } diff --git a/client/src/views/GameInterface/tabs/components/CraftModal.jsx b/client/src/views/GameInterface/tabs/components/CraftModal.jsx index d9a4d94..91258cf 100644 --- a/client/src/views/GameInterface/tabs/components/CraftModal.jsx +++ b/client/src/views/GameInterface/tabs/components/CraftModal.jsx @@ -16,8 +16,7 @@ const CraftModal = ({ const getFullTextureUrl = (path) => { if (!path) return "/assets/no-image.png"; - if (path.startsWith("http")) return path; - return `${ASSET_BASE_URL}${path}`; + return path.startsWith("http") ? path : `${ASSET_BASE_URL}${path}`; }; const isBusy = !!activeCraft; @@ -29,120 +28,131 @@ const CraftModal = ({ return (
e.stopPropagation()}> -
-

- Construction: {recipe.displayName} -

- -
+ -
- {/* Секція з картинкою предмета */} -
-
- {recipe.displayName} -
x{outputQty}
-
-
- PROTOTYPE_UNIT -

- {recipe.description || - "Technical data encrypted or unavailable."} -

+ {/* Header: Icon + Title */} +
+
+ {recipe.displayName} +
+ x{outputQty}
+
+ PROTOTYPE_UNIT +

{recipe.displayName}

+
+
-
-

- Required Resources -

-
- {recipe.ingredients?.map((ing) => { - const owned = getOwnedAmount(ing.itemId); - const hasEnough = owned >= ing.quantity; + {/* Description */} +
+

+ {recipe.description || + "Advanced composite material for high-tier construction."} +

+
- return ( -
+ Required Materials +
+ {recipe.ingredients?.map((ing) => { + const owned = getOwnedAmount(ing.itemId); + const hasEnough = owned >= ing.quantity; + return ( +
+ + + {ing.displayName} + + -
- - {ing.displayName} -
-
- - {owned} - - / {ing.quantity} -
-
- ); - })} -
-
- -
-

- Outcome -

-
-
- Result: - - {recipe.displayName} x{outputQty} - -
-
- Time: - {recipe.time_seconds}s -
-
+ {owned} / {ing.quantity} + +
+ ); + })}
-
+ {/* Outcome Info */} +
+ + Production Time: {recipe.time_seconds}s + +
+ + {/* Action Button */} +
{activeCraft && activeCraft.recipeId === recipe.id ? ( -
-
- Processing... {activeCraft.timeLeft}s +
+
+ Constructing... {activeCraft.timeLeft}s
-
+
) : ( - <> - - - + )}
diff --git a/client/src/views/GameInterface/tabs/components/ItemModal.css b/client/src/views/GameInterface/tabs/components/ItemModal.css index 26fa6c0..ed83873 100644 --- a/client/src/views/GameInterface/tabs/components/ItemModal.css +++ b/client/src/views/GameInterface/tabs/components/ItemModal.css @@ -193,3 +193,22 @@ opacity: 1; } } + +.item-qty-tag { + position: absolute; + bottom: -5px; + right: -5px; + background: #00d2ff; + color: #000; + padding: 2px 6px; + font-size: 11px; + font-weight: bold; + border-radius: 3px; + font-family: monospace; + box-shadow: 0 0 10px rgba(0, 210, 255, 0.5); +} + +.btn-equip { + letter-spacing: 2px; + font-weight: 600; +} diff --git a/client/src/views/GameInterface/tabs/components/ItemModal.jsx b/client/src/views/GameInterface/tabs/components/ItemModal.jsx index 599cc29..a72dd08 100644 --- a/client/src/views/GameInterface/tabs/components/ItemModal.jsx +++ b/client/src/views/GameInterface/tabs/components/ItemModal.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import "./ItemModal.css"; import { getServerUrl } from "../../../../config/api"; @@ -11,6 +11,25 @@ const ItemModal = ({ getStatIcon, formatStatName, }) => { + useEffect(() => { + const handleKeyDown = (event) => { + if (event.keyCode === 69) { + if (isEquipped) { + onUnequip(item.currentSlot); + } else if (item && item.canEquip) { + onEquip(item); + } + onClose(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [item, isEquipped, onEquip, onUnequip, onClose]); + if (!item) return null; const CONNECT_URL = getServerUrl(); @@ -77,7 +96,7 @@ const ItemModal = ({ onClose(); }} > - TERMINATE_CONNECTION + TERMINATE_CONNECTION (E) ) : ( item.canEquip && ( @@ -88,7 +107,7 @@ const ItemModal = ({ onClose(); }} > - INITIALIZE_EQUIP + INITIALIZE_EQUIP (E) ) )} diff --git a/client/src/views/GameInterface/tabs/styles/QuestsTab.css b/client/src/views/GameInterface/tabs/styles/QuestsTab.css index e69de29..dc58254 100644 --- a/client/src/views/GameInterface/tabs/styles/QuestsTab.css +++ b/client/src/views/GameInterface/tabs/styles/QuestsTab.css @@ -0,0 +1,211 @@ +.quests-container { + padding: 15px; + position: relative; + overflow-y: auto; + height: 100%; + box-sizing: border-box; +} + +.quests-header { + margin-bottom: 25px; + border-left: 3px solid #00d4ff; + padding-left: 15px; +} + +.glitch-text { + font-size: 1.2rem; + letter-spacing: 2px; + color: #fff; + text-transform: uppercase; + margin: 0; + font-weight: 900; +} + +.header-line { + height: 1px; + background: linear-gradient(90deg, #00d4ff, transparent); + margin-top: 5px; + width: 200px; +} + +.quests-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 15px; + position: relative; + z-index: 2; +} + +.quest-card { + background: rgba(10, 15, 24, 0.95) !important; + border: 1px solid #1a2638 !important; + border-radius: 2px !important; + padding: 20px !important; + transition: border-color 0.3s ease; +} + +/* Стан готовності - міняємо бордер на Cyan замість фіолетового */ +.quest-card.ready { + border-color: #00ff88 !important; /* Зеленуватий акцент для готових квестів */ + box-shadow: inset 0 0 10px rgba(0, 255, 136, 0.05); +} + +.quest-main h3 { + margin: 0 0 10px 0; + font-size: 1rem; + color: #00d4ff; + text-transform: uppercase; +} + +.quest-description { + font-size: 11px; + color: #4a5d75; + line-height: 1.4; + margin-bottom: 15px; +} + +.section-label { + font-size: 8px; + color: #4a5d75; + margin: 15px 0 8px 0; + letter-spacing: 1px; + font-weight: 900; + text-transform: uppercase; +} + +.objective-item { + margin-bottom: 12px; +} + +.objective-info { + display: flex; + justify-content: space-between; + font-size: 10px; + margin-bottom: 4px; + color: #fff; +} + +.objective-progress-track { + height: 3px; + background: #05080c; + border: 1px solid #1a2638; +} + +.objective-progress-fill { + height: 100%; + background: #00d4ff; + box-shadow: 0 0 5px rgba(0, 212, 255, 0.3); + transition: width 0.5s ease; +} + +.rewards-row { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.reward-pill { + padding: 4px 8px; + background: rgba(0, 0, 0, 0.4); + border-left: 2px solid #00d4ff; + font-size: 10px; + color: #fff; + font-family: "Space Mono", monospace; +} + +.reward-pill.credits { + border-left-color: #00ff88; + color: #00ff88; +} + +.reward-pill.xp { + border-left-color: #00d4ff; +} + +.claim-btn { + width: 100%; + margin-top: 20px; + background: #00ff88; + border: none; + color: #000; + padding: 10px; + cursor: pointer; + font-size: 10px; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 2px; + transition: all 0.2s; +} + +.claim-btn:hover { + background: #00cc6e; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 255, 136, 0.2); +} + +.no-quests { + grid-column: 1 / -1; + text-align: center; + padding: 40px; + color: #4a5d75; + border: 1px dashed #1a2638; +} + +.no-quests i { + font-size: 2rem; + margin-bottom: 10px; + display: block; +} + +.quest-tabs-nav { + display: flex; + gap: 20px; + margin-top: 15px; +} + +.nav-btn { + background: transparent; + border: none; + color: #4a5d75; + font-family: "Space Mono", monospace; + font-size: 10px; + font-weight: 900; + letter-spacing: 1px; + cursor: pointer; + padding: 5px 0; + position: relative; + transition: color 0.3s; + text-transform: uppercase; +} + +.nav-btn.active { + color: #00d4ff; +} + +.nav-btn.active::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background: #00d4ff; + box-shadow: 0 0 8px #00d4ff; +} + +.completed-stamp { + margin-top: 20px; + padding: 10px; + border: 1px solid #00ff88; + color: #00ff88; + text-align: center; + font-size: 10px; + font-weight: 900; + letter-spacing: 2px; + background: rgba(0, 255, 136, 0.05); +} + +.quest-card.completed { + opacity: 0.7; + border-color: #1a2638 !important; +} diff --git a/game-server/datapacks/core/assets/languages/en_US.json b/game-server/datapacks/core/assets/languages/en_US.json index be3ed90..1264097 100644 --- a/game-server/datapacks/core/assets/languages/en_US.json +++ b/game-server/datapacks/core/assets/languages/en_US.json @@ -1,40 +1,41 @@ { -"_comment_Admin" : "", - "admin.category.core.dungeon_list" : "Dungeon list", - "admin.category.core.hostile_list" : "Hostile list", - "admin.category.core.item_list" : "Item list", - "admin.category.core.player_list" : "Player list", - "admin.category.core.item_list.all" : "All", - "admin.category.core.hostile_list.all" : "All", - "admin.category.core.player_list.all" : "All", -"_comment_Stats" : "", - "stats.category.core.attack.base" : "Attack", - "stats.category.core.attack.chance" : "Attack Chance", - "stats.category.core.attack.rate" : "Attack Rate", - "stats.category.core.defence.base" : "Defence", - "stats.category.core.defence.chance" : "Defence Chance", - "stats.category.core.defence.rate" : "Defence Rate", - "stats.category.core.health" : "Health", - "stats.category.core.penetration.base" : "Penetration", - "stats.category.core.penetration.chance" : "Penetration Chance", - "stats.category.core.penetration.rate" : "Penetration Rate", - "stats.category.core.reflect.base" : "Reflect", - "stats.category.core.reflect.chance" : "Reflection Chance", - "stats.category.core.reflect.rate" : "Reflection Rate", - "stats.category.core.resistance.base" : "Resistance", - "stats.category.core.resistance.cold" : "Cold Resistance", - "stats.category.core.resistance.gamma" : "Gamma Resistance", - "stats.category.core.resistance.heat" : "Heat Resistance", - "stats.category.core.resistance.ion" : "Ion Resistance", - "stats.category.core.resistance.physical" : "Physical Resistance", - "stats.category.core.resistance.plasma" : "Plasma Resistance", -"_comment_Tabs" : "", - "category.tabs.core.crafting" : "Crafting", - "category.tabs.core.dashboard" : "Dashboard", - "category.tabs.core.datapack" : "Datapack", - "category.tabs.core.dungeons" : "Dungeons", - "category.tabs.core.inventory" : "Inventory", - "category.tabs.core.quest" : "Quests", - "category.tabs.core.shop" : "Shop", - "category.tabs.core.skills" : "Skills" -} \ No newline at end of file + "_comment_Admin": "", + "admin.category.core.dungeon_list": "Dungeon list", + "admin.category.core.hostile_list": "Hostile list", + "admin.category.core.item_list": "Item list", + "admin.category.core.player_list": "Player list", + "admin.category.core.item_list.all": "All", + "admin.category.core.hostile_list.all": "All", + "admin.category.core.player_list.all": "All", + "_comment_Stats": "", + "stats.category.core.attack.base": "Attack", + "stats.category.core.attack.chance": "Attack Chance", + "stats.category.core.attack.rate": "Attack Rate", + "stats.category.core.defence.base": "Defence", + "stats.category.core.defence.chance": "Defence Chance", + "stats.category.core.defence.rate": "Defence Rate", + "stats.category.core.health": "Health", + "stats.category.core.penetration.base": "Penetration", + "stats.category.core.penetration.chance": "Penetration Chance", + "stats.category.core.penetration.rate": "Penetration Rate", + "stats.category.core.reflect.base": "Reflect", + "stats.category.core.reflect.chance": "Reflection Chance", + "stats.category.core.reflect.rate": "Reflection Rate", + "stats.category.core.resistance.base": "Resistance", + "stats.category.core.resistance.cold": "Cold Resistance", + "stats.category.core.resistance.gamma": "Gamma Resistance", + "stats.category.core.resistance.heat": "Heat Resistance", + "stats.category.core.resistance.ion": "Ion Resistance", + "stats.category.core.resistance.physical": "Physical Resistance", + "stats.category.core.resistance.plasma": "Plasma Resistance", + "_comment_Tabs": "", + "category.tabs.core.crafting": "Crafting", + "category.tabs.core.dashboard": "Dashboard", + "category.tabs.core.datapack": "Datapack", + "category.tabs.core.dungeons": "Dungeons", + "category.tabs.core.inventory": "Inventory", + "category.tabs.core.quest": "Quests", + "category.tabs.core.shop": "Shop", + "category.tabs.core.skills": "Skills" +} + diff --git a/game-server/datapacks/original/assets/languages/en_US.json b/game-server/datapacks/original/assets/languages/en_US.json index a0d7a40..247539b 100644 --- a/game-server/datapacks/original/assets/languages/en_US.json +++ b/game-server/datapacks/original/assets/languages/en_US.json @@ -1,237 +1,243 @@ { -"_comment_Admin" : "", - "admin.category.original.hostile_list" : "Hostile List", - "admin.category.original.item_list" : "Item List", - "admin.category.original.player_list" : "Player List", - "admin.category.original.item_list.all" : "All", - "admin.category.original.item_list.alloys" : "Alloys", - "admin.category.original.item_list.circuits" : "Circuits", - "admin.category.original.item_list.customizables" : "Customizables", - "admin.category.original.item_list.ingots" : "Ingots", - "admin.category.original.item_list.maerials" : "Materials", - "admin.category.original.item_list.ores" : "Ores", - "admin.category.original.item_list.personal" : "Personal", - "admin.category.original.item_list.ships" : "Ships", - "admin.category.original.item_list.shields" : "Shields", - "admin.category.original.item_list.weapons" : "Weapons", - "admin.category.original.hostile_list.all" : "All", - "admin.category.original.hostile_list.ground" : "Ground Units", - "admin.category.original.hostile_list.ships" : "Ships", - "admin.category.original.player_list.all" : "All", - "admin.category.original.player_list.members" : "Members", - "admin.category.original.player_list.moderators" : "Moderators", - "admin.category.original.player_list.admins" : "Admins", -"_comment_Core_Systems" : "", - "core_systems.category.original.person.backpack" : "Personal Backpack", - "core_systems.category.original.person.helmet" : "Personal Helmet", - "core_systems.category.original.person.suit" : "Personal Suit", - "core_systems.category.original.person.gloves" : "Personal Gloves", - "core_systems.category.original.person.boots" : "Personal Boots", - "core_systems.category.original.person.accessory_1" : "Personal Accessory 1", - "core_systems.category.original.person.accessory_2" : "Personal Accessory 2", - "core_systems.category.original.person.accessory_3" : "Personal Accessory 3", - "core_systems.category.original.person.accessory_4" : "Personal Accessory 4", - "core_systems.category.original.person.weapon" : "Personal Weapon", - "core_systems.category.original.ship.hull" : "Ship Hull", - "core_systems.category.original.ship.shields" : "Ship Shield", - "core_systems.category.original.ship.engines" : "Ship Engine", - "core_systems.category.original.ship.weapon_1" : "Ship Weapon 1", - "core_systems.category.original.ship.weapon_2" : "Ship Weapon 2", - "core_systems.category.original.ship.thruster_1" : "Ship Thruster 1", - "core_systems.category.original.ship.thruster_2" : "Ship Thruster 2", - "core_systems.category.original.ship.thruster_3" : "Ship Thruster 3", - "core_systems.category.original.ship.thruster_4" : "Ship Thruster 4", -"_comment_Dungeons" : "", - "dungeons.original.pirate.pirates_outpost" : "Pirate Outpost", - "dungeons.original.pirate.pirates_outpost.desc" : "A hidden supply station belonging to the Black Mark syndicate.", - "dungeons.original.tutorial.tutorial" : "Tutorial", - "dungeons.original.tutorial.tutorial.desc" : "A one time dungeon.", -"_comment_Enemies" : "", - "enemies.original.pirate.black_mark_heavy_cruiser" : "Black Mark Heavy Cruiser", - "enemies.original.pirate.raider_frigate" : "Raider Frigate", - "enemies.original.pirate.snacher_clipper": "Snacher Clipper", - "enemies.original.pirate.corvid_corvette": "Corvid Corvette", - "enemies.original.pirate.scout_drone" : "Scout Drone", - "enemies.original.tutorial.tutorial_hostile" : "Tutorial hostile", - "enemies.original.tutorial.tutorial_boss_hostile" : "Tutorial Boss", -"_comment_Equipment_Personal" : "", - "items.materials.original.personal.accessory.basic_personal_accessory" :"Personal accessory", - "items.materials.original.personal.accessory.basic_personal_accessory.desc" :"Test accessory", - "items.materials.original.personal.backpack.basic_personal_backpack" :"Personal backpack", - "items.materials.original.personal.backpack.basic_personal_backpack.desc" :"Test backpack", - "items.materials.original.personal.armor.boots.basic_personal_boots" :"Personal boots", - "items.materials.original.personal.armor.boots.basic_personal_boots.desc" :"Test boots", - "items.materials.original.personal.armor.gloves.basic_personal_gloves" :"Personal gloves", - "items.materials.original.personal.armor.gloves.basic_personal_gloves.desc" :"Test gloves", - "items.materials.original.personal.suit.basic_personal_suit" :"Personal suit", - "items.materials.original.personal.suit.basic_personal_suit.desc" :"Test suit", - "items.materials.original.personal.weapon.basic_personal_weapon" :"Personal weapon", - "items.materials.original.personal.weapon.basic_personal_weapon.desc" :"Test weapon", -"_comment_Equipment_Ship" : "", - "items.materials.original.ship.engine.basic_ship_engines" :"Ship engines", - "items.materials.original.ship.engine.basic_ship_engines.desc" :"Test engines", - "items.materials.original.ship.engine.rtg.":"RTG", - "items.materials.original.ship.engine.rtg.desc":"very baisic and low power genarator with a long track record of reliability", - "items.materials.original.ship.engine.gen1_fission_reactor":"Gen 1 nuclear reactor", - "items.materials.original.ship.engine.gen1_fission_reactor.desc":"A boiling water reactor. Little more than a pile of glowing rocks in some hot water", - "items.materials.original.ship.engine.gen2_fission_reactor":"Gen 2 nuclear reactor", - "items.materials.original.ship.engine.gen2_fission_reactor.desc":"A Gas cooled reactor. Uses CO2 as a coolant allowing it to run hotter and at higher pressures and be much more power dense. The high power density and pressures comes at a price of reliability", - "items.materials.original.ship.engine.gen3_fission_reactor":"Gen 3 nuclear reactor", - "items.materials.original.ship.engine.gen3_fission_reactor.desc":"A Molten salt reactor. The pinical of fission power. Uses molten salt as its coolant allowing for very high temperatures and low pressures. Safer than most reactors", - "items.materials.original.ship.plating.basic_plating" :"Ship plating", - "items.materials.original.ship.plating.basic_plating.desc" :"Better at stopping sing large hits", - "items.materials.original.ship.plating.heavy_plating":"Heavy Plating", - "items.materials.original.ship.plating.heavy_plating.desc":"Strong plating capable of resisting high damage impacts", - "items.materials.original.ship.plating.reflective_plating.":"Reflective plating", - "items.materials.original.ship.plating.reflective_plating.desc":"About as strong as heavy plating but reflects damage back at attackers", - "items.materials.original.ship.shields.basic_shield" :"Ship shield", - "items.materials.original.ship.shields.basic_shield.desc" :"Better at stopping many smaller hits. Weak to ion damage", - "items.materials.original.ship.shields.heavy_shield.":"Heavy shield", - "items.materials.original.ship.shields.heavy_shield.desc":"A high capacity shild for dealing with lots of inbond threats", - "items.materials.original.ship.shields.reflecter_shield.":"Reflecter Shields", - "items.materials.original.ship.shields.reflecter_shield.desc":"About as strong as heavy shields but reflects a lot of damage back at the attacker", - "items.materials.original.ship.thruster.basic_ship_thruster" :"Ship thruster", - "items.materials.original.ship.thruster.basic_ship_thruster.desc" :"Test thruster", - "items.materials.original.ship.thruster.big_ship_thruster":"Big ship thruster", - "items.materials.original.ship.thruster.big_ship_thruster.desc":"Used as a main drive for a ship", - "items.materials.original.ship.weapon.basic_ship_weapon" :"Ship weapon", - "items.materials.original.ship.weapon.basic_ship_weapon.desc" :"Test weapon", - "items.materials.original.ship.weapon.unstable_partical_cannon" :"Unstable partical cannon", - "items.materials.original.ship.weapon.unstable_partical_cannon.desc" :"Does a lot of damage in a small space but to power it requires a lot of changes to the ships internals making it more prone to damage", -"_comment_Materials" : "", - "items.materials.original.bio.bio_pulp" : "Bio pulp", - "items.materials.original.bio.bio_pulp.desc" : "A pile of biological material.", - "items.materials.original.alloys.steel" : "Steel ingot", - "items.materials.original.alloys.steel.desc" : "A steel ingot.", - "items.materials.original.alloys.titanium_weave" : "Titanium weave", - "items.materials.original.alloys.titanium_weave.desc" : "used where flexibility does not compromise strength", - "items.materials.original.alloys.void_steel" : "Void steel", - "items.materials.original.alloys.void_steel.desc" : "Steel that is very strong and increadably light absorbent", - "items.materials.original.alloys.chronotanium" :"Chronotanium ingot", - "items.materials.original.alloys.chronotanium.desc" :"A chronite-titianium alloy for strong and energetic applications", - "items.materials.original.alloys.neutronium_composite" :"Neutronium composite", - "items.materials.original.alloys.neutronium_composite.desc" :"A compostite of neutronium of", - "items.materials.original.alloys.superconductor" : "Superconductor", - "items.materials.original.alloys.superconductor.desc" : "A material with no resistance and expells all magnetic fields very usefull in high energy compnents", - "items.materials.original.circuits.basic" : "Basic Circuit", - "items.materials.original.circuits.basic.desc" : "Basic electronics used in simple electromecanical systems. Probably made in someone's shed. Rated for common Tier systems.", - "items.materials.original.circuits.advanced" : "Advanced Circuit", - "items.materials.original.circuits.advanced.desc" : "Advanced electronics used in electromecanical systems, featuring transistors for compact switching. Made with industrial Machines. Rated for uncommon Tier systems.", - "items.materials.original.circuits.processing_unit" : "Processing unit", - "items.materials.original.circuits.processing_unit.desc" : "Highly Advanced electronics used in demanding systems, featuring Integrated circuts replacing entire boards. Made with precision UV lithography machines. Rated for Rare Tier systems.", - "items.materials.original.circuits.quantum_processor" : "Quantum Processor", - "items.materials.original.circuits.quantum_processor.desc" : "Increadably electronics used in complex systems, featuring quantum cores for unparalleled parallel computation. Made with . Rated for Epic Tier systems.", - "items.materials.original.circuits.ai_core" : "Ai Core", - "items.materials.original.circuits.ai_core.desc" : "A semi sapient general intelligence, featuring advanced reasoning skills and simulated simulations, it will never truly know if it is in another simulation. Made under incudulus supervison and adhears to strict laws. Warrrenty void if not reset every terran standerd season.", - "items.materials.original.crystal.flux" :"Flux crystal", - "items.materials.original.crystal.flux.desc" :"A crystal whose properties are in constant flux. Commenly used in high power electrical aplications", - "items.materials.original.crystal.flux_core" : "Flux Core", - "items.materials.original.crystal.flux_core.desc" : "The crystal tamed can nhow be used in more demanding applications", - "items.materials.original.crystal.void" :"Void crystal", - "items.materials.original.crystal.void.desc" :"A crystal that seems to sap the very light from the room. Commenly used in armor and stealth applications", - "items.materials.original.crystal.dimentional" : "Dimentional crystal", - "items.materials.original.crystal.dimentional.desc" : "reality warps at its edges, imagine the possibilities", - "items.materials.original.crystal.neutronium" :"Neutronium", - "items.materials.original.crystal.neutronium.desc" :"A hyper dense piece of along dead star", - "items.materials.original.ingots.aluminum" : "Aluminum ingot", - "items.materials.original.ingots.aluminum.desc" : "An aluminum ingot.", - "items.materials.original.ingots.carbon" : "carbon ingot", - "items.materials.original.ingots.carbon.desc" : "A carbon ingot.", - "items.materials.original.ingots.chronite" : "Chronite Ingot", - "items.materials.original.ingots.chronite.desc" : "A chronite ingot.", - "items.materials.original.ingots.copper" : "Copper ingot", - "items.materials.original.ingots.copper.desc" : "A copper ingot.", - "items.materials.original.ingots.gold" : "Gold ingot", - "items.materials.original.ingots.gold.desc" : "A gold ingot.", - "items.materials.original.ingots.iron" : "Iron Ingot", - "items.materials.original.ingots.iron.desc" : "A iron ingot.", - "items.materials.original.ingots.titanium" : "Titanium ingot", - "items.materials.original.ingots.titanium.desc" : "A titanium ingot.", - "items.materials.original.ingots.tungsten" : "Tungsten ingot", - "items.materials.original.ingots.tungsten.desc" : "A tungsten ingot.", - "items.materials.original.ores.bauxite" : "Bauxite ore", - "items.materials.original.ores.bauxite.desc" : "A pile of bauxite ore.", - "items.materials.original.ores.chronite" : "Chronium ore", - "items.materials.original.ores.chronite.desc" : "A pile of chronium ore.", - "items.materials.original.ores.coal" : "Coal ore", - "items.materials.original.ores.coal.desc" : "A pile of coal ore.", - "items.materials.original.ores.copper" : "Copper ore", - "items.materials.original.ores.copper.desc" : "A pile of copper ore.", - "items.materials.original.ores.gold" : "Gold ore", - "items.materials.original.ores.gold.desc" : "A pile of gold ore.", - "items.materials.original.ores.ilunite" : "Ilunite ore", - "items.materials.original.ores.ilunite.desc" : "A pile of ilunite ore.", - "items.materials.original.ores.iron" : "Iron ore", - "items.materials.original.ores.iron.desc" : "A pile of iron ore.", - "items.materials.original.ores.wolframite" : "Wolframite ore", - "items.materials.original.ores.wolframite.desc" : "A pile of wolframite ore.", - "items.materials.original.plating.basic_ship_plating" : "Ship plating", - "items.materials.original.plating.basic_ship_plating.desc" : "Just basic ship plating.", -"_comment_Quests" : "", - "quests.category.original.story" : "Story", - "quests.category.original.daily" : "Daily", - "quests.category.original.weekly" : "Weekly", - "quests.category.original.monthly" : "Monthly", - "quests.category.original.seasonal" : "Seasons", -"_comment_Recipes" : "", - "recipes.category.original.alloys" : "Alloys", - "recipes.category.original.circuits" : "Circuits", - "recipes.category.original.crystals" : "Crystals", - "recipes.category.original.food" : "Food", - "recipes.category.original.forging" : "Forging", - "recipes.category.original.hull_sections" : "Hull Sections", - "recipes.category.original.hulls" : "Hulls", - "recipes.category.original.organics" : "Organics", - "recipes.category.original.spacesuit_parts" : "Spacesuit Parts", -"_comment_Shop" : "", - "shop.category.original.consumables" : "Consumables", - "shop.category.original.defence" : "Defence", - "shop.category.original.featured" : "Featured", - "shop.category.original.materials" : "Materials", - "shop.category.original.premium" : "Premium", - "shop.category.original.ships" : "Ships", - "shop.category.original.weapons" : "Weapons", -"_comment_Skills" : "", - "skills.category.original.combat" : "Combat", - "skills.category.original.combat.weapon_effiency" : "Weapon Effiency", - "skills.category.original.combat.weapon_effiency.desc" : "Let's get those weapons better!", - "skills.category.original.crafting" : "Crafting", - "skills.category.original.crafting.blacksmithing" : "Blacksmithing", - "skills.category.original.crafting.blacksmithing.desc" : "To forge the basics.", - "skills.category.original.crafting.alloying" : "Alloying", - "skills.category.original.crafting.alloying.desc" : "Lets start alloy making.", - "skills.category.original.science" : "Science", - "skills.category.original.science.alien_technology" : "Alien Technology", - "skills.category.original.science.alien_technology.desc" : "Unknown Mysterious Tech", - "skills.category.original.science.biology_engineering" : "Biology Engineering", - "skills.category.original.science.biology_engineering.desc" : "Maybe we will unlock bio-tech?", -"_comment_Stats" : "", - "stats.category.original.attack.base" : "Attack", - "stats.category.original.attack.chance" : "Attack Chance", - "stats.category.original.attack.rate" : "Attack Rate", - "stats.category.original.defence.base" : "Defence", - "stats.category.original.defence.chance" : "Defence Chance", - "stats.category.original.defence.rate" : "Defence Rate", - "stats.category.original.health" : "Health", - "stats.category.original.penetration.base" : "Penetration", - "stats.category.original.penetration.chance" : "Penetration Chance", - "stats.category.original.penetration.rate" : "Penetration Rate", - "stats.category.original.reflect.base" : "Reflect", - "stats.category.original.reflect.chance" : "Reflection Chance", - "stats.category.original.reflect.rating" : "Reflection Rating", - "stats.category.original.resistance.base" : "Resistance", - "stats.category.original.resistance.cold" : "Cold Resistance", - "stats.category.original.resistance.gamma" : "Gamma Resistance", - "stats.category.original.resistance.heat" : "Heat Resistance", - "stats.category.original.resistance.ion" : "Ion Resistance", - "stats.category.original.resistance.physical" : "Physical Resistance", - "stats.category.original.resistance.plasma" : "Plasma Resistance", -"_comment_Tabs" : "", - "category.tabs.original.crafting" : "Crafting", - "category.tabs.original.dashboard" : "Dashboard", - "category.tabs.original.datapack" : "Debug Tab", - "category.tabs.original.dungeons" : "Dungeons", - "category.tabs.original.inventory" : "Inventory", - "category.tabs.original.shop" : "Shop", - "category.tabs.original.skills" : "Skills" -} \ No newline at end of file + "_comment_Admin": "", + "admin.category.original.hostile_list": "Hostile List", + "admin.category.original.item_list": "Item List", + "admin.category.original.player_list": "Player List", + "admin.category.original.item_list.all": "All", + "admin.category.original.item_list.alloys": "Alloys", + "admin.category.original.item_list.circuits": "Circuits", + "admin.category.original.item_list.customizables": "Customizables", + "admin.category.original.item_list.ingots": "Ingots", + "admin.category.original.item_list.maerials": "Materials", + "admin.category.original.item_list.ores": "Ores", + "admin.category.original.item_list.personal": "Personal", + "admin.category.original.item_list.ships": "Ships", + "admin.category.original.item_list.shields": "Shields", + "admin.category.original.item_list.weapons": "Weapons", + "admin.category.original.hostile_list.all": "All", + "admin.category.original.hostile_list.ground": "Ground Units", + "admin.category.original.hostile_list.ships": "Ships", + "admin.category.original.player_list.all": "All", + "admin.category.original.player_list.members": "Members", + "admin.category.original.player_list.moderators": "Moderators", + "admin.category.original.player_list.admins": "Admins", + "_comment_Core_Systems": "", + "core_systems.category.original.person.backpack": "Personal Backpack", + "core_systems.category.original.person.helmet": "Personal Helmet", + "core_systems.category.original.person.suit": "Personal Suit", + "core_systems.category.original.person.gloves": "Personal Gloves", + "core_systems.category.original.person.boots": "Personal Boots", + "core_systems.category.original.person.accessory_1": "Personal Accessory 1", + "core_systems.category.original.person.accessory_2": "Personal Accessory 2", + "core_systems.category.original.person.accessory_3": "Personal Accessory 3", + "core_systems.category.original.person.accessory_4": "Personal Accessory 4", + "core_systems.category.original.person.weapon": "Personal Weapon", + "core_systems.category.original.ship.hull": "Ship Hull", + "core_systems.category.original.ship.shields": "Ship Shield", + "core_systems.category.original.ship.engines": "Ship Engine", + "core_systems.category.original.ship.weapon_1": "Ship Weapon 1", + "core_systems.category.original.ship.weapon_2": "Ship Weapon 2", + "core_systems.category.original.ship.thruster_1": "Ship Thruster 1", + "core_systems.category.original.ship.thruster_2": "Ship Thruster 2", + "core_systems.category.original.ship.thruster_3": "Ship Thruster 3", + "core_systems.category.original.ship.thruster_4": "Ship Thruster 4", + "_comment_Dungeons": "", + "dungeons.original.pirate.pirates_outpost": "Pirate Outpost", + "dungeons.original.pirate.pirates_outpost.desc": "A hidden supply station belonging to the Black Mark syndicate.", + "dungeons.original.tutorial.tutorial": "Tutorial", + "dungeons.original.tutorial.tutorial.desc": "A one time dungeon.", + "_comment_Enemies": "", + "enemies.original.pirate.black_mark_heavy_cruiser": "Black Mark Heavy Cruiser", + "enemies.original.pirate.raider_frigate": "Raider Frigate", + "enemies.original.pirate.snacher_clipper": "Snacher Clipper", + "enemies.original.pirate.corvid_corvette": "Corvid Corvette", + "enemies.original.pirate.scout_drone": "Scout Drone", + "enemies.original.tutorial.tutorial_hostile": "Tutorial hostile", + "enemies.original.tutorial.tutorial_boss_hostile": "Tutorial Boss", + "_comment_Equipment_Personal": "", + "items.materials.original.personal.accessory.basic_personal_accessory": "Personal accessory", + "items.materials.original.personal.accessory.basic_personal_accessory.desc": "Test accessory", + "items.materials.original.personal.backpack.basic_personal_backpack": "Personal backpack", + "items.materials.original.personal.backpack.basic_personal_backpack.desc": "Test backpack", + "items.materials.original.personal.armor.boots.basic_personal_boots": "Personal boots", + "items.materials.original.personal.armor.boots.basic_personal_boots.desc": "Test boots", + "items.materials.original.personal.armor.gloves.basic_personal_gloves": "Personal gloves", + "items.materials.original.personal.armor.gloves.basic_personal_gloves.desc": "Test gloves", + "items.materials.original.personal.suit.basic_personal_suit": "Personal suit", + "items.materials.original.personal.suit.basic_personal_suit.desc": "Test suit", + "items.materials.original.personal.weapon.basic_personal_weapon": "Personal weapon", + "items.materials.original.personal.weapon.basic_personal_weapon.desc": "Test weapon", + "_comment_Equipment_Ship": "", + "items.materials.original.ship.engine.basic_ship_engines": "Ship engines", + "items.materials.original.ship.engine.basic_ship_engines.desc": "Test engines", + "items.materials.original.ship.engine.rtg.": "RTG", + "items.materials.original.ship.engine.rtg.desc": "very baisic and low power genarator with a long track record of reliability", + "items.materials.original.ship.engine.gen1_fission_reactor": "Gen 1 nuclear reactor", + "items.materials.original.ship.engine.gen1_fission_reactor.desc": "A boiling water reactor. Little more than a pile of glowing rocks in some hot water", + "items.materials.original.ship.engine.gen2_fission_reactor": "Gen 2 nuclear reactor", + "items.materials.original.ship.engine.gen2_fission_reactor.desc": "A Gas cooled reactor. Uses CO2 as a coolant allowing it to run hotter and at higher pressures and be much more power dense. The high power density and pressures comes at a price of reliability", + "items.materials.original.ship.engine.gen3_fission_reactor": "Gen 3 nuclear reactor", + "items.materials.original.ship.engine.gen3_fission_reactor.desc": "A Molten salt reactor. The pinical of fission power. Uses molten salt as its coolant allowing for very high temperatures and low pressures. Safer than most reactors", + "items.materials.original.ship.plating.basic_plating": "Ship plating", + "items.materials.original.ship.plating.basic_plating.desc": "Better at stopping sing large hits", + "items.materials.original.ship.plating.heavy_plating": "Heavy Plating", + "items.materials.original.ship.plating.heavy_plating.desc": "Strong plating capable of resisting high damage impacts", + "items.materials.original.ship.plating.reflective_plating.": "Reflective plating", + "items.materials.original.ship.plating.reflective_plating.desc": "About as strong as heavy plating but reflects damage back at attackers", + "items.materials.original.ship.shields.basic_shield": "Ship shield", + "items.materials.original.ship.shields.basic_shield.desc": "Better at stopping many smaller hits. Weak to ion damage", + "items.materials.original.ship.shields.heavy_shield.": "Heavy shield", + "items.materials.original.ship.shields.heavy_shield.desc": "A high capacity shild for dealing with lots of inbond threats", + "items.materials.original.ship.shields.reflecter_shield.": "Reflecter Shields", + "items.materials.original.ship.shields.reflecter_shield.desc": "About as strong as heavy shields but reflects a lot of damage back at the attacker", + "items.materials.original.ship.thruster.basic_ship_thruster": "Ship thruster", + "items.materials.original.ship.thruster.basic_ship_thruster.desc": "Test thruster", + "items.materials.original.ship.thruster.big_ship_thruster": "Big ship thruster", + "items.materials.original.ship.thruster.big_ship_thruster.desc": "Used as a main drive for a ship", + "items.materials.original.ship.weapon.basic_ship_weapon": "Ship weapon", + "items.materials.original.ship.weapon.basic_ship_weapon.desc": "Test weapon", + "items.materials.original.ship.weapon.unstable_partical_cannon": "Unstable partical cannon", + "items.materials.original.ship.weapon.unstable_partical_cannon.desc": "Does a lot of damage in a small space but to power it requires a lot of changes to the ships internals making it more prone to damage", + "_comment_Materials": "", + "items.materials.original.bio.bio_pulp": "Bio pulp", + "items.materials.original.bio.bio_pulp.desc": "A pile of biological material.", + "items.materials.original.alloys.steel": "Steel ingot", + "items.materials.original.alloys.steel.desc": "A steel ingot.", + "items.materials.original.alloys.titanium_weave": "Titanium weave", + "items.materials.original.alloys.titanium_weave.desc": "used where flexibility does not compromise strength", + "items.materials.original.alloys.void_steel": "Void steel", + "items.materials.original.alloys.void_steel.desc": "Steel that is very strong and increadably light absorbent", + "items.materials.original.alloys.chronotanium": "Chronotanium ingot", + "items.materials.original.alloys.chronotanium.desc": "A chronite-titianium alloy for strong and energetic applications", + "items.materials.original.alloys.neutronium_composite": "Neutronium composite", + "items.materials.original.alloys.neutronium_composite.desc": "A compostite of neutronium of", + "items.materials.original.alloys.superconductor": "Superconductor", + "items.materials.original.alloys.superconductor.desc": "A material with no resistance and expells all magnetic fields very usefull in high energy compnents", + "items.materials.original.circuits.basic": "Basic Circuit", + "items.materials.original.circuits.basic.desc": "Basic electronics used in simple electromecanical systems. Probably made in someone's shed. Rated for common Tier systems.", + "items.materials.original.circuits.advanced": "Advanced Circuit", + "items.materials.original.circuits.advanced.desc": "Advanced electronics used in electromecanical systems, featuring transistors for compact switching. Made with industrial Machines. Rated for uncommon Tier systems.", + "items.materials.original.circuits.processing_unit": "Processing unit", + "items.materials.original.circuits.processing_unit.desc": "Highly Advanced electronics used in demanding systems, featuring Integrated circuts replacing entire boards. Made with precision UV lithography machines. Rated for Rare Tier systems.", + "items.materials.original.circuits.quantum_processor": "Quantum Processor", + "items.materials.original.circuits.quantum_processor.desc": "Increadably electronics used in complex systems, featuring quantum cores for unparalleled parallel computation. Made with . Rated for Epic Tier systems.", + "items.materials.original.circuits.ai_core": "Ai Core", + "items.materials.original.circuits.ai_core.desc": "A semi sapient general intelligence, featuring advanced reasoning skills and simulated simulations, it will never truly know if it is in another simulation. Made under incudulus supervison and adhears to strict laws. Warrrenty void if not reset every terran standerd season.", + "items.materials.original.crystal.flux": "Flux crystal", + "items.materials.original.crystal.flux.desc": "A crystal whose properties are in constant flux. Commenly used in high power electrical aplications", + "items.materials.original.crystal.flux_core": "Flux Core", + "items.materials.original.crystal.flux_core.desc": "The crystal tamed can nhow be used in more demanding applications", + "items.materials.original.crystal.void": "Void crystal", + "items.materials.original.crystal.void.desc": "A crystal that seems to sap the very light from the room. Commenly used in armor and stealth applications", + "items.materials.original.crystal.dimentional": "Dimentional crystal", + "items.materials.original.crystal.dimentional.desc": "reality warps at its edges, imagine the possibilities", + "items.materials.original.crystal.neutronium": "Neutronium", + "items.materials.original.crystal.neutronium.desc": "A hyper dense piece of along dead star", + "items.materials.original.ingots.aluminum": "Aluminum ingot", + "items.materials.original.ingots.aluminum.desc": "An aluminum ingot.", + "items.materials.original.ingots.carbon": "carbon ingot", + "items.materials.original.ingots.carbon.desc": "A carbon ingot.", + "items.materials.original.ingots.chronite": "Chronite Ingot", + "items.materials.original.ingots.chronite.desc": "A chronite ingot.", + "items.materials.original.ingots.copper": "Copper ingot", + "items.materials.original.ingots.copper.desc": "A copper ingot.", + "items.materials.original.ingots.gold": "Gold ingot", + "items.materials.original.ingots.gold.desc": "A gold ingot.", + "items.materials.original.ingots.iron": "Iron Ingot", + "items.materials.original.ingots.iron.desc": "A iron ingot.", + "items.materials.original.ingots.titanium": "Titanium ingot", + "items.materials.original.ingots.titanium.desc": "A titanium ingot.", + "items.materials.original.ingots.tungsten": "Tungsten ingot", + "items.materials.original.ingots.tungsten.desc": "A tungsten ingot.", + "items.materials.original.ores.bauxite": "Bauxite ore", + "items.materials.original.ores.bauxite.desc": "A pile of bauxite ore.", + "items.materials.original.ores.chronite": "Chronium ore", + "items.materials.original.ores.chronite.desc": "A pile of chronium ore.", + "items.materials.original.ores.coal": "Coal ore", + "items.materials.original.ores.coal.desc": "A pile of coal ore.", + "items.materials.original.ores.copper": "Copper ore", + "items.materials.original.ores.copper.desc": "A pile of copper ore.", + "items.materials.original.ores.gold": "Gold ore", + "items.materials.original.ores.gold.desc": "A pile of gold ore.", + "items.materials.original.ores.ilunite": "Ilunite ore", + "items.materials.original.ores.ilunite.desc": "A pile of ilunite ore.", + "items.materials.original.ores.iron": "Iron ore", + "items.materials.original.ores.iron.desc": "A pile of iron ore.", + "items.materials.original.ores.wolframite": "Wolframite ore", + "items.materials.original.ores.wolframite.desc": "A pile of wolframite ore.", + "items.materials.original.plating.basic_ship_plating": "Ship plating", + "items.materials.original.plating.basic_ship_plating.desc": "Just basic ship plating.", + "_comment_Quests": "", + "quests.category.original.story": "Story", + "quests.category.original.daily": "Daily", + "quests.category.original.weekly": "Weekly", + "quests.category.original.monthly": "Monthly", + "quests.category.original.seasonal": "Seasons", + "quests.original.tutorial.starter_kit": "Starter Kit: Neural Link", + "quests.tutorial.slay_boss.name": "Trial by Fire", + "quests.tutorial.slay_boss.desc": "Prove your combat capabilities by neutralizing the Tutorial Boss unit.", + "quests.tutorial.slay_boss.obj1": "Defeat the Tutorial Boss", + "_comment_Recipes": "", + "recipes.category.original.alloys": "Alloys", + "recipes.category.original.circuits": "Circuits", + "recipes.category.original.crystals": "Crystals", + "recipes.category.original.food": "Food", + "recipes.category.original.forging": "Forging", + "recipes.category.original.hull_sections": "Hull Sections", + "recipes.category.original.hulls": "Hulls", + "recipes.category.original.organics": "Organics", + "recipes.category.original.spacesuit_parts": "Spacesuit Parts", + "_comment_Shop": "", + "shop.category.original.consumables": "Consumables", + "shop.category.original.defence": "Defence", + "shop.category.original.featured": "Featured", + "shop.category.original.materials": "Materials", + "shop.category.original.premium": "Premium", + "shop.category.original.ships": "Ships", + "shop.category.original.weapons": "Weapons", + "_comment_Skills": "", + "skills.category.original.combat": "Combat", + "skills.category.original.combat.weapon_effiency": "Weapon Effiency", + "skills.category.original.combat.weapon_effiency.desc": "Let's get those weapons better!", + "skills.category.original.crafting": "Crafting", + "skills.category.original.crafting.blacksmithing": "Blacksmithing", + "skills.category.original.crafting.blacksmithing.desc": "To forge the basics.", + "skills.category.original.crafting.alloying": "Alloying", + "skills.category.original.crafting.alloying.desc": "Lets start alloy making.", + "skills.category.original.science": "Science", + "skills.category.original.science.alien_technology": "Alien Technology", + "skills.category.original.science.alien_technology.desc": "Unknown Mysterious Tech", + "skills.category.original.science.biology_engineering": "Biology Engineering", + "skills.category.original.science.biology_engineering.desc": "Maybe we will unlock bio-tech?", + "_comment_Stats": "", + "stats.category.original.attack.base": "Attack", + "stats.category.original.attack.chance": "Attack Chance", + "stats.category.original.attack.rate": "Attack Rate", + "stats.category.original.defence.base": "Defence", + "stats.category.original.defence.chance": "Defence Chance", + "stats.category.original.defence.rate": "Defence Rate", + "stats.category.original.health": "Health", + "stats.category.original.penetration.base": "Penetration", + "stats.category.original.penetration.chance": "Penetration Chance", + "stats.category.original.penetration.rate": "Penetration Rate", + "stats.category.original.reflect.base": "Reflect", + "stats.category.original.reflect.chance": "Reflection Chance", + "stats.category.original.reflect.rating": "Reflection Rating", + "stats.category.original.resistance.base": "Resistance", + "stats.category.original.resistance.cold": "Cold Resistance", + "stats.category.original.resistance.gamma": "Gamma Resistance", + "stats.category.original.resistance.heat": "Heat Resistance", + "stats.category.original.resistance.ion": "Ion Resistance", + "stats.category.original.resistance.physical": "Physical Resistance", + "stats.category.original.resistance.plasma": "Plasma Resistance", + "_comment_Tabs": "", + "category.tabs.original.crafting": "Crafting", + "category.tabs.original.dashboard": "Dashboard", + "category.tabs.original.datapack": "Debug Tab", + "category.tabs.original.dungeons": "Dungeons", + "category.tabs.original.inventory": "Inventory", + "category.tabs.original.shop": "Shop", + "category.tabs.original.skills": "Skills", + "category.tabs.original.quests": "Quests" +} + diff --git a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json index 908825c..ea17452 100644 --- a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json +++ b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_boss_hostile.json @@ -3,7 +3,7 @@ "id": "original:tutorial/tutorial_boss_hostile", "displayName": "enemies.original.tutorial.tutorial_boss_hostile", "stats": { - "health": 90, + "health": 40, "defense": 1.3, "damage": 4, "critical.chance": 0.3, diff --git a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json index 6be80d6..6fe12d4 100644 --- a/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json +++ b/game-server/datapacks/original/data/enemies/hostiles/tutorial/tutorial_hostile.json @@ -12,7 +12,7 @@ "loot": [ { "id": "original:alloy_steel", - "chance": 0.4, + "chance": 0.8, "count": { "min": 1, "max": 2 diff --git a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json index 1234a19..af8b759 100644 --- a/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json +++ b/game-server/datapacks/original/data/enemies/rooms/tutorial/tutorial_enemy_room.json @@ -3,7 +3,10 @@ "id": "original:tutorial/tutorial_enemy_room", "displayName": "rooms.original.tutorial.tutorial_enemy_room.name", "description": "rooms.original.tutorial.tutorial_enemy_room.desc", - "hostiles": ["original:tutorial/tutorial_hostile"], + "hostiles": [ + "original:tutorial/tutorial_hostile", + "original:tutorial/tutorial_hostile" + ], "gainXp": 3, "credits": 30, "loot": [], diff --git a/game-server/datapacks/original/data/items/equipment/personal/weapns/basic_weapon.json b/game-server/datapacks/original/data/items/equipment/personal/weapons/basic_weapon.json similarity index 71% rename from game-server/datapacks/original/data/items/equipment/personal/weapns/basic_weapon.json rename to game-server/datapacks/original/data/items/equipment/personal/weapons/basic_weapon.json index 06c1218..4c8f569 100644 --- a/game-server/datapacks/original/data/items/equipment/personal/weapns/basic_weapon.json +++ b/game-server/datapacks/original/data/items/equipment/personal/weapons/basic_weapon.json @@ -1,18 +1,15 @@ { - "plating": { + "weapons": { "id": "original:basic_personal_weapon", "displayName": "items.materials.original.personal.weapon.basic_personal_weapon", "description": "items.materials.original.personal.weapon.basic_personal_weapon.desc", "texture": "original/assets/textures/equipment/personal/weapon/basic_weapon.png", "stats": { - "health": 15, - "resistance.base": 0.125, - "defence.rating": 0.9, - "reflect.chance": 0.0125 + "attack.base": 20 }, "meta": { "rarity": "common", - "equipmentSlot": "original:personal_suit", + "equipmentSlot": "original:personal_weapons", "storeCategory": "original:personal", "dungeon": "ground" } diff --git a/game-server/datapacks/original/data/quests/starter_kit.json b/game-server/datapacks/original/data/quests/starter_kit.json new file mode 100644 index 0000000..55133af --- /dev/null +++ b/game-server/datapacks/original/data/quests/starter_kit.json @@ -0,0 +1,55 @@ +{ + "quest": { + "id": "original:tutorial/starter_kit", + "displayName": "quests.original.tutorial.starter_kit", + "description": "Welcome, Commander. Your neural link is active. Initial equipment has been authorized.", + "category": "STORY", + "minLevel": 1, + + "objectives": [ + { + "type": "LOGIN", + "requiredAmount": 1, + "currentAmount": 0, + "description": "Initialize system uplink" + } + ], + + "rewards": { + "xp": 50, + "credits": 500, + "items": [ + { + "id": "original:basic_personal_accessory", + "count": 1 + }, + { + "id": "original:basic_personal_backpack", + "count": 1 + }, + { + "id": "original:basic_personal_boots", + "count": 1 + }, + { + "id": "original:basic_personal_gloves", + "count": 1 + }, + { + "id": "original:basic_personal_suit", + "count": 1 + }, + { + "id": "original:basic_personal_weapon", + "count": 1 + } + ] + }, + + "meta": { + "autoAccept": true, + "autoComplete": false, + "priority": 100 + } + } +} diff --git a/game-server/datapacks/original/data/quests/tutorial_boss.json b/game-server/datapacks/original/data/quests/tutorial_boss.json new file mode 100644 index 0000000..c77016f --- /dev/null +++ b/game-server/datapacks/original/data/quests/tutorial_boss.json @@ -0,0 +1,30 @@ +{ + "quest": { + "id": "original:tutorial/slay_tutorial_boss", + "displayName": "quests.tutorial.slay_boss.name", + "description": "quests.tutorial.slay_boss.desc", + "category": "STORY", + "meta": { + "autoAccept": true, + "priority": 10 + }, + "objectives": [ + { + "type": "KILL_ENEMY", + "targetId": "original:tutorial/tutorial_boss_hostile", + "requiredAmount": 1, + "description": "quests.tutorial.slay_boss.obj1" + } + ], + "rewards": { + "credits": 500, + "xp": 1000, + "items": [ + { + "id": "original:materials/data_core", + "count": 1 + } + ] + } + } +} diff --git a/game-server/src/config/db.js b/game-server/src/config/db.js index 5fadd0e..294bcda 100644 --- a/game-server/src/config/db.js +++ b/game-server/src/config/db.js @@ -32,7 +32,7 @@ sequelize.initDatabase = async () => { await sequelize.query("PRAGMA foreign_keys = OFF;"); } - await sequelize.sync({ alter: true }); + await sequelize.sync(); if (isLocal) { await sequelize.query("PRAGMA foreign_keys = ON;"); diff --git a/game-server/src/game/CombatService.js b/game-server/src/game/CombatService.js new file mode 100644 index 0000000..3ba99d9 --- /dev/null +++ b/game-server/src/game/CombatService.js @@ -0,0 +1,125 @@ +const DatapackLoader = require("./DatapackLoader"); + +class CombatService { + initializeBattle(player, hostiles) { + const equipmentStats = this.calculateEquipmentStats(player.equipment); + + const maxHp = 100 + (equipmentStats.health || 0); + const atk = 25 + (equipmentStats.attack || 0); + + const battle = { + player: { + id: player.id, + name: player.username || "Commander", + hp: maxHp, + maxHp: maxHp, + atk: atk, + stats: equipmentStats, + }, + enemies: hostiles.map((h, index) => ({ + ...h, + instanceId: `mob_${index}`, + id: h.id, + name: h.displayName || h.name || `Hostile ${index + 1}`, + hp: h.stats?.health || 50, + maxHp: h.stats?.health || 50, + atk: h.stats?.attack || 10, + isDead: false, + })), + turnOrder: [], + currentTurnIndex: 0, + turnStartTime: Date.now(), + isOver: false, + }; + + battle.turnOrder = ["player", ...battle.enemies.map((e) => e.instanceId)]; + return battle; + } + + calculateEquipmentStats(equipment) { + const totals = { + health: 0, + attack: 0, + defence: 0, + resistance: 0, + }; + + if (!equipment) return totals; + + Object.values(equipment).forEach((itemId) => { + if (!itemId) return; + + const itemData = DatapackLoader.getItem(itemId); + + if (itemData && itemData.stats) { + Object.entries(itemData.stats).forEach(([key, value]) => { + const lowerKey = key.toLowerCase(); + + if (lowerKey.includes("health") || lowerKey === "hp") { + totals.health += value; + } else if ( + lowerKey.includes("attack") || + lowerKey.includes("damage") || + lowerKey.includes("atk") + ) { + totals.attack += value; + } else if ( + lowerKey.includes("defence") || + lowerKey.includes("armor") || + lowerKey.includes("defense") + ) { + totals.defence += value; + } else if (lowerKey.includes("resistance")) { + totals.resistance += value; + } + }); + } + }); + + return totals; + } + + handleAttack(battle, targetInstanceId) { + const attackerId = battle.turnOrder[battle.currentTurnIndex]; + const log = []; + + if (attackerId === "player") { + const target = battle.enemies.find( + (e) => e.instanceId === targetInstanceId, + ); + if (target && !target.isDead) { + const damage = battle.player.atk; + target.hp -= damage; + + log.push(`Player dealt ${damage} damage to ${target.name}`); + + if (target.hp <= 0) { + target.hp = 0; + target.isDead = true; + log.push(`${target.name} destroyed!`); + } + } + } else { + const enemy = battle.enemies.find((e) => e.instanceId === attackerId); + if (enemy && !enemy.isDead) { + const playerDef = battle.player.stats.defence || 0; + const finalDamage = Math.max( + 1, + Math.round(enemy.atk - playerDef * 0.5), + ); + + battle.player.hp -= finalDamage; + log.push(`${enemy.name} deals ${finalDamage} damage to Player`); + + if (battle.player.hp <= 0) { + battle.player.hp = 0; + battle.isOver = true; + } + } + } + + return log; + } +} + +module.exports = new CombatService(); diff --git a/game-server/src/game/DatapackLoader.js b/game-server/src/game/DatapackLoader.js index 55a868f..2a21a8b 100644 --- a/game-server/src/game/DatapackLoader.js +++ b/game-server/src/game/DatapackLoader.js @@ -11,6 +11,7 @@ class DatapackLoader { dungeons: new Map(), enemies: new Map(), rooms: new Map(), + quests: new Map(), languages: new Map(), manifest: {}, }; @@ -70,9 +71,10 @@ class DatapackLoader { }); console.log( - `🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.languages.size} Langs, ${manifestCount} Manifests`, + `🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.quests.size} Quests, ${this.registry.languages.size} Langs, ${manifestCount} Manifests`, ); } + getRecipe(id) { return this.registry.recipes.get(id); } @@ -89,6 +91,7 @@ class DatapackLoader { ); return Array.from(categories); } + loadLanguages(langPath) { try { const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json")); @@ -156,7 +159,6 @@ class DatapackLoader { case "weapons": data.type = typeKey; this.registry.items.set(fullId, data); - this.registry.items.set(data.id, data); break; case "recipe": const recipeId = json.craft?.id || data.id; @@ -164,19 +166,18 @@ class DatapackLoader { break; case "skills": this.registry.skills.set(fullId, data); - this.registry.skills.set(data.id, data); break; case "dungeon": this.registry.dungeons.set(fullId, data); - this.registry.dungeons.set(data.id, data); break; case "hostile": this.registry.enemies.set(fullId, data); - this.registry.enemies.set(data.id, data); break; case "rooms": this.registry.rooms.set(fullId, data); - this.registry.rooms.set(data.id, data); + break; + case "quest": + this.registry.quests.set(fullId, data); break; } } catch (err) { @@ -187,15 +188,29 @@ class DatapackLoader { getItem(id) { return this.registry.items.get(id); } + getEnemy(id) { return this.registry.enemies.get(id); } + getDungeon(id) { return this.registry.dungeons.get(id); } + getRoom(id) { return this.registry.rooms.get(id); } + + getQuest(id) { + return this.registry.quests.get(id); + } + + getAutoStartQuests() { + return Array.from(this.registry.quests.values()).filter( + (q) => q.meta?.autoAccept, + ); + } + getRecipes() { return Array.from(this.registry.recipes.values()); } @@ -208,6 +223,7 @@ class DatapackLoader { dungeons: Array.from(this.registry.dungeons.values()), enemies: Array.from(this.registry.enemies.values()), rooms: Array.from(this.registry.rooms.values()), + quests: Array.from(this.registry.quests.values()), languages: Object.fromEntries(this.registry.languages), manifest: this.registry.manifest, }; diff --git a/game-server/src/game/DungeonManager.js b/game-server/src/game/DungeonManager.js index 4dca3a7..a92260d 100644 --- a/game-server/src/game/DungeonManager.js +++ b/game-server/src/game/DungeonManager.js @@ -1,23 +1,49 @@ const DatapackLoader = require("./DatapackLoader"); +const CombatService = require("./CombatService"); +const QuestsManager = require("./QuestsManager"); +const { Player } = require("../models"); class DungeonManager { constructor() { this.activeSessions = new Map(); } - startDungeon(playerId, dungeonId) { + async startDungeon(playerId, dungeonId) { const dungeon = DatapackLoader.getDungeon(dungeonId); if (!dungeon || !dungeon.rooms?.length) return null; + const player = await Player.findByPk(playerId); + if (!player) return null; + const session = { + playerId, dungeonId, currentRoomIndex: 0, isFinished: false, - currentEnemyHp: undefined, + battle: null, rewards: { xp: 0, credits: 0, items: [] }, }; + this.activeSessions.set(playerId, session); - return this.getCurrentRoomData(playerId); + return this.initRoom(playerId, player); + } + + async initRoom(playerId, playerInstance = null) { + const session = this.activeSessions.get(playerId); + if (!session) return null; + + const roomData = this.getCurrentRoomData(playerId); + const player = playerInstance || (await Player.findByPk(playerId)); + + if (roomData.hostiles.length > 0) { + session.battle = CombatService.initializeBattle( + player, + roomData.hostiles, + ); + } else { + session.battle = null; + } + return { ...roomData, battle: session.battle }; } getCurrentRoomData(playerId) { @@ -28,8 +54,6 @@ class DungeonManager { const roomRef = dungeon.rooms[session.currentRoomIndex]; const rawRoom = DatapackLoader.getRoom(roomRef.id); - if (!rawRoom) return null; - const hostiles = (rawRoom.hostiles || []) .map((hId) => DatapackLoader.getEnemy(hId)) .filter(Boolean); @@ -42,89 +66,141 @@ class DungeonManager { }; } - processCombatStep(playerId, enemyId) { + processCombatAction(playerId, targetInstanceId, socket = null) { const session = this.activeSessions.get(playerId); - if (!session || session.isFinished) return null; + if (!session || !session.battle || session.battle.isOver) return null; - const enemy = DatapackLoader.getEnemy(enemyId); - if (!enemy) return null; + const battle = session.battle; + const log = CombatService.handleAttack(battle, targetInstanceId); - if (session.currentEnemyHp === undefined) { - session.currentEnemyHp = enemy.stats?.health || 100; + const allEnemiesDead = battle.enemies.every((e) => e.isDead); + const playerDead = battle.player.hp <= 0; + + if (playerDead) { + battle.isOver = true; + battle.player.hp = 0; + return { + battle, + log: [...log, "CRITICAL_FAILURE: Mission terminated."], + status: "defeat", + }; } - const damage = Math.floor(Math.random() * 10) + 20; - session.currentEnemyHp -= damage; + if (allEnemiesDead) { + battle.isOver = true; - const isDefeated = session.currentEnemyHp <= 0; - let lootDropped = []; + const roomData = this.getCurrentRoomData(playerId); + const roomConfig = roomData.config; - if (isDefeated) { - if (enemy.loot) { - lootDropped = this._generateLoot(enemy.loot); - session.rewards.items.push(...lootDropped); + session.rewards.xp += roomConfig.gainXp || 0; + session.rewards.credits += roomConfig.credits || 0; + + if (roomConfig.loot && Array.isArray(roomConfig.loot)) { + roomConfig.loot.forEach((l) => { + if (Math.random() <= (l.chance || 1)) { + let finalCount = 1; + if (typeof l.count === "object") { + finalCount = + Math.floor(Math.random() * (l.count.max - l.count.min + 1)) + + l.count.min; + } else if (typeof l.count === "number") { + finalCount = l.count; + } + + const existingItem = session.rewards.items.find( + (i) => i.id === l.id, + ); + if (existingItem) { + existingItem.count += finalCount; + } else { + session.rewards.items.push({ id: l.id, count: finalCount }); + } + } + }); } - session.currentEnemyHp = undefined; + + battle.enemies.forEach((enemy) => { + session.rewards.xp += enemy.gainXp || 0; + session.rewards.credits += enemy.credits || 0; + + QuestsManager.trackProgress( + playerId, + "KILL_ENEMY", + enemy.id, + 1, + socket, + ); + + if (enemy.loot && Array.isArray(enemy.loot)) { + enemy.loot.forEach((l) => { + if (Math.random() <= (l.chance || 1)) { + let finalCount = 1; + if (typeof l.count === "object") { + finalCount = + Math.floor(Math.random() * (l.count.max - l.count.min + 1)) + + l.count.min; + } else if (typeof l.count === "number") { + finalCount = l.count; + } + + const existingItem = session.rewards.items.find( + (i) => i.id === l.id, + ); + if (existingItem) { + existingItem.count += finalCount; + } else { + session.rewards.items.push({ id: l.id, count: finalCount }); + } + } + }); + } + }); + + return { battle, log, status: "victory" }; } - return { - damageDealt: damage, - enemyHp: Math.max(0, session.currentEnemyHp || 0), - targetDefeated: isDefeated, - loot: lootDropped, - }; + return this._nextTurn(session, log); } - moveToNextRoom(playerId) { + _nextTurn(session, lastLog = []) { + const battle = session.battle; + battle.currentTurnIndex = + (battle.currentTurnIndex + 1) % battle.turnOrder.length; + + const currentEntityId = battle.turnOrder[battle.currentTurnIndex]; + if (currentEntityId !== "player") { + const enemy = battle.enemies.find( + (e) => e.instanceId === currentEntityId, + ); + if (!enemy || enemy.isDead) return this._nextTurn(session, lastLog); + + const enemyLog = CombatService.handleAttack(battle, null); + + if (battle.player.hp <= 0) { + battle.isOver = true; + return { battle, log: [...lastLog, ...enemyLog], status: "defeat" }; + } + + return this._nextTurn(session, [...lastLog, ...enemyLog]); + } + + return { battle, log: lastLog }; + } + + async moveToNextRoom(playerId) { const session = this.activeSessions.get(playerId); if (!session || session.isFinished) return null; const dungeon = DatapackLoader.getDungeon(session.dungeonId); - const roomRef = dungeon.rooms[session.currentRoomIndex]; - const rawRoom = DatapackLoader.getRoom(roomRef.id); - - if (rawRoom) { - if (rawRoom.hostiles) { - rawRoom.hostiles.forEach((hId) => { - const enemy = DatapackLoader.getEnemy(hId); - if (enemy) { - session.rewards.xp += enemy.gainXp || 0; - session.rewards.credits += enemy.credits || 0; - } - }); - } - session.rewards.xp += rawRoom.gainXp || 0; - session.rewards.credits += rawRoom.credits || 0; - if (rawRoom.loot) { - session.rewards.items.push(...this._generateLoot(rawRoom.loot)); - } - } - if (session.currentRoomIndex < dungeon.rooms.length - 1) { session.currentRoomIndex++; - return this.getCurrentRoomData(playerId); + return this.initRoom(playerId); } session.isFinished = true; return { status: "completed", rewards: session.rewards }; } - _generateLoot(lootTable) { - const dropped = []; - lootTable.forEach((entry) => { - if (Math.random() <= (entry.chance || 1.0)) { - const count = - typeof entry.count === "object" - ? Math.floor( - Math.random() * (entry.count.max - entry.count.min + 1), - ) + entry.count.min - : entry.count || 1; - dropped.push({ id: entry.id, count }); - } - }); - return dropped; - } - leaveDungeon(playerId) { this.activeSessions.delete(playerId); } diff --git a/game-server/src/game/QuestsManager.js b/game-server/src/game/QuestsManager.js new file mode 100644 index 0000000..b5f2909 --- /dev/null +++ b/game-server/src/game/QuestsManager.js @@ -0,0 +1,163 @@ +const DatapackLoader = require("./DatapackLoader"); +const { PlayerQuest, Player, Inventory, sequelize } = require("../models"); + +class QuestsManager { + async onPlayerLogin(playerId, socket = null) { + try { + await this.checkAutoQuests(playerId, socket); + await this.trackProgress(playerId, "LOGIN", null, 1, socket); + } catch (err) { + console.error(err); + } + } + + async checkAutoQuests(playerId, socket = null) { + const allQuests = Array.from(DatapackLoader.registry.quests); + const player = await Player.findByPk(playerId); + + for (const quest of allQuests) { + if (quest.meta?.autoAccept && player.level >= (quest.minLevel || 0)) { + const [pq, created] = await PlayerQuest.findOrCreate({ + where: { playerId, questId: quest.id }, + defaults: { + status: "active", + progress: quest.objectives.map((obj) => ({ + ...obj, + currentAmount: 0, + })), + }, + }); + + if (created && socket) { + socket.emit("quest:new", { + id: pq.questId, + status: pq.status, + objectives: pq.progress, + }); + } + } + } + } + + async trackProgress(playerId, type, targetId, amount = 1, socket = null) { + try { + const activeQuests = await PlayerQuest.findAll({ + where: { playerId, status: "active" }, + }); + + for (const pq of activeQuests) { + const staticData = DatapackLoader.getQuest(pq.questId); + if (!staticData) continue; + + let isChanged = false; + const currentProgress = pq.progress; + + const updatedProgress = currentProgress.map((obj) => { + if ( + obj.type === type && + (obj.targetId === targetId || type === "LOGIN") + ) { + if (obj.currentAmount < obj.requiredAmount) { + obj.currentAmount = Math.min( + obj.currentAmount + amount, + obj.requiredAmount, + ); + isChanged = true; + } + } + return obj; + }); + + if (isChanged) { + const isReady = updatedProgress.every( + (obj) => obj.currentAmount >= obj.requiredAmount, + ); + + await pq.update({ + progress: updatedProgress, + status: isReady ? "ready" : "active", + }); + + if (socket) { + socket.emit("quest:update", { + id: pq.questId, + status: isReady ? "ready" : "active", + objectives: updatedProgress, + }); + } + + if (isReady && staticData.meta?.autoComplete) { + await this.claimRewards(playerId, pq.questId); + } + } + } + } catch (err) { + console.error(err); + } + } + + async claimRewards(playerId, questId) { + const t = await sequelize.transaction(); + + try { + const pq = await PlayerQuest.findOne({ + where: { playerId, questId, status: "ready" }, + transaction: t, + lock: t.LOCK.UPDATE, + }); + + if (!pq) { + await t.rollback(); + throw new Error("QUEST_NOT_READY_OR_CLAIMED"); + } + + const staticData = DatapackLoader.getQuest(questId); + const player = await Player.findByPk(playerId, { transaction: t }); + const rewards = staticData.rewards; + + await pq.update({ status: "completed" }, { transaction: t }); + + if (rewards.credits) { + await player.increment("credits", { + by: rewards.credits, + transaction: t, + }); + } + + if (rewards.xp) { + await player.increment("experience", { + by: rewards.xp, + transaction: t, + }); + } + + if (rewards.items?.length > 0) { + for (const item of rewards.items) { + const [invItem] = await Inventory.findOrCreate({ + where: { playerId, itemId: item.id }, + defaults: { quantity: 0 }, + transaction: t, + }); + await invItem.increment("quantity", { + by: item.count, + transaction: t, + }); + } + } + + await t.commit(); + const updatedPlayer = await player.reload(); + + return { + success: true, + rewards, + newTotalCredits: updatedPlayer.credits, + }; + } catch (err) { + if (t) await t.rollback(); + throw err; + } + } +} + +module.exports = new QuestsManager(); diff --git a/game-server/src/models/PlayerQuest.js b/game-server/src/models/PlayerQuest.js new file mode 100644 index 0000000..27e1b91 --- /dev/null +++ b/game-server/src/models/PlayerQuest.js @@ -0,0 +1,41 @@ +const { DataTypes } = require("sequelize"); +const sequelize = require("../config/db"); + +const PlayerQuest = sequelize.define( + "PlayerQuest", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + playerId: { + type: DataTypes.STRING, + allowNull: false, + }, + questId: { + type: DataTypes.STRING, + allowNull: false, + }, + status: { + type: DataTypes.ENUM("active", "ready", "completed"), + defaultValue: "active", + }, + progress: { + type: DataTypes.JSON, + defaultValue: [], + }, + }, + { + timestamps: true, + tableName: "player_quests", + indexes: [ + { + unique: true, + fields: ["playerId", "questId"], + }, + ], + }, +); + +module.exports = PlayerQuest; diff --git a/game-server/src/models/index.js b/game-server/src/models/index.js index e733245..8d26a81 100644 --- a/game-server/src/models/index.js +++ b/game-server/src/models/index.js @@ -4,9 +4,14 @@ const Inventory = require("./Inventory"); const setupAssociations = require("./associations"); const Notification = require("./Notification"); const Friend = require("./Friend.js"); +const PlayerQuest = require("./PlayerQuest.js"); Player.hasMany(Inventory, { foreignKey: "playerId", as: "inventory" }); Inventory.belongsTo(Player, { foreignKey: "playerId" }); + +Player.hasMany(PlayerQuest, { foreignKey: "playerId", as: "quests" }); +PlayerQuest.belongsTo(Player, { foreignKey: "playerId" }); + setupAssociations(); module.exports = { @@ -15,4 +20,5 @@ module.exports = { Inventory, Notification, Friend, + PlayerQuest, }; diff --git a/game-server/src/sockets/handlers/connectionHandler.js b/game-server/src/sockets/handlers/connectionHandler.js index a129d95..32dba92 100644 --- a/game-server/src/sockets/handlers/connectionHandler.js +++ b/game-server/src/sockets/handlers/connectionHandler.js @@ -2,6 +2,7 @@ const { Op } = require("sequelize"); const Player = require("../../models/Player"); const sessionManager = require("../../game/SessionManager"); const economyService = require("../../game/EconomyService.js"); +const QuestsManager = require("../../game/QuestsManager"); module.exports = async (io, socket) => { const userId = socket.user?.id; @@ -26,6 +27,9 @@ module.exports = async (io, socket) => { } } + await QuestsManager.onPlayerLogin(userId, socket); + await player.reload(); + const offlineCredits = await economyService.handleOfflineEarnings(player); socket.playerId = userId; @@ -34,9 +38,9 @@ module.exports = async (io, socket) => { const playerRaw = player.get({ plain: true }); sessionManager.addPlayer(socket.id, playerRaw); + const onlinePlayersData = sessionManager.getAllOnline(); const onlineUsernames = onlinePlayersData.map((p) => p.username); - const onlineIds = onlinePlayersData.map((p) => p.id); const offlinePlayersModels = await Player.findAll({ @@ -64,6 +68,7 @@ module.exports = async (io, socket) => { socket.broadcast.emit("player:joined", { username: playerRaw.username }); socket.join(`user_${socket.user.id}`); + socket.on("player:get_dashboard", async () => { try { const p = await Player.findByPk(userId); diff --git a/game-server/src/sockets/handlers/dungeonHandler.js b/game-server/src/sockets/handlers/dungeonHandler.js index c1ec67a..7cd3714 100644 --- a/game-server/src/sockets/handlers/dungeonHandler.js +++ b/game-server/src/sockets/handlers/dungeonHandler.js @@ -18,36 +18,49 @@ module.exports = (io, socket) => { return socket.emit("error", { message: "Insufficient energy" }); await player.decrement("energy", { by: energyCost }); - const firstRoom = dungeonManager.startDungeon(userId, dungeonId); - + const startData = await dungeonManager.startDungeon(userId, dungeonId); socket.emit("dungeon:started", { dungeonId: dungeon.id, - room: firstRoom.config, - hostiles: firstRoom.hostiles, - roomIndex: firstRoom.roomIndex, - totalRooms: firstRoom.totalRooms, + room: startData.config, + hostiles: startData.hostiles, + battle: startData.battle, + roomIndex: startData.roomIndex, + totalRooms: startData.totalRooms, remainingEnergy: player.energy - energyCost, }); } catch (err) { - socket.emit("error", { message: "Critical deployment failure" }); + socket.emit("error", { message: "Deployment failure" }); } }); - socket.on("dungeon:combat_step", async ({ enemyId }) => { - const result = dungeonManager.processCombatStep(userId, enemyId); - if (!result) return; + socket.on("dungeon:combat_action", async ({ targetInstanceId }) => { + try { + if (!userId) return; + const result = dungeonManager.processCombatAction( + userId, + targetInstanceId, + ); + if (!result) return; - socket.emit("dungeon:combat_result", { - ...result, - message: result.targetDefeated - ? "Enemy eliminated!" - : `Strike successful. Dealt ${result.damageDealt} damage.`, - }); + socket.emit("dungeon:battle_update", { + battle: result.battle, + log: result.log, + status: result.status, + }); + + if (result.status === "defeat") { + dungeonManager.leaveDungeon(userId); + socket.emit("dungeon:failed", { message: "Neural link severed." }); + } + } catch (err) { + socket.emit("error", { message: "Synchronization error" }); + } }); socket.on("dungeon:next_room", async () => { try { - const nextRoom = dungeonManager.moveToNextRoom(userId); + if (!userId) return; + const nextRoom = await dungeonManager.moveToNextRoom(userId); if (!nextRoom) return socket.emit("error", { message: "Navigation error" }); @@ -57,7 +70,7 @@ module.exports = (io, socket) => { socket.emit("dungeon:room_update", nextRoom); } } catch (err) { - socket.emit("error", { message: "Navigation system error" }); + socket.emit("error", { message: "Navigation error" }); } }); @@ -90,13 +103,16 @@ async function finalizeDungeon(socket, sessionRewards) { await invItem.increment("quantity", { by: totalCount }); } + // Оновлюємо масив для фронтенда, щоб не було дублікатів у списку sessionRewards.items = Object.entries(consolidated).map( ([id, count]) => ({ id, count }), ); } + console.log("FINAL REWARDS SAVED:", sessionRewards); socket.emit("dungeon:completed", { rewards: sessionRewards }); } catch (err) { + console.error(err); socket.emit("error", { message: "Failed to save rewards" }); } finally { dungeonManager.leaveDungeon(userId); diff --git a/game-server/src/sockets/handlers/questsHandler.js b/game-server/src/sockets/handlers/questsHandler.js new file mode 100644 index 0000000..e57fc46 --- /dev/null +++ b/game-server/src/sockets/handlers/questsHandler.js @@ -0,0 +1,89 @@ +const questsManager = require("../../game/QuestsManager"); +const DatapackLoader = require("../../game/DatapackLoader"); +const { PlayerQuest } = require("../../models"); + +module.exports = (io, socket) => { + const playerId = socket.user?.id; + + socket.on("quest:get_list", async () => { + if (!playerId) return; + + try { + const autoQuests = DatapackLoader.getAutoStartQuests(); + const existingQuests = await PlayerQuest.findAll({ + where: { playerId }, + attributes: ["questId"], + raw: true, + }); + + const existingIds = existingQuests.map((q) => q.questId); + const missingQuests = autoQuests.filter( + (aq) => !existingIds.includes(aq.id), + ); + + if (missingQuests.length > 0) { + const toCreate = missingQuests.map((aq) => ({ + playerId, + questId: aq.id, + status: "active", + progress: aq.objectives.map((obj) => ({ ...obj, currentAmount: 0 })), + })); + + await PlayerQuest.bulkCreate(toCreate, { ignoreDuplicates: true }); + } + + const all = await PlayerQuest.findAll({ + where: { playerId }, + order: [["updatedAt", "DESC"]], + }); + + socket.emit( + "quest:list_data", + all.map((q) => ({ + id: q.questId, + status: q.status, + objectives: q.progress, + rewards: DatapackLoader.getQuest(q.questId)?.rewards, + })), + ); + } catch (err) { + console.error("Quest sync error:", err); + } + }); + + socket.on("quest:claim_reward", async ({ questId }) => { + try { + const result = await questsManager.claimRewards(playerId, questId); + + socket.emit("player:credits_update", { + totalCredits: result.newTotalCredits, + }); + + socket.emit("quest:reward_claimed", { + questId, + rewards: result.rewards, + }); + + const all = await PlayerQuest.findAll({ + where: { playerId }, + order: [["updatedAt", "DESC"]], + }); + + socket.emit( + "quest:list_data", + all.map((q) => ({ + id: q.questId, + status: q.status, + objectives: q.progress, + rewards: DatapackLoader.getQuest(q.questId)?.rewards, + })), + ); + } catch (err) { + const msg = + err.message === "QUEST_NOT_READY_OR_CLAIMED" + ? "Reward already claimed or objective not met." + : "Failed to claim reward."; + socket.emit("error", { message: msg }); + } + }); +}; diff --git a/game-server/src/sockets/socket.js b/game-server/src/sockets/socket.js index 43494bf..df6804b 100644 --- a/game-server/src/sockets/socket.js +++ b/game-server/src/sockets/socket.js @@ -7,6 +7,7 @@ const dungeonHandler = require("./handlers/dungeonHandler"); const chatHandler = require("./handlers/chatHandler"); const notificationHandler = require("./handlers/notificationHandler"); const socialHandler = require("./handlers/socialHandler"); +const questsHandler = require("./handlers/questsHandler"); const initSockets = (io) => { io.use(socketAuth); @@ -20,6 +21,7 @@ const initSockets = (io) => { chatHandler(io, socket); socialHandler(io, socket); notificationHandler(io, socket); + questsHandler(io, socket); }); };