Added DungeonFinish view
This commit is contained in:
parent
86bfae2228
commit
7b16a8819b
@ -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 (
|
||||
<div className="dungeon-active-screen">
|
||||
{summary && (
|
||||
<DungeonFinish
|
||||
rewards={summary}
|
||||
onExit={() => window.location.reload()}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="dungeon-header">
|
||||
<div className="room-progress">
|
||||
<div className="progress-text">
|
||||
|
||||
117
client/src/views/GameInterface/tabs/components/DungeonFinish.css
Normal file
117
client/src/views/GameInterface/tabs/components/DungeonFinish.css
Normal 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);
|
||||
}
|
||||
@ -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;
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user