Added DungeonFinish view

This commit is contained in:
MaksSlyzar 2026-04-18 15:08:49 +03:00
parent 86bfae2228
commit 7b16a8819b
4 changed files with 235 additions and 6 deletions

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import GameDataManager from "../../../services/GameDataManager.js"; import GameDataManager from "../../../services/GameDataManager.js";
import "./DungeonScreen.css"; import "./DungeonScreen.css";
import DungeonFinish from "../tabs/components/DungeonFinish.jsx";
const DungeonScreen = ({ session, socket }) => { const DungeonScreen = ({ session, socket }) => {
const [roomData, setRoomData] = useState(session.room); const [roomData, setRoomData] = useState(session.room);
@ -9,6 +10,7 @@ const DungeonScreen = ({ session, socket }) => {
const [enemyHp, setEnemyHp] = useState(null); const [enemyHp, setEnemyHp] = useState(null);
const [isEnemyDefeated, setIsEnemyDefeated] = useState(false); const [isEnemyDefeated, setIsEnemyDefeated] = useState(false);
const [isLooted, setIsLooted] = useState(false); const [isLooted, setIsLooted] = useState(false);
const [summary, setSummary] = useState(null);
const [log, setLog] = useState([ const [log, setLog] = useState([
"SYSTEM: Neural link established. Scanning sector...", "SYSTEM: Neural link established. Scanning sector...",
]); ]);
@ -47,6 +49,7 @@ const DungeonScreen = ({ session, socket }) => {
socket.on("dungeon:combat_result", (data) => { socket.on("dungeon:combat_result", (data) => {
if (data.message) addLog(data.message); if (data.message) addLog(data.message);
if (data.enemyHp !== undefined) { if (data.enemyHp !== undefined) {
const maxHp = currentEnemy?.stats?.health || 100; const maxHp = currentEnemy?.stats?.health || 100;
const hpPercent = (data.enemyHp / maxHp) * 100; const hpPercent = (data.enemyHp / maxHp) * 100;
@ -55,12 +58,27 @@ const DungeonScreen = ({ session, socket }) => {
if (data.targetDefeated) { if (data.targetDefeated) {
setIsEnemyDefeated(true); setIsEnemyDefeated(true);
addLog("TARGET_NEUTRALIZED: Threat eliminated."); 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 () => { return () => {
socket.off("dungeon:room_update"); socket.off("dungeon:room_update");
socket.off("dungeon:combat_result"); socket.off("dungeon:combat_result");
socket.off("dungeon:completed");
}; };
}, [socket, currentEnemy]); }, [socket, currentEnemy]);
@ -91,6 +109,13 @@ const DungeonScreen = ({ session, socket }) => {
return ( return (
<div className="dungeon-active-screen"> <div className="dungeon-active-screen">
{summary && (
<DungeonFinish
rewards={summary}
onExit={() => window.location.reload()}
/>
)}
<div className="dungeon-header"> <div className="dungeon-header">
<div className="room-progress"> <div className="room-progress">
<div className="progress-text"> <div className="progress-text">

View File

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

View File

@ -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 (
<div className="dungeon-summary-overlay">
<div className="summary-card">
<div className="summary-header">
<div className="glitch-wrapper">
<h2 className="summary-title" data-text="MISSION_ACCOMPLISHED">
MISSION_ACCOMPLISHED
</h2>
</div>
<div className="summary-line"></div>
</div>
<div className="summary-body">
<div className="reward-stats">
<div className="stat-box">
<span className="stat-label">EXPERIENCE_DATA</span>
<span className="stat-value">+{rewards.xp || 0} XP</span>
</div>
<div className="stat-box">
<span className="stat-label">CREDITS_TRANSFER</span>
<span className="stat-value cyan-text">
+{rewards.credits || 0} CR
</span>
</div>
</div>
<div className="loot-section">
<h4 className="section-label">ASSETS_RECOVERED</h4>
<div className="loot-grid">
{rewards.items && rewards.items.length > 0 ? (
rewards.items.map((item, idx) => {
const itemData = GameDataManager.getItem(item.id);
const textureUrl = getFullTextureUrl(itemData?.texture);
return (
<div key={idx} className="loot-item-slot">
<div className="loot-img-container">
<img src={textureUrl} />
</div>
<span className="loot-qty">x{item.count}</span>
<div className="loot-name-hint"></div>
</div>
);
})
) : (
<div className="no-loot-msg">NO_RESOURCES_FOUND</div>
)}
</div>
</div>
</div>
<button className="summary-btn" onClick={onExit}>
CONFIRM & RETURN TO BASE
</button>
</div>
</div>
);
};
export default DungeonFinish;

View File

@ -51,7 +51,7 @@ module.exports = (io, socket) => {
if (!session || session.isFinished) return; if (!session || session.isFinished) return;
const rawEnemy = DatapackLoader.getEnemy(enemyId); const rawEnemy = DatapackLoader.getEnemy(enemyId);
if (!rawEnemy || !rawEnemy) { if (!rawEnemy) {
return socket.emit("error", { message: "Target data corrupted" }); return socket.emit("error", { message: "Target data corrupted" });
} }
@ -142,19 +142,31 @@ async function finalizeDungeon(socket, sessionRewards) {
await player.increment("credits", { by: sessionRewards.credits }); 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 }); await player.increment("xp", { by: sessionRewards.xp });
} }
if (sessionRewards.items.length > 0) { if (sessionRewards.items.length > 0) {
for (const item of sessionRewards.items) { const consolidatedItems = sessionRewards.items.reduce((acc, curr) => {
const [invItem, created] = await Inventory.findOrCreate({ acc[curr.id] = (acc[curr.id] || 0) + curr.count;
where: { playerId: userId, itemId: item.id }, return acc;
}, {});
for (const [itemId, totalCount] of Object.entries(consolidatedItems)) {
const [invItem] = await Inventory.findOrCreate({
where: { playerId: userId, itemId: itemId },
defaults: { quantity: 0 }, 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", { socket.emit("dungeon:completed", {
@ -163,6 +175,7 @@ async function finalizeDungeon(socket, sessionRewards) {
}); });
} catch (err) { } catch (err) {
console.error("Finalize Error:", err); console.error("Finalize Error:", err);
socket.emit("error", { message: "Failed to save mission rewards" });
} finally { } finally {
dungeonManager.leaveDungeon(userId); dungeonManager.leaveDungeon(userId);
} }