244 lines
7.8 KiB
JavaScript
244 lines
7.8 KiB
JavaScript
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);
|
|
const [hostiles, setHostiles] = useState(session.hostiles || []);
|
|
const [roomIndex, setRoomIndex] = useState(session.roomIndex);
|
|
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...",
|
|
]);
|
|
|
|
const logEndRef = useRef(null);
|
|
|
|
const currentEnemy = hostiles.length > 0 ? hostiles[0] : null;
|
|
const dungeonData = GameDataManager.getDungeon(session.dungeonId);
|
|
|
|
const getEnemyDisplayName = (enemy) => {
|
|
if (!enemy) return "UNKNOWN_ENTITY";
|
|
const data = GameDataManager.getEnemy(enemy.id);
|
|
return data?.displayName || enemy.displayName || enemy.id;
|
|
};
|
|
|
|
const getRoomDisplayName = (room) => {
|
|
if (!room) return "UNKNOWN_LOCATION";
|
|
const data = GameDataManager.getRoom(room.id);
|
|
return data?.displayName || room.displayName || room.id;
|
|
};
|
|
|
|
useEffect(() => {
|
|
logEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [log]);
|
|
|
|
useEffect(() => {
|
|
socket.on("dungeon:room_update", (data) => {
|
|
setRoomData(data.room);
|
|
setHostiles(data.hostiles || []);
|
|
setRoomIndex(data.roomIndex);
|
|
setIsEnemyDefeated(false);
|
|
setIsLooted(false);
|
|
setEnemyHp(null);
|
|
addLog(`--- ENTERING SECTOR ${data.roomIndex + 1} ---`);
|
|
});
|
|
|
|
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;
|
|
setEnemyHp(hpPercent);
|
|
}
|
|
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]);
|
|
|
|
const addLog = (text) => {
|
|
const time = new Date().toLocaleTimeString([], {
|
|
hour12: false,
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
});
|
|
setLog((prev) => [...prev, `[${time}] ${text}`]);
|
|
};
|
|
|
|
const handleCombat = () => {
|
|
if (isEnemyDefeated || !currentEnemy) return;
|
|
socket.emit("dungeon:combat_step", { enemyId: currentEnemy.id });
|
|
addLog(`Initiating strike sequence...`);
|
|
};
|
|
|
|
const handleLoot = () => {
|
|
setIsLooted(true);
|
|
addLog("Loot encryption bypassed. Resources transferred.");
|
|
};
|
|
|
|
const handleNextRoom = () => {
|
|
socket.emit("dungeon:next_room");
|
|
};
|
|
|
|
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">
|
|
SECTOR {roomIndex + 1} / {session.totalRooms}
|
|
</div>
|
|
<div className="progress-bar">
|
|
<div
|
|
className="fill"
|
|
style={{
|
|
width: `${((roomIndex + 1) / session.totalRooms) * 100}%`,
|
|
}}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div className="dungeon-title-area">
|
|
<div className="dungeon-name">
|
|
{dungeonData?.displayName || "MISSION_ACTIVE"}
|
|
</div>
|
|
<div className="dungeon-status-tag">LIVE_FEED</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="environment-panel">
|
|
<div className="env-header">ENVIRONMENT_SCAN</div>
|
|
<div className="env-info">
|
|
<div className="env-icon">
|
|
<i
|
|
className={`fas ${
|
|
roomData?.id?.includes("boss")
|
|
? "fa-skull"
|
|
: roomData?.id?.includes("loot")
|
|
? "fa-box-open"
|
|
: "fa-microchip"
|
|
}`}
|
|
></i>
|
|
</div>
|
|
<div className="env-details">
|
|
<div className="env-id">{getRoomDisplayName(roomData)}</div>
|
|
<div className="env-type">
|
|
TYPE: {hostiles.length > 0 ? "COMBAT_ZONE" : "SECURE_AREA"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="battle-layout">
|
|
<div
|
|
className={`enemy-display ${isEnemyDefeated ? "target-lost" : ""}`}
|
|
>
|
|
{currentEnemy ? (
|
|
<div className={`enemy-card ${isEnemyDefeated ? "defeated" : ""}`}>
|
|
<div className="threat-tag">
|
|
{isEnemyDefeated ? "SIGNAL_LOST" : "HOSTILE_DETECTED"}
|
|
</div>
|
|
<div className="enemy-icon">
|
|
<i
|
|
className={`fas ${isEnemyDefeated ? "fa-skull-crossbones" : "fa-robot"}`}
|
|
></i>
|
|
</div>
|
|
<h3 className="enemy-name">
|
|
{getEnemyDisplayName(currentEnemy)}
|
|
</h3>
|
|
<div className="enemy-hp-container">
|
|
<div className="hp-label">STRUCTURE INTEGRITY</div>
|
|
<div className="hp-bar-mini">
|
|
<div
|
|
className="hp-fill-mini"
|
|
style={{
|
|
width: isEnemyDefeated
|
|
? "0%"
|
|
: `${enemyHp !== null ? enemyHp : 100}%`,
|
|
}}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div className="enemy-info-footer">
|
|
<span>LVL: {currentEnemy.level || 1}</span>
|
|
<span className="card-id">{currentEnemy.id}</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="empty-room">
|
|
<i className="fas fa-satellite-dish"></i>
|
|
<p>NO HOSTILES IN RANGE</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="combat-log-wrapper">
|
|
<div className="log-header">COMBAT_LOG_V3.0</div>
|
|
<div className="combat-log custom-scroll">
|
|
{log.map((entry, i) => (
|
|
<div key={i} className="log-entry">
|
|
<span className="log-arrow">></span> {entry}
|
|
</div>
|
|
))}
|
|
<div ref={logEndRef} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="dungeon-controls">
|
|
{!isEnemyDefeated && currentEnemy && (
|
|
<button className="ctrl-btn combat" onClick={handleCombat}>
|
|
<i className="fas fa-bolt"></i> ENGAGE
|
|
</button>
|
|
)}
|
|
|
|
{isEnemyDefeated && !isLooted && (
|
|
<button className="ctrl-btn loot" onClick={handleLoot}>
|
|
<i className="fas fa-download"></i> COLLECT_ASSETS
|
|
</button>
|
|
)}
|
|
|
|
{(isLooted || !currentEnemy) && (
|
|
<button className="ctrl-btn next" onClick={handleNextRoom}>
|
|
PROCEED <i className="fas fa-chevron-right"></i>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DungeonScreen;
|