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);
}