From 7b16a8819b4e66560462f615dd1bd841e09726ed Mon Sep 17 00:00:00 2001 From: MaksSlyzar Date: Sat, 18 Apr 2026 15:08:49 +0300 Subject: [PATCH] Added DungeonFinish view --- .../components/DungeonScreen.jsx | 25 ++++ .../tabs/components/DungeonFinish.css | 117 ++++++++++++++++++ .../tabs/components/DungeonFinish.jsx | 74 +++++++++++ .../src/sockets/handlers/dungeonHandler.js | 25 +++- 4 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 client/src/views/GameInterface/tabs/components/DungeonFinish.css create mode 100644 client/src/views/GameInterface/tabs/components/DungeonFinish.jsx diff --git a/client/src/views/GameInterface/components/DungeonScreen.jsx b/client/src/views/GameInterface/components/DungeonScreen.jsx index cc9600f..a4dd728 100644 --- a/client/src/views/GameInterface/components/DungeonScreen.jsx +++ b/client/src/views/GameInterface/components/DungeonScreen.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef } from "react"; import GameDataManager from "../../../services/GameDataManager.js"; import "./DungeonScreen.css"; +import DungeonFinish from "../tabs/components/DungeonFinish.jsx"; const DungeonScreen = ({ session, socket }) => { const [roomData, setRoomData] = useState(session.room); @@ -9,6 +10,7 @@ const DungeonScreen = ({ session, socket }) => { const [enemyHp, setEnemyHp] = useState(null); const [isEnemyDefeated, setIsEnemyDefeated] = useState(false); const [isLooted, setIsLooted] = useState(false); + const [summary, setSummary] = useState(null); const [log, setLog] = useState([ "SYSTEM: Neural link established. Scanning sector...", ]); @@ -47,6 +49,7 @@ const DungeonScreen = ({ session, socket }) => { socket.on("dungeon:combat_result", (data) => { if (data.message) addLog(data.message); + if (data.enemyHp !== undefined) { const maxHp = currentEnemy?.stats?.health || 100; const hpPercent = (data.enemyHp / maxHp) * 100; @@ -55,12 +58,27 @@ const DungeonScreen = ({ session, socket }) => { 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}`); + }); + } } }); + socket.on("dungeon:completed", (data) => { + setSummary(data.rewards); + addLog("MISSION_SUCCESS: All objectives secured."); + }); + return () => { socket.off("dungeon:room_update"); socket.off("dungeon:combat_result"); + socket.off("dungeon:completed"); }; }, [socket, currentEnemy]); @@ -91,6 +109,13 @@ const DungeonScreen = ({ session, socket }) => { return (
+ {summary && ( + window.location.reload()} + /> + )} +
diff --git a/client/src/views/GameInterface/tabs/components/DungeonFinish.css b/client/src/views/GameInterface/tabs/components/DungeonFinish.css new file mode 100644 index 0000000..0471d1a --- /dev/null +++ b/client/src/views/GameInterface/tabs/components/DungeonFinish.css @@ -0,0 +1,117 @@ +.dungeon-summary-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(2, 5, 8, 0.95); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + backdrop-filter: blur(8px); +} + +.summary-card { + width: 490px; + background: #0a0f18; + border: 1px solid #00d4ff; + padding: 40px; + position: relative; + box-shadow: 0 0 50px rgba(0, 212, 255, 0.15); +} + +.summary-title { + color: #00d4ff; + font-family: "Orbitron", sans-serif; + letter-spacing: 4px; + margin-bottom: 5px; + font-size: 1.5rem; +} + +.summary-line { + height: 2px; + background: linear-gradient(90deg, #00d4ff, transparent); + margin-bottom: 30px; +} + +.reward-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-bottom: 30px; +} + +.stat-box { + background: rgba(26, 38, 56, 0.3); + padding: 15px; + border-left: 3px solid #00d4ff; + display: flex; + flex-direction: column; +} + +.stat-label { + font-size: 10px; + color: #4a5d75; + margin-bottom: 5px; +} + +.stat-value { + font-size: 1.2rem; + font-weight: bold; +} + +.loot-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); + gap: 15px; + margin-top: 15px; + max-height: 250px; + overflow-y: auto; + padding-right: 5px; +} + +.loot-item-slot { + width: 70px; + height: 70px; + background: #05080c; + border: 1px solid #1a2638; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.loot-img-container img { + max-width: 80%; + max-height: 80%; +} + +.loot-qty { + position: absolute; + bottom: 2px; + right: 5px; + font-size: 11px; + color: #00d4ff; + font-weight: bold; + text-shadow: 1px 1px 2px #000; +} + +.summary-btn { + margin-top: 40px; + width: 100%; + padding: 15px; + background: transparent; + border: 1px solid #00d4ff; + color: #00d4ff; + font-family: "Orbitron", sans-serif; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 2px; +} + +.summary-btn:hover { + background: rgba(0, 212, 255, 0.1); + box-shadow: inset 0 0 15px rgba(0, 212, 255, 0.3); +} diff --git a/client/src/views/GameInterface/tabs/components/DungeonFinish.jsx b/client/src/views/GameInterface/tabs/components/DungeonFinish.jsx new file mode 100644 index 0000000..03a6aa4 --- /dev/null +++ b/client/src/views/GameInterface/tabs/components/DungeonFinish.jsx @@ -0,0 +1,74 @@ +import React from "react"; +import GameDataManager from "../../../../services/GameDataManager.js"; +import { getServerUrl } from "../../../../config/api.js"; +import "./DungeonFinish.css"; +const DungeonFinish = ({ rewards, onExit }) => { + const CONNECT_URL = getServerUrl(); + const ASSET_BASE_URL = `${CONNECT_URL}/static/`; + + const getFullTextureUrl = (path) => { + if (!path) return "/assets/no-image.png"; + if (path.startsWith("http")) return path; + return `${ASSET_BASE_URL}${path}`; + }; + + return ( +
+
+
+
+

+ MISSION_ACCOMPLISHED +

+
+
+
+ +
+
+
+ EXPERIENCE_DATA + +{rewards.xp || 0} XP +
+
+ CREDITS_TRANSFER + + +{rewards.credits || 0} CR + +
+
+ +
+

ASSETS_RECOVERED

+
+ {rewards.items && rewards.items.length > 0 ? ( + rewards.items.map((item, idx) => { + const itemData = GameDataManager.getItem(item.id); + const textureUrl = getFullTextureUrl(itemData?.texture); + + return ( +
+
+ +
+ x{item.count} +
+
+ ); + }) + ) : ( +
NO_RESOURCES_FOUND
+ )} +
+
+
+ + +
+
+ ); +}; + +export default DungeonFinish; diff --git a/game-server/src/sockets/handlers/dungeonHandler.js b/game-server/src/sockets/handlers/dungeonHandler.js index 945532f..279f319 100644 --- a/game-server/src/sockets/handlers/dungeonHandler.js +++ b/game-server/src/sockets/handlers/dungeonHandler.js @@ -51,7 +51,7 @@ module.exports = (io, socket) => { if (!session || session.isFinished) return; const rawEnemy = DatapackLoader.getEnemy(enemyId); - if (!rawEnemy || !rawEnemy) { + if (!rawEnemy) { return socket.emit("error", { message: "Target data corrupted" }); } @@ -142,19 +142,31 @@ async function finalizeDungeon(socket, sessionRewards) { await player.increment("credits", { by: sessionRewards.credits }); } - if (sessionRewards.xp > 0 && player.xp !== undefined) { + if (sessionRewards.xp > 0) { await player.increment("xp", { by: sessionRewards.xp }); } if (sessionRewards.items.length > 0) { - for (const item of sessionRewards.items) { - const [invItem, created] = await Inventory.findOrCreate({ - where: { playerId: userId, itemId: item.id }, + const consolidatedItems = sessionRewards.items.reduce((acc, curr) => { + acc[curr.id] = (acc[curr.id] || 0) + curr.count; + return acc; + }, {}); + + for (const [itemId, totalCount] of Object.entries(consolidatedItems)) { + const [invItem] = await Inventory.findOrCreate({ + where: { playerId: userId, itemId: itemId }, defaults: { quantity: 0 }, }); - await invItem.increment("quantity", { by: item.count }); + await invItem.increment("quantity", { by: totalCount }); } + + sessionRewards.items = Object.entries(consolidatedItems).map( + ([id, count]) => ({ + id, + count, + }), + ); } socket.emit("dungeon:completed", { @@ -163,6 +175,7 @@ async function finalizeDungeon(socket, sessionRewards) { }); } catch (err) { console.error("Finalize Error:", err); + socket.emit("error", { message: "Failed to save mission rewards" }); } finally { dungeonManager.leaveDungeon(userId); }