Added DungeonFinish view
This commit is contained in:
parent
86bfae2228
commit
7b16a8819b
@ -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">
|
||||||
|
|||||||
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;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user